http-message-signatures
Advanced tools
Comparing version 1.0.0 to 1.0.1
@@ -198,3 +198,3 @@ "use strict"; | ||
`signature="${signature.toString('base64')}"`, | ||
].join(', '); | ||
].join(','); | ||
return { | ||
@@ -201,0 +201,0 @@ ...message, |
{ | ||
"name": "http-message-signatures", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "HTTP message signature implementation", | ||
@@ -14,3 +14,3 @@ "main": "lib/index.js", | ||
"test": "mocha -r ts-node/register -r test/bootstrap.ts test/**/*.ts", | ||
"test:coverage": "nyc --reporter=lcov --reporter=text-summary npm run test" | ||
"test:coverage": "nyc --reporter=cobertura --reporter=text-summary npm run test" | ||
}, | ||
@@ -36,2 +36,10 @@ "repository": { | ||
"devDependencies": { | ||
"@commitlint/cli": "^17.6.7", | ||
"@commitlint/config-conventional": "^17.6.7", | ||
"@semantic-release/changelog": "^6.0.3", | ||
"@semantic-release/commit-analyzer": "^10.0.1", | ||
"@semantic-release/git": "^10.0.1", | ||
"@semantic-release/github": "^9.0.4", | ||
"@semantic-release/npm": "^10.0.4", | ||
"@semantic-release/release-notes-generator": "^11.0.4", | ||
"@tsconfig/node12": "^12.1.0", | ||
@@ -43,4 +51,4 @@ "@types/chai": "^4.3.3", | ||
"@types/sinon-chai": "^3.2.8", | ||
"@typescript-eslint/eslint-plugin": "^5.36.1", | ||
"@typescript-eslint/parser": "^5.36.1", | ||
"@typescript-eslint/eslint-plugin": "^6.2.1", | ||
"@typescript-eslint/parser": "^6.2.1", | ||
"chai": "^4.3.6", | ||
@@ -51,3 +59,4 @@ "eslint": "^8.24.0", | ||
"nyc": "^15.1.0", | ||
"sinon": "^14.0.0", | ||
"semantic-release": "^21.0.7", | ||
"sinon": "^15.2.0", | ||
"sinon-chai": "^3.7.0", | ||
@@ -58,4 +67,4 @@ "ts-node": "^10.9.1", | ||
"dependencies": { | ||
"structured-headers": "^0.5.0" | ||
"structured-headers": "^1.0.1" | ||
} | ||
} |
254
README.md
@@ -5,5 +5,9 @@ # HTTP Message Signatures | ||
Based on the draft specifications for HTTP Message Signatures, this library facilitates the signing | ||
of HTTP messages before being sent. | ||
This library provides a way to perform HTTP message signing as per the HTTP Working Group draft specification for | ||
[HTTP Message Signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures). | ||
HTTP Message Signatures are designed to provide a way to verify the authenticity and integrity of *parts* of an HTTP | ||
message by performing a deterministic serialisation of components of an HTTP Message. More details can be found in the | ||
specifications. | ||
## Specifications | ||
@@ -13,16 +17,17 @@ | ||
1. [HTTPbis](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures) | ||
2. [Cavage](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) and subsequent [RichAnna](https://datatracker.ietf.org/doc/html/draft-richanna-http-message-signatures) | ||
1. [HTTP Working Group spec](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures) | ||
2. [Network Working Group spec](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) | ||
## Approach | ||
As the Cavage/RichAnna specification is now expired and superseded by the HTTPbis one, this library takes a | ||
"HTTPbis-first" approach. This means that most support and maintenance will go into the HTTPbis | ||
implementation and syntax. The syntax is then back-ported to the as much as possible. | ||
As the Network WG specification is now expired and superseded by the HTTP WG one. This library takes a | ||
"HTTP WG" approach. This means that most support and maintenance will go into the HTTP WG | ||
implementation and syntax. The syntax is then back-ported to the legacy specification as much as possible. | ||
## Caveats | ||
The Cavage/RichAnna specifications have changed over time, introducing new features. The aim is to support | ||
the [latest version of the specification](https://datatracker.ietf.org/doc/html/draft-richanna-http-message-signatures) | ||
and not to try to support each version in isolation. | ||
The specifications are in draft and are liable to change over time, introducing new features and removing existing ones. | ||
The aim is to support the [latest version of the specification](https://datatracker.ietf.org/doc/html/draft-richanna-http-message-signatures) | ||
and not to try to support each version in isolation. However, this library was last updated against | ||
[revision 13](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures-13) of the HTTP WG specification. | ||
@@ -48,3 +53,3 @@ ## Limitations in compliance with the specification | ||
component is intended to be the equivalent to the "request target portion of the request line". | ||
See the specification for examples of what this means. In NodeJS, this line in requests is automatically | ||
See the specification for examples of what this means. In Node.js, this line in requests is automatically | ||
constructed for consumers, so it's not possible to know for certainty what this will be. For incoming | ||
@@ -67,3 +72,3 @@ requests, it is possible to extract, but for simplicity’s sake this library does not process the raw | ||
As described in [section 7.5.7](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures-13#section-7.5.7) | ||
it is expected that the NodeJS application has taken steps to ensure that headers are valid and not | ||
it is expected that the Node.js application has taken steps to ensure that headers are valid and not | ||
"garbage". For this library to take on that obligation would be to widen the scope of the library to | ||
@@ -74,43 +79,224 @@ a complete HTTP Message validator. | ||
### Signing a request | ||
> NB: These examples show the "minimal" signature implementation. That is, they provider a proof of possession of the | ||
key by the sender, but don't provide any integrity over the message. To do that, you must add HTTP fields / components | ||
to the signing object. Please see the tests for further examples, or the type definitions. | ||
### Signing a request (Node.js) | ||
This library has built-in signers/verifiers for Node.js using the native `cryto` package to perform all the required | ||
cryptographic operations. However, this is designed to be easily replaced with any other crypto library/runtime | ||
including `SubtleCrypto` or even a hosted KMS (Key Management Service). | ||
```js | ||
const { sign, createSigner } = require('http-message-signing'); | ||
const { httpbis: { signMessage }, createSigner } = require('http-message-signatures'); | ||
(async () => { | ||
const signedRequest = await sign({ | ||
// 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': 'text/plain', | ||
'content-type': 'application/json', | ||
'content-digest': 'sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCsyRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:', | ||
'content-length': '19', | ||
}, | ||
body: 'test', | ||
}, { | ||
components: [ | ||
'@method', | ||
'@authority', | ||
'content-type', | ||
], | ||
parameters: { | ||
created: Math.floor(Date.now() / 1000), | ||
}, | ||
keyId: 'my-hmac-secret', | ||
signer: createSigner('hmac-sha256'), | ||
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' | ||
} | ||
``` | ||
### Signing with your own signer | ||
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 implement a callable that has the `alg` prop set to a valid | ||
algorithm value. It's possible to use proprietary algorithm values if you have some internal signing | ||
logic you need to support. | ||
management service). To do so, you must create an object that conforms to the `SigningKey` interface. | ||
For example, using SubtleCrypto: | ||
```js | ||
const mySigner = async (data) => { | ||
return Buffer.from('my sig'); | ||
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)); | ||
}, | ||
}; | ||
} | ||
mySigner.alg = 'custom-123'; | ||
``` | ||
### Verifying a request | ||
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); | ||
``` | ||
### Verifying a response with request components | ||
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); | ||
``` | ||
### Verifying with your own verifier | ||
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); | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
112265
298
0
26
+ Addedstructured-headers@1.0.1(transitive)
- Removedstructured-headers@0.5.0(transitive)
Updatedstructured-headers@^1.0.1