Signing
The sign()
function adds the Signature
and Signature-Input
headers to an HTTP request or response, ensuring integrity and authenticity of the data sent between clients and servers.
The Signature
header contains the signature of the HTTP message, and the Signature-Input
header contains the components that were used to generate the signature.
Signer
This library does not provide any cryptographic functionality. Instead, it relies on a Signer
object to sign the HTTP message. The Signer
object must have the following properties:
keyid
: A unique identifier for the key used to sign the messagealg
: The algorithm used to sign the messagesign
: A function that takes astring
and returns anUint8Array
containing the signature
The verifier should be able to use the keyid
to retrieve the public key used to verify the signature.
Example
import { sign } from '@ltonetwork/http-message-signatures';
const signer = {
keyid: 'test-key',
alg: 'hmac-sha256',
sign: (data) => {
// ... Sign the data using your preferred cryptographic library
}
};
const request = {
method: 'POST',
url: 'https://example.com/api/data',
headers: {
'Content-Type': 'application/json',
'Digest': 'sha-256=base64encodeddigest',
}
};
(async () => {
const signedRequest = await sign(request, { signer });
// ... Send the signed request to the server
})();
The signed request will now have the following headers:
Signature-Input: sig1=("@method","@path","@authority","content-type","digest"); keyid="test-key"; created=1625653823
Signature: sig1=:base64signature:
For platform-specific examples on how to sign HTTP messages, refer to the following guides:
Components
Components are parts of the message that are included in the signature. Only the components that are included in the signature can be modified without invalidating the signature.
If a component is not included in the signature, it can be modified without a problem. If a header is explicitly left out, it should also be a component; otherwise, it can be added without signature verification failing.
Special Components
The components that can be signed include special components starting with an @
symbol. The supported special components are:
@method
: HTTP request method@path
: HTTP request path@query
: HTTP request query string@authority
: HTTP request authority (host and port)@status
: HTTP response status code@scheme
: HTTP request scheme (e.g., ‘http’ or ‘https’)
Note that the @query-params
and @request-response
components are not supported in this library.
Header Components
Any header can be included in the signature. The header name must be included in the signature, and the header value must be included in the signature. The header name is case-insensitive, but the header value is case-sensitive.
Common headers that are included in the signature are:
content-type
content-length
digest
Example
const components = ['@method', '@path', '@authority', 'content-type', 'digest'];
(async () => {
const signedRequest = await sign(request, { signer, components });
// ... Send the signed request to the server
})();
Expiration time
HTTP Message Signatures have metadata properties that provide information regarding the signature’s generation and verification. These properties are included in the Signature-Input
header, and are separated by semicolons.
The following parameters are supported:
keyid
: A unique identifier for the key used to sign the message.created
: Creation time. This defaults to the current time.expires
: . By default, the signature never expires.nonce
: A random unique value generated for this signature value.tag
: An application-specific tag for the signature. This value is used by applications to help identify signatures- relevant for specific applications or protocols.
In addition to these, custom parameters will also be included in the Signature-Input
header. A parameter value must be a string, number, date, or boolean.
Digest
To secure the integrity of the HTTP message body, you can use the Digest
header. This header contains a hash of the message body.
Before signing the HTTP message, you’ll need to create the digest and add it to the request or response headers. To create a Digest header using the SHA-256 hashing algorithm:
- Calculate the SHA-256 hash of the message body.
- Encode the hash in Base64 format.
- Add the Digest header to the request or response headers with the format
Digest: SHA-256=<Base64EncodedHash>
.
After adding the Digest
header to the HTTP message, include the digest component when signing the message. This ensures the integrity of the message body, as any changes to the body will result in a different hash, causing signature verification to fail.
Digest: SHA-256=4VYMyeX0tNLQ7opuAJeMECP3HgfLswAG3n+IqQprO0Q=
Content-Digest
The Content-Digest
header is defined in the draft-ietf-httpbis-digest-headers-11, which aims to obsolete the current RFC 3230 standard, which defines the Digest
header.
It serves a similar purpose as the Digest
header for message integrity. However, since the draft has not yet become an official standard, this guide uses the Digest
header for broader compatibility and adherence to the currently established standard.
Even though the standard is still a draft, you may use the Content-Digest
header instead of the Digest
header if you prefer.
Content-Digest: sha-256=:4VYMyeX0tNLQ7opuAJeMECP3HgfLswAG3n+IqQprO0Q=: