6

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:

  1. PUBLIC_KEY is used to lookup the SECRET_KEY in the DB.
  2. SECRET_KEY is used to create an HMAC of the request object from the payload.
  3. The 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.
  4. Given that we can now trust the 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.

AJB
  • 7,389
  • 14
  • 57
  • 88
  • 1
    The main flow I see is that JSON.stringify does not guarantee to give the same output for object. See http://stackoverflow.com/q/8931967/1016033 – Alexey Ten May 23 '14 at 04:22
  • Thanks for the comment, Alexey. However, the ordering of the properties within the object is not a concern. In fact, that's the whole reason for using JSON objects as the form of the payload, to obviate the need for ordered parameters in the `request` method. – AJB May 23 '14 at 19:03
  • Ah, I see what you mean now. The `result` `error` `request` parameters of the callback function are a a misrepresentation in this post. I'll edit. – AJB May 23 '14 at 20:13

2 Answers2

3

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.

Alexey Ten
  • 13,794
  • 6
  • 44
  • 54
2

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.