Skip to content

Commit

Permalink
Default new format issuer instance to not include created.
Browse files Browse the repository at this point in the history
- Allow optional `created` to be included via `includeCreated`
  cryptosuite option.
  • Loading branch information
dlongley committed Sep 23, 2024
1 parent 2863936 commit 9ef86c9
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 30 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# bedrock-vc-issuer ChangeLog

## 28.0.0 - 2024-09-dd

### Changed
- **BREAKING**: Any issuer configuration created using the non-legacy format
will no longer include the optional `created` field in its proofs. To
include `created` in the proofs for a particular cryptosuite, specify
`includeCreated=true` in the `cryptosuite` options for that cryptosuite. Any
issuer instances created using the legacy format will continue to have
`created` appear in the generated proof. A new instance or a configuration
change to the new format is required to stop including `created` in
legacy issuer instances. This approach ensures that deployments that only
use legacy issuer instances in production can include this update without
any changes.

## 27.1.0 - 2024-09-18

### Added
Expand Down
34 changes: 23 additions & 11 deletions lib/suites.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export function getSuiteParams({config, suiteName, cryptosuite}) {
referenceId = cryptosuite.zcapReferenceIds.assertionMethod;
zcap = config.zcaps[referenceId];
} else {
// legacy mode, generate `cryptosuite`...
referenceId = 'assertionMethod';
zcap = config.zcaps[referenceId];
if(!zcap) {
Expand All @@ -93,6 +94,10 @@ export function getSuiteParams({config, suiteName, cryptosuite}) {
name: suiteName,
zcapReferenceIds: {
assertionMethod: referenceId
},
options: {
// legacy mode always includes `created`
includeCreated: true
}
};
}
Expand Down Expand Up @@ -125,36 +130,36 @@ export function getSuiteParams({config, suiteName, cryptosuite}) {
return {zcap, createSuite, referenceId, cryptosuite};
}

function _createEddsa2022Suite({signer}) {
function _createEddsa2022Suite({signer, cryptosuiteConfig}) {
return new DataIntegrityProof({
signer,
date: _getISODateTime(),
date: _getCreated({cryptosuiteConfig}),
cryptosuite: eddsa2022CryptoSuite,
legacyContext: true
});
}

function _createEcdsa2019Suite({signer} = {}) {
function _createEcdsa2019Suite({signer, cryptosuiteConfig} = {}) {
return new DataIntegrityProof({
signer,
date: _getISODateTime(),
date: _getCreated({cryptosuiteConfig}),
cryptosuite: ecdsa2019CryptoSuite,
legacyContext: true
});
}

function _createEddsaRdfc2022Suite({signer}) {
function _createEddsaRdfc2022Suite({signer, cryptosuiteConfig}) {
return new DataIntegrityProof({
signer,
date: _getISODateTime(),
date: _getCreated({cryptosuiteConfig}),
cryptosuite: eddsaRdfc2022CryptoSuite
});
}

function _createEcdsaRdfc2019Suite({signer} = {}) {
function _createEcdsaRdfc2019Suite({signer, cryptosuiteConfig} = {}) {
return new DataIntegrityProof({
signer,
date: _getISODateTime(),
date: _getCreated({cryptosuiteConfig}),
cryptosuite: ecdsaRdfc2019CryptoSuite
});
}
Expand All @@ -178,7 +183,7 @@ function _createEcdsaSd2023Suite({signer, options, cryptosuiteConfig} = {}) {
});
const diProof = new DataIntegrityProof({
signer,
date: _getISODateTime(),
date: _getCreated({cryptosuiteConfig}),
cryptosuite
});
diProof.proof = {id: `urn:uuid:${uuid()}`};
Expand Down Expand Up @@ -230,14 +235,21 @@ async function _createBbs2023Suite({signer, options, cryptosuiteConfig} = {}) {
});
const diProof = new DataIntegrityProof({
signer,
date: _getISODateTime(),
date: _getCreated({cryptosuiteConfig}),
cryptosuite
});
diProof.proof = {id: `urn:uuid:${uuid()}`};
return diProof;
}

function _getISODateTime(date = new Date()) {
function _getCreated({cryptosuiteConfig, date = new Date()}) {
if(cryptosuiteConfig.options?.includeCreated === true) {
return _getISODateTime(date);
}
return null;
}

function _getISODateTime(date) {
// remove milliseconds precision
return date.toISOString().replace(/\.\d+Z$/, 'Z');
}
3 changes: 3 additions & 0 deletions schemas/bedrock-vc-issuer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const cryptosuite = {
type: 'object',
additionalProperties: false,
properties: {
includeCreated: {
type: 'boolean'
},
mandatoryPointers
}
},
Expand Down
10 changes: 8 additions & 2 deletions test/mocha/20-issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ describe('issue', () => {
suiteName: 'eddsa-rdfc-2022',
algorithm: 'Ed25519',
issueOptions: {},
statusOptions: {},
statusOptions: {
suiteName: 'eddsa-rdfc-2022',
algorithm: 'Ed25519'
},
tags: ['general']
},
'ecdsa-rdfc-2019, P-256': {
Expand Down Expand Up @@ -76,7 +79,10 @@ describe('issue', () => {
suiteName: 'Ed25519Signature2020',
algorithm: 'Ed25519',
issueOptions: {},
statusOptions: {},
statusOptions: {
suiteName: 'Ed25519Signature2020',
algorithm: 'Ed25519'
},
tags: []
}
};
Expand Down
89 changes: 86 additions & 3 deletions test/mocha/assertions/issueWithoutStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const require = createRequire(import.meta.url);
const mockCredential = require('../mock-credential.json');
const mockCredentialV2 = require('../mock-credential-v2.json');

export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
export function testIssueWithoutStatus({
suiteName, algorithm, issueOptions, tags
}) {
const depOptions = {
status: false,
suiteOptions: {
Expand All @@ -26,18 +28,21 @@ export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
zcaps: true
};
describe('issue with no status', function() {
let issuer;
let capabilityAgent;
let zcaps;
let noStatusListIssuerId;
let noStatusListIssuerRootZcap;
before(async () => {
// provision dependencies
({capabilityAgent, zcaps} = await helpers.provisionDependencies(
const {cryptosuites} = depOptions;
({issuer, capabilityAgent, zcaps} = await helpers.provisionDependencies(
depOptions));

// create issuer instance w/ no status list options
const issueOptions = helpers.createIssueOptions({issuer, cryptosuites});
const noStatusListIssuerConfig = await helpers.createIssuerConfig(
{capabilityAgent, zcaps, suiteName});
{capabilityAgent, zcaps, issueOptions});
noStatusListIssuerId = noStatusListIssuerConfig.id;
noStatusListIssuerRootZcap = helpers.createRootZcap({
url: noStatusListIssuerId
Expand All @@ -55,6 +60,9 @@ export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
});
should.exist(verifiableCredential.id);
should.not.exist(verifiableCredential.credentialStatus);
// `created` should not be set by default because new issue config
// mechanism was used w/o requesting it
should.not.exist(verifiableCredential.proof.created);
});
it('issues a VC 2.0 credential w/no "credentialStatus"', async () => {
const credential = klona(mockCredentialV2);
Expand All @@ -68,6 +76,9 @@ export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
});
should.exist(verifiableCredential.id);
should.not.exist(verifiableCredential.credentialStatus);
// `created` should not be set by default because new issue config
// mechanism was used w/o requesting it
should.not.exist(verifiableCredential.proof.created);
});

it('fails to issue an empty credential', async () => {
Expand Down Expand Up @@ -115,4 +126,76 @@ export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
typeError.details.params.missingProperty.should.equal('type');
});
});

// only add additional tests if testing `general` behavior
if(!tags?.includes('general')) {
return;
}

const depOptionsWithCreated = {
status: false,
suiteOptions: {
suiteName, algorithm, issueOptions
},
cryptosuites: [{
name: suiteName,
algorithm,
options: {
includeCreated: true
}
}],
zcaps: true
};
describe('issue with no status and include "created"', function() {
let issuer;
let capabilityAgent;
let zcaps;
let noStatusListIssuerId;
let noStatusListIssuerRootZcap;
before(async () => {
// provision dependencies
const {cryptosuites} = depOptionsWithCreated;
({issuer, capabilityAgent, zcaps} = await helpers.provisionDependencies(
depOptionsWithCreated));

// create issuer instance w/ no status list options
const issueOptions = helpers.createIssueOptions({issuer, cryptosuites});
const noStatusListIssuerConfig = await helpers.createIssuerConfig(
{capabilityAgent, zcaps, issueOptions});
noStatusListIssuerId = noStatusListIssuerConfig.id;
noStatusListIssuerRootZcap = helpers.createRootZcap({
url: noStatusListIssuerId
});
});
it('issues a valid credential w/no "credentialStatus"', async () => {
const credential = klona(mockCredential);
const zcapClient = helpers.createZcapClient({capabilityAgent});
const {verifiableCredential} = await assertions.issueAndAssert({
configId: noStatusListIssuerId,
credential,
issueOptions,
zcapClient,
capability: noStatusListIssuerRootZcap
});
should.exist(verifiableCredential.id);
should.not.exist(verifiableCredential.credentialStatus);
// `created` should be set because it was requested
should.exist(verifiableCredential.proof.created);
});
it('issues a VC 2.0 credential w/no "credentialStatus"', async () => {
const credential = klona(mockCredentialV2);
const zcapClient = helpers.createZcapClient({capabilityAgent});
const {verifiableCredential} = await assertions.issueAndAssert({
configId: noStatusListIssuerId,
credential,
issueOptions,
zcapClient,
capability: noStatusListIssuerRootZcap
});
should.exist(verifiableCredential.id);
should.not.exist(verifiableCredential.credentialStatus);
// `created` should be set because it was requested
should.exist(verifiableCredential.proof.created);
});
});
}
10 changes: 8 additions & 2 deletions test/mocha/assertions/testBitstringStatusList.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ function testStatusPurpose({
zcaps: true
};
describe(`BitstringStatusList, statusPurpose: ${statusPurpose}`, function() {
let issuer;
let capabilityAgent;
let zcaps;
let bslInstance;
before(async () => {
// provision dependencies
({capabilityAgent, zcaps} = await helpers.provisionDependencies(
({issuer, capabilityAgent, zcaps} = await helpers.provisionDependencies(
depOptions));

// create issuer instance w/ bitstring status list options
Expand All @@ -62,8 +63,10 @@ function testStatusPurpose({
createCredentialStatusList: 'createCredentialStatusList'
}
}];
const {cryptosuites} = depOptions;
const issueOptions = helpers.createIssueOptions({issuer, cryptosuites});
bslInstance = await helpers.createIssuerConfigAndDependencies({
capabilityAgent, zcaps, suiteName, statusListOptions, depOptions
capabilityAgent, zcaps, issueOptions, statusListOptions, depOptions
});
});
describe('issue', () => {
Expand Down Expand Up @@ -100,6 +103,9 @@ function testStatusPurpose({
should.exist(verifiableCredential.credentialStatus);
should.exist(verifiableCredential.proof);
verifiableCredential.proof.should.be.an('object');
// `created` should not be set by default because new issue config
// mechanism was used w/o requesting it
should.not.exist(verifiableCredential.proof.created);

await assertions.assertStoredCredential({
configId: bslInstance.issuerId,
Expand Down
9 changes: 6 additions & 3 deletions test/mocha/assertions/testTerseBitstringStatusList.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ export function testTerseBitstringStatusList({
zcaps: true
};
describe('TerseBitstringStatusList', function() {
let issuer;
let capabilityAgent;
let zcaps;
let terseMultistatus;
beforeEach(async () => {
before(async () => {
// provision dependencies
({capabilityAgent, zcaps} = await helpers.provisionDependencies(
({issuer, capabilityAgent, zcaps} = await helpers.provisionDependencies(
depOptions));

// create issuer instance w/ terse bitstring status list options
Expand All @@ -46,9 +47,11 @@ export function testTerseBitstringStatusList({
createCredentialStatusList: 'createCredentialStatusList'
}
}];
const {cryptosuites} = depOptions;
const issueOptions = helpers.createIssueOptions({issuer, cryptosuites});
terseMultistatus = await helpers
.createIssuerConfigAndDependencies({
capabilityAgent, zcaps, suiteName, statusListOptions, depOptions
capabilityAgent, zcaps, issueOptions, statusListOptions, depOptions
});

// insert example context for issuing VCs w/terse status entries
Expand Down
Loading

0 comments on commit 9ef86c9

Please sign in to comment.