Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this API signed request methodology secure?

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.

like image 383
AJB Avatar asked May 23 '14 02:05

AJB


2 Answers

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.

like image 177
Alexey Ten Avatar answered Nov 18 '22 05:11

Alexey Ten


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.

like image 23
user3397809 Avatar answered Nov 18 '22 05:11

user3397809