Watch Out For Base64 Encoded API Secret Keys

Although not very specific to Mule, this post is a bit of a heads up to API users of Cornerstone and similar SaaS providers. It is becoming a very common API authentication design to use Session Tokens even though RESTful itself isn’t stateful. This design helps in avoiding to perform authentication checks for every request (which can create huge bottlenecks on Authentication Gateways).

The figure below enlists steps to a typical authentication in this design. In short, an initial request to authenticate will result in a response containing a session token and an expiry timestamp, which is then used in all subsequent requests until the token expires.

Watch Out For Base64 Encoded API Secret Keys

Postman Is A Nifty Tool For macOS Users

Pre-request script feature of Postman, which is quite a nifty tool for macOS users to test RESTful APIs, helps us write a little javascript to obtain the signature we need in order to get a session token. We also use environment variables and construct the request to be sent. See below.

var currDate = new Date();
currDate.setMilliseconds(0);
var currDateStr = currDate.toISOString();
currDateStr = currDateStr.replace("Z","");
currDateStr = currDateStr.toLowerCase();
var str = request.method + "\n" + "x-csod-api-key:" + environment.apiKey.toLowerCase() + "\n" + "x-csod-date:" + currDateStr + "\n" + environment.apiStsPath;
console.log(str);
var hash = CryptoJS.HmacSHA512(str, environment.apiSecret);
var sig = CryptoJS.enc.Base64.stringify(hash);
console.log(sig);
postman.setEnvironmentVariable('currDate', currDateStr);
postman.setEnvironmentVariable('apiSignature', sig);

The request is sent to the authentication endpoint https://{corp}.csod.com/services/api/sts/session along with parameters userName provided by Cornerstone and a random unique string for alias. The final URL will look like https://abc-stg.csod.com/services/api/sts/session?userName=dev1&alias=test20170505150310

This POST request will also contain three headers, x-csod-api-key which is same as the API key provided by Cornerstone, x-csod-date which should be UTC date/time in the ISO 8601 format including milliseconds but without the trailing “Z”, i.e. yyyy-MM-ddTHH:mm:ss.000 (not sure if a random millisecond would work, but 000 definitely works).

Session Token Response

When we send the above request we get a 401 error, which is as documented tells us something is wrong.

<cornerstoneApi>
    <status>401</status>
    <timeStamp>2017-05-05T14:03:11+0000</timeStamp>
    <error>
        <errorId>b612bc6b-d713-40fc-bc3c-f53d635d6584</errorId>
        <message>CSOD Unauthorized Exception:Check your credentials.</message>
        <code>401</code>
        <description>Check your access URL</description>
    </error>
</cornerstoneApi>

After checking with Cornerstone it was found that the end point wasn’t enabled. But the error persists even after. The signature is generally a prime suspect as it is not straightforward to verify. Using Postman’s console one could easily validate all the variables going in. In this scenario, all values are as expected - except the signature which is not easy to verify. Further, it is easy to see if the CryptoJS.HmacSHA512 is returning right result, by using an online tool.

Devil Is In The Detail :smile:

Looking carefully at the sample C# code documented by Cornerstone revealed that the secret key they provided was actually encoded in Base64. CrypoJS.HmacSHA512 function requires either instances of CryptoJS.lib.WordArray (32-bit WordArrays) or strings as key. This caused the Base64 encoded secret key to be treated as regular strings resulting in invalid signature. So passing CryptoJS.enc.Base64.parse(environment.apiSecret) finally resolved the issue.

<cornerstoneApi xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Validations/>
    <status>201</status>
    <timeStamp>2017-05-05T14:10:12+0000</timeStamp>
    <createdRecords>1</createdRecords>
    <data xmlns:a="www.CornerStoneOnDemand.com/Core/">
        <a:Session>
            <a:Token>vjyaggwvdd2ek</a:Token>
            <a:Secret>oLkZ/RPad-r-removed-d-XOPE93US0hdxTi0w==</a:Secret>
            <a:Alias>2017-05-05t14:44:42.000</a:Alias>
            <a:ExpiresOn>2017-05-05T15:44:42+0000</a:ExpiresOn>
        </a:Session>
    </data>
</cornerstoneApi>