Self Signing Requests in Postman

Many Web APIs require a Digital Signature to be provided with a message body to ensure the authenticity and integrity of the message. The Digital Signature (when combined with other measures) makes it possible to prove that the request was sent by a specific party, and that it has not been tampered with or modified in flight. This is vital for security and non-repudiation. It's relatively easy to do programmatically when integrating two software platforms together. But it adds complications to the process of manually poking an API with tools like Postman.

Here we describe how you can configure Postman to automatically Hash, Sign and Encode request bodies to generate a Digital Signature. We focus on configuring postman specifically for Clear Bank's Financial Institution (FI) API but the steps can be modified to suit any API with a similar security configuration.

Background

As detailed in the Developer Portal, Clear Bank's FI API utilises an Authentication Profile that is built up of:

The Digital Signature is a hash of the request's message body signed using your Private Key. The FI API can then use your Public Key to verify that the Digital Signature matches the request's message body. If the Digital Signature doesn't match the message body exactly, we're unable to verify that the request was sent by the correct party or that it hasn't been tampered with in transit. We will therefore reject the request.

Creating your Authentication Profile

Before you can provide a valid Digital Signature you must first create your Authentication Profile. This means creating your Public/Private Key Pair and generating your unique API Token.

Creating you Public/Private Key Pair

Clear Bank's Test environment allows the use of open source solutions to generate the Certificate. This is not suitable for use in our production environment - more information about setting up certificates for use in our Production environment can be found in the Developer Portal

This guide uses OpenSSL to create the Public/Private Key Pair though any open source solution would be suitable. OpenSSL can be installed using Chocolatey using the command choco install openssl.

Once installed, the following command can be used to generate the Public/Private key files. Once generated, make sure you store these files in a safe place.

openssl req -out .\certificate_name.csr -new -sha256 -newkey rsa:2048 -nodes -subj "/ST=localhost/L=localhost/CN=localhost/" -keyout .\certificate_name.key

Creating your API Token

Clear Bank's API Tokens are generated using the Clear Bank's Institution Portal. You can do this by navigating to Institution > Certificates and Tokens and clicking the Generate API Token.

In the dialog box upload the certificate_name.csr that you generated earlier and set the Token Name and Expiry Date as required. Make sure you store your API Token somewhere safe; you won't be able to see it again once generated.

Configuring Postman

Once you have a valid API Token and Public/Private Key Pair you are ready to configure Postman.

GET Requests

Requests that don't contain a message body only require an API Token. To test your API Token works you can use our Test GET Endpoint with the following values.

Variables:
fi-api-baseUrl      - The Url to the Institution API
fi-api-authToken    - The API Token generated above
Request:
GET {{fi-api-baseUrl}}/v1/test

Authorization Type: Bearer Token
Token: {{fi-api-authToken}}

Custom Headers
X-Request-ID: {{$guid}}

POST/PATCH Requests

Requests with bodies require a Digital Signature in the headers. This means we need to go a little bit further to configure Postman to automatically generate the Digital Signature and set the headers accordingly.

1. Store your Private Key in Postman

Add the contents of the certificate_name.key file generated earlier in an environment variable. This will be used by a Pre-Request script to generate your Digital Signature.

You must copy the entire contents of the file, including the 'BEGIN PRIVATE KEY' and 'END PRIVATE KEY' prefix/suffix.

Variables:
fi-api-privateKey:  -----BEGIN PRIVATE KEY----- {key} -----END PRIVATE KEY-----

2. Create a Global Variable to store a Crypto Utility Library

Natively Postman only supports a small handful of JavaScript libraries but it is possible to import JavaScript libraries using variables. Information about the library we are using and a step by step guid to automatically configure Postman can be found here.

Or you can create the variable (called pmlib_code) manually by storing the contents of this bundle in it.

3. Configure the Pre-Request Script

Then in the Pre-Request Script for the request use the following script:

eval(pm.globals.get('pmlib_code'))
var CryptoJS = require("crypto-js");
var Property = require('postman-collection').Property;

var sig = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"});
sig.init(pm.environment.get("FI_API_PrivateKey").replace(/\\n/g, "\n"));

var requestBody = Property.replaceSubstitutions(pm.request.body.raw || "", pm.variables.toObject());
var hash = sig.signString(requestBody);

const signedEncoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(hash));

pm.request.headers.add({
    key: 'DigitalSignature',
    value: signedEncoded
});

pm.request.body.update(requestBody);

if (!pm.request.headers.has("Content-Type"))
{
    pm.request.headers.add({
        key: 'Content-Type',
        value: "application/json"
    });
}

Pre-Scripts can be stored at a folder level in Postman. Doing this means the script will run before any request (and before that request's Pre-Request Script) stored under that folder.

How the Digital Signature is Calculated

To compute the Digital Signature, we need to have access to the message body as it is going to be sent over the wire. This is done by extracting the raw request body, substituting all of the variables (environment variables and computed variables like {{$guid}}). We then need to override the raw body to make sure that computed values are not computed more than once. I.e. A different guid isn't used in the request vs the digital signature.

var Property = require('postman-collection').Property;
var requestBody = Property.replaceSubstitutions(pm.request.body.raw || "", pm.variables.toObject());
pm.request.body.update(requestBody);

The only thing to look out for with this approach is that Postman is no longer able to correctly determine the Content-Type header. This is because the auto-generated headers are generated after the Pre-Requests scripts are executed. In the script we have assumed that a Content-Type of application/json is required unless an override (non-automatically generated) Content-Type header is provided with the request to your request.

if (!pm.request.headers.has("Content-Type"))
{
    pm.request.headers.add({
        key: 'Content-Type',
        value: "application/json"
    });
}

4. Test the body Signing

To test that your API Token and Digital Signature you can use our Test POST Endpoint with the following configuration.

Request:
POST {{fi-api-baseUrl}}/v1/test

Authorization Type: Bearer Token
Token: {{fi-api-authToken}}

Custom Headers
X-Request-ID: {{$guid}}

Body:
{
    "hello": "{{$guid}}"
}

Postman Collections

Over on our GitHub we've published a Postman Collection with the Pre-Request Script pre-configured and a bunch of example requests. Once you have configured your global/environment variables you can simply import the Postman Collections and start sending requests to our APIs.

Jamie Peacock

Senior Software Engineer, ClearBank