Skip to content

Java http signature sample code

This sample relies on the tomitribe's http-signatures-java.
Full project can be downloaded here

package io.knotcity;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.tomitribe.auth.signatures.Algorithm;
import org.tomitribe.auth.signatures.Join;
import org.tomitribe.auth.signatures.PEM;
import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;
import org.tomitribe.auth.signatures.SigningAlgorithm;

/**
 * Hello world!
 */
public final class App {
    private static final HttpClient httpClient = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_1_1)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

    private final String BASE_ENDPOINT = "https://staging.staas.knotcity.io";

    private final String privateKeyPem = "-----BEGIN PRIVATE KEY-----\n" +
    "enter base64 byte of your key here\n" +
    "-----END PRIVATE KEY-----";
    private final String KEY_ID = "enter your key id here";

    private App() {
    }

    /**
     * 
     * @param verb get, post, delete, put, etc.
     * @param urn the part of the url after the domain
     * @param headers headers to sign, X-Api-Key and x-knot-date should always be signed.
     * @throws InvalidKeySpecException
     * @throws IOException
     */
    private String signRequest(String verb, String urn, Map<String, String> headers) throws InvalidKeySpecException, IOException {

        PrivateKey privateKey = PEM.readPrivateKey(new ByteArrayInputStream(privateKeyPem.getBytes()));
        headers.put("(request-target)", verb + " " + urn);

        Signer signer = new Signer(privateKey,
            new Signature(KEY_ID, SigningAlgorithm.HS2019, Algorithm.ECDSA_SHA256, null, null, Arrays.asList(headers.keySet().toArray(new String[0]))));

        Signature signed = signer.sign(verb, urn, headers);
        return "Signature " +
                "keyId=\"" + signed.getKeyId() + '\"' +
                ",algorithm=\"" + signed.getSigningAlgorithm() + '\"' +
                ",headers=\"" + Join.join(" ", signed.getHeaders()) + '\"' +
                ",signature=\"" + signed.getSignature() + '\"';
    }

    private String sendGet(String urn) throws Exception {

        Map<String, String> headers = new HashMap<String, String>();
        headers.put("Content-Type", "application/json");
        headers.put("X-Api-Key", KEY_ID);
        headers.put("x-knot-date", String.valueOf(ZonedDateTime.now().toInstant().toEpochMilli()));

        List<String> headersList = new ArrayList<String>();
        headers.forEach((key, value) -> {
            headersList.add(key);
            headersList.add(value);
        });

        headersList.add("Authorization");
        headersList.add(this.signRequest("get", urn, headers));

        HttpRequest request = HttpRequest.newBuilder()
                .GET()
                .uri(URI.create(BASE_ENDPOINT + urn))
                .headers(headersList.toArray(new String[0]))
                .setHeader("User-Agent", "Java 11 HttpClient Bot")
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        // print response body
        return response.body();
    }

    /**
     * Says hello to the world.
     * @param args The arguments of the program.
     */
    public static void main(String[] args) {
        try {
            String body = new App().sendGet("/v1/enabled");
            System.out.println(body);
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}