@interop/http-digest-header 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2019-2025, Digital Bazaar, Inc.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # HTTP Digest Header Library _(@interop/http-digest-header)_
2
+
3
+ [![Node.js CI](https://github.com/interop-alliance/http-digest-header/actions/workflows/main.yml/badge.svg)](https://github.com/interop-alliance/http-digest-header/actions/workflows/main.yml)
4
+
5
+ > JavaScript library (Node.js, browser and React Native) for creating and verifying Digest headers for HTTP Signatures
6
+
7
+ ## Table of Contents
8
+
9
+ - [Background](#background)
10
+ - [Install](#install)
11
+ - [Usage](#usage)
12
+ - [Contribute](#contribute)
13
+ - [Commercial Support](#commercial-support)
14
+ - [License](#license)
15
+
16
+ ## Background
17
+
18
+ **FORKED FROM**: https://github.com/digitalbazaar/http-digest-header to provide
19
+ support for React Native, and add TypeScript types.
20
+
21
+ * For React Native use: Peer dependency `crypto-expo` is required.
22
+
23
+ Originally, this library was implemented based on the `Digest` header as
24
+ mentioned in **[HTTP Signatures IETF draft](https://tools.ietf.org/html/draft-cavage-http-signatures)**.
25
+
26
+ Since then, the `Digest` header got its own standards-track spec, at
27
+ https://tools.ietf.org/html/draft-ietf-httpbis-digest-headers.
28
+
29
+ This is a library specifically for creating and verifying the `Digest:` header,
30
+ for use with HTTP Signatures and similar mechanisms.
31
+
32
+ It's intended to be isomorphic (for use both in the browser and server-side,
33
+ with Node.js).
34
+
35
+ ## Install
36
+
37
+ - Browsers and Node.js 22+ supported.
38
+ - [Web Crypto API][] required. Older browsers and Node.js 14 must use a
39
+ polyfill.
40
+
41
+ To install from `npm`:
42
+
43
+ ```
44
+ npm install @interop/http-digest-header
45
+ ```
46
+
47
+ To install locally (for development):
48
+
49
+ ```
50
+ git clone https://github.com/interop-alliance/http-digest-header.git
51
+ cd http-digest-header
52
+ npm install
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ```js
58
+ import * as httpDigest from '@interop/http-digest-header';
59
+
60
+ const data = `{"hello": "world"}`;
61
+
62
+ const headerValue = await httpDigest.
63
+ createHeaderValue({data, algorithm: 'sha256', useMultihash: false});
64
+ // -> SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
65
+
66
+ const dataToVerify1 = `{"hello": "world"}`;
67
+ const dataToVerify2 = `{"hello": "planet earth"}`;
68
+
69
+ const verifyResult = await httpDigest.verifyHeaderValue({data: dataToVerify1, headerValue});
70
+ // -> { verified: true }
71
+ const verifyResult = await httpDigest.verifyHeaderValue({data: dataToVerify2, headerValue});
72
+ // -> { verified: false }
73
+ ```
74
+
75
+ ## Contribute
76
+
77
+ Please follow the existing code style.
78
+
79
+ PRs accepted.
80
+
81
+ If editing the Readme, please conform to the
82
+ [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
83
+
84
+ ## Commercial Support
85
+
86
+ Commercial support for this library is available upon request from
87
+ Digital Bazaar: support@digitalbazaar.com
88
+
89
+ ## License
90
+
91
+ [BSD-3-Clause](LICENSE.md) © 2019-2025 Digital Bazaar
92
+
93
+ [Web Crypto API]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as Crypto from 'expo-crypto';
5
+
6
+ export async function sha256(data) {
7
+ return new Uint8Array(
8
+ await Crypto.digest(Crypto.CryptoDigestAlgorithm.SHA256, data));
9
+ }
package/lib/digest.js ADDED
@@ -0,0 +1,8 @@
1
+ /*!
2
+ * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ const {crypto} = globalThis;
5
+
6
+ export async function sha256(data) {
7
+ return new Uint8Array(await crypto.subtle.digest({name: 'SHA-256'}, data));
8
+ }
@@ -0,0 +1,127 @@
1
+ /*!
2
+ * Copyright (c) 2019-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import {base64Encode, base64urlEncode} from './util.js';
5
+ import {sha256} from './digest.js';
6
+
7
+ /**
8
+ * Creates a value suitable for the HTTP `Digest` header.
9
+ *
10
+ * @param {object} options - The options to use.
11
+ * @param {string|object|Blob|Uint8Array} [options.data] - The data to be
12
+ * hashed (a request or response body).
13
+ * @param {string} [options.algorithm] - Hash algorithm to use.
14
+ * (e.g. 'sha256').
15
+ * @param {boolean} [options.useMultihash=true] - Whether to encode via
16
+ * multihash; if false, the hash will be base64-encoded (non-url).
17
+ *
18
+ * @returns {Promise<string>} Resolves to `Digest` header value.
19
+ */
20
+ export async function createHeaderValue({
21
+ data, algorithm = 'sha256', useMultihash = true
22
+ } = {}) {
23
+ const {key, encodedDigest} = await _createHeaderValueComponents({
24
+ data, algorithm, useMultihash
25
+ });
26
+ return `${key}=${encodedDigest}`;
27
+ }
28
+
29
+ /**
30
+ * Verifies the HTTP `Digest` header value against the given HTTP body `data`.
31
+ *
32
+ * @param {object} options - The options to use.
33
+ * @param {string|object|Blob|Uint8Array} options.data - The data to be
34
+ * verified (a request or response body).
35
+ * @param {string} options.headerValue - The digest header value to verify
36
+ * the data against.
37
+ *
38
+ * @returns {Promise<{verified: boolean, error?: Error}>}
39
+ */
40
+ export async function verifyHeaderValue({data, headerValue}) {
41
+ try {
42
+ const {key, algorithm, encodedDigest} = _parseHeaderValue(headerValue);
43
+ const {encodedDigest: expectedDigest} = await _createHeaderValueComponents({
44
+ data, algorithm, useMultihash: key === 'mh'
45
+ });
46
+ return {verified: encodedDigest === expectedDigest};
47
+ } catch(error) {
48
+ return {verified: false, error};
49
+ }
50
+ }
51
+
52
+ async function _createHeaderValueComponents({
53
+ data, algorithm = 'sha256', useMultihash = true
54
+ }) {
55
+ if(algorithm !== 'sha256') {
56
+ throw new Error(`Algorithm "${algorithm}" is not supported.`);
57
+ }
58
+ const digest = await _getDigest({data, algorithm});
59
+ if(useMultihash) {
60
+ return {key: 'mh', encodedDigest: _createMultihash({digest})};
61
+ }
62
+ return {key: 'SHA-256', encodedDigest: base64Encode(digest)};
63
+ }
64
+
65
+ function _createMultihash({digest}) {
66
+ // format as multihash digest
67
+ // sha2-256: 0x12, length: 32 (0x20), digest value
68
+ const mh = new Uint8Array(34);
69
+ mh[0] = 0x12;
70
+ mh[1] = 0x20;
71
+ mh.set(digest, 2);
72
+ // encode multihash using multibase, base64url: `u`
73
+ return `u${base64urlEncode(mh)}`;
74
+ }
75
+
76
+ function _parseHeaderValue(headerValue) {
77
+ const [key, digestValue] = headerValue.split(/=(.+)/);
78
+
79
+ let encodedDigest;
80
+ let algorithm;
81
+ if(key === 'mh') {
82
+ encodedDigest = digestValue;
83
+
84
+ // if `encodedDigest` starts with `uEi`, then it is a base64url-encoded
85
+ // sha-256 multihash
86
+ if(encodedDigest.startsWith('uEi')) {
87
+ algorithm = 'sha256';
88
+ } else {
89
+ throw new Error(
90
+ `Only base64url-encoded, sha-256 multihash is supported.`);
91
+ }
92
+ } else {
93
+ // per RFC 9651, the digest value could be a structured field value,
94
+ // expressed as a base64-encoded byte array wrapped in colons
95
+ encodedDigest = digestValue?.replace(/^:(.*):$/, '$1');
96
+
97
+ algorithm = key.replace('-', '').toLowerCase();
98
+ if(algorithm !== 'sha256') {
99
+ throw new Error(`Algorithm "${algorithm}" is not supported.`);
100
+ }
101
+ }
102
+ return {key, algorithm, encodedDigest};
103
+ }
104
+
105
+ async function _getDigest({data, algorithm}) {
106
+ data = await _normalizeData(data);
107
+ if(algorithm === 'sha256') {
108
+ return sha256(data);
109
+ }
110
+ throw new Error(`Algorithm "${algorithm}" is not unsupported.`);
111
+ }
112
+
113
+ // normalize all inputs to a `Uint8Array` for hashing
114
+ async function _normalizeData(data) {
115
+ if(data instanceof Uint8Array) {
116
+ return data;
117
+ }
118
+ if(data instanceof Blob) {
119
+ // `Blob.bytes()` is only available in node.js 22+;
120
+ // fallback to `Blob.arrayBuffer()`
121
+ return data?.bytes?.() ?? data.arrayBuffer();
122
+ }
123
+ if(typeof data !== 'string') {
124
+ data = JSON.stringify(data);
125
+ }
126
+ return (new TextEncoder()).encode(data);
127
+ }
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ /*!
2
+ * Copyright (c) 2019-2020 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ export {createHeaderValue, verifyHeaderValue} from './httpDigest.js';
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ export function base64Encode(bytes) {
5
+ if(bytes.toBase64) {
6
+ return bytes.toBase64();
7
+ }
8
+ return btoa(Array.from(bytes, b => String.fromCodePoint(b)).join(''));
9
+ }
10
+
11
+ export function base64urlEncode(bytes) {
12
+ if(bytes.toBase64) {
13
+ return bytes.toBase64({alphabet: 'base64url', omitPadding: true});
14
+ }
15
+ return base64Encode(bytes)
16
+ .replace(/\+/g, '-')
17
+ .replace(/\//g, '_')
18
+ .replaceAll('=', '');
19
+ }
package/lib/util.js ADDED
@@ -0,0 +1,12 @@
1
+ /*!
2
+ * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ export function base64Encode(bytes) {
5
+ return Buffer.from(bytes.buffer, bytes.offset, bytes.length)
6
+ .toString('base64');
7
+ }
8
+
9
+ export function base64urlEncode(bytes) {
10
+ return Buffer.from(bytes.buffer, bytes.offset, bytes.length)
11
+ .toString('base64url');
12
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@interop/http-digest-header",
3
+ "version": "2.3.2",
4
+ "description": "Minimal isomorphic library (Node.js and browser) for creating and verifying Digest headers for HTTP Signatures",
5
+ "license": "BSD-3-Clause",
6
+ "scripts": {
7
+ "test": "npm run test-node",
8
+ "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 --require test/test-mocha.js test/**/*.spec.js",
9
+ "test-karma": "karma start test/karma.conf.cjs",
10
+ "coverage": "cross-env NODE_ENV=test c8 npm test",
11
+ "coverage-ci": "cross-env NODE_ENV=test c8 --reporter=lcovonly --reporter=text-summary --reporter=text npm test",
12
+ "coverage-report": "c8 report",
13
+ "build": "tsc",
14
+ "lint": "eslint ."
15
+ },
16
+ "type": "module",
17
+ "exports": "./lib/index.js",
18
+ "types": "./types/index.d.ts",
19
+ "browser": {
20
+ "./lib/util.js": "./lib/util-browser.js"
21
+ },
22
+ "react-native": {
23
+ "./lib/util.js": "./lib/util-browser.js",
24
+ "./lib/digest.js": "./lib/digest-reactnative.js"
25
+ },
26
+ "files": [
27
+ "lib/**/*.js",
28
+ "types/**/*.d.ts"
29
+ ],
30
+ "peerDependencies": {
31
+ "expo-crypto": ">=13"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "expo-crypto": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "devDependencies": {
39
+ "c8": "^10.1.3",
40
+ "chai": "^4.3.6",
41
+ "cross-env": "^10.1.0",
42
+ "eslint": "^8.57.1",
43
+ "eslint-config-digitalbazaar": "^5.2.0",
44
+ "eslint-plugin-jsdoc": "^50.8.0",
45
+ "eslint-plugin-unicorn": "^56.0.1",
46
+ "karma": "^6.3.20",
47
+ "karma-chai": "^0.1.0",
48
+ "karma-chrome-launcher": "^3.1.1",
49
+ "karma-mocha": "^2.0.1",
50
+ "karma-mocha-reporter": "^2.2.5",
51
+ "karma-sourcemap-loader": "^0.3.8",
52
+ "karma-webpack": "^5.0.0",
53
+ "mocha": "^11.7.4",
54
+ "mocha-lcov-reporter": "^1.3.0",
55
+ "typescript": "^6.0.3",
56
+ "webpack": "^5.73.0"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "https://github.com/interop-alliance/http-digest-header"
61
+ },
62
+ "keywords": [
63
+ "http",
64
+ "signatures",
65
+ "digest"
66
+ ],
67
+ "author": {
68
+ "name": "Digital Bazaar, Inc.",
69
+ "email": "support@digitalbazaar.com",
70
+ "url": "https://digitalbazaar.com/"
71
+ },
72
+ "bugs": {
73
+ "url": "https://github.com/digitalbazaar/http-digest-header/issues"
74
+ },
75
+ "homepage": "https://github.com/digitalbazaar/http-digest-header",
76
+ "engines": {
77
+ "node": ">=20"
78
+ },
79
+ "c8": {
80
+ "reporter": [
81
+ "lcov",
82
+ "text-summary",
83
+ "text"
84
+ ]
85
+ }
86
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Creates a value suitable for the HTTP `Digest` header.
3
+ *
4
+ * @param {object} options - The options to use.
5
+ * @param {string|object|Blob|Uint8Array} [options.data] - The data to be
6
+ * hashed (a request or response body).
7
+ * @param {string} [options.algorithm] - Hash algorithm to use.
8
+ * (e.g. 'sha256').
9
+ * @param {boolean} [options.useMultihash=true] - Whether to encode via
10
+ * multihash; if false, the hash will be base64-encoded (non-url).
11
+ *
12
+ * @returns {Promise<string>} Resolves to `Digest` header value.
13
+ */
14
+ export function createHeaderValue({ data, algorithm, useMultihash }?: {
15
+ data?: string | object | Blob | Uint8Array;
16
+ algorithm?: string;
17
+ useMultihash?: boolean;
18
+ }): Promise<string>;
19
+ /**
20
+ * Verifies the HTTP `Digest` header value against the given HTTP body `data`.
21
+ *
22
+ * @param {object} options - The options to use.
23
+ * @param {string|object|Blob|Uint8Array} options.data - The data to be
24
+ * verified (a request or response body).
25
+ * @param {string} options.headerValue - The digest header value to verify
26
+ * the data against.
27
+ *
28
+ * @returns {Promise<{verified: boolean, error?: Error}>}
29
+ */
30
+ export function verifyHeaderValue({ data, headerValue }: {
31
+ data: string | object | Blob | Uint8Array;
32
+ headerValue: string;
33
+ }): Promise<{
34
+ verified: boolean;
35
+ error?: Error;
36
+ }>;
@@ -0,0 +1 @@
1
+ export { createHeaderValue, verifyHeaderValue } from "./httpDigest.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Creates a value suitable for the HTTP `Digest` header.
3
+ *
4
+ * @param {object} options - The options to use.
5
+ * @param {string|object|Blob|Uint8Array} [options.data] - The data to be
6
+ * hashed (a request or response body).
7
+ * @param {string} [options.algorithm] - Hash algorithm to use.
8
+ * (e.g. 'sha256').
9
+ * @param {boolean} [options.useMultihash=true] - Whether to encode via
10
+ * multihash; if false, the hash will be base64-encoded (non-url).
11
+ *
12
+ * @returns {Promise<string>} Resolves to `Digest` header value.
13
+ */
14
+ export function createHeaderValue({ data, algorithm, useMultihash }?: {
15
+ data?: string | object | Blob | Uint8Array;
16
+ algorithm?: string;
17
+ useMultihash?: boolean;
18
+ }): Promise<string>;
19
+ /**
20
+ * Verifies the HTTP `Digest` header value against the given HTTP body `data`.
21
+ *
22
+ * @param {object} options - The options to use.
23
+ * @param {string|object|Blob|Uint8Array} options.data - The data to be
24
+ * verified (a request or response body).
25
+ * @param {string} options.headerValue - The digest header value to verify
26
+ * the data against.
27
+ *
28
+ * @returns {Promise<{verified: boolean, error?: Error}>}
29
+ */
30
+ export function verifyHeaderValue({ data, headerValue }: {
31
+ data: string | object | Blob | Uint8Array;
32
+ headerValue: string;
33
+ }): Promise<{
34
+ verified: boolean;
35
+ error?: Error;
36
+ }>;
@@ -0,0 +1 @@
1
+ export { createHeaderValue, verifyHeaderValue } from "./httpDigest.js";
@@ -0,0 +1,5 @@
1
+ /*!
2
+ * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ export function base64Encode(bytes: any): any;
5
+ export function base64urlEncode(bytes: any): any;
@@ -0,0 +1,5 @@
1
+ /*!
2
+ * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ export function base64Encode(bytes: any): any;
5
+ export function base64urlEncode(bytes: any): any;