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

Default new format issuer instance to not include created. #180

Merged
merged 3 commits into from
Sep 23, 2024
Merged
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# 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. Note that for the legacy `Ed25519Signature2020` cryptosuite,
the `created` date will always be present regardless of these options.

## 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
95 changes: 92 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,12 @@ export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
});
should.exist(verifiableCredential.id);
should.not.exist(verifiableCredential.credentialStatus);
// not supported with old `Ed25519Signature2020`
if(suiteName !== 'Ed25519Signature2020') {
// `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 +79,12 @@ export function testIssueWithoutStatus({suiteName, algorithm, issueOptions}) {
});
should.exist(verifiableCredential.id);
should.not.exist(verifiableCredential.credentialStatus);
// not supported with old `Ed25519Signature2020`
if(suiteName !== 'Ed25519Signature2020') {
// `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 +132,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);
});
});
}
13 changes: 11 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,12 @@ function testStatusPurpose({
should.exist(verifiableCredential.credentialStatus);
should.exist(verifiableCredential.proof);
verifiableCredential.proof.should.be.an('object');
// not supported with old `Ed25519Signature2020`
if(suiteName !== 'Ed25519Signature2020') {
// `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
15 changes: 12 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 Expand Up @@ -102,6 +105,12 @@ export function testTerseBitstringStatusList({
.should.be.a('number');
should.exist(verifiableCredential.proof);
verifiableCredential.proof.should.be.an('object');
// not supported with old `Ed25519Signature2020`
if(suiteName !== 'Ed25519Signature2020') {
// `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('updates multiple TerseBitstringStatusList statuses', async () => {
Expand Down
Loading