As my previous article I describe the architecture that I use to build REST servers. I also presented some C++ sample code that you can easily expanded to build your own server. In todays adventure in our continual soiree we will cover how to use modern cryptography to securely authenticate REST requests coming into our server.
What do I mean by Authentication?
When I talk about authentication in this article I am referring to preventing unauthorized access of our IoT project through our REST server API. This is a very separate topic than encrypting the messages or preventing someone from hacking your Raspberry Pi platform. In my case, I didn’t want some hacker telling my server to open the door to my chicken coop.
There are lots of options available today to control access of web services. For example the JSON Web Token (JWT) is popular in some circles. But I felt that for this implementation it would be overkill and would require some other server to generate and deploy the JWT.
Instead I wanted something simpler, and decided to create a system similar to how AWS signs and authenticating REST requests. So excuse me for a minute while I whip out some cryptography.
The Secure Hash
As part of our authentication system we will start with a secure hash function. Hash functions all have similar properties. They reduce a variable length string of bits or data into a fixed length value in a deterministic way. Given the same input, it reproduces the same result over and over again.
So let’s say we take all the text that comprises your favorite movie script, or the bits from your last instagram selfie photo, and run it through one of these hash functions like SHA-256. It would give us back a 256 bit value. This value obviously doesn’t hold enough information to derive the input that created the hash, it’s a one way process. It’s not possible to work backwards from the hash to determine the original source of data. In fact, any slight change in the input, like a pixel in the photo or a word in the script will result in a radically large change in the hash value.
For example, let’s assume we had a secure hash of script from the original “Planet of the Apes”. This hash would be very different from hash of a slight revision of it the script, or a complete rewrite. The idea is that given enough bits these collisions become mathematically highly improbable.
In the case of our REST API, we hash the JSON commands in the body of the HTTP request, along with the url path and the HTTP method.
If we were able to send the hash along with our request it would only tell us that the request hasn’t been fooled with. And a good hacker would simply record our request and play it back later. So it would be prudent to also hash in a timestamp.
Still the hash value isn’t enough. What we really want is to create a digital signature from this hash that could only originate from the authorized user. This requires us to introduce another cryptographic tool. The HMAC.
Hash-based message authentication code (HMAC)
I mentioned before that with a secure hash it is not possible to work backwards from the hash to determine the original source of data. We could take the result from hashing the REST request and then hashed that with a secret key that was only possessed by the client and server. And while that would give a result that only the client could produce and the server could easily verify, there are some mathematic vulnerabilities that can be introduced.
HMACs are designed specifically for this kind of authentication.
Let me introduce two more items, lets call them API key and API Secret. Think of them as a user name and a password. One is public while the other is kept secret. The only reason we have an API key is it gives us a way to distinguish between multiple users.
Now take the result of the hash we obtained from our request and we run through an HMAC using our shared API secret key. This gives us an authentication code.
To be specific about the algorithm:
X-auth-date = string(Unix_Epoch)
X-auth-key = API-key
bodyHash = hexString( SHA-256(http_body))
stringToSign = http_method + "|" + urlPath + "|" + bodyHash + "|" + daytimeHeader + "|" + API-key
Authentication = HMAC(API-Secret, SHA-256, stringToSign)
And for you Swift fans, here is the actual code I use in my client to create an authentication:
func calculateSignature(forRequest: URLRequest, apiSecret: String ) -> String {
if let method: String = forRequest.httpMethod,
let urlPath = forRequest.url?.path ,
let daytimeHeader =
forRequest.value(forHTTPHeaderField: "X-auth-date"),
let apiKey = forRequest.value(forHTTPHeaderField:
"X-auth-key")
{
var bodyHash:String = ""
if let body = forRequest.httpBody {
bodyHash = body.sha256String()
}
else {
bodyHash = Data().sha256String()
}
let stringToSign = method + "|" + urlPath + "|"
+ bodyHash + "|" + daytimeHeader + "|" + apiKey
let signatureString = stringToSign.hmac(key: apiSecret)
return signatureString;
}
return "";
}
Enabling authentication
If you want to test out the authentication system, all you need to do is compile the demoserver sample code with the SHOULD_AUTHENTICATE define set to 1 the main.cpp file.
#define SHOULD_AUTHENTICATE 1
This will cause the demo to use the apiSecretGetSecret method of the DemoSecretMgr class. For this example, I have hardcoded a fixed APIkey and APISecret. Obviously a finished application would keep a database of APIkey and APISecret.
bool DemoSecretMgr::apiSecretGetSecret
(string APIkey, string &APISecret) {
// given the APIkey return the proper APISecret
// we are hardcoding it.
if(APIkey == "test") {
APISecret = "12345678";
return true;
}
return false;
}
The code that does the reciprocal calculation on the server can be found at RESTServerConnection::validateRequestCredentials() You might notice that I have coded in a 5 minute window in the timestamp validation.
Testing the authentication
Before you go and turn this on, you will need a way to generate a signature. In my previous article I used the cURL utility to create the server REST requests. But once you turn on authentication you will need to fill these additional HTTP header values:
X-auth-date - The Unix Epoch as a string
X-auth-key - The API key (“test”)
Authorization - The derived signature for API-Secret ("12345678")
Doing this calculation manually is going to require you to jump through few hoops, and it’s not something that will easy to debug without some tools to automate the requests. I’ll talk about these later.
What Authentication is not
The authentication method I describe here only serves the purpose of preventing unauthorized access to the REST API. The connection is unencrypted. Someone who is sniffing the TCP connections could see the values your receive and commands you send. I expressly did not talk about encrypting the conversation. To do this properly you would need to add a TLS layer under the HTTP protocol. But that out of the scope of what I need to do right now.
Just pawing around
In my next article I will talk about PAW. One of my favorite developer tools I use regularly for debugging the REST API. Not only that but PAW allows me automatically describe and document the REST APIs that I use.