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 the SECRET_KEY
in the DB.SECRET_KEY
is used to create an HMAC of the request
object from the payload.signature
hash sent in the payload is compared to the hash of the request
object created on the server. If they match, we move on to authenticating the timestamp
.timestamp
sent in the cleartext request
object since it was included in the signature
hash sent from the client, the timestamp
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.
JSON.stringify
does not guarantee order of properties. For example, object
{
a: 1,
b: 2
}
could be serialized in two ways: {"a":1,"b":2}
or {"b":2,"a":1}
. They are the same from JSON point of view but they will result it different HMACs.
Imaging, that for signings your JSON.stringify
produced first form, but for checking signature second one. Your signature check will fail although signature was valid.
The only fishy thing I see here would be the JSON.stringify as posted in other comments, but you can use:
https://www.npmjs.com/package/json-stable-stringify
That way you can have a deterministic hash for you signs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With