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
| Header | Description | Example |
|---|---|---|
(request-target) | HTTP method and path (auto-generated by signing libraries) | get /ics/v3/banks |
x-originating-host | Host name from which the request originates | neonomics.io |
x-originating-date | Request creation timestamp (RFC 7231 format) | Mon, 07 Feb 2023 00:28:05 GMT |
Signature | Generated signature string | See examples below |
POST, PUT, and PATCH Requests
| Header | Description | Example |
|---|---|---|
(request-target) | HTTP method and path | post /ics/v3/session |
x-originating-host | Host name from which the request originates | neonomics.io |
x-originating-date | Request creation timestamp (RFC 7231 format) | Mon, 07 Feb 2023 00:28:05 GMT |
Content-Type | MIME type of the request body | application/json |
Digest | Base64-encoded SHA-256 hash of the request body | SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= |
Signature | Generated signature string | See 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
- Hash the Body - Calculate SHA-256 hash of the request body bytes
- Encode to Base64 - Convert the hash bytes to Base64 string
- 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:
- Test thoroughly with different request types and endpoints
- Implement error handling for signature failures and retries
- Cache your private key to avoid repeated file I/O operations
- Monitor signature performance in production environments
- 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.
Updated 3 days ago
