HTTP message signature implementation
npm install http-message-signatures@request-target
cryto package to perform all the required
SubtleCrypto or even a hosted KMS (Key Management Service).
js
const { httpbis: { signMessage }, createSigner } = require('http-message-signatures');
(async () => {
// create a signing key using Node's built in crypto engine.
// you can supply RSA kets, ECDSA, or ED25519 keys.
const key = createSigner('sharedsecret', 'hmac-sha256', 'my-key-id');
// minimal signing of a request - more aspects of the request can be signed by providing additional
// parameters to the first argument of signMessage.
const signedRequest = await signMessage({
key,
}, {
method: 'POST',
url: 'https://example.com',
headers: {
'content-type': 'application/json',
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:',
'content-length': '19',
},
body: '{"hello": "world"}\n',
});
// signedRequest now has the Signature and Signature-Input headers
console.log(signedRequest);
})().catch(console.error);
`
This will output the following object (note the new Signature and Signature-Input headers):
`js
{
method: 'POST',
url: 'https://example.com',
headers: {
'content-type': 'application/json',
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:',
'content-length': '19',
'Signature': 'sig=:RkplfaUzQ4xIkSVP9hT+Y55yAYX9VwSeHmjS5X7d0fE=:',
'Signature-Input': 'sig=();keyid="my-key-id";alg="hmac-sha256";created=1700669009;expires=1700669309'
},
body: '{"hello": "world"}\n'
}
`
$3
It's possible to provide your own signer (this is useful if you're using a secure enclave or key
management service). To do so, you must create an object that conforms to the SigningKey interface.
For example, using SubtleCrypto:
`js
const { webcrypto: crypto } = require('node:crypto');
function createMySigner() {
return {
id: 'my-key-id',
alg: 'hmac-sha256',
async sign(data) {
const key = await crypto.subtle.importKey('raw', Buffer.from('sharedsecret'), {
name: 'HMAC',
hash: 'SHA-256',
}, true, ['sign', 'verify']);
return Buffer.from(await crypto.subtle.sign('HMAC', key, data));
},
};
}
`
$3
Verifying a message requires that there is a key-store that can be used to look-up keys based on the signature parameters,
for example via the signatures keyid.
`js
const { httpbis: { verifyMessage }, createVerifier } = require('http-message-signatures');
(async () => {
// an example keystore for looking up keys by ID
const keys = new Map();
keys.set('my-key-id', {
id: 'my-key-id',
algs: ['hmac-sha256'],
// as with signing, you can provide your own verifier here instead of using the built-in helpers
verify: createVerifier('sharedsecret', 'hmac-sha256'),
});
// minimal verification
const verified = await verifyMessage({
// logic for finding a key based on the signature parameters
async keyLookup(params) {
const keyId = params.keyid;
// lookup and return key - note, we could also lookup using the alg too (params.alg)
// if there is no key, verifyMessage() will throw an error
return keys.get(keyId);
},
}, {
method: 'POST',
url: 'https://example.com',
headers: {
'content-type': 'application/json',
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:',
'content-length': '19',
'signature': 'sig=:RkplfaUzQ4xIkSVP9hT+Y55yAYX9VwSeHmjS5X7d0fE=:',
'signature-input': 'sig=();keyid="my-key-id";alg="hmac-sha256";created=1700669009;expires=1700669309',
},
});
console.log(verified);
})().catch(console.error);
`
$3
The HTTP Message Signatures specification allows for responses to reference parts of the request and incorporate them
within the signature, tightly binding the response to the request. If you expect that request bound signatures will be
used, you can provide the request as an optional parameter to the verifyMessage() method:
`js
const { httpbis: { verifyMessage }, createVerifier } = require('http-message-signatures');
(async () => {
// an example keystore for looking up keys by ID
const keys = new Map();
keys.set('my-key-id', {
id: 'my-key-id',
alg: 'hmac-sha256',
// as with signing, you can provide your own verifier here instead of using the built-in helpers
verify: createVerifier('sharedsecret', 'hmac-sha256'),
});
// minimal verification
const verified = await verifyMessage({
// logic for finding a key based on the signature parameters
async keyLookup(params) {
const keyId = params.keyid;
// lookup and return key - note, we could also lookup using the alg too (params.alg)
// if there is no key, verifyMessage() will throw an error
return keys.get(keyId);
},
}, {
// the response
status: 200,
headers: {
'content-type': 'application/json',
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:',
'content-length': '19',
'signature': 'sig=:RkplfaUzQ4xIkSVP9hT+Y55yAYX9VwSeHmjS5X7d0fE=:',
'signature-input': 'sig=();keyid="my-key-id";alg="hmac-sha256";created=1700669009;expires=1700669309',
},
}, {
// the request
method: 'POST',
url: 'https://example.com',
headers: {
'content-type': 'application/json',
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:',
'content-length': '19',
'signature': 'sig=:RkplfaUzQ4xIkSVP9hT+Y55yAYX9VwSeHmjS5X7d0fE=:',
'signature-input': 'sig=();keyid="my-key-id";alg="hmac-sha256";created=1700669009;expires=1700669309',
},
});
console.log(verified);
})().catch(console.error);
`
$3
As with signing, it's possible to provide your own verifier (this is useful if you're running in an environment that
may not have access to Node.js' native crypto package). To do so, you must create an object that conforms to the
VerifyingKey interface.
For example, using SubtleCrypto:
`js
const { webcrypto: crypto } = require('node:crypto');
const { httpbis: { verifyMessage } } = require('http-message-signatures');
(async () => {
// an example keystore for looking up keys by ID
const keys = new Map();
keys.set('my-key-id', {
id: 'my-key-id',
alg: 'hmac-sha256',
// provide a custom verify function
async verify(data, signature, parameters) {
const key = await crypto.subtle.importKey('raw', Buffer.from('sharedsecret'), {
name: 'HMAC',
hash: 'SHA-256',
}, true, ['sign', 'verify']);
return crypto.subtle.verify('HMAC', key, signature, data);
},
});
// minimal verification
const verified = await verifyMessage({
// logic for finding a key based on the signature parameters
async keyLookup(params) {
const keyId = params.keyid;
// lookup and return key - note, we could also lookup using the alg too (params.alg)
// if there is no key, verifyMessage() will throw an error
return keys.get(keyId);
},
}, {
// the request
method: 'POST',
url: 'https://example.com',
headers: {
'content-type': 'application/json',
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:',
'content-length': '19',
'signature': 'sig=:RkplfaUzQ4xIkSVP9hT+Y55yAYX9VwSeHmjS5X7d0fE=:',
'signature-input': 'sig=();keyid="my-key-id";alg="hmac-sha256";created=1700669009;expires=1700669309',
},
});
console.log(verified);
})().catch(console.error);
``