1

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');

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 TLS)

var payload = {
    request: request,
    publicKey : publicKey,
    signature : signature
}

POST Request

MyAPI.post('/', payload, function(error, response, body){
    console.log(result);
});

Resultant Payload

{
  "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",
}

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 hash sent in the payload is compared to the hash created on the server. If they match, the PUBLIC_KEY is authenticated.
  4. The timestamp is evaluated and the authentication is rejected if the request is too old, otherwise, the timestamp 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.

1

One question you may want to consider is why you believe that signed requests are necessary if all APIs are SSL only? SSL provides both tamper & replay protection, so unless you have other concerns, this may be redundant.

Although you plan to use SSL everywhere, which is great! My comments below explore the possibility that there is no SSL in use.

1 - URL & HTTP Verb is not signed

In your sample above, the intended destination of the payload is not included in the payload signature. Consider if the payload could be captured, it could be redirected to another undesired location. Perhaps a request intended to be sent to /users/123 is routed to /users/456, or a request sent to 'v1.api.mysite.com' is sent to 'v2.api.mysite.com', or a POST is changed to a PATCH.

2 - Keyrolling Support

This may be outside the scope of your question, but I see no support to roll the signing key without the possibility of customer impact. It would be advantageous if your service could maintain two authorized secrets for a given public id. This way the key could be rotated regularly (say out of an abundance of caution) without tight coordination between client & service.

3 - Signing 'Agility'

What happens to your application if a new weakness is found in SHA2 or the current HMAC construction? What if you have a client that prefers to use ECDSA over HMAC? Consider having a version number, or signature 'scheme' identifier as part of the signed payload. This way your service can understand what approach the client has used to sign the payload.

4 - Too Old

You mention in your post that the service can reject requests that are too old (to mitigate replay attacks). What if the client doesn't agree with your definition of too old? Consider allowing the client to specify how old is too old in the signed payload. Again, this is not so much a weakness in your approach at generating signatures, more of a comment on the protocol in general.

5 - Other Nits

  • The 'nonce' field looks redundant. You already have a random GUID serving as a request id, unless your service requires the nonce, this can be safely removed.

  • Depending on your JSON parser (& how/when you deseralize), this may not catch all modifications to your payload. For example, if I modify the payload to escape a character that does not require escaping, the result is semantically equivalent. Because you are deseralizing the JSON before verifying if it's been modified, you may be vulnerable to attacks seeking to exploit your deseralizer, or attacks targeting subtle bugs in how escaped characters are handled.

But lastly, I repeat what I said earlier - signing your request may be redundant when you only support SSL as the transport. Simply authenticating your caller may be sufficient.

  • Thanks a tonne for taking the time, @Tails. An amazing answer! I have to run out right now, but I'll be coming back to this with more detailed comments ... – AJB May 24 '14 at 16:22
  • Okay, got some time to go over this properly. 1. The server only accepts a single POST endpoint: POST '/'. All other verbs and endpoints result in an error response instructing the user to use the single endpoint. I do like the idea of including the verb and endpoint in the signature though and possibly tracking any attempts to request other verbs or endpoints. – AJB May 24 '14 at 17:54
  • 2. My issuing strategy for the keys is still in the works. There's a couple other complicating factors: a) The application itself (as in, the GUI client within any given browser) is actually going to be a client of the API layer with elevated privileges that allow any given application client I build to use both the 'private' and 'public' Classes and Methods offered by the API. b) The application is actually a suite of apps (e.g. Calendar, Contacts, Profile, etc.) where each User on any given Project may have different privileges for different apps in the suite. – AJB May 24 '14 at 18:02
  • 2.1 I will certainly now do my homework and apply some Hammock Driven Development theory to key rotation. – AJB May 24 '14 at 18:03
  • 3. I spent part of yesterday implementing the ability to use different HMAC algorithms. I'm thinking about starting by forcing sha512, but I'm a little concerned about the signing process performance on slower, older servers, and especially mobile devices so I'm going to have to do some testing/benchmarking. However, no matter the policy I choose, the possibility to use different HMAC algorithms is now baked-in. – AJB May 24 '14 at 18:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.