Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ecdsa-sd-2023 test where a CBOR Tag is used (C -> B) #100

Open
wants to merge 7 commits into
base: add-sd-proof-value-check
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions tests/suites/algorithms-sd.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {createInitialVc} 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';

export function sd2023Algorithms({
credential,
Expand Down Expand Up @@ -161,8 +162,11 @@ export function sd2023Algorithms({
'NOT be used on any of the components. Append the produced encoded ' +
'value to proofValue.', async function() {
this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=and%20mandatoryPointers.-,CBOR%2Dencode%20components%20per%20%5BRFC8949%5D%20where%20CBOR%20tagging%20MUST,-NOT%20be%20used';
this.test.cell.skipMessage = 'Not Implemented';
this.skip();
await assertions.verificationFail({
verifier,
credential: fixtures.get(keyType).get('invalidCborTag'),
reason: 'Should not verify proofValue created with cbor tag'
});
});
it('If the proofValue string does not start with u, indicating ' +
'that it is a multibase-base64url-no-pad-encoded value, an error ' +
Expand Down Expand Up @@ -344,13 +348,24 @@ async function _setup({
const _credential = structuredClone(credential);
_credential.issuer = keyPair.controller;
credentials.set('invalidCreated', await issueCloned(invalidCreated({
credential: structuredClone(_credential),
credential: _credential,
...getSuites({
signer,
suiteName,
selectivePointers,
mandatoryPointers
})
})));
const cborTagSuites = getSuites({
signer,
suiteName,
selectivePointers,
mandatoryPointers
});
credentials.set('invalidCborTag', await issueCloned({
credential: _credential,
suite: cborTagSuites.suite,
selectiveSuite: invalidCborTagProxy(cborTagSuites.selectiveSuite)
}));
return credentials;
}
111 changes: 1 addition & 110 deletions tests/suites/algorithms.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
generators,
issueCloned
} from 'data-integrity-test-suite-assertion';
import crypto from 'node:crypto';
import {invalidHashProxy, unsafeProxy} from './proxies.js';
import {getMultiKey} from '../vc-generator/key-gen.js';
import {getSuites} from './helpers.js';

Expand Down Expand Up @@ -273,35 +273,6 @@ function _generateNoTypeCryptosuite({
return invalidCryptosuite({...noType, cryptosuiteName: ''});
}

function unsafeProxy(suite) {
if(typeof suite !== 'object') {
return suite;
}
// if the suite has a cryptosuite object proxy it
if(suite._cryptosuite) {
suite._cryptosuite = new Proxy(suite._cryptosuite, {
get(target, prop) {
if(prop === 'canonize') {
return function(doc, options) {
return target.canonize(doc, {...options, safe: false});
};
}
return Reflect.get(...arguments);
}
});
}
return new Proxy(suite, {
get(target, prop) {
if(prop === 'canonize') {
return function(doc, options) {
return target.canonize(doc, {...options, safe: false});
};
}
return Reflect.get(...arguments);
}
});
}

async function _commonSetup({
credential,
mandatoryPointers,
Expand Down Expand Up @@ -331,83 +302,3 @@ async function _commonSetup({
}));
return credentials;
}

function invalidHashProxy({
suiteName,
keyType,
suite,
}) {
if(typeof suite !== 'object') {
return suite;
}
if(suite._cryptosuite) {
if(suiteName !== 'ecdsa-rdfc-2019') {
throw new Error(`Unsupported suite ${suiteName}`);
}
suite._cryptosuite = new Proxy(suite._cryptosuite, {
get(target, prop) {
if(prop === 'createVerifyData') {
return async function({
cryptosuite, document, proof,
documentLoader, dataIntegrityProof
} = {}) {
// this switch the hash to the wrong hash for that keyType
const algorithm = (keyType === 'P-256') ? 'sha384' : 'sha256';
const c14nOptions = {
documentLoader,
safe: true,
base: null,
skipExpansion: false,
messageDigestAlgorithm: algorithm
};

// await both c14n proof hash and c14n document hash
const [proofHash, docHash] = await Promise.all([
// canonize and hash proof
_canonizeProof(proof, {
document, cryptosuite, dataIntegrityProof, c14nOptions
}).then(c14nProofOptions => sha({
algorithm,
string: c14nProofOptions
})),
// canonize and hash document
cryptosuite.canonize(document, c14nOptions).then(
c14nDocument => sha({algorithm, string: c14nDocument}))
]);
// concatenate hash of c14n proof options and hash of c14n document
return _concat(proofHash, docHash);
};
}
return Reflect.get(...arguments);
}
});
}
return suite;
}

function _concat(b1, b2) {
const rval = new Uint8Array(b1.length + b2.length);
rval.set(b1, 0);
rval.set(b2, b1.length);
return rval;
}

export async function sha({algorithm, string}) {
return new Uint8Array(crypto.createHash(algorithm).update(string).digest());
}

async function _canonizeProof(proof, {
document, cryptosuite, dataIntegrityProof, c14nOptions
}) {
// `proofValue` must not be included in the proof options
proof = {
'@context': document['@context'],
...proof
};
dataIntegrityProof.ensureSuiteContext({
document: proof, addSuiteContext: true
});
delete proof.proofValue;
return cryptosuite.canonize(proof, c14nOptions);
}

165 changes: 165 additions & 0 deletions tests/suites/proxies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*!
* Copyright 2024 Digital Bazaar, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
import crypto from 'node:crypto';
import {stubDerive} from './stubs.js';
/**
* Creates a proxy of an object with stubs.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
*
* @param {object} options - Options to use.
* @param {object} options.original - The original object.
* @param {object} options.stubs - Stubs to replace the original objects
* properties and methods.
*
* @returns {Proxy<object>} Returns a Proxy.
*/
export function createProxy({original, stubs}) {
if(typeof original !== 'object') {
throw new Error(`Expected parameter original to be an object received ` +
`${typeof original}`);
}
return new Proxy(original, {
get(target, prop) {
if(stubs[prop]) {
return stubs[prop];
}
return Reflect.get(...arguments);
}
});
}

/**
* The major jsonld api suites use is canonize.
* This function intercepts calls on canonize and
* pass safe: false allowing for invalid jsonld to
* be issued.
*
* @param {object} suite - A DataIntegrityProof.
*
* @returns {Proxy<object>} Returns a proxy of the proof.
*/
export function unsafeProxy(suite) {
if(typeof suite !== 'object') {
return suite;
}
// if the suite has a cryptosuite object proxy it
if(suite._cryptosuite) {
suite._cryptosuite = new Proxy(suite._cryptosuite, {
get(target, prop) {
if(prop === 'canonize') {
return function(doc, options) {
return target.canonize(doc, {...options, safe: false});
};
}
return Reflect.get(...arguments);
}
});
}
return new Proxy(suite, {
get(target, prop) {
if(prop === 'canonize') {
return function(doc, options) {
return target.canonize(doc, {...options, safe: false});
};
}
return Reflect.get(...arguments);
}
});
}

//ecdsa-rdfc-2019 proxy
export function invalidHashProxy({
suiteName,
keyType,
suite,
}) {
if(typeof suite !== 'object') {
return suite;
}
if(suite._cryptosuite) {
if(suiteName !== 'ecdsa-rdfc-2019') {
throw new Error(`Unsupported suite ${suiteName}`);
}
suite._cryptosuite = new Proxy(suite._cryptosuite, {
get(target, prop) {
if(prop === 'createVerifyData') {
return async function({
cryptosuite, document, proof,
documentLoader, dataIntegrityProof
} = {}) {
// this switch the hash to the wrong hash for that keyType
const algorithm = (keyType === 'P-256') ? 'sha384' : 'sha256';
const c14nOptions = {
documentLoader,
safe: true,
base: null,
skipExpansion: false,
messageDigestAlgorithm: algorithm
};

// await both c14n proof hash and c14n document hash
const [proofHash, docHash] = await Promise.all([
// canonize and hash proof
_canonizeProof(proof, {
document, cryptosuite, dataIntegrityProof, c14nOptions
}).then(c14nProofOptions => sha({
algorithm,
string: c14nProofOptions
})),
// canonize and hash document
cryptosuite.canonize(document, c14nOptions).then(
c14nDocument => sha({algorithm, string: c14nDocument}))
]);
// concatenate hash of c14n proof options and hash of c14n document
return _concat(proofHash, docHash);
};
}
return Reflect.get(...arguments);
}
});
}
return suite;
}

// ecdsa-rdfc-2019 concat 2 unit8Arrays together
function _concat(b1, b2) {
const rval = new Uint8Array(b1.length + b2.length);
rval.set(b1, 0);
rval.set(b2, b1.length);
return rval;
}

// ecdsa-rdfc-2019 sha hashing function
export async function sha({algorithm, string}) {
return new Uint8Array(crypto.createHash(algorithm).update(string).digest());
}

// ecdsa-rdfc-2019 _canonizeProof method
async function _canonizeProof(proof, {
document, cryptosuite, dataIntegrityProof, c14nOptions
}) {
// `proofValue` must not be included in the proof options
proof = {
'@context': document['@context'],
...proof
};
dataIntegrityProof.ensureSuiteContext({
document: proof, addSuiteContext: true
});
delete proof.proofValue;
return cryptosuite.canonize(proof, c14nOptions);
}

export function invalidCborTagProxy(suite) {
const stubs = {derive: stubDerive};
if(suite._cryptosuite) {
suite._cryptosuite = createProxy({
original: suite._cryptosuite,
stubs
});
}
return suite;
}
Loading