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_KEY
is used to lookup theSECRET_KEY
in the DB.SECRET_KEY
is used to create an HMAC of therequest
object from the payload.- The
signature
hash sent in the payload is compared to the hash of therequest
object created on the server. If they match, we move on to authenticating thetimestamp
. - Given that we can now trust the
timestamp
sent in the cleartextrequest
object since it was included in thesignature
hash sent from the client, thetimestamp
is 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.stringify
is 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.