I'm working on authentication for my JSON-RPC API and my current working strategy is using signed requests sent via POST over SSL.
I'm wondering if anyone can see any vulnerabilities that I haven't taken into consideration with the following signature method.
All communication between the client and the server is done via POST requests sent over SSL. Insecure http requests are denied outright by the API server.
Dependencies
var uuid = require('node-uuid');
var crypto = require('crypto');
var moment = require('moment');
var MyAPI = require('request-json').newClient('https://api.myappdomain.com');
Dependency Links: node-uuid, crypto, moment, request-json
Vars
var apiVersion = '1.0';
var publicKey = 'MY_PUBLIC_KEY_UUID';
var secretKey = 'MY_SECRET_KEY_UUID';
Request Object
var request = {
requestID : uuid.v4(),
apiVersion : apiVersion,
nonce : uuid.v4(),
timestamp : moment.utc( new Date() ),
params : params
}
Signature
var signature = crypto.createHmac('sha512',secretKey).update(JSON.stringify(request)).digest('hex');
Payload Packaging (Sent as cleartext via POST over SSL)
var payload = {
request: request,
publicKey : publicKey,
signature : signature
}
Resultant Payload JSON Document
{
"request" : {
"requestID" : "687de6b4-bb02-4d2c-8d3a-adeacd2d183e",
"apiVersion" : "1.0",
"nonce" : "eb7e4171-9e23-408a-aa2b-cd437a78af22",
"timestamp" : "2014-05-23T01:36:52.225Z",
"params" : {
"class" : "User"
"method" : "getProfile",
"data" : {
"id" : "SOME_USER_ID"
}
}
},
"publicKey" : "PUBLIC_KEY",
"signature" : "7e0a06b560220c24f8eefda1fda792e428abb0057998d5925cf77563a20ec7b645dacdf96da3fc57e1918950719a7da70a042b44eb27eabc889adef95ea994d1",
}
POST Request
MyAPI.post('/', payload, function(response){
/// Handle any errors ...
/// Do something with the result ...
/// Inspect the request you sent ...
});
Server-Side
And then on the server-side the following occurs to authenticate the request:
PUBLIC_KEYis used to lookup theSECRET_KEYin the DB.SECRET_KEYis used to create an HMAC of therequestobject from the payload.- The
signaturehash sent in the payload is compared to the hash of therequestobject created on the server. If they match, we move on to authenticating thetimestamp. - Given that we can now trust the
timestampsent in the cleartextrequestobject since it was included in thesignaturehash sent from the client, thetimestampis evaluated and the authentication is rejected if the request is too old. Otherwise, the request is authenticated.
So far as I understand, this is a secure method for signing and authentication requests sent over SSL. Is this correct?
Thanks in advance for any help.
Update on JSON Property Order
The order of properties when using
JSON.stringifyis essentially random, which could cause signature mis-matches.
Using this signing process over the past few weeks I haven't run into any hash mis-match issues due to the order of the properties in the JSON request object. I believe it's because I only stringify the request object literal once, right before the client-side hash is calculated. Then, the request object is in JSON format as part of the payload. Once received by the server, the hash is created directly from the JSON object received in the payload, there's no second JSON.stringify method invoked, so the signature always matches because the order of the properties is determined once, by the client. I'll keep looking into this though as it seems like a weak point, if not a security concern.