From 6424feff75345015cda1be08949fd7b7e1f32156 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 10:49:08 -0500 Subject: [PATCH 1/7] Add test fixture for invalid proofValue prefix. --- tests/suites/algorithms-sd.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/suites/algorithms-sd.js b/tests/suites/algorithms-sd.js index 7ccbae2a..b20837bc 100644 --- a/tests/suites/algorithms-sd.js +++ b/tests/suites/algorithms-sd.js @@ -173,15 +173,11 @@ export function sd2023Algorithms({ 'MUST be raised and SHOULD convey an error type of ' + 'PROOF_VERIFICATION_ERROR.', async function() { this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=produced%20as%20output.-,If%20the%20proofValue%20string%20does%20not%20start%20with%20u%2C%20indicating%20that%20it%20is%20a%20multibase%2Dbase64url%2Dno%2Dpad%2Dencoded%20value%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.,-Initialize%20decodedProofValue%20to'; - this.test.cell.skipMessage = 'Not Implemented'; - this.skip(); - /* await assertions.verificationFail({ verifier, - credential: fixtures.get('invalidProofValuePrefix'), + credential: fixtures.get(keyType).get('invalidProofValuePrefix'), reason: 'Should not verify VC with invalid proofValue prefix' }); - */ }); it('If the decodedProofValue does not start with the ECDSA-SD ' + 'base proof header bytes 0xd9, 0x5d, and 0x00, an error MUST be ' + @@ -190,13 +186,7 @@ export function sd2023Algorithms({ this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=If%20the%20decodedProofValue%20does%20not%20start%20with%20the%20ECDSA%2DSD%20base%20proof%20header%20bytes%200xd9%2C%200x5d%2C%20and%200x00%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.'; this.test.cell.skipMessage = 'Not Implemented'; this.skip(); - /* - await assertions.verificationFail({ - verifier, - credential: fixtures.get('invalidBaseProofHeader'), - reason: 'Should not verify VC with invalid base proof header' - }); - */ + assertIssuer(); }); it('If the decodedProofValue does not start with the ECDSA-SD ' + 'disclosure proof header bytes 0xd9, 0x5d, and 0x01, an error ' + @@ -367,5 +357,18 @@ async function _setup({ suite: cborTagSuites.suite, selectiveSuite: invalidCborTagProxy(cborTagSuites.selectiveSuite) })); + const securedCredential = await issueCloned({ + credential: _credential, + ...getSuites({ + signer, + suiteName, + selectivePointers, + mandatoryPointers + }) + }); + const nonbase64ProofValue = structuredClone(securedCredential); + nonbase64ProofValue.proof.proofValue = + nonbase64ProofValue.proof.proofValue.substring(1); + credentials.set('invalidProofValuePrefix', nonbase64ProofValue); return credentials; } From f2563cd3d7c2f21caca7fa1c515a3c1ba866e54b Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 11:56:40 -0500 Subject: [PATCH 2/7] Move base proof assertion to assertions and reuse. --- tests/assertions.js | 34 ++++++++++++++++++++++++++++++ tests/suites/algorithms-sd.js | 39 +++++------------------------------ 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/tests/assertions.js b/tests/assertions.js index 5072e82f..958bd0cf 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -13,6 +13,7 @@ import {klona} from 'klona'; import varint from 'varint'; const should = chai.should(); +const {expect} = chai; // RegExp with bs58 characters in it const bs58 = @@ -182,3 +183,36 @@ export function itRejectsInvalidCryptosuite(expectedValidSuites, { await verificationFail({credential, verifier: endpoint}); }); } + +export async function shouldBeBaseProofValue({proof, name}) { + expect( + proof, + `Expected VC from issuer ${name} to have an ' + + '"ecdsa-sd-2023" proof`).to.exist; + expect( + proof.proofValue, + `Expected VC from issuer ${name} to have a ' + + '"proof.proofValue"` + ).to.exist; + expect( + proof.proofValue, + `Expected VC "proof.proofValue" from issuer ${name} to be ` + + 'a string.' + ).to.be.a.string; + //Ensure the proofValue string starts with u, indicating that it + //is a multibase-base64url-no-pad-encoded value, throwing an + //error if it does not. + expect( + proof.proofValue.startsWith('u'), + `Expected "proof.proofValue" to start with u received ` + + `${proof.proofValue[0]}`).to.be.true; + // now test the encoding which is bs64 url no pad for this suite + expect( + shouldBeBs64UrlNoPad(proof.proofValue), + 'Expected "proof.proofValue" to be bs64 url no pad encoded.' + ).to.be.true; + await shouldHaveHeaderBytes( + proof.proofValue, + new Uint8Array([0xd9, 0x5d, 0x00]) + ); +} diff --git a/tests/suites/algorithms-sd.js b/tests/suites/algorithms-sd.js index b20837bc..9081e75f 100644 --- a/tests/suites/algorithms-sd.js +++ b/tests/suites/algorithms-sd.js @@ -8,6 +8,7 @@ import { issueCloned } from 'data-integrity-test-suite-assertion'; import { + shouldBeBaseProofValue, shouldBeBs64UrlNoPad, shouldHaveHeaderBytes, } from '../assertions.js'; @@ -93,38 +94,8 @@ export function sd2023Algorithms({ 'specific cryptosuite proof generation algorithm.', async function() { this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#base-proof-serialization-ecdsa-sd-2023:~:text=When%20generating%20ECDSA%20signatures%2C%20the%20signature%20value%20MUST%20be%20expressed%20according%20to%20section%207'; assertIssuer(); - const _proof = proofs.find(p => - p?.cryptosuite === 'ecdsa-sd-2023'); - expect( - _proof, - `Expected VC from issuer ${name} to have an ' + - '"ecdsa-sd-2023" proof`).to.exist; - expect( - _proof.proofValue, - `Expected VC from issuer ${name} to have a ' + - '"proof.proofValue"` - ).to.exist; - expect( - _proof.proofValue, - `Expected VC "proof.proofValue" from issuer ${name} to be ` + - 'a string.' - ).to.be.a.string; - //Ensure the proofValue string starts with u, indicating that it - //is a multibase-base64url-no-pad-encoded value, throwing an - //error if it does not. - expect( - _proof.proofValue.startsWith('u'), - `Expected "proof.proofValue" to start with u received ` + - `${_proof.proofValue[0]}`).to.be.true; - // now test the encoding which is bs64 url no pad for this suite - expect( - shouldBeBs64UrlNoPad(_proof.proofValue), - 'Expected "proof.proofValue" to be bs64 url no pad encoded.' - ).to.be.true; - await shouldHaveHeaderBytes( - _proof.proofValue, - new Uint8Array([0xd9, 0x5d, 0x00]) - ); + const proof = proofs.find(p => p?.cryptosuite === 'ecdsa-sd-2023'); + await shouldBeBaseProofValue({proof, name}); }); it('If source has an id that is not a blank node identifier, set ' + 'selection.id to its value. Note: All non-blank node identifiers ' + @@ -184,9 +155,9 @@ export function sd2023Algorithms({ 'raised and SHOULD convey an error type of PROOF_VERIFICATION_ERROR.', async function() { this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=If%20the%20decodedProofValue%20does%20not%20start%20with%20the%20ECDSA%2DSD%20base%20proof%20header%20bytes%200xd9%2C%200x5d%2C%20and%200x00%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.'; - this.test.cell.skipMessage = 'Not Implemented'; - this.skip(); assertIssuer(); + const proof = proofs.find(p => p?.cryptosuite === 'ecdsa-sd-2023'); + await shouldBeBaseProofValue({proof, name}); }); it('If the decodedProofValue does not start with the ECDSA-SD ' + 'disclosure proof header bytes 0xd9, 0x5d, and 0x01, an error ' + From 31e6d795b5db3ac8a050bfea41a6729fed2ebc0b Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 12:06:45 -0500 Subject: [PATCH 3/7] Start on invalid disclosure proofValue header. --- tests/suites/algorithms-sd.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/suites/algorithms-sd.js b/tests/suites/algorithms-sd.js index 9081e75f..f8480561 100644 --- a/tests/suites/algorithms-sd.js +++ b/tests/suites/algorithms-sd.js @@ -7,16 +7,11 @@ import { generators, issueCloned } from 'data-integrity-test-suite-assertion'; -import { - shouldBeBaseProofValue, - shouldBeBs64UrlNoPad, - shouldHaveHeaderBytes, -} from '../assertions.js'; -import {createInitialVc} from '../helpers.js'; -import {expect} from 'chai'; +import {createInitialVc, getBs64UrlBytes} from '../helpers.js'; import {getMultiKey} from '../vc-generator/key-gen.js'; import {getSuites} from './helpers.js'; import {invalidCborTagProxy} from './proxies.js'; +import {shouldBeBaseProofValue} from '../assertions.js'; export function sd2023Algorithms({ credential, @@ -164,16 +159,12 @@ export function sd2023Algorithms({ 'MUST be raised and SHOULD convey an error type of ' + 'PROOF_VERIFICATION_ERROR.', async function() { this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=If%20the%20decodedProofValue%20does%20not%20start%20with%20the%20ECDSA%2DSD%20disclosure%20proof%20header%20bytes%200xd9%2C%200x5d%2C%20and%200x01%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.'; - this.test.cell.skipMessage = 'Not Implemented'; - this.skip(); - /* await assertions.verificationFail({ verifier, credential: fixtures.get('invalidDisclosureProofHeader'), reason: 'Should not verify VC with invalid disclosure proof ' + 'header' }); - */ }); it('If the result is not an array of the following five elements ' + '— a byte array of length 64; a byte array of length 36; an array ' + @@ -341,5 +332,9 @@ async function _setup({ nonbase64ProofValue.proof.proofValue = nonbase64ProofValue.proof.proofValue.substring(1); credentials.set('invalidProofValuePrefix', nonbase64ProofValue); + const invalidProofValueHeader = structuredClone(securedCredential); + const disclosureBytes = getBs64UrlBytes( + invalidProofValueHeader?.proof?.proofValue); + credentials.set('invalidDisclosureProofHeader', null); return credentials; } From 6ae2f34bd817f8a50248a4aeb36f0b24a695872b Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 12:28:49 -0500 Subject: [PATCH 4/7] Fix: getBs funcs are not async. --- tests/assertions.js | 14 +++++++------- tests/helpers.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/assertions.js b/tests/assertions.js index 958bd0cf..b0ca97fc 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -30,18 +30,18 @@ export const proofBytes = { 'P-384': 96 }; -export const shouldHaveByteLength = async ( +export const shouldHaveByteLength = ( multibaseString, expectedByteLength ) => { - const bytes = await getBs58Bytes(multibaseString); + const bytes = getBs58Bytes(multibaseString); bytes.length.should.eql( expectedByteLength, `Expected byteLength of ${expectedByteLength} received ${bytes.length}.`); }; -export const shouldHaveHeaderBytes = async (multibaseString, headerBytes) => { - const bytes = await getBs64UrlBytes(multibaseString); +export const shouldHaveHeaderBytes = (multibaseString, headerBytes) => { + const bytes = getBs64UrlBytes(multibaseString); const actualHeaderBytes = Array.from(bytes.slice(0, headerBytes.length)); actualHeaderBytes.should.eql( Array.from(headerBytes), @@ -54,7 +54,7 @@ export const shouldBeMulticodecEncoded = async s => { if(s.startsWith(multibaseMultikeyHeaderP256)) { // example of a P-256 publicKeyMultibase - // zDnaepHgv4AU1btQ8dp6EYbvgJ6M1ovzKnSpXJUPU2hshXLvp - const bytes = await getBs58Bytes(s); + const bytes = getBs58Bytes(s); bytes.length.should.equal(35); // bytes example => Uint8Array(35) [ // 128, 36, 3, 98, 121, 153, 205, 199, @@ -72,7 +72,7 @@ export const shouldBeMulticodecEncoded = async s => { } if(s.startsWith(multibaseMultikeyHeaderP384)) { - const bytes = await getBs58Bytes(s); + const bytes = getBs58Bytes(s); bytes.length.should.equal(51); // get the two-byte prefix const prefix = Array.from(bytes.slice(0, 2)); @@ -211,7 +211,7 @@ export async function shouldBeBaseProofValue({proof, name}) { shouldBeBs64UrlNoPad(proof.proofValue), 'Expected "proof.proofValue" to be bs64 url no pad encoded.' ).to.be.true; - await shouldHaveHeaderBytes( + shouldHaveHeaderBytes( proof.proofValue, new Uint8Array([0xd9, 0x5d, 0x00]) ); diff --git a/tests/helpers.js b/tests/helpers.js index 04d907dd..70db1ba5 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -14,8 +14,8 @@ export const require = createRequire(import.meta.url); // takes a multibase string starting with z lops the z off // and gets the bytes -export const getBs58Bytes = async s => bs58.decode(s.slice(1)); -export const getBs64UrlBytes = async s => bs64.decode(s.slice(1)); +export const getBs58Bytes = s => bs58.decode(s.slice(1)); +export const getBs64UrlBytes = s => bs64.decode(s.slice(1)); // Javascript's default ISO timestamp contains milliseconds. // This lops off the MS part of the UTC RFC3339 TimeStamp and replaces From 94171726e9820efd800d739643fbd6be040d3f0a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 12:32:02 -0500 Subject: [PATCH 5/7] Restore missing expect to sd suite. --- tests/suites/algorithms-sd.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/suites/algorithms-sd.js b/tests/suites/algorithms-sd.js index f8480561..421ebc48 100644 --- a/tests/suites/algorithms-sd.js +++ b/tests/suites/algorithms-sd.js @@ -8,6 +8,7 @@ import { issueCloned } from 'data-integrity-test-suite-assertion'; import {createInitialVc, getBs64UrlBytes} from '../helpers.js'; +import {expect} from 'chai'; import {getMultiKey} from '../vc-generator/key-gen.js'; import {getSuites} from './helpers.js'; import {invalidCborTagProxy} from './proxies.js'; From e59889f9e0808b5b31946424c6dbd8f4f3ba53e5 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 12:55:17 -0500 Subject: [PATCH 6/7] Append proofValue with invalid proof header bytes. --- tests/helpers.js | 1 + tests/suites/algorithms-sd.js | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/helpers.js b/tests/helpers.js index 70db1ba5..9f8bf3ac 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -16,6 +16,7 @@ export const require = createRequire(import.meta.url); // and gets the bytes export const getBs58Bytes = s => bs58.decode(s.slice(1)); export const getBs64UrlBytes = s => bs64.decode(s.slice(1)); +export const encodeBs64Url = bytes => bs64.encode(bytes); // Javascript's default ISO timestamp contains milliseconds. // This lops off the MS part of the UTC RFC3339 TimeStamp and replaces diff --git a/tests/suites/algorithms-sd.js b/tests/suites/algorithms-sd.js index 421ebc48..a0800905 100644 --- a/tests/suites/algorithms-sd.js +++ b/tests/suites/algorithms-sd.js @@ -7,7 +7,11 @@ import { generators, issueCloned } from 'data-integrity-test-suite-assertion'; -import {createInitialVc, getBs64UrlBytes} from '../helpers.js'; +import { + createInitialVc, + encodeBs64Url, + getBs64UrlBytes +} from '../helpers.js'; import {expect} from 'chai'; import {getMultiKey} from '../vc-generator/key-gen.js'; import {getSuites} from './helpers.js'; @@ -334,8 +338,17 @@ async function _setup({ nonbase64ProofValue.proof.proofValue.substring(1); credentials.set('invalidProofValuePrefix', nonbase64ProofValue); const invalidProofValueHeader = structuredClone(securedCredential); + // get the bytes const disclosureBytes = getBs64UrlBytes( invalidProofValueHeader?.proof?.proofValue); - credentials.set('invalidDisclosureProofHeader', null); + // remove the 3 byte prefix and replace with an invalid prefix + const invalidBuffer = Buffer.concat( + [Buffer.from([0xA1, 0x44, 0x01]), disclosureBytes.slice(3)], + disclosureBytes.length + ); + // replace the proofValue with a newer proofValue with an + // invalid 3 byte header + invalidProofValueHeader.proof.proofValue = `u${encodeBs64Url(invalidBuffer)}`; + credentials.set('invalidDisclosureProofHeader', invalidProofValueHeader); return credentials; } From ccccede5a050631db1ef924e992cd7771c406de4 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 17 Nov 2024 12:57:01 -0500 Subject: [PATCH 7/7] Account for keyType when fetching invalid proof header bytes fixture. --- tests/suites/algorithms-sd.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/suites/algorithms-sd.js b/tests/suites/algorithms-sd.js index a0800905..4343431c 100644 --- a/tests/suites/algorithms-sd.js +++ b/tests/suites/algorithms-sd.js @@ -166,7 +166,8 @@ export function sd2023Algorithms({ this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=If%20the%20decodedProofValue%20does%20not%20start%20with%20the%20ECDSA%2DSD%20disclosure%20proof%20header%20bytes%200xd9%2C%200x5d%2C%20and%200x01%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.'; await assertions.verificationFail({ verifier, - credential: fixtures.get('invalidDisclosureProofHeader'), + credential: fixtures.get(keyType).get( + 'invalidDisclosureProofHeader'), reason: 'Should not verify VC with invalid disclosure proof ' + 'header' });