From acab94a8856ee8abf248faf663b05914db47d0cc Mon Sep 17 00:00:00 2001 From: Adam Kecskes Date: Tue, 7 Oct 2025 12:17:16 +0200 Subject: [PATCH 1/3] Support base64-encoded digest bytes wrapped in colons. --- lib/httpDigest.js | 5 ++++- test/unit/httpDigest.spec.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/httpDigest.js b/lib/httpDigest.js index c6550e8..6efe669 100644 --- a/lib/httpDigest.js +++ b/lib/httpDigest.js @@ -76,7 +76,10 @@ function _createMultihash({digest}) { } function _parseHeaderValue(headerValue) { - const [key, encodedDigest] = headerValue.split(/=(.+)/); + const [key, digest] = headerValue.split(/=(.+)/); + + // Unwrap in case the base64-encoded digest bytes are wrapped in colons + const encodedDigest = digest?.replace(/^:(.*):$/, '$1'); let algorithm; if(key === 'mh') { diff --git a/test/unit/httpDigest.spec.js b/test/unit/httpDigest.spec.js index 7bd3f46..2da3d0f 100644 --- a/test/unit/httpDigest.spec.js +++ b/test/unit/httpDigest.spec.js @@ -143,6 +143,21 @@ describe('http-signature-digest', () => { should.exist(verifyResult); verifyResult.verified.should.equal(true); }); + it('should verify a digest wrapped in colons', async () => { + const data = `{"hello": "world"}`; + const headerValue = + `sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:`; + let verifyResult; + let err; + try { + verifyResult = await verifyHeaderValue({data, headerValue}); + } catch(e) { + err = e; + } + should.not.exist(err); + should.exist(verifyResult); + verifyResult.verified.should.equal(true); + }); it('should verify false if verifying bad data object', async () => { const data = {hello: 'world'}; const headerValue = await createHeaderValue( From 0cd3533da32911d5f5b06ec2cd9d6a7e492b9d45 Mon Sep 17 00:00:00 2001 From: Adam Kecskes Date: Thu, 9 Oct 2025 08:59:35 +0200 Subject: [PATCH 2/3] Do not use incompatible RFC 9651 delimiter w/ multihash algorithm. --- lib/httpDigest.js | 8 +++++--- test/unit/httpDigest.spec.js | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/httpDigest.js b/lib/httpDigest.js index 6efe669..63e2add 100644 --- a/lib/httpDigest.js +++ b/lib/httpDigest.js @@ -78,11 +78,10 @@ function _createMultihash({digest}) { function _parseHeaderValue(headerValue) { const [key, digest] = headerValue.split(/=(.+)/); - // Unwrap in case the base64-encoded digest bytes are wrapped in colons - const encodedDigest = digest?.replace(/^:(.*):$/, '$1'); - + let encodedDigest; let algorithm; if(key === 'mh') { + encodedDigest = digest; // if `encodedDigest` starts with `uEi`, then it is a base64url-encoded // sha-256 multihash if(encodedDigest.startsWith('uEi')) { @@ -92,6 +91,9 @@ function _parseHeaderValue(headerValue) { `Only base64url-encoded, sha-256 multihash is supported.`); } } else { + // Unwrap in case the base64-encoded digest bytes are wrapped in colons + encodedDigest = digest?.replace(/^:(.*):$/, '$1'); + algorithm = key.replace('-', '').toLowerCase(); if(algorithm !== 'sha256') { throw new Error(`Algorithm "${algorithm}" is not supported.`); diff --git a/test/unit/httpDigest.spec.js b/test/unit/httpDigest.spec.js index 2da3d0f..162e414 100644 --- a/test/unit/httpDigest.spec.js +++ b/test/unit/httpDigest.spec.js @@ -158,6 +158,22 @@ describe('http-signature-digest', () => { should.exist(verifyResult); verifyResult.verified.should.equal(true); }); + it('should verify false if a multihash digest is wrapped in colons', + async () => { + const data = '{"hello":"world"}'; + const headerValue = + `mh=:uEiCTojlxqRTl6svwqNJRVM2jCcPBxy-7mRTUfGDzy2gViA:`; + let verifyResult; + let err; + try { + verifyResult = await verifyHeaderValue({data, headerValue}); + } catch(e) { + err = e; + } + should.not.exist(err); + should.exist(verifyResult); + verifyResult.verified.should.equal(false); + }); it('should verify false if verifying bad data object', async () => { const data = {hello: 'world'}; const headerValue = await createHeaderValue( From 29a35dd315599b6004e7a3ec3e4ea3e247ed005d Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 9 Oct 2025 11:44:18 -0400 Subject: [PATCH 3/3] Update changelog; clean up comments/style a bit. --- CHANGELOG.md | 7 +++++++ lib/httpDigest.js | 10 ++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d0c282..5dfc590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # @digitalbazaar/http-digest-header Changelog +## 2.3.0 - 2025-10-dd + +### Added +- Support parsing a digest from structured field value, i.e., expressed as + a base64-encoded byte array that is wrapped in colons; + see: https://datatracker.ietf.org/doc/html/rfc9651#name-byte-sequences. + ## 2.2.1 - 2025-10-08 ### Fixed diff --git a/lib/httpDigest.js b/lib/httpDigest.js index 63e2add..1bd8132 100644 --- a/lib/httpDigest.js +++ b/lib/httpDigest.js @@ -76,12 +76,13 @@ function _createMultihash({digest}) { } function _parseHeaderValue(headerValue) { - const [key, digest] = headerValue.split(/=(.+)/); + const [key, digestValue] = headerValue.split(/=(.+)/); let encodedDigest; let algorithm; if(key === 'mh') { - encodedDigest = digest; + encodedDigest = digestValue; + // if `encodedDigest` starts with `uEi`, then it is a base64url-encoded // sha-256 multihash if(encodedDigest.startsWith('uEi')) { @@ -91,8 +92,9 @@ function _parseHeaderValue(headerValue) { `Only base64url-encoded, sha-256 multihash is supported.`); } } else { - // Unwrap in case the base64-encoded digest bytes are wrapped in colons - encodedDigest = digest?.replace(/^:(.*):$/, '$1'); + // per RFC 9651, the digest value could be a structured field value, + // expressed as a base64-encoded byte array wrapped in colons + encodedDigest = digestValue?.replace(/^:(.*):$/, '$1'); algorithm = key.replace('-', '').toLowerCase(); if(algorithm !== 'sha256') {