Technical Documentation

HMAC Request Signing and Keypairs

69 views 0

The Zephr REST API is secured using keypair authentication. Once a keypair has been created against an Admin User, a request can be signed using an HMAC algorithm in order to allow the request to be executed with the role and identity of the Admin User who owns the keypair.

Creating a Keypair

Keypairs are managed under the user icon in the top right of the Zephr admin dashboard:

Click “Issue keypair” and take note of the secret key.

You will not be able to retrieve the secret key after it is initially display.\

\

Keypairs created in the admin console allow an integration service to act on behalf of the user who generates them. For keypairs that are not linked to a user, contact support@zephr.com.

You can add notes to the keypair from the context menu in the list of keypairs.

One can also create keypairs via the REST API, if required:

POST /v3/admin/users/{user_id}/keypairs

Note that no body is required in this request.

The response will be:

{   
   "access_key": 
  "access key...",   "secret_key": "secret key...",   
  "message": "Keypair created: you will not be able to recover the secret, so take note of it" 
}

The secret key can never be recovered so it is important to record the payload from this request and store it securely.

Signing a request

To execute a secure request you must provide an Authorization header with a request signature:

GET /v3/users Authorization: BLAIZE-HMAC-{{ALGORITHM}} 
{{ACCESS_KEY}}:{{TIMESTAMP}}:{{NONCE}}:{{HASH}}

Where:

  • ALGORITHM is “SHA256”
  • ACCESS_KEY is the access key from the keypair
  • TIMESTAMP is the current number of milliseconds since the Epoc
  • NONCE is a  random string (usually a number) that is unique – if you make two requests you must change the nonce between them
  • HASH is a hash generated by digesting the following inputs into an SHA-256 message digest algorithm and outputting the resulting hex value:
    • the secret key from the keypair
    • the request’s body
    • the requests path (not including the host, e.g. “/v3/users”)
    • the request method, in capitals, i.e. “POST”, “PUT”, “GET”, “DELETE”
    • the timestamp which must exactly match the TIMESTAMP part of the signature
    • the nonce which must exactly match the NONCE part of the signature

Reference implementations

Java

The Zephr HmacSigner is available as part of the Zephr API project. Please contact support@zephr.com to arrange access or otherwise you may copy and distribute the following code without licence.

package io.blaize.api.utilities.security;

import io.blaize.api.exception.HmacException; 

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

public class HmacSigner { 

    private final String algorithm;

    public HmacSigner(String algorithm) { 
        if ("SHA256".equals(algorithm)) {
            this.algorithm = "SHA-256";
        } else {
            this.algorithm = algorithm;
        }
    }

    public String signRequest(String secretKey, String body, String path, String method, 
String timestamp, String nonce) throws HmacException {


        Objects.requireNonNull(secretKey);
        Objects.requireNonNull(body);
        Objects.requireNonNull(path);
        Objects.requireNonNull(method);
        Objects.requireNonNull(timestamp);
        Objects.requireNonNull(nonce);

        try {

            MessageDigest messageDigest = 
MessageDigest.getInstance(algorithm);

            messageDigest.update(secretKey.getBytes());
            messageDigest.update(body.getBytes());
            messageDigest.update(path.getBytes());
            messageDigest.update(method.getBytes());
            messageDigest.update(timestamp.getBytes());
            messageDigest.update(nonce.getBytes());

            byte[] digest = messageDigest.digest();

            StringBuffer hash = new StringBuffer();

 for (byte digestByte : digest) {

                Integer unsignedInteger = new 
Integer(Byte.toUnsignedInt(digestByte));
                hash.append(Integer.toHexString(unsignedInteger));
            }
             
             return hash.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new HmacException(e);
        }

    }

}

Then a signature can be used as follows:

The following code is non-normative and only intended as a reference.

String protocol = "http";
String host = "admin.test.blaize.io";
String path = "/v3/users";
String method = "POST";
String body = "{\"identifiers\": { \"email_adress\": \"test@test.com\" }, \"validators\": { \"password\": \"sup3rsecret\" }}";

String accessKey = "x";
String secretKey = loadSecretKeySecurely(accessKey);

String timestamp = String.valueOf(new Date().getTime());
String nonce = UUID.randomUUID().toString();

String hash = new HmacSigner("SHA-256").
signRequest(secretKey, body, path, method, timestamp, nonce);

String authorizationHeaderValue = "BLAIZE-HMAC-SHA256 "
+ inputs.compute("accessKey", this::replaceVariables) + ":" + timestamp + ":" + nonce + ":" + hash;

// This is a standard library implementation for illustration only
HttpURLConnection connection = (HttpURLConnection) new URL(protocol + "://" + host + path).openConnection();
connection.setRequestMethod(method);
connection.addRequestProperty("Authorization", authorizationHeaderValue);

connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(payload);
outputStream.flush();
outputStream.close();

int status = connection.getResponseCode();
if (this.status >= 200 && this.status < 400) {
System.out.println(new BufferedReader(new InputStreamReader(connection.getInputStream())).lines().collect(Collectors.joining("\n")));
} else {
System.err.println(status);