QSeal API Signature Generation

Complete guide to creating QSeal signatures for Neonomics API requests, including HTTP headers, digest creation, and signature verification with Java and JavaScript examples.

QSeal API Signature Generation

This comprehensive guide explains how to create and sign API requests to Neonomics using QSeal certificates, ensuring message integrity and authentication for secure communication.


Quick Start

Get up and running with QSeal signatures in minutes using our ready-to-use examples

Security First

Enterprise-grade authentication using industry-standard QSeal certificates


What You'll Learn

This guide provides everything you need to implement QSeal signing:

  • HTTP Header Requirements - Required headers for different request types
  • Digest Creation - SHA-256 hash generation for request bodies
  • Signature Verification - Step-by-step signature creation and validation
  • Ready-to-Use Examples - Complete implementations in Java and JavaScript

Why QSeal Signatures?

QSeal (Qualified Electronic Seal) certificates provide legally recognized digital signatures that ensure:

  • Authentication - Verify the request sender's identity
  • Integrity - Detect any tampering with request data
  • Non-repudiation - Legal proof of transaction authenticity



Request Signing Overview

Each API request to Neonomics must include a cryptographic signature created with your QSeal certificate. The signing process varies based on the HTTP method:


Requests without a body

These requests require basic headers for identification and timing, plus the signature.




Required Headers

The headers you need depend on your HTTP method:


GET and DELETE Requests

HeaderDescriptionExample
(request-target)HTTP method and path (auto-generated by signing libraries)get /ics/v3/banks
x-originating-hostHost name from which the request originatesneonomics.io
x-originating-dateRequest creation timestamp (RFC 7231 format)Mon, 07 Feb 2023 00:28:05 GMT
SignatureGenerated signature stringSee examples below

POST, PUT, and PATCH Requests

HeaderDescriptionExample
(request-target)HTTP method and pathpost /ics/v3/session
x-originating-hostHost name from which the request originatesneonomics.io
x-originating-dateRequest creation timestamp (RFC 7231 format)Mon, 07 Feb 2023 00:28:05 GMT
Content-TypeMIME type of the request bodyapplication/json
DigestBase64-encoded SHA-256 hash of the request bodySHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
SignatureGenerated signature stringSee examples below



Digest Creation

For requests with a body, you must create a digest header containing the SHA-256 hash:

Digest: SHA-256=<base64(sha256(request_body))>

Digest Calculation Process
  1. Hash the Body - Calculate SHA-256 hash of the request body bytes
  2. Encode to Base64 - Convert the hash bytes to Base64 string
  3. Format Header - Prepend "SHA-256=" to create the digest header

Neonomics validates this digest by hashing the received payload and comparing values.


Example:

Digest: SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=



Signature Format

Key Requirements

  • Key ID: Must be your Neonomics application/client ID
  • Algorithm: Always use rsa-sha256
  • Headers: List all headers included in the signature

Without Request Body (GET/DELETE)

Signature: keyId="your-neonomics-app-id",
algorithm="rsa-sha256",
headers="(request-target) x-originating-host x-originating-date",
signature="MEUCIQD1r3F3EwVQx1D9YVjfq0V..."

With Request Body (POST/PUT/PATCH)

Signature: keyId="your-neonomics-app-id",
algorithm="rsa-sha256", 
headers="(request-target) x-originating-host x-originating-date content-type digest",
signature="goYZZGdm2TI30jWpn9LR7KUZV6FHTen3e5M23PbXOfmmn+SjX29vXBNDieUSgDzxEEKodqwVh/Nf7mGi251vB34VVdzTGi6vJ5jditpwIXilsBZjn8ctmDLOPgAZlCx7PKE0ad+DTw3mDSbamB/m2xwBnhaAOD6g5ikXCyjATTFRhGZuBdF8qYXpcthbD0+EPrRWUszPj38nMdrfxZJCZ1Ef2XX88Qw7s4XSez9MOPbjl0EscR+Y4jbb6PckjEPLnqBAJJ8e2tYUbdAeTKWpZV1C1LtUML3o/y2BdJf2L6PMqzGHd3GwaNTBIVgNLEcOKafTyju9am1u87hX5GJUSw=="



Implementation Examples

Choose your preferred language to get started quickly:


Java Implementation

Complete example using the Tomitribe HTTP Signatures library.


Maven Dependency

<dependency>
    <groupId>org.tomitribe</groupId>
    <artifactId>tomitribe-http-signatures</artifactId>
    <version>1.8</version>
</dependency>

Complete Example

import org.tomitribe.auth.signatures.*;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Base64;

/**
 * QSeal signature generation for Neonomics API requests
 * 
 * This example demonstrates the complete flow:
 * 1. Loading QSeal private key from PEM file
 * 2. Creating request digest (for POST/PUT/PATCH)
 * 3. Generating cryptographic signature
 * 4. Building complete HTTP headers
 */
public class NeonomicsQSealSigner {

    public static void main(String[] args) throws Exception {
        // Example usage
        SignedRequest request = createSignedRequest();
        
        System.out.println("=== SIGNED REQUEST HEADERS ===");
        request.getHeaders().forEach((key, value) -> 
            System.out.println(key + ": " + value));
            
        System.out.println("\n=== REQUEST BODY ===");
        System.out.println(request.getBody());
    }

    /**
     * Create a complete signed request ready to send to Neonomics
     */
    public static SignedRequest createSignedRequest() throws Exception {
        // Load your QSeal private key
        RSAPrivateKey privateKey = loadPrivateKey("path/to/qseal-key.pem");
        
        SignedRequest request = new SignedRequest();
        request.setMethod("POST");
        request.setUrl("https://api.neonomics.io/ics/v3/session");
        request.setBody("{\"bankId\":\"YWt0aWEuZmkudjFIRUxTRklISA==\"}");

        // Step 1: Create digest for request body
        String digest = createDigest(request.getBody());

        // Step 2: Prepare required headers
        Map<String, String> headers = new LinkedHashMap<>();
        headers.put("x-originating-host", "neonomics.io");
        headers.put("x-originating-date", getCurrentRFC7231Date());
        headers.put("content-type", "application/json");
        headers.put("digest", digest);

        // Step 3: Generate signature
        String signature = generateSignature(request, headers, privateKey);
        headers.put("signature", signature);

        request.setHeaders(headers);
        return request;
    }

    /**
     * Load RSA private key from PEM file
     */
    public static RSAPrivateKey loadPrivateKey(String filePath) throws Exception {
        String keyContent = Files.readString(new File(filePath).toPath());
        
        // Remove PEM headers and whitespace
        String privateKeyPEM = keyContent
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");

        // Decode and create key
        byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }

    /**
     * Create SHA-256 digest header for request body
     */
    private static String createDigest(String body) throws NoSuchAlgorithmException {
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
        byte[] hash = sha256.digest(body.getBytes(Charset.forName("UTF-8")));
        return "SHA-256=" + Base64.getEncoder().encodeToString(hash);
    }

    /**
     * Generate cryptographic signature using Tomitribe library
     */
    private static String generateSignature(SignedRequest request, 
                                          Map<String, String> headers, 
                                          PrivateKey privateKey) throws IOException {
        URL url = new URL(request.getUrl());
        
        // Define headers to include in signature
        List<String> headerNames = headers.keySet().stream()
                .map(String::toLowerCase)
                .collect(Collectors.toList());
        headerNames.add("(request-target)");

        // Create signature configuration
        String keyId = "your-neonomics-app-id"; // Replace with your actual ID
        Signature signatureConfig = new Signature(keyId, "rsa-sha256", null, headerNames);
        Signer signer = new Signer(privateKey, signatureConfig);

        // Generate path with query parameters
        String path = url.getPath();
        if (url.getQuery() != null) {
            path += "?" + url.getQuery();
        }

        // Create and return signature
        Signature signature = signer.sign(request.getMethod(), path, headers);
        return signature.toString();
    }

    /**
     * Get current date in RFC 7231 format
     */
    private static String getCurrentRFC7231Date() {
        // Implementation would return current date in RFC 7231 format
        // For example: "Mon, 07 Feb 2023 00:28:05 GMT"
        return "Mon, 07 Feb 2023 00:28:05 GMT";
    }

    /**
     * Simple data structure to hold request information
     */
    public static class SignedRequest {
        private String method;
        private String url;
        private String body;
        private Map<String, String> headers = new HashMap<>();

        // Getters and setters
        public String getMethod() { return method; }
        public void setMethod(String method) { this.method = method; }
        
        public String getUrl() { return url; }
        public void setUrl(String url) { this.url = url; }
        
        public String getBody() { return body; }
        public void setBody(String body) { this.body = body; }
        
        public Map<String, String> getHeaders() { return headers; }
        public void setHeaders(Map<String, String> headers) { this.headers = headers; }
    }
}

Key Features

  • Automatic digest calculation for request bodies
  • PEM key loading with proper format handling
  • Complete signature generation using industry standards
  • Ready-to-use structure that integrates with your existing code



Testing Your Implementation

Verify Digest

Test your digest generation by comparing the SHA-256 hash with online tools or other implementations.

Validate Headers

Ensure all required headers are present and correctly formatted according to the HTTP Signature specification.


Common Issues and Solutions

Signature Verification Failed

Common causes:

  • Incorrect key ID (must match your Neonomics application ID)
  • Wrong header order in the signature
  • Malformed digest calculation
  • Incorrect date format (must be RFC 7231)

Solution: Double-check each header value and ensure they match exactly what was used to generate the signature.


Digest Mismatch

Common causes:

  • Character encoding issues (ensure UTF-8)
  • Including extra whitespace or newlines
  • Wrong hash algorithm (must be SHA-256)

Solution: Hash the exact request body bytes that will be sent over the wire.




Next Steps

Once you have QSeal signatures working:

  1. Test thoroughly with different request types and endpoints
  2. Implement error handling for signature failures and retries
  3. Cache your private key to avoid repeated file I/O operations
  4. Monitor signature performance in production environments
  5. Keep certificates up to date and handle rotation properly

Ready to integrate?

Copy the examples above and adapt them to your specific needs. Both Java and JavaScript implementations are production-ready and follow industry best practices.


What’s Next