From 53de31647d54279a2af72c98ba737ba6cccf0ba1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 18:34:10 +0000 Subject: [PATCH] Separate issuance from verifier tests. --- test/10-issue.spec.js | 364 ++++++++++++++++++ test/{10-verify.spec.js => 20-verify.spec.js} | 272 ------------- ...dateRegex.spec.js => 30-dateRegex.spec.js} | 0 3 files changed, 364 insertions(+), 272 deletions(-) create mode 100644 test/10-issue.spec.js rename test/{10-verify.spec.js => 20-verify.spec.js} (78%) rename test/{20-dateRegex.spec.js => 30-dateRegex.spec.js} (100%) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js new file mode 100644 index 00000000..ff1bb501 --- /dev/null +++ b/test/10-issue.spec.js @@ -0,0 +1,364 @@ +/*! + * Copyright (c) 2019-2024 Digital Bazaar, Inc. All rights reserved. + */ +import * as Bls12381Multikey from '@digitalbazaar/bls12-381-multikey'; +import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey'; +import * as vc from '../lib/index.js'; +import { + documentLoader, + remoteDocuments, +} from './testDocumentLoader.js'; +import {assertionController} from './mocks/assertionController.js'; +import chai from 'chai'; +import {createSkewedTimeStamp} from './helpers.js'; +import {Ed25519Signature2018} from '@digitalbazaar/ed25519-signature-2018'; +import { + Ed25519VerificationKey2018 +} from '@digitalbazaar/ed25519-verification-key-2018'; +import {v4 as uuid} from 'uuid'; +import {versionedCredentials} from './mocks/credential.js'; + +const should = chai.should(); + +// do ed25519 setup... +let suite; +let keyPair; +before(async () => { + // set up the Ed25519 key pair that will be signing and verifying + keyPair = await Ed25519VerificationKey2018.generate({ + id: 'https://example.edu/issuers/keys/1', + controller: 'https://example.edu/issuers/565049' + }); + + // add the key to the controller doc (authorizes its use for assertion) + assertionController.assertionMethod.push(keyPair.id); + // also add the key for authentication (VP) purposes + // FIXME: this shortcut to reuse the same key and sign VPs as issuer can + // confuse developers trying to learn from the test suite and it should + // be changed + assertionController.authentication.push(keyPair.id); + + // register the controller document and the key document with documentLoader + remoteDocuments.set( + 'https://example.edu/issuers/565049', assertionController); + remoteDocuments.set( + 'https://example.edu/issuers/keys/1', + await keyPair.export({publicKey: true})); + + // set up the signature suite, using the generated key + suite = new Ed25519Signature2018({ + verificationMethod: 'https://example.edu/issuers/keys/1', + key: keyPair + }); +}); + +// do ecdsa setup... +let ecdsaKeyPair; +before(async () => { + // set up the ECDSA key pair that will be signing and verifying + ecdsaKeyPair = await EcdsaMultikey.generate({ + curve: 'P-256', + id: 'https://example.edu/issuers/keys/2', + controller: 'https://example.edu/issuers/565049' + }); + + // add the key to the controller doc (authorizes its use for assertion) + assertionController.assertionMethod.push(ecdsaKeyPair.id); + // register the key document with documentLoader + remoteDocuments.set( + 'https://example.edu/issuers/keys/2', + await ecdsaKeyPair.export({publicKey: true})); +}); + +// do BBS setup... +let bbsKeyPair; +before(async () => { + // set up the BBS key pair that will be signing and verifying + bbsKeyPair = await Bls12381Multikey.generateBbsKeyPair({ + algorithm: 'BBS-BLS12-381-SHA-256', + id: 'https://example.edu/issuers/keys/3', + controller: 'https://example.edu/issuers/565049' + }); + + // add the key to the controller doc (authorizes its use for assertion) + assertionController.assertionMethod.push(bbsKeyPair.id); + // register the key document with documentLoader + remoteDocuments.set( + 'https://example.edu/issuers/keys/3', + await bbsKeyPair.export({publicKey: true})); +}); + +// run tests on each version of VCs +for(const [version, mockCredential] of versionedCredentials) { + describe(`Verifiable Credentials Data Model ${version}`, async function() { + describe('vc.issue()', () => { + it('should issue a verifiable credential with proof', async () => { + const credential = mockCredential(); + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); + it('should issue a verifiable credential with out id', async () => { + const credential = mockCredential(); + delete credential.id; + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + should.not.exist(verifiableCredential.id, 'Expected no "vc.id".'); + }); + it('should throw an error on missing verificationMethod', async () => { + const suite = new Ed25519Signature2018({ + // Note no key id or verificationMethod passed to suite + key: await Ed25519VerificationKey2018.generate() + }); + let error; + try { + await vc.issue({ + credential: mockCredential(), + suite + }); + } catch(e) { + error = e; + } + should.exist(error, + 'Should throw error when "verificationMethod" property is missing'); + error.should.be.instanceof(TypeError); + error.message.should + .contain('"suite.verificationMethod" property is required.'); + }); + if(version === 1.0) { + it('should issue an expired verifiable credential', async () => { + const keyPair = await Ed25519VerificationKey2018.generate(); + const fp = Ed25519VerificationKey2018 + .fingerprintFromPublicKey({ + publicKeyBase58: keyPair.publicKeyBase58 + }); + keyPair.id = `did:key:${fp}#${fp}`; + const credential = mockCredential(); + credential.id = `urn:uuid:${uuid()}`; + credential.issuer = `did:key:${fp}`; + credential.expirationDate = '2020-05-31T19:21:25Z'; + const verifiableCredential = await vc.issue({ + credential, + suite: new Ed25519Signature2018({ + key: keyPair + }), + // set `now` to expiration date, allowing the credential + // to be issued + // without failing the expired check + now: (new Date('2020-05-31T19:21:25Z')), + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); + it('should add "issuanceDate" to verifiable credentials', async () => { + const credential = mockCredential(); + delete credential.issuanceDate; + const now = new Date(); + const expectedIssuanceDate = `${now.toISOString().slice(0, -5)}Z`; + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader, + now + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + verifiableCredential.should.have.property( + 'issuanceDate', + expectedIssuanceDate + ); + }); + } + if(version === 2.0) { + it('should issue "validUntil" in the future', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + // set validUntil one year in the future + credential.validUntil = createSkewedTimeStamp({skewYear: 1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing "validUntil" in future'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validUntil is present and has correct timestamp + verifiableCredential.should.have.property( + 'validUntil', + credential.validUntil + ); + }); + it('should issue "validUntil" in the past', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + // set validUntil one year in the past + credential.validUntil = createSkewedTimeStamp({skewYear: -1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing with "validUntil" in past'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validUntil is present and has correct timestamp + verifiableCredential.should.have.property( + 'validUntil', + credential.validUntil + ); + }); + it('should issue "validFrom" in the past', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + credential.validFrom = createSkewedTimeStamp({skewYear: -1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing "validFrom" in past'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validFrom is present and has correct timestamp + verifiableCredential.should.have.property( + 'validFrom', + credential.validFrom + ); + }); + it('should issue "validFrom" in the future', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + credential.validFrom = createSkewedTimeStamp({skewYear: 1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing "validFrom" in future'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validFrom is present and has correct timestamp + verifiableCredential.should.have.property( + 'validFrom', + credential.validFrom + ); + }); + it('should issue both "validFrom" and "validUntil"', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + credential.validFrom = createSkewedTimeStamp({skewYear: -1}); + credential.validUntil = createSkewedTimeStamp({skewYear: 1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist( + error, + 'Should not throw when issuing VC with both "validFrom" and' + + '"validUntil"' + ); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validFrom & validUntil are present + // and have correct timestamps + verifiableCredential.should.have.property( + 'validFrom', + credential.validFrom + ); + verifiableCredential.should.have.property( + 'validUntil', + credential.validUntil + ); + }); + } + }); + describe('vc.signPresentation()', () => { + it('should create a signed VP', async () => { + const presentation = vc.createPresentation({ + verifiableCredential: mockCredential(), + id: 'test:ebc6f1c2', + holder: 'did:ex:holder123', + version + }); + const vp = await vc.signPresentation({ + presentation, + suite, // from before() block + challenge: '12ec21', + documentLoader + }); + vp.should.have.property('proof'); + vp.proof.should.have.property('type', 'Ed25519Signature2018'); + vp.proof.should.have.property('proofPurpose', 'authentication'); + vp.proof.should.have.property('verificationMethod', + 'https://example.edu/issuers/keys/1'); + vp.proof.should.have.property('challenge', '12ec21'); + vp.proof.should.have.property('created'); + vp.proof.should.have.property('jws'); + }); + }); + }); +} diff --git a/test/10-verify.spec.js b/test/20-verify.spec.js similarity index 78% rename from test/10-verify.spec.js rename to test/20-verify.spec.js index 968f36df..9c368b23 100644 --- a/test/10-verify.spec.js +++ b/test/20-verify.spec.js @@ -101,252 +101,6 @@ before(async () => { // run tests on each version of VCs for(const [version, mockCredential] of versionedCredentials) { describe(`Verifiable Credentials Data Model ${version}`, async function() { - describe('vc.issue()', () => { - it('should issue a verifiable credential with proof', async () => { - const credential = mockCredential(); - const verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - }); - it('should issue a verifiable credential with out id', async () => { - const credential = mockCredential(); - delete credential.id; - const verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - should.not.exist(verifiableCredential.id, 'Expected no "vc.id".'); - }); - it('should throw an error on missing verificationMethod', async () => { - const suite = new Ed25519Signature2018({ - // Note no key id or verificationMethod passed to suite - key: await Ed25519VerificationKey2018.generate() - }); - let error; - try { - await vc.issue({ - credential: mockCredential(), - suite - }); - } catch(e) { - error = e; - } - should.exist(error, - 'Should throw error when "verificationMethod" property is missing'); - error.should.be.instanceof(TypeError); - error.message.should - .contain('"suite.verificationMethod" property is required.'); - }); - if(version === 1.0) { - it('should issue an expired verifiable credential', async () => { - const keyPair = await Ed25519VerificationKey2018.generate(); - const fp = Ed25519VerificationKey2018 - .fingerprintFromPublicKey({ - publicKeyBase58: keyPair.publicKeyBase58 - }); - keyPair.id = `did:key:${fp}#${fp}`; - const credential = mockCredential(); - credential.id = `urn:uuid:${uuid()}`; - credential.issuer = `did:key:${fp}`; - credential.expirationDate = '2020-05-31T19:21:25Z'; - const verifiableCredential = await vc.issue({ - credential, - suite: new Ed25519Signature2018({ - key: keyPair - }), - // set `now` to expiration date, allowing the credential - // to be issued - // without failing the expired check - now: (new Date('2020-05-31T19:21:25Z')), - documentLoader - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - }); - it('should add "issuanceDate" to verifiable credentials', async () => { - const credential = mockCredential(); - delete credential.issuanceDate; - const now = new Date(); - const expectedIssuanceDate = `${now.toISOString().slice(0, -5)}Z`; - const verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader, - now - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - verifiableCredential.should.have.property( - 'issuanceDate', - expectedIssuanceDate - ); - }); - } - if(version === 2.0) { - it('should issue "validUntil" in the future', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - // set validUntil one year in the future - credential.validUntil = createSkewedTimeStamp({skewYear: 1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing "validUntil" in future'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validUntil is present and has correct timestamp - verifiableCredential.should.have.property( - 'validUntil', - credential.validUntil - ); - }); - it('should issue "validUntil" in the past', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - // set validUntil one year in the past - credential.validUntil = createSkewedTimeStamp({skewYear: -1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing with "validUntil" in past'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validUntil is present and has correct timestamp - verifiableCredential.should.have.property( - 'validUntil', - credential.validUntil - ); - }); - it('should issue "validFrom" in the past', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - credential.validFrom = createSkewedTimeStamp({skewYear: -1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing "validFrom" in past'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validFrom is present and has correct timestamp - verifiableCredential.should.have.property( - 'validFrom', - credential.validFrom - ); - }); - it('should issue "validFrom" in the future', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - credential.validFrom = createSkewedTimeStamp({skewYear: 1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing "validFrom" in future'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validFrom is present and has correct timestamp - verifiableCredential.should.have.property( - 'validFrom', - credential.validFrom - ); - }); - it('should issue both "validFrom" and "validUntil"', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - credential.validFrom = createSkewedTimeStamp({skewYear: -1}); - credential.validUntil = createSkewedTimeStamp({skewYear: 1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist( - error, - 'Should not throw when issuing VC with both "validFrom" and' + - '"validUntil"' - ); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validFrom & validUntil are present - // and have correct timestamps - verifiableCredential.should.have.property( - 'validFrom', - credential.validFrom - ); - verifiableCredential.should.have.property( - 'validUntil', - credential.validUntil - ); - }); - } - }); - describe('vc.createPresentation()', () => { it('should create an unsigned presentation', () => { const presentation = vc.createPresentation({ @@ -361,32 +115,6 @@ for(const [version, mockCredential] of versionedCredentials) { presentation.should.not.have.property('proof'); }); }); - - describe('vc.signPresentation()', () => { - it('should create a signed VP', async () => { - const presentation = vc.createPresentation({ - verifiableCredential: mockCredential(), - id: 'test:ebc6f1c2', - holder: 'did:ex:holder123', - version - }); - const vp = await vc.signPresentation({ - presentation, - suite, // from before() block - challenge: '12ec21', - documentLoader - }); - vp.should.have.property('proof'); - vp.proof.should.have.property('type', 'Ed25519Signature2018'); - vp.proof.should.have.property('proofPurpose', 'authentication'); - vp.proof.should.have.property('verificationMethod', - 'https://example.edu/issuers/keys/1'); - vp.proof.should.have.property('challenge', '12ec21'); - vp.proof.should.have.property('created'); - vp.proof.should.have.property('jws'); - }); - }); - describe('verify API (credentials)', () => { it('should verify a vc', async () => { const verifiableCredential = await vc.issue({ diff --git a/test/20-dateRegex.spec.js b/test/30-dateRegex.spec.js similarity index 100% rename from test/20-dateRegex.spec.js rename to test/30-dateRegex.spec.js