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

Validate created #34

Merged
merged 26 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ba9b98b
CHANGELOG minor feature: check created & expires.
aljones15 Sep 1, 2024
f255359
Add 3 initial test titles for created.
aljones15 Sep 1, 2024
3d5238a
Lint created test titles and add proof.
aljones15 Sep 1, 2024
104c9e8
Replace JSON.parse(JSON.stringify with structuredClone in tests.
aljones15 Sep 1, 2024
101f4a3
Fill in 2 created tests.
aljones15 Sep 1, 2024
e30fc59
Add signedCredInvalidCreated to mock.data.js.
aljones15 Sep 1, 2024
e798648
Assert on verified for nonXMLSCHEMA11-2 created.
aljones15 Sep 3, 2024
4413a2f
Assert on verification results for created in future.
aljones15 Sep 3, 2024
d830bfc
Add first part of incorrect serialization test.
aljones15 Sep 3, 2024
f6eabd7
Lint mock.data & add additional invalid created.
aljones15 Sep 3, 2024
aa62e45
Add positive test to created incorrect serialization test.
aljones15 Sep 3, 2024
8d9eb52
Add XMLDateTimeRegExp to utils.js.
aljones15 Sep 3, 2024
18ca43e
Add initial logic for created compare.
aljones15 Sep 3, 2024
5e39e5e
Correct typo interpreted not interprested.
aljones15 Sep 3, 2024
1a62bfe
Add convertTimeStamp to utils.
aljones15 Sep 3, 2024
fa092b2
Reduce features in PR to just created checks.
aljones15 Sep 3, 2024
f73d29d
Add JSDOC for DI constructor & maxClockSkew.
aljones15 Sep 4, 2024
df8f6fd
Use maxClockSkew to make now more forgiving for created.
aljones15 Sep 4, 2024
7ae3981
Make now a parameter to verifyProof.
aljones15 Sep 4, 2024
be6a1a2
Remove created date checks, clockSkew, & now + 1 test.
aljones15 Sep 5, 2024
d48b1c4
Update CHANGELOG.md with more precise wording of created check.
aljones15 Sep 5, 2024
4da6fa0
Update lib/DataIntegrityProof.js make date description more explicit.
aljones15 Sep 5, 2024
81970c4
Update lib/DataIntegrityProof.js check proof created even when falsey.
aljones15 Sep 5, 2024
6beace5
Update lib/util.js Clarify unexpected timestamp error message.
aljones15 Sep 5, 2024
0f66109
Revert spacing of verifyProof.
aljones15 Sep 5, 2024
73d78ed
Revert new line in constructor for DI Class.
aljones15 Sep 5, 2024
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# @digitalbazaar/data-integrity Changelog

## 2.4.0 -

### Added
- `verify()` now checks that `created`, if present, is properly formatted.

## 2.3.0 - 2024-08-26

### Changed
Expand Down
18 changes: 16 additions & 2 deletions lib/DataIntegrityProof.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -20,6 +20,16 @@ const PROOF_TYPE = 'DataIntegrityProof';
const VC_2_0_CONTEXT = 'https://www.w3.org/ns/credentials/v2';

export class DataIntegrityProof extends LinkedDataProof {
/**
* 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 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.
*/
constructor({signer, date, cryptosuite, legacyContext = false} = {}) {
super({type: PROOF_TYPE});
const {
Expand Down Expand Up @@ -292,7 +302,11 @@ export class DataIntegrityProof extends LinkedDataProof {
if(!verified) {
throw new Error('Invalid signature.');
}

if(proof.created !== undefined) {
if(!util.isW3cDate(proof.created)) {
throw new Error('Invalid XML TimeStamp');
}
}
return {verified: true, verificationMethod};
} catch(error) {
return {verified: false, error};
Expand Down
24 changes: 24 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ 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
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);

export const convertTimeStamp = timestamp => {
if(!timestamp) {
throw new Error(`Unexpected timestamp ("${timestamp}") received.`);
}
if(!timezoneOffset.test(timestamp)) {
return new Date(`${timestamp}Z`);
}
return new Date(timestamp);
};

/**
* Concatenates two Uint8Arrays.
*
Expand Down
73 changes: 60 additions & 13 deletions test/DataIntegrityProof.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import * as Ed25519Multikey from '@digitalbazaar/ed25519-multikey';
import {
credential,
credentialWithLegacyContext,
ed25519MultikeyKeyPair
ed25519MultikeyKeyPair,
signedCredentialCreatedNoOffset,
signedCredentialWithInvalidCreated
} from './mock-data.js';
import {DataIntegrityProof} from '../lib/index.js';
import {
Expand Down Expand Up @@ -247,7 +249,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});
Expand All @@ -271,7 +273,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});
Expand All @@ -295,7 +297,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() {
Expand Down Expand Up @@ -324,7 +326,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() {
Expand Down Expand Up @@ -517,8 +519,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 = {};

Expand All @@ -541,8 +542,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;

Expand All @@ -566,8 +566,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';

Expand All @@ -591,8 +590,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';

Expand All @@ -607,5 +605,54 @@ describe('DataIntegrityProof', () => {
expect(result.verified).to.be.false;
expect(errors[0].name).to.equal('NotFoundError');
});
it('should fail verification if proof created is not XMLSCHEMA11-2',
async function() {
const signedCredentialCopy = structuredClone(
signedCredentialWithInvalidCreated);
const suite = new DataIntegrityProof({
cryptosuite: eddsa2022CryptoSuite
});
const result = await jsigs.verify(signedCredentialCopy, {
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 non XMLSCHEMA11-2 created to not verify.'
);
});
it('should interpret proof created as UTC if incorrectly serialized',
async function() {
// 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
});
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 interpreted as a UTC date in the past.'
);
});
});
});
62 changes: 62 additions & 0 deletions test/mock-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,65 @@ 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: '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'
}
};
Loading