From ba9b98b867fa788c31b6a91de6e0cb65aa67d119 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 1 Sep 2024 10:15:38 -0400 Subject: [PATCH 01/26] CHANGELOG minor feature: check created & expires. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe01218..04565eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # @digitalbazaar/data-integrity Changelog +## 2.4.0 - + +### Added +- verify now checks `created` and `expires`. + ## 2.3.0 - 2024-08-26 ### Changed From f2553593ba74f066d98be5915b5b3e770e9298f9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 1 Sep 2024 11:39:34 -0400 Subject: [PATCH 02/26] Add 3 initial test titles for created. --- test/DataIntegrityProof.spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index f800c58..47d73e9 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -607,5 +607,14 @@ describe('DataIntegrityProof', () => { expect(result.verified).to.be.false; expect(errors[0].name).to.equal('NotFoundError'); }); + it('should fail verification if created is not XMLSCHEMA11-2', async function() { + + }); + it('should fail verification if created is in the future', async function() { + + }); + it('should interpret created as UTC if incorrectly serialized', async function() { + + }); }); }); From 3d5238ad0ac6638b48f9d849157c926f57d6e6c2 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 1 Sep 2024 11:52:49 -0400 Subject: [PATCH 03/26] Lint created test titles and add proof. --- test/DataIntegrityProof.spec.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index 47d73e9..432577c 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -607,13 +607,16 @@ describe('DataIntegrityProof', () => { expect(result.verified).to.be.false; expect(errors[0].name).to.equal('NotFoundError'); }); - it('should fail verification if created is not XMLSCHEMA11-2', async function() { + it('should fail verification if proof created is not XMLSCHEMA11-2', + async function() { }); - it('should fail verification if created is in the future', async function() { + it('should fail verification if proof created is in the future', + async function() { }); - it('should interpret created as UTC if incorrectly serialized', async function() { + it('should interpret proof created as UTC if incorrectly serialized', + async function() { }); }); From 104c9e84dc9508568626f5814c7cb118a0afdb54 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 1 Sep 2024 11:56:17 -0400 Subject: [PATCH 04/26] Replace JSON.parse(JSON.stringify with structuredClone in tests. --- test/DataIntegrityProof.spec.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index 432577c..e93d9b8 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -247,7 +247,7 @@ describe('DataIntegrityProof', () => { }); it('should fail to sign with undefined term', async () => { - const unsignedCredential = JSON.parse(JSON.stringify(credential)); + const unsignedCredential = structuredClone(credential); unsignedCredential.undefinedTerm = 'foo'; const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); @@ -271,7 +271,7 @@ describe('DataIntegrityProof', () => { }); it('should fail to sign with relative type URL', async () => { - const unsignedCredential = JSON.parse(JSON.stringify(credential)); + const unsignedCredential = structuredClone(credential); unsignedCredential.type.push('UndefinedType'); const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); @@ -295,7 +295,7 @@ describe('DataIntegrityProof', () => { }); it('should fail to sign with custom "createVerifyData"', async () => { - const unsignedCredential = JSON.parse(JSON.stringify(credential)); + const unsignedCredential = structuredClone(credential); const brokenCryptosuite = { ...eddsa2022CryptoSuite, async createVerifyData() { @@ -324,7 +324,7 @@ describe('DataIntegrityProof', () => { }); it('should fail to sign with custom "createProofValue"', async () => { - const unsignedCredential = JSON.parse(JSON.stringify(credential)); + const unsignedCredential = structuredClone(credential); const brokenCryptosuite = { ...eddsa2022CryptoSuite, async createProofValue() { @@ -517,8 +517,7 @@ describe('DataIntegrityProof', () => { const suite = new DataIntegrityProof({ cryptosuite: eddsa2022CryptoSuite }); - const signedCredentialCopy = - JSON.parse(JSON.stringify(signedCredential)); + const signedCredentialCopy = structuredClone(signedCredential); // intentionally modify proofValue type to not be string signedCredentialCopy.proof.proofValue = {}; @@ -541,8 +540,7 @@ describe('DataIntegrityProof', () => { const suite = new DataIntegrityProof({ cryptosuite: eddsa2022CryptoSuite }); - const signedCredentialCopy = - JSON.parse(JSON.stringify(signedCredential)); + const signedCredentialCopy = structuredClone(signedCredential); // intentionally modify proofValue to be undefined signedCredentialCopy.proof.proofValue = undefined; @@ -566,8 +564,7 @@ describe('DataIntegrityProof', () => { const suite = new DataIntegrityProof({ cryptosuite: eddsa2022CryptoSuite }); - const signedCredentialCopy = - JSON.parse(JSON.stringify(signedCredential)); + const signedCredentialCopy = structuredClone(signedCredential); // intentionally modify proofValue to not start with 'z' signedCredentialCopy.proof.proofValue = 'a'; @@ -591,8 +588,7 @@ describe('DataIntegrityProof', () => { const suite = new DataIntegrityProof({ cryptosuite: eddsa2022CryptoSuite }); - const signedCredentialCopy = - JSON.parse(JSON.stringify(signedCredential)); + const signedCredentialCopy = structuredClone(signedCredential); // intentionally modify proof type to be InvalidSignature2100 signedCredentialCopy.proof.type = 'InvalidSignature2100'; From 101f4a3ac1f6db704c4d01c84e0aaab7e9e7662c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 1 Sep 2024 17:33:55 -0400 Subject: [PATCH 05/26] Fill in 2 created tests. --- test/DataIntegrityProof.spec.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index e93d9b8..f793065 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -605,11 +605,36 @@ describe('DataIntegrityProof', () => { }); it('should fail verification if proof created is not XMLSCHEMA11-2', async function() { - + const unsignedCredential = {...credential}; + const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); + const suite = new DataIntegrityProof({ + signer: keyPair.signer(), cryptosuite: eddsa2022CryptoSuite + }); + suite.proof = {created: 'May-23-2022'}; + const signedCredential = await jsigs.sign(unsignedCredential, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader + }); + console.log(JSON.stringify({signedCredential}, null, 2)); }); it('should fail verification if proof created is in the future', async function() { - + const unsignedCredential = {...credential}; + const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); + const date = new Date(); + date.setUTCFullYear(date.getUTCFullYear() + 2); + const suite = new DataIntegrityProof({ + cryptosuite: eddsa2022CryptoSuite, + signer: keyPair.signer(), + date + }); + const signedCredential = await jsigs.sign(unsignedCredential, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader + }); + console.log(JSON.stringify({signedCredential}, null, 2)); }); it('should interpret proof created as UTC if incorrectly serialized', async function() { From e30fc5961734e551cbc03b50a1cd74efdc8c8f2d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 1 Sep 2024 18:27:37 -0400 Subject: [PATCH 06/26] Add signedCredInvalidCreated to mock.data.js. --- test/mock-data.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/mock-data.js b/test/mock-data.js index 22549eb..48c25a1 100644 --- a/test/mock-data.js +++ b/test/mock-data.js @@ -67,3 +67,33 @@ export const credentialWithLegacyContext = { alumniOf: 'Example University' } }; + +export const signedCredentialWithInvalidCreated = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + AlumniCredential: 'https://schema.org#AlumniCredential', + alumniOf: 'https://schema.org#alumniOf' + }, + 'https://w3id.org/security/data-integrity/v2' + ], + id: 'http://example.edu/credentials/1872', + type: [ + 'VerifiableCredential', + 'AlumniCredential' + ], + issuer: 'https://example.edu/issuers/565049', + issuanceDate: '2010-01-01T19:23:24Z', + credentialSubject: { + id: 'https://example.edu/students/alice', + alumniOf: 'Example University' + }, + proof: { + created: 'May-23-2022', + type: 'DataIntegrityProof', + verificationMethod: 'https://example.edu/issuers/565049#z6MkwXG2WjeQnNxSoynSGYU8V9j3QzP3JSqhdmkHc6SaVWoT', + cryptosuite: 'eddsa-2022', + proofPurpose: 'assertionMethod', + proofValue: 'z4sKdR9XJ1CuUKTzjRqWDZXTPr6HRwPaLTkqw9Co3RXmDGsyfbs5czpzaMridAyd4Kq14hW5rxHuEjkMMpVcNTZN4' + } +}; From e798648f4304f527e96765556a0248113c13f8c3 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 09:51:35 -0400 Subject: [PATCH 07/26] Assert on verified for nonXMLSCHEMA11-2 created. --- test/DataIntegrityProof.spec.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index f793065..a037c11 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -10,7 +10,8 @@ import * as Ed25519Multikey from '@digitalbazaar/ed25519-multikey'; import { credential, credentialWithLegacyContext, - ed25519MultikeyKeyPair + ed25519MultikeyKeyPair, + signedCredentialWithInvalidCreated } from './mock-data.js'; import {DataIntegrityProof} from '../lib/index.js'; import { @@ -605,18 +606,25 @@ describe('DataIntegrityProof', () => { }); it('should fail verification if proof created is not XMLSCHEMA11-2', async function() { - const unsignedCredential = {...credential}; - const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); + const signedCredentialCopy = structuredClone( + signedCredentialWithInvalidCreated); const suite = new DataIntegrityProof({ - signer: keyPair.signer(), cryptosuite: eddsa2022CryptoSuite + cryptosuite: eddsa2022CryptoSuite }); - suite.proof = {created: 'May-23-2022'}; - const signedCredential = await jsigs.sign(unsignedCredential, { + const result = await jsigs.verify(signedCredentialCopy, { suite, purpose: new AssertionProofPurpose(), documentLoader }); - console.log(JSON.stringify({signedCredential}, null, 2)); + should.exist(result, 'Expected verification results to exist.'); + should.exist( + result.verified, + 'Expected verification results to have property verified.' + ); + result.verified.should.equal( + false, + 'Expected credential with non XMLSCHEMA11-2 created to not verify.' + ); }); it('should fail verification if proof created is in the future', async function() { From 4413a2fbcf20b24176e045124682cbae5d67d13c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 09:55:24 -0400 Subject: [PATCH 08/26] Assert on verification results for created in future. --- test/DataIntegrityProof.spec.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index a037c11..d270eda 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -628,7 +628,7 @@ describe('DataIntegrityProof', () => { }); it('should fail verification if proof created is in the future', async function() { - const unsignedCredential = {...credential}; + const unsignedCredential = structuredClone(credential); const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); const date = new Date(); date.setUTCFullYear(date.getUTCFullYear() + 2); @@ -642,7 +642,20 @@ describe('DataIntegrityProof', () => { purpose: new AssertionProofPurpose(), documentLoader }); - console.log(JSON.stringify({signedCredential}, null, 2)); + const result = await jsigs.verify(signedCredential, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader + }); + should.exist(result, 'Expected verification results to exist.'); + should.exist( + result.verified, + 'Expected verification results to have property verified.' + ); + result.verified.should.equal( + false, + 'Expected credential with created in the future to not verify.' + ); }); it('should interpret proof created as UTC if incorrectly serialized', async function() { From d830bfcfc7192cace411fe7e37a40cb4289a4413 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 10:09:05 -0400 Subject: [PATCH 09/26] Add first part of incorrect serialization test. --- test/DataIntegrityProof.spec.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index d270eda..3753bf1 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -659,7 +659,35 @@ describe('DataIntegrityProof', () => { }); it('should interpret proof created as UTC if incorrectly serialized', async function() { - + const unsignedCredential = structuredClone(credential); + const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); + const date = new Date(); + date.setUTCFullYear(date.getUTCFullYear() + 2); + const suite = new DataIntegrityProof({ + cryptosuite: eddsa2022CryptoSuite, + signer: keyPair.signer(), + }); + suite.proof = {created: date.toISOString().substr(0, 19)}; + const signedCredential = await jsigs.sign(unsignedCredential, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader + }); + const result = await jsigs.verify(signedCredential, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader + }); + should.exist(result, 'Expected verification results to exist.'); + should.exist( + result.verified, + 'Expected verification results to have property verified.' + ); + result.verified.should.equal( + false, + 'Expected credential with created that should be interpreted as ' + + 'in the future to not verify.' + ); }); }); }); From f6eabd7a863e0d85e288ef884f401a33bec50436 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 10:16:35 -0400 Subject: [PATCH 10/26] Lint mock.data & add additional invalid created. --- test/mock-data.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/mock-data.js b/test/mock-data.js index 48c25a1..03167f6 100644 --- a/test/mock-data.js +++ b/test/mock-data.js @@ -94,6 +94,38 @@ export const signedCredentialWithInvalidCreated = { verificationMethod: 'https://example.edu/issuers/565049#z6MkwXG2WjeQnNxSoynSGYU8V9j3QzP3JSqhdmkHc6SaVWoT', cryptosuite: 'eddsa-2022', proofPurpose: 'assertionMethod', - proofValue: 'z4sKdR9XJ1CuUKTzjRqWDZXTPr6HRwPaLTkqw9Co3RXmDGsyfbs5czpzaMridAyd4Kq14hW5rxHuEjkMMpVcNTZN4' + proofValue: 'z4sKdR9XJ1CuUKTzjRqWDZXTPr6HRwPaLTkqw9Co3RXmDGsyfbs5czpzaMr' + + 'idAyd4Kq14hW5rxHuEjkMMpVcNTZN4' + } +}; + +export const signedCredentialCreatedNoOffset = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + AlumniCredential: 'https://schema.org#AlumniCredential', + alumniOf: 'https://schema.org#alumniOf' + }, + 'https://w3id.org/security/data-integrity/v2' + ], + id: 'http://example.edu/credentials/1872', + type: [ + 'VerifiableCredential', + 'AlumniCredential' + ], + issuer: 'https://example.edu/issuers/565049', + issuanceDate: '2010-01-01T19:23:24Z', + credentialSubject: { + id: 'https://example.edu/students/alice', + alumniOf: 'Example University' + }, + proof: { + created: '2024-09-03T14:13:10', + type: 'DataIntegrityProof', + verificationMethod: 'https://example.edu/issuers/565049#z6MkwXG2WjeQnNxSoynSGYU8V9j3QzP3JSqhdmkHc6SaVWoT', + cryptosuite: 'eddsa-2022', + proofPurpose: 'assertionMethod', + proofValue: 'z2RkqdgtNvAhi7PK99ZVJqDHyMXNwYjg7hBP2XC4uxH17zrdAf2YqSDteu' + + 'ALnyq4yMpGteVzPV3CjZY4mbRqfMVxd' } }; From aa62e45ee4ecd4edcaeb18e1089ec7356c4ccd0a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 10:52:46 -0400 Subject: [PATCH 11/26] Add positive test to created incorrect serialization test. --- test/DataIntegrityProof.spec.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index 3753bf1..4f5d4c7 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -11,6 +11,7 @@ import { credential, credentialWithLegacyContext, ed25519MultikeyKeyPair, + signedCredentialCreatedNoOffset, signedCredentialWithInvalidCreated } from './mock-data.js'; import {DataIntegrityProof} from '../lib/index.js'; @@ -659,6 +660,8 @@ describe('DataIntegrityProof', () => { }); it('should interpret proof created as UTC if incorrectly serialized', async function() { + // this is a little hard to test so a negative and positive test are + // used const unsignedCredential = structuredClone(credential); const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); const date = new Date(); @@ -673,21 +676,40 @@ describe('DataIntegrityProof', () => { purpose: new AssertionProofPurpose(), documentLoader }); - const result = await jsigs.verify(signedCredential, { + const negativeResult = await jsigs.verify(signedCredential, { suite, purpose: new AssertionProofPurpose(), documentLoader }); - should.exist(result, 'Expected verification results to exist.'); + should.exist(negativeResult, 'Expected verification results to exist.'); should.exist( - result.verified, - 'Expected verification results to have property verified.' + negativeResult.verified, + 'Expected negative verification results to have property verified.' ); - result.verified.should.equal( + negativeResult.verified.should.equal( false, 'Expected credential with created that should be interpreted as ' + 'in the future to not verify.' ); + const signedCredentialCopy = structuredClone( + signedCredentialCreatedNoOffset); + const positiveResult = await jsigs.verify(signedCredentialCopy, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader + }); + should.exist( + positiveResult, + 'Expected positive verification result to exist.' + ); + should.exist( + positiveResult.verified, + 'Expected positive result to have property verified.' + ); + positiveResult.verified.should.equal( + true, + 'Expected created to be interprested as a UTC date in the past.' + ); }); }); }); From 8d9eb52d082713a16681cede6af04b49ecbe541f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 11:58:05 -0400 Subject: [PATCH 12/26] Add XMLDateTimeRegExp to utils.js. --- lib/util.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/util.js b/lib/util.js index c3cdca9..c9ecaef 100644 --- a/lib/util.js +++ b/lib/util.js @@ -20,6 +20,17 @@ export const w3cDate = date => { return str.slice(0, - 5) + 'Z'; }; +// Z and T must be uppercase +// xml schema date time RegExp +// @see https://www.w3.org/TR/xmlschema11-2/#dateTime +export const XMLDateTimeRegExp = new RegExp( + '-?([1-9][0-9]{3,}|0[0-9]{3})' + + '-(0[1-9]|1[0-2])' + + '-(0[1-9]|[12][0-9]|3[01])' + + 'T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?))' + + '(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?'); +export const isW3cDate = timeStamp => XMLDateTimeRegExp.test(timeStamp); + /** * Concatenates two Uint8Arrays. * From 18ca43e3a18e0d298ceb5f7f786b4218bf50ba3a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 12:10:02 -0400 Subject: [PATCH 13/26] Add initial logic for created compare. --- lib/DataIntegrityProof.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 0bfd650..04a1a80 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -292,6 +292,17 @@ export class DataIntegrityProof extends LinkedDataProof { if(!verified) { throw new Error('Invalid signature.'); } + if(proof.created) { + if(!util.isW3cDate(proof.created)) { + throw new Error('invalid datetime format'); + } + const now = new Date(); + const created = new Date(proof.created); + const deltaCreated = now - created; + if(deltaCreated < 0) { + throw new Error('created in the future'); + } + } return {verified: true, verificationMethod}; } catch(error) { From 5e39e5e894d08eed1361d26049890d7c1a43891f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 12:10:48 -0400 Subject: [PATCH 14/26] Correct typo interpreted not interprested. --- test/DataIntegrityProof.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index 4f5d4c7..83bab85 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -708,7 +708,7 @@ describe('DataIntegrityProof', () => { ); positiveResult.verified.should.equal( true, - 'Expected created to be interprested as a UTC date in the past.' + 'Expected created to be interpreted as a UTC date in the past.' ); }); }); From 1a62bfebdba93538497e0535adcbab62c28a2eac Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 14:33:14 -0400 Subject: [PATCH 15/26] Add convertTimeStamp to utils. --- lib/DataIntegrityProof.js | 2 +- lib/util.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 04a1a80..162b97f 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -297,7 +297,7 @@ export class DataIntegrityProof extends LinkedDataProof { throw new Error('invalid datetime format'); } const now = new Date(); - const created = new Date(proof.created); + const created = util.convertTimeStamp(proof.created); const deltaCreated = now - created; if(deltaCreated < 0) { throw new Error('created in the future'); diff --git a/lib/util.js b/lib/util.js index c9ecaef..fe3a72f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -20,6 +20,9 @@ export const w3cDate = date => { return str.slice(0, - 5) + 'Z'; }; +export const timezoneOffset = new RegExp( + '(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))$'); + // Z and T must be uppercase // xml schema date time RegExp // @see https://www.w3.org/TR/xmlschema11-2/#dateTime @@ -31,6 +34,16 @@ export const XMLDateTimeRegExp = new RegExp( '(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?'); export const isW3cDate = timeStamp => XMLDateTimeRegExp.test(timeStamp); +export const convertTimeStamp = timestamp => { + if(!timestamp) { + throw new Error(`Expected timestamp recieved: ${timestamp}`); + } + if(!timezoneOffset.test(timestamp)) { + return new Date(`${timestamp}Z`); + } + return new Date(timestamp); +}; + /** * Concatenates two Uint8Arrays. * From fa092b23a671031aae477c026147470ce8b8df6c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 3 Sep 2024 14:33:55 -0400 Subject: [PATCH 16/26] Reduce features in PR to just created checks. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04565eb..90ced4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 2.4.0 - ### Added -- verify now checks `created` and `expires`. +- verify now checks `created`. ## 2.3.0 - 2024-08-26 From f73d29dcdfffa61e72e4e8592d687317c1041558 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 4 Sep 2024 13:17:46 +0000 Subject: [PATCH 17/26] Add JSDOC for DI constructor & maxClockSkew. --- lib/DataIntegrityProof.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 162b97f..b51dd54 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -20,7 +20,22 @@ const PROOF_TYPE = 'DataIntegrityProof'; const VC_2_0_CONTEXT = 'https://www.w3.org/ns/credentials/v2'; export class DataIntegrityProof extends LinkedDataProof { - constructor({signer, date, cryptosuite, legacyContext = false} = {}) { + /** + * The constructor for the DataIntegrityProof Class. + * + * @param {object} options - Options for the Class. + * @param {object} [options.signer] - A signer for the suite. + * @param {string|Date|number} [options.date] - A date for created. + * @param {object} options.cryptosuite - A compliant cryptosuite. + * @param {boolean} [options.legacyContext = false] - Toggles between + * the current DI context and a legacy DI context. + * @param {number} [options.maxClockSkew = 300] - The maximum acceptable + * delta between clocks in the verification process in seconds. + */ + constructor({ + signer, date, cryptosuite, + legacyContext = false, maxClockSkew = 300 + } = {}) { super({type: PROOF_TYPE}); const { canonize, createVerifier, name, requiredAlgorithm, @@ -66,6 +81,7 @@ export class DataIntegrityProof extends LinkedDataProof { const vm = _processSignatureParams({signer, requiredAlgorithm}); this.verificationMethod = vm.verificationMethod; this.signer = vm.signer; + this.maxClockSkew = maxClockSkew; } /** @@ -294,13 +310,13 @@ export class DataIntegrityProof extends LinkedDataProof { } if(proof.created) { if(!util.isW3cDate(proof.created)) { - throw new Error('invalid datetime format'); + throw new Error('Invalid XML TimeStamp'); } const now = new Date(); const created = util.convertTimeStamp(proof.created); const deltaCreated = now - created; if(deltaCreated < 0) { - throw new Error('created in the future'); + throw new Error('"proof.created" in the future'); } } From df8f6fdbe027d220426e8bc4b390939f1fca6e1e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 4 Sep 2024 13:21:13 +0000 Subject: [PATCH 18/26] Use maxClockSkew to make now more forgiving for created. --- lib/DataIntegrityProof.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index b51dd54..2a56fb0 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -312,7 +312,7 @@ export class DataIntegrityProof extends LinkedDataProof { if(!util.isW3cDate(proof.created)) { throw new Error('Invalid XML TimeStamp'); } - const now = new Date(); + const now = Date.now() + (this.maxClockSkew * 1000); const created = util.convertTimeStamp(proof.created); const deltaCreated = now - created; if(deltaCreated < 0) { From 7ae398140e89a20df6a6b3f830d71b4028011146 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 4 Sep 2024 14:41:08 +0000 Subject: [PATCH 19/26] Make now a parameter to verifyProof. --- lib/DataIntegrityProof.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 2a56fb0..821502c 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2022-2023 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved. */ import * as base58btc from 'base58-universal'; import * as base64url from 'base64url-universal'; @@ -275,10 +275,15 @@ export class DataIntegrityProof extends LinkedDataProof { * @param {Array} options.proofSet - Any existing proof set. * @param {object} options.document - The document to create a proof for. * @param {Function} options.documentLoader - The document loader to use. + * @param {Date} [options.now] - A date for now. * * @returns {Promise<{object}>} Resolves with the verification result. */ - async verifyProof({proof, proofSet, document, documentLoader}) { + async verifyProof({ + proof, proofSet, + document, documentLoader, + now = new Date() + }) { try { // fetch verification method const verificationMethod = await this.getVerificationMethod({ @@ -312,9 +317,8 @@ export class DataIntegrityProof extends LinkedDataProof { if(!util.isW3cDate(proof.created)) { throw new Error('Invalid XML TimeStamp'); } - const now = Date.now() + (this.maxClockSkew * 1000); - const created = util.convertTimeStamp(proof.created); - const deltaCreated = now - created; + const deltaCreated = (now.getTime() + (this.maxClockSkew * 1000)) - + util.convertTimeStamp(proof.created).getTime(); if(deltaCreated < 0) { throw new Error('"proof.created" in the future'); } From be6a1a20f287705aad3628480dbdc8787ff4cb5a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 11:36:43 -0400 Subject: [PATCH 20/26] Remove created date checks, clockSkew, & now + 1 test. --- lib/DataIntegrityProof.js | 13 +------ test/DataIntegrityProof.spec.js | 63 ++------------------------------- 2 files changed, 4 insertions(+), 72 deletions(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 821502c..1a35c2e 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -29,12 +29,10 @@ export class DataIntegrityProof extends LinkedDataProof { * @param {object} options.cryptosuite - A compliant cryptosuite. * @param {boolean} [options.legacyContext = false] - Toggles between * the current DI context and a legacy DI context. - * @param {number} [options.maxClockSkew = 300] - The maximum acceptable - * delta between clocks in the verification process in seconds. */ constructor({ signer, date, cryptosuite, - legacyContext = false, maxClockSkew = 300 + legacyContext = false } = {}) { super({type: PROOF_TYPE}); const { @@ -81,7 +79,6 @@ export class DataIntegrityProof extends LinkedDataProof { const vm = _processSignatureParams({signer, requiredAlgorithm}); this.verificationMethod = vm.verificationMethod; this.signer = vm.signer; - this.maxClockSkew = maxClockSkew; } /** @@ -275,14 +272,12 @@ export class DataIntegrityProof extends LinkedDataProof { * @param {Array} options.proofSet - Any existing proof set. * @param {object} options.document - The document to create a proof for. * @param {Function} options.documentLoader - The document loader to use. - * @param {Date} [options.now] - A date for now. * * @returns {Promise<{object}>} Resolves with the verification result. */ async verifyProof({ proof, proofSet, document, documentLoader, - now = new Date() }) { try { // fetch verification method @@ -317,13 +312,7 @@ export class DataIntegrityProof extends LinkedDataProof { if(!util.isW3cDate(proof.created)) { throw new Error('Invalid XML TimeStamp'); } - const deltaCreated = (now.getTime() + (this.maxClockSkew * 1000)) - - util.convertTimeStamp(proof.created).getTime(); - if(deltaCreated < 0) { - throw new Error('"proof.created" in the future'); - } } - return {verified: true, verificationMethod}; } catch(error) { return {verified: false, error}; diff --git a/test/DataIntegrityProof.spec.js b/test/DataIntegrityProof.spec.js index 83bab85..9d279de 100644 --- a/test/DataIntegrityProof.spec.js +++ b/test/DataIntegrityProof.spec.js @@ -627,70 +627,13 @@ describe('DataIntegrityProof', () => { 'Expected credential with non XMLSCHEMA11-2 created to not verify.' ); }); - it('should fail verification if proof created is in the future', - async function() { - const unsignedCredential = structuredClone(credential); - const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); - const date = new Date(); - date.setUTCFullYear(date.getUTCFullYear() + 2); - const suite = new DataIntegrityProof({ - cryptosuite: eddsa2022CryptoSuite, - signer: keyPair.signer(), - date - }); - const signedCredential = await jsigs.sign(unsignedCredential, { - suite, - purpose: new AssertionProofPurpose(), - documentLoader - }); - const result = await jsigs.verify(signedCredential, { - suite, - purpose: new AssertionProofPurpose(), - documentLoader - }); - should.exist(result, 'Expected verification results to exist.'); - should.exist( - result.verified, - 'Expected verification results to have property verified.' - ); - result.verified.should.equal( - false, - 'Expected credential with created in the future to not verify.' - ); - }); it('should interpret proof created as UTC if incorrectly serialized', async function() { - // this is a little hard to test so a negative and positive test are - // used - const unsignedCredential = structuredClone(credential); - const keyPair = await Ed25519Multikey.from({...ed25519MultikeyKeyPair}); - const date = new Date(); - date.setUTCFullYear(date.getUTCFullYear() + 2); + // this is a little hard to test so we just assume + // a datetime with out an offset is accepted const suite = new DataIntegrityProof({ - cryptosuite: eddsa2022CryptoSuite, - signer: keyPair.signer(), - }); - suite.proof = {created: date.toISOString().substr(0, 19)}; - const signedCredential = await jsigs.sign(unsignedCredential, { - suite, - purpose: new AssertionProofPurpose(), - documentLoader - }); - const negativeResult = await jsigs.verify(signedCredential, { - suite, - purpose: new AssertionProofPurpose(), - documentLoader + cryptosuite: eddsa2022CryptoSuite }); - should.exist(negativeResult, 'Expected verification results to exist.'); - should.exist( - negativeResult.verified, - 'Expected negative verification results to have property verified.' - ); - negativeResult.verified.should.equal( - false, - 'Expected credential with created that should be interpreted as ' + - 'in the future to not verify.' - ); const signedCredentialCopy = structuredClone( signedCredentialCreatedNoOffset); const positiveResult = await jsigs.verify(signedCredentialCopy, { From d48b1c43b6d8832ac76c6b99b54d7e604628f3e2 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 12:05:36 -0400 Subject: [PATCH 21/26] Update CHANGELOG.md with more precise wording of created check. Co-authored-by: Dave Longley --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ced4d..3344f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 2.4.0 - ### Added -- verify now checks `created`. +- `verify()` now checks that `created`, if present, is properly formatted. ## 2.3.0 - 2024-08-26 From 4da6fa0c10ee805311fb275673e0d8ed4b83fa89 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 12:06:02 -0400 Subject: [PATCH 22/26] Update lib/DataIntegrityProof.js make date description more explicit. Co-authored-by: Dave Longley --- lib/DataIntegrityProof.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 1a35c2e..bb43bda 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -25,7 +25,7 @@ export class DataIntegrityProof extends LinkedDataProof { * * @param {object} options - Options for the Class. * @param {object} [options.signer] - A signer for the suite. - * @param {string|Date|number} [options.date] - A date for created. + * @param {string|Date|number} [options.date] - A date to use for `created`. * @param {object} options.cryptosuite - A compliant cryptosuite. * @param {boolean} [options.legacyContext = false] - Toggles between * the current DI context and a legacy DI context. From 81970c4a939570ac26e10671872ca8fdb445690c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 12:06:51 -0400 Subject: [PATCH 23/26] Update lib/DataIntegrityProof.js check proof created even when falsey. Co-authored-by: Dave Longley --- lib/DataIntegrityProof.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index bb43bda..24c3734 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -308,7 +308,7 @@ export class DataIntegrityProof extends LinkedDataProof { if(!verified) { throw new Error('Invalid signature.'); } - if(proof.created) { + if(proof.created !== undefined) { if(!util.isW3cDate(proof.created)) { throw new Error('Invalid XML TimeStamp'); } From 6beace577450b44a3f3b7ab40ebee517f942151c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 12:07:21 -0400 Subject: [PATCH 24/26] Update lib/util.js Clarify unexpected timestamp error message. Co-authored-by: Dave Longley --- lib/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.js b/lib/util.js index fe3a72f..9cfa30f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -36,7 +36,7 @@ export const isW3cDate = timeStamp => XMLDateTimeRegExp.test(timeStamp); export const convertTimeStamp = timestamp => { if(!timestamp) { - throw new Error(`Expected timestamp recieved: ${timestamp}`); + throw new Error(`Unexpected timestamp ("${timestamp}") received.`); } if(!timezoneOffset.test(timestamp)) { return new Date(`${timestamp}Z`); From 0f66109ccc5da09d7c79ad5170f97c119525ef8e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 12:09:38 -0400 Subject: [PATCH 25/26] Revert spacing of verifyProof. --- lib/DataIntegrityProof.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index 24c3734..cccfb9b 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -275,10 +275,7 @@ export class DataIntegrityProof extends LinkedDataProof { * * @returns {Promise<{object}>} Resolves with the verification result. */ - async verifyProof({ - proof, proofSet, - document, documentLoader, - }) { + async verifyProof({proof, proofSet, document, documentLoader}) { try { // fetch verification method const verificationMethod = await this.getVerificationMethod({ From 73d78ed0e904d007742662e276dae1da79d42ef4 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 5 Sep 2024 12:12:49 -0400 Subject: [PATCH 26/26] Revert new line in constructor for DI Class. --- lib/DataIntegrityProof.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/DataIntegrityProof.js b/lib/DataIntegrityProof.js index cccfb9b..64389d6 100644 --- a/lib/DataIntegrityProof.js +++ b/lib/DataIntegrityProof.js @@ -30,10 +30,7 @@ export class DataIntegrityProof extends LinkedDataProof { * @param {boolean} [options.legacyContext = false] - Toggles between * the current DI context and a legacy DI context. */ - constructor({ - signer, date, cryptosuite, - legacyContext = false - } = {}) { + constructor({signer, date, cryptosuite, legacyContext = false} = {}) { super({type: PROOF_TYPE}); const { canonize, createVerifier, name, requiredAlgorithm,