Skip to content

Commit

Permalink
Add did:web issuance tests with proof sets.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Apr 15, 2024
1 parent 534af3c commit f40a77f
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 7 deletions.
247 changes: 247 additions & 0 deletions test/mocha/40-did-web-issuer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*!
* Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved.
*/
import * as bedrock from '@bedrock/core';
import * as helpers from './helpers.js';
import {agent} from '@bedrock/https-agent';
import {createRequire} from 'node:module';
import {httpClient} from '@digitalbazaar/http-client';
import {klona} from 'klona';
import {mockData} from './mock.data.js';
import {v4 as uuid} from 'uuid';

const require = createRequire(import.meta.url);

const {baseUrl} = mockData;
const serviceType = 'vc-issuer';

// NOTE: using embedded context in mockCredential:
// https://www.w3.org/2018/credentials/examples/v1
const mockCredential = require('./mock-credential.json');
const mockCredentialV2 = require('./mock-credential-v2.json');

describe('issue using "did:web" issuer', () => {
let suites;
let capabilityAgent;
let noStatusListIssuerId;
let noStatusListIssuerRootZcap;
beforeEach(async () => {
// generate a proof set using all of these suites in each test
suites = [{
name: 'Ed25519Signature2020',
algorithm: 'Ed25519'
}, {
name: 'eddsa-rdfc-2022',
algorithm: 'Ed25519'
}, {
name: 'ecdsa-rdfc-2019',
algorithm: 'P-256'
}, {
name: 'ecdsa-sd-2023',
algorithm: 'P-256'
}, {
name: 'ecdsa-xi-2023',
algorithm: 'P-256'
}, {
name: 'bbs-2023',
algorithm: 'Bls12381G2'
}];

// provision dependencies
({capabilityAgent} = await helpers.provisionDependencies({status: false}));

// create keystore for capability agent
const keystoreAgent = await helpers.createKeystoreAgent({capabilityAgent});

// generate a `did:web` DID for the issuer
const {host} = bedrock.config.server;
const localId = uuid();
const did = `did:web:${encodeURIComponent(host)}:did-web:${localId}`;

// generate an assertion method key for each suite to use
for(const suite of suites) {
const {algorithm} = suite;
let assertionMethodKey;
const publicAliasTemplate = `${did}#{publicKeyMultibase}`;
if(['P-256', 'P-384', 'Bls12381G2'].includes(algorithm)) {
assertionMethodKey = await helpers._generateMultikey({
keystoreAgent,
type: `urn:webkms:multikey:${algorithm}`,
publicAliasTemplate
});
} else {
assertionMethodKey = await keystoreAgent.generateKey({
type: 'asymmetric',
publicAliasTemplate
});
}
suite.assertionMethodKey = assertionMethodKey;
}

// create EDV for storage (creating hmac and kak in the process)
const {
edvConfig,
hmac,
keyAgreementKey
} = await helpers.createEdv({capabilityAgent, keystoreAgent});

// get service agent to delegate to
const serviceAgentUrl =
`${baseUrl}/service-agents/${encodeURIComponent(serviceType)}`;
const {data: serviceAgent} = await httpClient.get(serviceAgentUrl, {agent});

// delegate edv, hmac, and key agreement key zcaps to service agent
const {id: edvId} = edvConfig;
const zcaps = {};
zcaps.edv = await helpers.delegate({
controller: serviceAgent.id,
delegator: capabilityAgent,
invocationTarget: edvId
});
const {keystoreId} = keystoreAgent;
zcaps.hmac = await helpers.delegate({
capability: `urn:zcap:root:${encodeURIComponent(keystoreId)}`,
controller: serviceAgent.id,
invocationTarget: hmac.id,
delegator: capabilityAgent
});
zcaps.keyAgreementKey = await helpers.delegate({
capability: `urn:zcap:root:${encodeURIComponent(keystoreId)}`,
controller: serviceAgent.id,
invocationTarget: keyAgreementKey.kmsId,
delegator: capabilityAgent
});

// delegate assertion method keys
for(const suite of suites) {
const {assertionMethodKey} = suite;
const zcap = await helpers.delegate({
capability: `urn:zcap:root:${encodeURIComponent(keystoreId)}`,
controller: serviceAgent.id,
invocationTarget: assertionMethodKey.kmsId,
delegator: capabilityAgent
});
suite.zcapReferenceIds = {
assertionMethod: uuid()
};
zcaps[suite.zcapReferenceIds.assertionMethod] = zcap;
}

// create issuer options
const issuerOptions = {
issuer: did,
cryptosuites: suites.map(
({name, zcapReferenceIds}) => ({name, zcapReferenceIds}))
};

// create `did:web` DID document for issuer
const didDocument = {
'@context': [
'https://www.w3.org/ns/did/v1',
'https://w3id.org/security/suites/ed25519-2020/v1',
'https://w3id.org/security/multikey/v1'
],
id: did,
verificationMethod: [],
assertionMethod: []
};
for(const {assertionMethodKey} of suites) {
const description = await assertionMethodKey.getKeyDescription();
delete description['@context'];
didDocument.verificationMethod.push(description);
didDocument.assertionMethod.push(description.id);
}
// add DID doc to map with DID docs to be served
mockData.didWebDocuments.set(localId, didDocument);

// create issuer instance w/ no status list options
const noStatusListIssuerConfig = await helpers.createIssuerConfig({
capabilityAgent, zcaps, issuerOptions
});
noStatusListIssuerId = noStatusListIssuerConfig.id;
noStatusListIssuerRootZcap =
`urn:zcap:root:${encodeURIComponent(noStatusListIssuerId)}`;
});
describe('/credentials/issue', () => {
it('issues a VC 1.1 credential with a proof set', async () => {
const credential = klona(mockCredential);
let error;
let result;
try {
const zcapClient = helpers.createZcapClient({capabilityAgent});
result = await zcapClient.write({
url: `${noStatusListIssuerId}/credentials/issue`,
capability: noStatusListIssuerRootZcap,
json: {
credential,
options: {
extraInformation: 'abc'
}
}
});
} catch(e) {
error = e;
}
assertNoError(error);
should.exist(result.data);
should.exist(result.data.verifiableCredential);
const {verifiableCredential} = result.data;
verifiableCredential.should.be.an('object');
should.exist(verifiableCredential['@context']);
should.exist(verifiableCredential.id);
should.exist(verifiableCredential.type);
should.exist(verifiableCredential.issuer);
should.exist(verifiableCredential.issuanceDate);
should.exist(verifiableCredential.credentialSubject);
verifiableCredential.credentialSubject.should.be.an('object');
should.not.exist(verifiableCredential.credentialStatus);
should.exist(verifiableCredential.proof);
verifiableCredential.proof.should.be.an('array');
verifiableCredential.proof.length.should.equal(suites.length);
const parsedCryptosuites = verifiableCredential.proof.map(
({type, cryptosuite}) => cryptosuite ?? type);
const expectedCryptosuites = suites.map(({name}) => name);
parsedCryptosuites.should.deep.equal(expectedCryptosuites);
});
it('issues a VC 2.0 credential with a proof set', async () => {
const credential = klona(mockCredentialV2);
let error;
let result;
try {
const zcapClient = helpers.createZcapClient({capabilityAgent});
result = await zcapClient.write({
url: `${noStatusListIssuerId}/credentials/issue`,
capability: noStatusListIssuerRootZcap,
json: {
credential,
options: {
extraInformation: 'abc',
mandatoryPointers: ['issuer']
}
}
});
} catch(e) {
error = e;
}
assertNoError(error);
should.exist(result.data);
should.exist(result.data.verifiableCredential);
const {verifiableCredential} = result.data;
verifiableCredential.should.be.an('object');
should.exist(verifiableCredential['@context']);
should.exist(verifiableCredential.id);
should.exist(verifiableCredential.type);
should.exist(verifiableCredential.issuer);
should.exist(verifiableCredential.credentialSubject);
verifiableCredential.credentialSubject.should.be.an('object');
should.not.exist(verifiableCredential.credentialStatus);
should.exist(verifiableCredential.proof);
verifiableCredential.proof.should.be.an('array');
verifiableCredential.proof.length.should.equal(suites.length);
const parsedCryptosuites = verifiableCredential.proof.map(
({type, cryptosuite}) => cryptosuite ?? type);
const expectedCryptosuites = suites.map(({name}) => name);
parsedCryptosuites.should.deep.equal(expectedCryptosuites);
});
});
});
13 changes: 6 additions & 7 deletions test/mocha/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,23 @@ async function keyResolver({id}) {
return data;
}

export async function provisionDependencies({suiteOptions}) {
export async function provisionDependencies({suiteOptions, status = true}) {
const secret = '53ad64ce-8e1d-11ec-bb12-10bf48838a41';
const handle = 'test';
const capabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
if(!status) {
return {capabilityAgent};
}

// create keystore for capability agent
const keystoreAgent = await createKeystoreAgent({capabilityAgent});

const {
statusConfig,
issuerCreateStatusListZcap
} = await provisionStatus({
capabilityAgent, keystoreAgent, suiteOptions
});
} = await provisionStatus({capabilityAgent, keystoreAgent, suiteOptions});

return {
statusConfig, issuerCreateStatusListZcap, capabilityAgent
};
return {statusConfig, issuerCreateStatusListZcap, capabilityAgent};
}

export async function provisionIssuerForStatus({
Expand Down

0 comments on commit f40a77f

Please sign in to comment.