diff --git a/bundle-esm-stats.html b/bundle-esm-stats.html index 65640a54b..ff0477135 100644 --- a/bundle-esm-stats.html +++ b/bundle-esm-stats.html @@ -2199,7 +2199,7 @@ step = start, start = stop, stop = step; step = i0, i0 = i1, i1 = step; } - + while (maxIter-- > 0) { step = tickIncrement(start, stop, count); if (step === prestep) { @@ -2599,7 +2599,7 @@ } else { return path.replace(/\/$/, '').replace(/.*\//, ''); } - }; + }; } (utils$3)); const utils$2 = utils$3; @@ -2951,9 +2951,9 @@ if (opts.tokens) { if (idx === 0 && start !== 0) { tokens[idx].isPrefix = true; - tokens[idx].value = prefix; + tokens[idx].dateTimeStamp = prefix; } else { - tokens[idx].value = value; + tokens[idx].dateTimeStamp = value; } depth(tokens[idx]); state.maxDepth += tokens[idx].depth; @@ -2969,7 +2969,7 @@ parts.push(value); if (opts.tokens) { - tokens[tokens.length - 1].value = value; + tokens[tokens.length - 1].dateTimeStamp = value; depth(tokens[tokens.length - 1]); state.maxDepth += tokens[tokens.length - 1].depth; } diff --git a/src/data/i18n.json b/src/data/i18n.json index 1ec140871..f642c6545 100644 --- a/src/data/i18n.json +++ b/src/data/i18n.json @@ -48,7 +48,9 @@ "compareIssuingAddressLabel": "Compare addresses", "compareIssuingAddressLabelPending": "Comparing addresses", "checkImagesIntegrityLabel": "Verify Images Integrity", - "checkImagesIntegrityLabelPending": "Verifying Images Integrity" + "checkImagesIntegrityLabelPending": "Verifying Images Integrity", + "validateDateFormatLabel": "Validate date format", + "validateDateFormatLabelPending": "Validating date format" }, "revocation": { "preReason": "Reason given:", @@ -106,8 +108,9 @@ "issuerProfileNotSet": "no issuer address given", "issuerProfileInvalid": "retrieved file does not seem to be a valid profile", "parseIssuerKeys": "Unable to parse JSON out of issuer identification data.", - "checkImagesIntegrity":"Image integrity verification proved that the content was modified after issuance.", - "ensureValidityPeriodStarted":"This certificate is not yet valid." + "checkImagesIntegrity": "Image integrity verification proved that the content was modified after issuance.", + "ensureValidityPeriodStarted": "This certificate is not yet valid.", + "validateDateFormat":"The date format specified does not conform with the spec requirements (RFC3339). Property:" } }, "fr": { @@ -159,7 +162,9 @@ "compareIssuingAddressLabel": "Comparaison des adresses", "compareIssuingAddressLabelPending": "Comparaison des adresses", "checkImagesIntegrityLabel": "Vérification de l'Intégrité des Images", - "checkImagesIntegrityLabelPending": "Vérification de l'Intégrité des Images" + "checkImagesIntegrityLabelPending": "Vérification de l'Intégrité des Images", + "validateDateFormatLabel": "Vérification du format des dates", + "validateDateFormatLabelPending": "Vérification du format des dates" }, "revocation": { "preReason": "Raison :", @@ -218,7 +223,8 @@ "issuerProfileInvalid": "le document distant ne semble pas être un profil d'émetteur valide", "parseIssuerKeys": "Impossible de lire le JSON d'identification de l'émetteur", "checkImagesIntegrity": "La vérification des images a prouvé que le contenu a été modifié après l'émission.", - "ensureValidityPeriodStarted": "Ce certificat n'est pas encore valide." + "ensureValidityPeriodStarted": "Ce certificat n'est pas encore valide.", + "validateDateFormat": "Le format de date spécifié n'est pas conforme aux exigences de la spécification (RFC3339). Propriété :" } }, "es": { @@ -270,7 +276,9 @@ "compareIssuingAddressLabel": "Comparar direcciones", "compareIssuingAddressLabelPending": "Comparando direcciones", "checkImagesIntegrityLabel": "Comprobar Integridad de las Imagens", - "checkImagesIntegrityLabelPending": "Comprobando Integridad de las Imagens" + "checkImagesIntegrityLabelPending": "Comprobando Integridad de las Imagens", + "validateDateFormatLabel": "Verificar formato de fecha", + "validateDateFormatLabelPending": "Verificando formato de fecha" }, "revocation": { "preReason": "Razón dada:", @@ -329,7 +337,8 @@ "issuerProfileInvalid": "el documento recogido no parece ser un perfil de emisor valido", "parseIssuerKeys": "No se ha podido analizar el JSON de la información de identificación del emisor", "checkImagesIntegrity": "La verificación de la integridad de los imagens provó alteración del contenido después de la emisión.", - "ensureValidityPeriodStarted": "Este certificado aún no está valido." + "ensureValidityPeriodStarted": "Este certificado aún no está valido.", + "validateDateFormat": "El formato de fecha especificado no cumple con los requisitos de la especificación (RFC3339). Propiedad:" } }, "mt": { @@ -381,7 +390,9 @@ "compareIssuingAddressLabel": "Compare addresses", "compareIssuingAddressLabelPending": "Comparing addresses", "checkImagesIntegrityLabel": "Verify Images Integrity", - "checkImagesIntegrityLabelPending": "Verifying Images Integrity" + "checkImagesIntegrityLabelPending": "Verifying Images Integrity", + "validateDateFormatLabel": "Validate date format", + "validateDateFormatLabelPending": "Validating date format" }, "revocation": { "preReason": "Raġuni mogħtija:", @@ -440,7 +451,8 @@ "issuerProfileInvalid": "retrieved file does not seem to be a valid profile", "parseIssuerKeys": "Ma jistax jiġi estratt JSON mid-data tal-identifikazzjoni tal-emittent", "checkImagesIntegrity": "Image integrity verification proved that the content was modified after issuance.", - "ensureValidityPeriodStarted": "This certificate is not yet valid." + "ensureValidityPeriodStarted": "This certificate is not yet valid.", + "validateDateFormat": "The date format specified does not conform with the spec requirements (RFC3339). Property:" } }, "it-IT": { @@ -492,7 +504,9 @@ "compareIssuingAddressLabel": "Compare addresses", "compareIssuingAddressLabelPending": "Comparing addresses", "checkImagesIntegrityLabel": "Verify Images Integrity", - "checkImagesIntegrityLabelPending": "Verifying Images Integrity" + "checkImagesIntegrityLabelPending": "Verifying Images Integrity", + "validateDateFormatLabel": "Validate date format", + "validateDateFormatLabelPending": "Validating date format" }, "revocation": { "preReason": "Motivo indicato:", @@ -551,7 +565,8 @@ "issuerProfileInvalid": "retrieved file does not seem to be a valid profile", "parseIssuerKeys": "Impossibile analizzare JSON dai dati di identificazione dell'emittente.", "checkImagesIntegrity": "Image integrity verification proved that the content was modified after issuance.", - "ensureValidityPeriodStarted": "This certificate is not yet valid." + "ensureValidityPeriodStarted": "This certificate is not yet valid.", + "validateDateFormat": "The date format specified does not conform with the spec requirements (RFC3339). Property:" } }, "ja": { @@ -603,7 +618,9 @@ "compareIssuingAddressLabel": "アドレスの照合", "compareIssuingAddressLabelPending": "アドレスを照合", "checkImagesIntegrityLabel": "画像の保全性の確認", - "checkImagesIntegrityLabelPending": "画像の保全性を確認" + "checkImagesIntegrityLabelPending": "画像の保全性を確認", + "validateDateFormatLabel": "Validate date format", + "validateDateFormatLabelPending": "Validating date format" }, "revocation": { "preReason": "理由:", @@ -662,7 +679,8 @@ "issuerProfileInvalid": "取得されたファイルが有効なプロフィールではないようです", "parseIssuerKeys": "発行者識別データからJSONの解析ができません", "checkImagesIntegrity": "データが発行後に変更されたことが画像保全性の検証によって証明されました。", - "ensureValidityPeriodStarted": "This certificate is not yet valid." + "ensureValidityPeriodStarted": "This certificate is not yet valid.", + "validateDateFormat": "The date format specified does not conform with the spec requirements (RFC3339). Property:" } } } diff --git a/src/domain/verifier/entities/verificationSteps.ts b/src/domain/verifier/entities/verificationSteps.ts index a9520bdcc..dbcf697fd 100644 --- a/src/domain/verifier/entities/verificationSteps.ts +++ b/src/domain/verifier/entities/verificationSteps.ts @@ -20,7 +20,8 @@ export enum SUB_STEPS { checkExpiresDate = 'checkExpiresDate', controlVerificationMethod = 'controlVerificationMethod', ensureValidityPeriodStarted = 'ensureValidityPeriodStarted', - checkCredentialSchemaConformity = 'checkCredentialSchemaConformity' + checkCredentialSchemaConformity = 'checkCredentialSchemaConformity', + validateDateFormat = 'validateDateFormat' } export type TVerificationStepsList = { @@ -30,7 +31,8 @@ export type TVerificationStepsList = { export const verificationMap = { [VerificationSteps.formatValidation]: [ SUB_STEPS.checkImagesIntegrity, - SUB_STEPS.checkCredentialSchemaConformity + SUB_STEPS.checkCredentialSchemaConformity, + SUB_STEPS.validateDateFormat ], [VerificationSteps.proofVerification]: [], [VerificationSteps.identityVerification]: [ diff --git a/src/domain/verifier/useCases/getDatesToValidate.ts b/src/domain/verifier/useCases/getDatesToValidate.ts new file mode 100644 index 000000000..0d771e750 --- /dev/null +++ b/src/domain/verifier/useCases/getDatesToValidate.ts @@ -0,0 +1,35 @@ +import type { BlockcertsV3 } from '../../../models/BlockcertsV3'; + +export interface DatesToValidate { + property: string; + dateTimeStamp: string; +} + +export default function getDatesToValidate (credential: BlockcertsV3): DatesToValidate[] { + const dates: DatesToValidate[] = []; + if (credential.validFrom) { + dates.push({ + property: 'validFrom', + dateTimeStamp: credential.validFrom + }); + } + + if (credential.validUntil) { + dates.push({ + property: 'validUntil', + dateTimeStamp: credential.validUntil + }); + } + + const proof = !Array.isArray(credential.proof) ? [credential.proof] : credential.proof; + for (const proofItem of proof) { + if (proofItem.created) { + dates.push({ + property: `proof ${proofItem.cryptosuite ?? proofItem.type} created`, + dateTimeStamp: proofItem.created + }); + } + } + + return dates; +} diff --git a/src/domain/verifier/useCases/getVerificationMap.ts b/src/domain/verifier/useCases/getVerificationMap.ts index 1eae55f23..461ef5186 100644 --- a/src/domain/verifier/useCases/getVerificationMap.ts +++ b/src/domain/verifier/useCases/getVerificationMap.ts @@ -4,7 +4,13 @@ import { removeEntry } from '../../../helpers/array'; import type VerificationSubstep from '../valueObjects/VerificationSubstep'; import type { IVerificationMapItem } from '../../../models/VerificationMap'; -export function getVerificationStepsForCurrentCase (hasDid: boolean, hasHashlinks: boolean, hasValidFrom: boolean, hasCredentialSchema: boolean): SUB_STEPS[] { +export function getVerificationStepsForCurrentCase ( + hasDid: boolean, + hasHashlinks: boolean, + hasValidFrom: boolean, + hasCredentialSchema: boolean, + isVCV2: boolean +): SUB_STEPS[] { const verificationSteps = Object.values(SUB_STEPS); if (!hasDid) { @@ -23,6 +29,10 @@ export function getVerificationStepsForCurrentCase (hasDid: boolean, hasHashlink removeEntry(verificationSteps, SUB_STEPS.checkCredentialSchemaConformity); } + if (!isVCV2) { + removeEntry(verificationSteps, SUB_STEPS.validateDateFormat); + } + return verificationSteps; } @@ -44,11 +54,23 @@ function getFullStepsWithSubSteps (verificationSubStepsList: SUB_STEPS[]): IVeri })); } -export default function getVerificationMap (hasDid: boolean = false, hasHashlinks: boolean = false, hasValidFrom: boolean = false, hasCredentialSchema: boolean = false): { - verificationMap: IVerificationMapItem[]; - verificationProcess: SUB_STEPS[]; -} { - const verificationProcess: SUB_STEPS[] = getVerificationStepsForCurrentCase(hasDid, hasHashlinks, hasValidFrom, hasCredentialSchema); +export default function getVerificationMap ( + hasDid: boolean = false, + hasHashlinks: boolean = false, + hasValidFrom: boolean = false, + hasCredentialSchema: boolean = false, + isVCV2: boolean = false +): { + verificationMap: IVerificationMapItem[]; + verificationProcess: SUB_STEPS[]; + } { + const verificationProcess: SUB_STEPS[] = getVerificationStepsForCurrentCase( + hasDid, + hasHashlinks, + hasValidFrom, + hasCredentialSchema, + isVCV2 + ); return { verificationProcess, verificationMap: getFullStepsWithSubSteps(verificationProcess) diff --git a/src/domain/verifier/useCases/index.ts b/src/domain/verifier/useCases/index.ts index c73ed05ee..6115548f8 100644 --- a/src/domain/verifier/useCases/index.ts +++ b/src/domain/verifier/useCases/index.ts @@ -1,5 +1,6 @@ import convertToVerificationSubsteps from './convertToVerificationSubsteps'; import findVerificationSubstep from './findVerificationSubstep'; +import getDatesToValidate from './getDatesToValidate'; import getIssuerProfile from './getIssuerProfile'; import getRevokedAssertions from './getRevokedAssertions'; import getVerificationMap from './getVerificationMap'; @@ -10,6 +11,7 @@ import parseRevocationKey from './parseRevocationKey'; export { convertToVerificationSubsteps, findVerificationSubstep, + getDatesToValidate, getIssuerProfile, getRevokedAssertions, getVerificationMap, diff --git a/src/helpers/date.ts b/src/helpers/date.ts index 5ff60d182..cf9bbf02f 100644 --- a/src/helpers/date.ts +++ b/src/helpers/date.ts @@ -1,5 +1,8 @@ /* eslint no-useless-escape: 0 prefer-spread: 0 */ // TODO: at some point fix this +// https://www.w3.org/TR/vc-data-model-2.0/#example-regular-expression-to-detect-a-valid-xml-schema-1-1-part-2-datetimestamp +const RFC3339_DATE_REGEX = /-?([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))$/; + function noOffset (s): Date { let day = s.slice(0, -5).split(/\D/).map(function (itm) { return parseInt(itm, 10) || 0; @@ -18,8 +21,8 @@ function noOffset (s): Date { function dateFromRegex (s: string): Date { let day; let tz; - const rx = /^(\d{4}\-\d\d\-\d\d([tT][\d:\.]*)?)([zZ]|([+\-])(\d\d):?(\d\d))?$/; - const p = rx.exec(s) ?? []; + + const p = RFC3339_DATE_REGEX.exec(s) ?? []; if (p[1]) { day = p[1].split(/\D/).map(function (itm) { return parseInt(itm, 10) || 0; @@ -73,3 +76,8 @@ export function dateToUnixTimestamp (date: Date | string): number { // TODO: cle export function timestampToDateObject (timestamp: number): Date { return new Date(timestamp * 1000); } + +// https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp +export function validateDateTimeStamp (dateTimeStamp: string): boolean { + return RFC3339_DATE_REGEX.test(dateTimeStamp); +} diff --git a/src/inspectors/validateDateFormat.ts b/src/inspectors/validateDateFormat.ts new file mode 100644 index 000000000..8fc85626f --- /dev/null +++ b/src/inspectors/validateDateFormat.ts @@ -0,0 +1,14 @@ +import { validateDateTimeStamp } from '../helpers/date'; +import { VerifierError } from '../models'; +import { SUB_STEPS } from '../domain/verifier/entities/verificationSteps'; +import { getText } from '../domain/i18n/useCases'; +import type { DatesToValidate } from '../domain/verifier/useCases/getDatesToValidate'; + +export default function validateDateFormat (dates: DatesToValidate[]): void { + for (const { dateTimeStamp, property } of dates) { + if (!validateDateTimeStamp(dateTimeStamp)) { + console.error('Date', dateTimeStamp, 'is not valid:', property); + throw new VerifierError(SUB_STEPS.validateDateFormat, `${getText('errors', 'validateDateFormat')} ${property}`); + } + } +} diff --git a/src/parsers/helpers/retrieveVCVersion.ts b/src/parsers/helpers/retrieveVCVersion.ts new file mode 100644 index 000000000..f7a49362b --- /dev/null +++ b/src/parsers/helpers/retrieveVCVersion.ts @@ -0,0 +1,35 @@ +import { CONTEXT_URLS } from '@blockcerts/schemas'; +import { isString } from '../../helpers/string'; +import type { JsonLDContext } from '../../models/Blockcerts'; + +export interface VCVersion { + versionNumber: number; +} + +export function isVCV2 (context: JsonLDContext | string): boolean { + return retrieveVCVersion(context).versionNumber === 2; +} + +export default function retrieveVCVersion (context: JsonLDContext | string): VCVersion { + if (typeof context === 'string') { + context = [context]; + } + + const VCContextsUrls = [CONTEXT_URLS.VERIFIABLE_CREDENTIAL_V1_CONTEXT, CONTEXT_URLS.VERIFIABLE_CREDENTIAL_V2_CONTEXT]; + + const VCContext: string = context.filter(isString).find((ctx: string) => VCContextsUrls.includes(ctx)); + + let versionNumber: number = -1; + + if (VCContext?.includes('v1')) { + versionNumber = 1; + } + + if (VCContext?.includes('v2')) { + versionNumber = 2; + } + + return { + versionNumber + }; +} diff --git a/src/parsers/parseV3.ts b/src/parsers/parseV3.ts index cfb75c69b..04508935e 100644 --- a/src/parsers/parseV3.ts +++ b/src/parsers/parseV3.ts @@ -1,6 +1,6 @@ import domain from '../domain'; import type { Issuer } from '../models/Issuer'; -import type { BlockcertsV3 } from '../models/BlockcertsV3'; +import type { BlockcertsV3, VCProof } from '../models/BlockcertsV3'; import type { ParsedCertificate } from './index'; function getRecipientFullName (certificateJson): string { @@ -17,10 +17,18 @@ export default async function parseV3 (certificateJson: BlockcertsV3): Promise

{ + await this.executeStep( + SUB_STEPS.validateDateFormat, + () => { + const datesToValidate = domain.verifier.getDatesToValidate(this.documentToVerify as BlockcertsV3); + validateDateFormat(datesToValidate); + } + ); + } + private async checkCredentialSchemaConformity (): Promise { const { default: checkCredentialSchemaConformity } = await import('./inspectors/checkCredentialSchemaConformity'); await this.executeStep( diff --git a/test/application/domain/verifier/useCases/getDatesToValidate.test.ts b/test/application/domain/verifier/useCases/getDatesToValidate.test.ts new file mode 100644 index 000000000..91c6075ee --- /dev/null +++ b/test/application/domain/verifier/useCases/getDatesToValidate.test.ts @@ -0,0 +1,26 @@ +import { it, describe, expect } from 'vitest'; +import fixture from '../../../../fixtures/v3/mocknet-vc-v2-validUntil-expired.json'; +import domain from '../../../../../src/domain'; +import type { DatesToValidate } from '../../../../../src/domain/verifier/useCases/getDatesToValidate'; + +describe('domain verifier getDatesToValidate test suite', function () { + it('return the expected array of dates from the document', function () { + const expected: DatesToValidate[] = [ + { + dateTimeStamp: '2022-07-13T20:21:40.088Z', + property: 'validFrom' + }, + { + dateTimeStamp: '2024-01-05T00:00:00.000Z', + property: 'validUntil' + }, + { + dateTimeStamp: '2024-01-30T16:07:01.438179', + property: 'proof MerkleProof2019 created' + } + ]; + + const result = domain.verifier.getDatesToValidate(fixture); + expect(result).toEqual(expected); + }); +}); diff --git a/test/application/helpers/date.test.ts b/test/application/helpers/date.test.ts index 042b97065..92bda309f 100644 --- a/test/application/helpers/date.test.ts +++ b/test/application/helpers/date.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { dateToUnixTimestamp, timestampToDateObject } from '../../../src/helpers/date'; +import { dateToUnixTimestamp, timestampToDateObject, validateDateTimeStamp } from '../../../src/helpers/date'; describe('dateToUnixTimestamp method', function () { describe('when given an empty string', function () { @@ -26,3 +26,89 @@ describe('timestampToDateObject method', function () { expect(timestampToDateObject(fixtureTimestamp)).toEqual(assertionDateObject); }); }); + +describe('validateDateTimestamp method', function () { + describe('when the date is a valid ISO8601 timestamp', function () { + describe('UTC', function () { + it('should return true', function () { + const fixture = '2018-06-18T22:37:22.325Z'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(true); + }); + }); + + describe('UTC with no millisecond', function () { + it('should return true', function () { + const fixture = '2018-06-18T22:37:22Z'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(true); + }); + }); + + describe('UTC no z', function () { + it('should return false', function () { + const fixture = '2018-06-18T22:37:22'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(false); + }); + }); + + describe('UTC with timezone offset', function () { + it('should return true', function () { + const fixture = '2018-06-18T22:37:22.325+08:00'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(true); + }); + }); + + describe('UTC with timezone offset no millisecond', function () { + it('should return true', function () { + const fixture = '2018-06-18T22:37:22+08:00'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(true); + }); + }); + }); + + describe('when the date is an invalid ISO8601 timestamp', function () { + describe('UTC no hour', function () { + it('should return false', function () { + const fixture = '2018-06-18'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(false); + }); + }); + + describe('US ordering (YYYY-DD-MM)', function () { + it('should return false', function () { + const fixture = '2018-18-06T22:37:22.325Z'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(false); + }); + }); + + describe('out of bounds month', function () { + it('should return false', function () { + const fixture = '2018-13-06T22:37:22.325Z'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(false); + }); + }); + + describe('out of bounds day', function () { + it('should return false', function () { + const fixture = '2018-12-36T22:37:22.325Z'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(false); + }); + }); + + describe('malformed', function () { + it('should return true', function () { + const fixture = '2018-06-1822:37:22+08:00'; + const result = validateDateTimeStamp(fixture); + expect(result).toBe(false); + }); + }); + }); +}); diff --git a/test/application/parsers/helpers/retrieveVCVersion.test.ts b/test/application/parsers/helpers/retrieveVCVersion.test.ts new file mode 100644 index 000000000..ce5067932 --- /dev/null +++ b/test/application/parsers/helpers/retrieveVCVersion.test.ts @@ -0,0 +1,35 @@ +import { expect, describe, it } from 'vitest'; +import { CONTEXT_URLS } from '@blockcerts/schemas'; +import retrieveVCVersion from '../../../../src/parsers/helpers/retrieveVCVersion'; + +describe('retrieveVCVersion test suite', function () { + describe('when the credential context is a v1', function () { + it('should return version number 1', function () { + const fixture = [CONTEXT_URLS.VERIFIABLE_CREDENTIAL_V1_CONTEXT, CONTEXT_URLS.BLOCKCERTS_V3_1_CONTEXT]; + expect(retrieveVCVersion(fixture)).toEqual({ versionNumber: 1 }); + }); + }); + + describe('when the credential context is a v2', function () { + it('should return version number 2', function () { + const fixture = [CONTEXT_URLS.VERIFIABLE_CREDENTIAL_V2_CONTEXT, CONTEXT_URLS.BLOCKCERTS_V3_1_CONTEXT]; + expect(retrieveVCVersion(fixture)).toEqual({ versionNumber: 2 }); + }); + }); + + describe('when the credential context is not a v1 or v2', function () { + it('should return version number -1', function () { + const fixture = [CONTEXT_URLS.BLOCKCERTS_V3_1_CONTEXT]; + expect(retrieveVCVersion(fixture)).toEqual({ versionNumber: -1 }); + }); + }); + + describe('when the credential context is a string', function () { + describe('and is a verifiable credential v1 context', function () { + it('should return version number 1', function () { + const fixture = CONTEXT_URLS.VERIFIABLE_CREDENTIAL_V1_CONTEXT; + expect(retrieveVCVersion(fixture)).toEqual({ versionNumber: 1 }); + }); + }); + }); +}); diff --git a/test/application/parsers/parser-v3.0.test.ts b/test/application/parsers/parser-v3.0.test.ts index 541bec7f4..8f45d07a5 100644 --- a/test/application/parsers/parser-v3.0.test.ts +++ b/test/application/parsers/parser-v3.0.test.ts @@ -85,5 +85,51 @@ describe('Parser v3 test suite', function () { expect(parsedCertificate.expires).toEqual(fixtureCopy.expirationDate); }); }); + + describe('when the validFrom property is set', function () { + it('should set the validFrom property', async function () { + const fixtureCopy = JSON.parse(JSON.stringify(fixture)); + fixtureCopy.validFrom = '2021-04-27T00:00:00Z'; + const parsedCertificate = await parseJSON(fixtureCopy); + expect(parsedCertificate.validFrom).toBe(fixtureCopy.validFrom); + }); + }); + + describe('when the validFrom property is not set', function () { + describe('and the proof object is an array', function () { + it('should set the validFrom property to the created property of the first proof', async function () { + const fixtureCopy = JSON.parse(JSON.stringify(fixture)); + delete fixtureCopy.validFrom; + const initialProof = fixture.proof; + fixtureCopy.proof = [initialProof, { + type: 'MerkleProof2019', + created: '2024-04-05T13:43:10.870521', + proofValue: 'z4zvrPUULnHmaio37FZuwYZDyU39wMYujJCMeypmxMWhh2XoCSMSVoeVRBKeEKUVnqccnmgggyPYLx2xubmvDCP2HWMCcTCLrcpBHJMEzUiwQrixSFStZbxQq9yPVNoYysMcxinfxZTpmH1j5mmGsC2fUP1LEMruXA1fKgupM3Ea97PzUGjgDgSfZqJNKjmFMJYL5tC1R7XoRqYvpKg3NhMrFY9YtyuERDW9do92EPeSw17j5xUZLpj6uGieJVrf5ps4AScoB4tXXTm4eFi4ZkQbbbvkRmPK9bZsyKKxGQ2Bq5cfwPbvPHiaGLSHEBrAYh75so7LwoiKi1VCw7NdsybWmMUf1E547PZhbqTB5hXJD5VBYN6hpoGzc18L6boKN1oveFaHAoFrQsEjmBJ', + proofPurpose: 'assertionMethod', + verificationMethod: 'did:ion:EiA_Z6LQILbB2zj_eVrqfQ2xDm4HNqeJUw5Kj2Z7bFOOeQ#key-1' + }]; + const parsedCertificate = await parseJSON(fixtureCopy); + expect(parsedCertificate.validFrom).toBe(fixtureCopy.proof[0].created); + }); + }); + + describe('and the proof object is not an array', function () { + it('should set the validFrom property to the created property of the proof', async function () { + const fixtureCopy = JSON.parse(JSON.stringify(fixture)); + delete fixtureCopy.validFrom; + const parsedCertificate = await parseJSON(fixtureCopy); + expect(parsedCertificate.validFrom).toBe(fixture.proof.created); + }); + }); + }); + + describe('when the validUntil property is set', function () { + it('should set the validUntil property', async function () { + const fixtureCopy = JSON.parse(JSON.stringify(fixture)); + fixtureCopy.validUntil = '2022-04-27T00:00:00Z'; + const parsedCertificate = await parseJSON(fixtureCopy); + expect(parsedCertificate.expires).toBe(fixtureCopy.validUntil); + }); + }); }); }); diff --git a/test/assertions/verification-steps-v3-credentialSchema-mocknet.ts b/test/assertions/verification-steps-v3-credentialSchema-mocknet.ts index 174339dcb..bb35bc832 100644 --- a/test/assertions/verification-steps-v3-credentialSchema-mocknet.ts +++ b/test/assertions/verification-steps-v3-credentialSchema-mocknet.ts @@ -17,6 +17,13 @@ export default [ labelPending: defaultLanguageSet.subSteps.checkCredentialSchemaConformityLabelPending, parentStep: VerificationSteps.formatValidation, status: VERIFICATION_STATUSES.DEFAULT + }, + { + code: 'validateDateFormat', + label: defaultLanguageSet.subSteps.validateDateFormatLabel, + labelPending: defaultLanguageSet.subSteps.validateDateFormatLabelPending, + parentStep: VerificationSteps.formatValidation, + status: VERIFICATION_STATUSES.DEFAULT } ] }, diff --git a/test/assertions/verification-steps-v3-hashlink.ts b/test/assertions/verification-steps-v3-hashlink.ts index 01826bf71..d10f84568 100644 --- a/test/assertions/verification-steps-v3-hashlink.ts +++ b/test/assertions/verification-steps-v3-hashlink.ts @@ -101,6 +101,13 @@ export default [ parentStep: VerificationSteps.statusCheck, status: VERIFICATION_STATUSES.DEFAULT }, + { + code: SUB_STEPS.ensureValidityPeriodStarted, + label: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabel, + labelPending: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabelPending, + parentStep: VerificationSteps.statusCheck, + status: VERIFICATION_STATUSES.DEFAULT + }, { code: SUB_STEPS.checkExpiresDate, label: defaultLanguageSet.subSteps.checkExpiresDateLabel, diff --git a/test/assertions/verification-steps-v3-multiple-proofs.ts b/test/assertions/verification-steps-v3-multiple-proofs.ts index 448b561a5..1cdca3992 100644 --- a/test/assertions/verification-steps-v3-multiple-proofs.ts +++ b/test/assertions/verification-steps-v3-multiple-proofs.ts @@ -147,6 +147,13 @@ export default [ parentStep: VerificationSteps.statusCheck, status: VERIFICATION_STATUSES.DEFAULT }, + { + code: SUB_STEPS.ensureValidityPeriodStarted, + label: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabel, + labelPending: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabelPending, + parentStep: VerificationSteps.statusCheck, + status: VERIFICATION_STATUSES.DEFAULT + }, { code: SUB_STEPS.checkExpiresDate, label: defaultLanguageSet.subSteps.checkExpiresDateLabel, diff --git a/test/assertions/verification-steps-v3-no-did.ts b/test/assertions/verification-steps-v3-no-did.ts index 68b38b133..ec36df50f 100644 --- a/test/assertions/verification-steps-v3-no-did.ts +++ b/test/assertions/verification-steps-v3-no-did.ts @@ -87,6 +87,13 @@ export default [ parentStep: VerificationSteps.statusCheck, status: VERIFICATION_STATUSES.DEFAULT }, + { + code: SUB_STEPS.ensureValidityPeriodStarted, + label: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabel, + labelPending: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabelPending, + parentStep: VerificationSteps.statusCheck, + status: VERIFICATION_STATUSES.DEFAULT + }, { code: SUB_STEPS.checkExpiresDate, label: defaultLanguageSet.subSteps.checkExpiresDateLabel, diff --git a/test/assertions/verification-steps-v3-validFrom-mocknet.ts b/test/assertions/verification-steps-v3-validFrom-mocknet.ts index 55410823f..da4edf390 100644 --- a/test/assertions/verification-steps-v3-validFrom-mocknet.ts +++ b/test/assertions/verification-steps-v3-validFrom-mocknet.ts @@ -6,6 +6,20 @@ import { VERIFICATION_STATUSES } from '../../src'; const defaultLanguageSet = i18n[currentLocale.locale]; export default [ + { + code: VerificationSteps.formatValidation, + label: defaultLanguageSet.steps.formatValidationLabel, + labelPending: defaultLanguageSet.steps.formatValidationLabelPending, + subSteps: [ + { + code: 'validateDateFormat', + label: defaultLanguageSet.subSteps.validateDateFormatLabel, + labelPending: defaultLanguageSet.subSteps.validateDateFormatLabelPending, + parentStep: VerificationSteps.formatValidation, + status: VERIFICATION_STATUSES.DEFAULT + } + ] + }, { code: VerificationSteps.proofVerification, label: defaultLanguageSet.steps.signatureVerificationLabel, diff --git a/test/assertions/verification-steps-v3-with-did.ts b/test/assertions/verification-steps-v3-with-did.ts index 39e6c1d74..50ab480a9 100644 --- a/test/assertions/verification-steps-v3-with-did.ts +++ b/test/assertions/verification-steps-v3-with-did.ts @@ -122,6 +122,13 @@ export default [ parentStep: VerificationSteps.statusCheck, status: VERIFICATION_STATUSES.DEFAULT }, + { + code: SUB_STEPS.ensureValidityPeriodStarted, + label: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabel, + labelPending: defaultLanguageSet.subSteps.ensureValidityPeriodStartedLabelPending, + parentStep: VerificationSteps.statusCheck, + status: VERIFICATION_STATUSES.DEFAULT + }, { code: SUB_STEPS.checkExpiresDate, label: defaultLanguageSet.subSteps.checkExpiresDateLabel, diff --git a/test/contract/signers.v3.contract.data-integrity-proof.test.ts b/test/contract/signers.v3.contract.data-integrity-proof.test.ts index fd1cec8b8..282b031b5 100644 --- a/test/contract/signers.v3.contract.data-integrity-proof.test.ts +++ b/test/contract/signers.v3.contract.data-integrity-proof.test.ts @@ -32,7 +32,7 @@ describe('Certificate API Contract test suite', function () { }); it('should expose the signingDate', function () { - expect(instance.signers[0].signingDate).toBe('2024-02-15T15:22:35.361231'); + expect(instance.signers[0].signingDate).toBe('2024-02-15T15:22:35Z'); }); it('should expose the signatureSuiteType', function () { diff --git a/test/contract/verification-steps.contract.test.ts b/test/contract/verification-steps.contract.test.ts index 48ba8a702..d076e6f8e 100644 --- a/test/contract/verification-steps.contract.test.ts +++ b/test/contract/verification-steps.contract.test.ts @@ -17,7 +17,7 @@ import MainnetV2Valid from '../fixtures/v2/mainnet-valid-2.0.json'; import BlockcertsV3 from '../fixtures/v3/testnet-v3-did.json'; import BlockcertsV3NoDid from '../fixtures/v3/testnet-v3--no-did.json'; import BlockcertsV3Hashlink from '../fixtures/v3/testnet-v3-hashlink.json'; -import BlockcertsV3ValidFrom from '../fixtures/v3/mocknet-vc-v2-validFrom-valid.json'; +import BlockcertsV3ValidFrom from '../fixtures/v3/mocknet-vc-v2-invalid-date-format.json'; import BlockcertsV3CredentialSchema from '../fixtures/v3/mocknet-vc-v2-credential-schema.json'; describe('Certificate API Contract test suite', function () { diff --git a/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.test.ts b/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.test.ts index 5816c4400..9d0e08700 100644 --- a/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.test.ts +++ b/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest'; import { Certificate, VERIFICATION_STATUSES } from '../../../src'; import MocknetVCV2DataIntegrityProofMultipleSignaturesNonChained from '../../fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.json'; import fixtureBlockcertsIssuerProfile from '../../fixtures/issuer-blockcerts.json'; +import fixtureCredentialSchema from '../../fixtures/credential-schema-example-id-card.json'; describe('given the certificate is signed with multiple non chained DataIntegrityProof Merkle Proof 2019', function () { it('should be a valid verification', async function () { @@ -13,6 +14,10 @@ describe('given the certificate is signed with multiple non chained DataIntegrit if (url === 'https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json') { return JSON.stringify(fixtureBlockcertsIssuerProfile); } + + if (url === 'https://www.blockcerts.org/samples/3.0/example-id-card-schema.json') { + return JSON.stringify(fixtureCredentialSchema); + } } }; }); diff --git a/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures.test.ts b/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures.test.ts index fa50b9222..23f147009 100644 --- a/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures.test.ts +++ b/test/e2e/verifier/mocknet-vc-v2-data-integrity-proof-multiple-signatures.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest'; import { Certificate, VERIFICATION_STATUSES } from '../../../src'; import MocknetVCV2DataIntegrityProofMultipleSignatures from '../../fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures.json'; import fixtureBlockcertsIssuerProfile from '../../fixtures/issuer-blockcerts.json'; +import fixtureCredentialSchema from '../../fixtures/credential-schema-example-id-card.json'; describe('given the certificate is signed with multiple chained DataIntegrityProof Merkle Proof 2019', function () { it('should be a valid verification', async function () { @@ -13,6 +14,10 @@ describe('given the certificate is signed with multiple chained DataIntegrityPro if (url === 'https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json') { return JSON.stringify(fixtureBlockcertsIssuerProfile); } + + if (url === 'https://www.blockcerts.org/samples/3.0/example-id-card-schema.json') { + return JSON.stringify(fixtureCredentialSchema); + } } }; }); diff --git a/test/e2e/verifier/mocknet-vc-v2-invalid-date-format.test.ts b/test/e2e/verifier/mocknet-vc-v2-invalid-date-format.test.ts new file mode 100644 index 000000000..6543ccdfd --- /dev/null +++ b/test/e2e/verifier/mocknet-vc-v2-invalid-date-format.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; +import { Certificate, VERIFICATION_STATUSES } from '../../../src'; +import MocknetVCV2InvalidDateFormat from '../../fixtures/v3/mocknet-vc-v2-invalid-date-format.json'; +import fixtureBlockcertsIssuerProfile from '../../fixtures/issuer-blockcerts.json'; + +describe('given the certificate is an invalid mocknet (v3.0) - invalid date format', function () { + let certificate; + let result; + + beforeAll(async function () { + vi.mock('@blockcerts/explorer-lookup', async (importOriginal) => { + const explorerLookup = await importOriginal(); + return { + ...explorerLookup, + request: async function ({ url }) { + if (url === 'https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json') { + return JSON.stringify(fixtureBlockcertsIssuerProfile); + } + } + }; + }); + + afterAll(function () { + vi.restoreAllMocks(); + }); + + certificate = new Certificate(MocknetVCV2InvalidDateFormat); + await certificate.init(); + result = await certificate.verify(); + }); + + // this test will expire in 2039 + it('should fail verification', function () { + expect(result.status).toBe(VERIFICATION_STATUSES.FAILURE); + }); + + it('return the correct error message', function () { + expect(result.message).toBe('The date format specified does not conform with the spec requirements (RFC3339). Property: proof MerkleProof2019 created'); + }); +}); diff --git a/test/fixtures/v3/mocknet-vc-v2-credential-schema-invalid.json b/test/fixtures/v3/mocknet-vc-v2-credential-schema-invalid.json index 028a90232..3914f980d 100644 --- a/test/fixtures/v3/mocknet-vc-v2-credential-schema-invalid.json +++ b/test/fixtures/v3/mocknet-vc-v2-credential-schema-invalid.json @@ -41,7 +41,7 @@ ], "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", "validFrom": "2024-03-01T00:00:00Z", - "validUntil": "2039-28-02T23:59:59Z", + "validUntil": "2039-02-28T23:59:59Z", "id": "urn:uuid:4f5f0100-ccbf-4ca9-9cfc-4f5fc3052d28", "credentialSchema": [ { @@ -63,12 +63,12 @@ "content": "

Yo
" }, "proof": { - "id": "urn:uuid:e65bfeee-951f-49e2-85e2-f723bac935ff", + "id": "urn:uuid:1aca4326-dcbf-4b1d-b761-ad3941a0992d", "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-03-20T14:27:37.594454", - "proofValue": "zEuZQLZTYrdsDv2cqjvQPZkFzGmGraUDb4EPwPv8LrjW5Y6RJeLncSZQTHBkRW8MevX1afRrksA7JzVpSW7QisPwLWGAbKDqp6PRGGzuFR4DoGaXQ3sAHbMdc2HPS3MA9SSR8hDS2UBF7ybuFiZiKztfebxxGzH1FmgGxeSEwuZexr6H49BADTrUEnHALVztDiQ9FNExpPxrwrwX65LH345qk2umU1Q9Tziw5N5nnizNKLXZfyFrKgYUfaHQfwqLXDBEqYcGCtvNDGFy97zVqZScNBsxtdakq2r2feNbQyRR8x8", + "created": "2024-10-15T14:12:22Z", + "proofValue": "z3hSV9ktEtnCfHQemVgQgpvjKZijck2aWvDr7NCjmtXhRhwqRQJxaYeaYYDy9h8KdEQRGEECjSm9qHdjrJyDKfNGvtGanW7db1mzULWPcKE3eWgFahze9q1RZti9qykCHf3NxtnQCkFQQtQ4NZatErRHm8asMi9t4w9EcERHYgD2g5wP5TcHYkC9TDdHR8wSV31sXkBCAA1H5uwd27ennG6TLTL4C65QdSLJ729aZgqqqAyRkyrdBN9qzVCUsJ76viqcZSSwHmXWR7WMVt419FuAUegtMx8fR9oiPr4q6KmL58gjCCn3xREuR3cSiZXbRcCH4sXoW87un42PLs7HXXtzDkkU8kWTfYkHpkHALt4EPgZjbasQgkQJxo1SBacsVnjkGAziRckvNvk", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" } } diff --git a/test/fixtures/v3/mocknet-vc-v2-credential-schema.json b/test/fixtures/v3/mocknet-vc-v2-credential-schema.json index 989c00d1f..b942f8492 100644 --- a/test/fixtures/v3/mocknet-vc-v2-credential-schema.json +++ b/test/fixtures/v3/mocknet-vc-v2-credential-schema.json @@ -41,7 +41,7 @@ ], "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", "validFrom": "2024-03-01T00:00:00Z", - "validUntil": "2039-28-02T23:59:59Z", + "validUntil": "2039-02-28T23:59:59Z", "id": "urn:uuid:4f5f0100-ccbf-4ca9-9cfc-4f5fc3052d28", "credentialSchema": [ { @@ -64,12 +64,12 @@ "content": "
Yo
" }, "proof": { - "id": "urn:uuid:efea6ffc-6ea8-4130-8cba-6e71aaf957e1", + "id": "urn:uuid:3516c929-ebf7-4e45-bd80-c22096782bca", "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-03-20T14:29:42.705922", - "proofValue": "z3hSV9ktErQhBgc5Xy4wXsJwhWkR6oGwvXK8m3B7bCD6UvwXrgqDVZvnN6zf8TRTZg4pVfgxbDSCFcGpkPgi5V8p4xKyCyrwJm8GXDZNFEtLgunPZPLfL4jF2LMu5Xt95QWhX6BtBeKLVxi9BquC7uzD47YX2Rc7a7pfeZBjM5ned6BaMJNa6t8QQUD5WMByvGhzGfzpQQxCD7gUJh9MdcXAJdykcEKwxbSAGk6r93qTnRfvg1i6aD7DMWjSM49gWvB3kbkgsHzA6CdMnESXMQndWkzYnPmH8D21MDu12Rq3vbgXwW4oBy8UDHBa3Xk7xfRVVozRtoYBjthxqB98qhKGkztnQvK8G2DZhh6J42M4u36EYuiadbVCta4d41AwBACDVNMmSj5er1E", + "created": "2024-10-15T14:12:22Z", + "proofValue": "z3hSV9ktErQhBgc1M88j5x6EBcioqSkzSL1Qx44DXgrPDQqeVEj3LHQ6W8Wd8CvkhH42f1uC7Fed92urxKd76diyqRADnxXpn6wLHBaDTYAJ1MLAakcy4zCdRk7FWMppUTYE5TDpkH4eY8aqXAxpajzTuxr3WZQ667v6NFzxfczo6qjLjivpPqE6h3XZT8JuxBhNpSReQQBDS8eLNoXLDLVZsnJQgZzMLVzHie83Aw3kpo5yzi4azjXhmD77cnU17XgkJ4pFKe2bSgvkBgbGjD73qhFJg2rQLgigASx4AuxYYuWbvqgZbo47kssxJPo4bcQ1kPaYEXw37CNV4nmarp2EnyukvSjaCdh8jB2FReSbWCWsoveoj5REGaTpUHtdeoC3VhxCYLkwMe8", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" } -} +} \ No newline at end of file diff --git a/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.json b/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.json index 761184952..7e87e41b3 100644 --- a/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.json +++ b/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures-non-chained.json @@ -1,49 +1,85 @@ { "@context": [ "https://www.w3.org/ns/credentials/v2", + { + "DOB": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/DOB", + "@type": "https://schema.org/Text" + }, + "nationality": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/nationality", + "@type": "https://schema.org/Text" + }, + "height": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/height", + "@type": "https://schema.org/Text" + }, + "residentialAddressStreet": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/residentialAddressStreet", + "@type": "https://schema.org/Text" + }, + "residentialAddressTown": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/residentialAddressTown", + "@type": "https://schema.org/Text" + }, + "residentialAddressPostCode": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/residentialAddressPostCode", + "@type": "https://schema.org/Text" + }, + "IdCardCredential": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/IdCardCredential", + "@type": "https://schema.org/DataType" + } + }, "https://w3id.org/security/data-integrity/v2", "https://w3id.org/blockcerts/v3.1" ], - "id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", "type": [ "VerifiableCredential", - "BlockcertsCredential" + "BlockcertsCredential", + "IdCardCredential" ], "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", - "validFrom": "2022-07-13T20:21:40.088Z", - "validUntil": "2039-07-13T00:00:00.000Z", + "validFrom": "2024-03-01T00:00:00Z", + "validUntil": "2039-02-28T23:59:59Z", + "id": "urn:uuid:4f5f0100-ccbf-4ca9-9cfc-4f5fc3052d28", + "credentialSchema": [ + { + "id": "https://www.blockcerts.org/samples/3.0/example-id-card-schema.json", + "type": "JsonSchema" + } + ], "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "name": "Julien Fraichot", - "email": "julien.fraichot@hyland.com", - "publicKey": "ecdsa-koblitz-pubkey:1BPQXndcz5Uf3qZQkgnvJC87LUD5n7a2mC", - "claim": { - "name": "Master of Puppets", - "description": "Awarded to those who rock" - } + "name": "John Smith", + "nationality": "Canada", + "DOB": "05/10/1983", + "height": "1.80m", + "residentialAddressStreet": "6 Maple Tree street", + "residentialAddressTown": "Toronto", + "residentialAddressPostCode": "YYZYUL" }, - "metadata": "{\"classOf\":\"2021\"}", "display": { "contentMediaType": "text/html", "content": "
Yo
" }, "proof": [ { - "id": "urn:uuid:a68a8c11-556b-42dc-b840-55677639db7d", + "id": "urn:uuid:3516c929-ebf7-4e45-bd80-c22096782bca", "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-02-15T15:22:35.361231", - "proofValue": "zEuZQLZTYrdsDv2FbpkZtthsgpG54RGrfTSvyUNDGi1ecmJdY9KMtFSxbbs6pEspqa38GgXJD9ReRosEBrXsXKu4Nk7xMGnm6wfzuCUiwwBCXKrfs8e8r6a1gbyXbzifiJprkeVD9UtaVtdb5QXKHPRikuQR8F9epBejTF7MfQukr1T5U7mZ8nSuKXDJ5epzg6mCANdCFt8ZrYZiLUZGrWpd33BfeuyaJxSGYcfMgjzJGvLnoZpAXHi1MWpNFhxxuTn6BtUbivTJqQhgzUDoXaErWeMEe1z8wRR5TgzNaoULxGp", + "created": "2024-10-15T14:12:22Z", + "proofValue": "z3hSV9ktErQhBgc1M88j5x6EBcioqSkzSL1Qx44DXgrPDQqeVEj3LHQ6W8Wd8CvkhH42f1uC7Fed92urxKd76diyqRADnxXpn6wLHBaDTYAJ1MLAakcy4zCdRk7FWMppUTYE5TDpkH4eY8aqXAxpajzTuxr3WZQ667v6NFzxfczo6qjLjivpPqE6h3XZT8JuxBhNpSReQQBDS8eLNoXLDLVZsnJQgZzMLVzHie83Aw3kpo5yzi4azjXhmD77cnU17XgkJ4pFKe2bSgvkBgbGjD73qhFJg2rQLgigASx4AuxYYuWbvqgZbo47kssxJPo4bcQ1kPaYEXw37CNV4nmarp2EnyukvSjaCdh8jB2FReSbWCWsoveoj5REGaTpUHtdeoC3VhxCYLkwMe8", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" }, { - "id": "urn:uuid:8342717b-cc66-48bc-906e-5abf191d4ddd", + "id": "urn:uuid:bb618c8e-7e9f-4bd9-b03a-bf24651f9882", "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-02-29T15:11:20.395980", - "proofValue": "zEuZQLZTYrdsDv2cqZws1GG2poQZff31axabb5RkhpPoZCWin6UtdjSimFRtNr5nKyaY6Yxz26dtn1fenP2h7TMu6TYZhmVj2kTVFZMkPQLcWwfnzSHKv3iGTS19xkxhLakkax9mcMgsoFsWDGvLCPZi9TxyB6z8oSMFCwxE36V5AkfQWswbsNAoaNrgT1yekMMLh9PTDAtKM9FSCDs2kYADLDukoKKRf5Aq34TLshWR3jvrPZr1ccPcff4BbNVe2yMSRzJkFnEVQu3MEuw3zZsLbusXUMo5Q5ZoQYraa6eNPqA", + "created": "2024-10-15T14:24:09Z", + "proofValue": "zEuZQLZTYrdsDv2GsAyrzVmKpX34SzAdEFqY1EL8usmFvgfh3nU92tVAuWZabmy6Ro3nrxRZXQZaJvYCzwykvZJgpEpYQuuX2MXqWWUEThb1HgjeDbNaxpNvp1T3t8NyhmzFnaGrxQQuQKB1eMByZr1VkoG7FJ5MaAeX4gHDfeosjYob7vjp96vyWTmTJvVZgadE6zsiYaH5uPw8x1C5S2NSpdCzXriCVQddsZJn79Xio6hXsK9mdpVJ6Las8DTh6vKV9t1GPoJRhFDdbhE9JgAjAg3t7heqdEViMGkEyvFCc2U", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" } ] diff --git a/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures.json b/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures.json index e5e406c0f..c3e12cfea 100644 --- a/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures.json +++ b/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof-multiple-signatures.json @@ -1,51 +1,87 @@ { "@context": [ "https://www.w3.org/ns/credentials/v2", + { + "DOB": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/DOB", + "@type": "https://schema.org/Text" + }, + "nationality": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/nationality", + "@type": "https://schema.org/Text" + }, + "height": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/height", + "@type": "https://schema.org/Text" + }, + "residentialAddressStreet": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/residentialAddressStreet", + "@type": "https://schema.org/Text" + }, + "residentialAddressTown": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/residentialAddressTown", + "@type": "https://schema.org/Text" + }, + "residentialAddressPostCode": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/residentialAddressPostCode", + "@type": "https://schema.org/Text" + }, + "IdCardCredential": { + "@id": "https://schemas.learningmachine.com/2017/blockcerts/IdCardCredential", + "@type": "https://schema.org/DataType" + } + }, "https://w3id.org/security/data-integrity/v2", "https://w3id.org/blockcerts/v3.1" ], - "id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", "type": [ "VerifiableCredential", - "BlockcertsCredential" + "BlockcertsCredential", + "IdCardCredential" ], "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", - "validFrom": "2022-07-13T20:21:40.088Z", - "validUntil": "2039-07-13T00:00:00.000Z", + "validFrom": "2024-03-01T00:00:00Z", + "validUntil": "2039-02-28T23:59:59Z", + "id": "urn:uuid:4f5f0100-ccbf-4ca9-9cfc-4f5fc3052d28", + "credentialSchema": [ + { + "id": "https://www.blockcerts.org/samples/3.0/example-id-card-schema.json", + "type": "JsonSchema" + } + ], "credentialSubject": { "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "name": "Julien Fraichot", - "email": "julien.fraichot@hyland.com", - "publicKey": "ecdsa-koblitz-pubkey:1BPQXndcz5Uf3qZQkgnvJC87LUD5n7a2mC", - "claim": { - "name": "Master of Puppets", - "description": "Awarded to those who rock" - } + "name": "John Smith", + "nationality": "Canada", + "DOB": "05/10/1983", + "height": "1.80m", + "residentialAddressStreet": "6 Maple Tree street", + "residentialAddressTown": "Toronto", + "residentialAddressPostCode": "YYZYUL" }, - "metadata": "{\"classOf\":\"2021\"}", "display": { "contentMediaType": "text/html", "content": "
Yo
" }, "proof": [ { - "id": "urn:uuid:a68a8c11-556b-42dc-b840-55677639db7d", + "id": "urn:uuid:3516c929-ebf7-4e45-bd80-c22096782bca", "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-02-15T15:22:35.361231", - "proofValue": "zEuZQLZTYrdsDv2FbpkZtthsgpG54RGrfTSvyUNDGi1ecmJdY9KMtFSxbbs6pEspqa38GgXJD9ReRosEBrXsXKu4Nk7xMGnm6wfzuCUiwwBCXKrfs8e8r6a1gbyXbzifiJprkeVD9UtaVtdb5QXKHPRikuQR8F9epBejTF7MfQukr1T5U7mZ8nSuKXDJ5epzg6mCANdCFt8ZrYZiLUZGrWpd33BfeuyaJxSGYcfMgjzJGvLnoZpAXHi1MWpNFhxxuTn6BtUbivTJqQhgzUDoXaErWeMEe1z8wRR5TgzNaoULxGp", + "created": "2024-10-15T14:12:22Z", + "proofValue": "z3hSV9ktErQhBgc1M88j5x6EBcioqSkzSL1Qx44DXgrPDQqeVEj3LHQ6W8Wd8CvkhH42f1uC7Fed92urxKd76diyqRADnxXpn6wLHBaDTYAJ1MLAakcy4zCdRk7FWMppUTYE5TDpkH4eY8aqXAxpajzTuxr3WZQ667v6NFzxfczo6qjLjivpPqE6h3XZT8JuxBhNpSReQQBDS8eLNoXLDLVZsnJQgZzMLVzHie83Aw3kpo5yzi4azjXhmD77cnU17XgkJ4pFKe2bSgvkBgbGjD73qhFJg2rQLgigASx4AuxYYuWbvqgZbo47kssxJPo4bcQ1kPaYEXw37CNV4nmarp2EnyukvSjaCdh8jB2FReSbWCWsoveoj5REGaTpUHtdeoC3VhxCYLkwMe8", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" }, { - "id": "urn:uuid:d1bf1ef0-efb2-4959-973d-b2abf84f3376", + "id": "urn:uuid:e0341578-a164-44e6-9e6b-89fcce044057", "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-02-15T15:23:14.293905", - "proofValue": "zEuZQLZTYrdsDv2cqZws1GG2poQZff31axabb5RkhpPoZCWin6UtdjSimFRtNr5nKyaY6Yxz26dtn1fenP2h7TMu6TYZhmVj2kTVFZMkPQLcWwfnzSHKv3iGTS19xkxhLakkax9mcMgsoFsWDGvLCPZi9TxyB6z8oSMFCwxE36V5AkfQWswbsNAoaNrgT1yekMMLh9PTDAtKM9FSCDs2kYADLDukoKKRf5Aq34TLshWR3jvrPZr1ccPcff4BbNVe2yMSRzJkFnEVQu3MEuw3zZsLbusXUMo5Q5ZoQYraa6eNPqA", + "created": "2024-10-15T14:24:54Z", + "proofValue": "zEuZQLZTYrdsDv2GsAyrzVmKpX34SzAdEFqY1EL8usmFvgfh3nU92tVAuWZabmy6Ro3nrxRZXQZaJvYCzwykvZJgpEpYQuuX2MXqWWUEThb1HgjeDbNaxpNvp1T3t8NyhmzFnaGrxQQuQKB1eMByZr1VkoG7FJ5MaAeX4gHDfeosjYob7vjp96vyWTmTJvVZgadE6zsiYaH5uPw8x1C5S2NSpdCzXriCVQddsZJn79Xio6hXsK9mdpVJ6Las8DTh6vKV9t1GPoJRhFDdbhE9JgAjAg3t7heqdEViMGkEyvFCc2U", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1", - "previousProof": "urn:uuid:a68a8c11-556b-42dc-b840-55677639db7d" + "previousProof": "urn:uuid:3516c929-ebf7-4e45-bd80-c22096782bca" } ] -} +} \ No newline at end of file diff --git a/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof.json b/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof.json index 7e196d549..17b034826 100644 --- a/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof.json +++ b/test/fixtures/v3/mocknet-vc-v2-data-integrity-proof.json @@ -32,7 +32,7 @@ "type": "DataIntegrityProof", "cryptosuite": "merkle-proof-2019", "proofPurpose": "assertionMethod", - "created": "2024-02-15T15:22:35.361231", + "created": "2024-02-15T15:22:35Z", "proofValue": "zEuZQLZTYrdsDv2FbpkZtthsgpG54RGrfTSvyUNDGi1ecmJdY9KMtFSxbbs6pEspqa38GgXJD9ReRosEBrXsXKu4Nk7xMGnm6wfzuCUiwwBCXKrfs8e8r6a1gbyXbzifiJprkeVD9UtaVtdb5QXKHPRikuQR8F9epBejTF7MfQukr1T5U7mZ8nSuKXDJ5epzg6mCANdCFt8ZrYZiLUZGrWpd33BfeuyaJxSGYcfMgjzJGvLnoZpAXHi1MWpNFhxxuTn6BtUbivTJqQhgzUDoXaErWeMEe1z8wRR5TgzNaoULxGp", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" } diff --git a/test/fixtures/v3/mocknet-vc-v2-invalid-date-format.json b/test/fixtures/v3/mocknet-vc-v2-invalid-date-format.json new file mode 100644 index 000000000..37ab85e5c --- /dev/null +++ b/test/fixtures/v3/mocknet-vc-v2-invalid-date-format.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/blockcerts/v3" + ], + "id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", + "type": [ + "VerifiableCredential", + "BlockcertsCredential" + ], + "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", + "validFrom": "2022-07-13T20:21:40.088Z", + "validUntil": "2039-07-13T00:00:00.000Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "name": "Julien Fraichot", + "email": "julien.fraichot@hyland.com", + "publicKey": "ecdsa-koblitz-pubkey:1BPQXndcz5Uf3qZQkgnvJC87LUD5n7a2mC", + "claim": { + "name": "Master of Puppets", + "description": "Awarded to those who rock" + } + }, + "metadata": "{\"classOf\":\"2021\"}", + "display": { + "contentMediaType": "text/html", + "content": "
Yo
" + }, + "proof": { + "type": "MerkleProof2019", + "created": "2024-01-30T17:36:01.833602", + "proofValue": "z3hSV9ktErQhBgc5C758PsevUUCuxBPcFYGxLVGGcT4ZcWr64zeQnkUet1gJJzWmewShoTg72Yb4mZY347sPy6DYgpTfyZUdhzsLBFo8omHLYAEHPc6NAd811A1JxTViWeyZDHzoMwRtuRuehkkrwdQXLjsfTbtLEQKaCJoWug4PXyd5ab86ehRhkSumeNnZrA3GCFcT6upUUSKUfyxM6jJKfAb7YQnTxBgQzu8CuKJUKgZEHLVbYSJ2DZs1USLriQR2t91NyUHU8UhJ8GjXxMUMwT4gai4kYyurnsTZs36yQ7JmURWF1vXfEz9fJgrfJmaizLy1iTAKvSG8cp4mjFJfk8PchPVQti7gMQEMcn9LGfEQZW6ScBhAUZ4aV7eCt3X4cL5qXwBoiCg", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" + } +} diff --git a/test/fixtures/v3/mocknet-vc-v2-validFrom-valid.json b/test/fixtures/v3/mocknet-vc-v2-validFrom-valid.json index d81aabb3d..c508ee398 100644 --- a/test/fixtures/v3/mocknet-vc-v2-validFrom-valid.json +++ b/test/fixtures/v3/mocknet-vc-v2-validFrom-valid.json @@ -1 +1,36 @@ -{"@context": ["https://www.w3.org/ns/credentials/v2", "https://w3id.org/blockcerts/v3"], "id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", "type": ["VerifiableCredential", "BlockcertsCredential"], "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", "validFrom": "2022-07-13T20:21:40.088Z", "validUntil": "2039-07-13T00:00:00.000Z", "credentialSubject": {"id": "did:example:ebfeb1f712ebc6f1c276e12ec21", "name": "Julien Fraichot", "email": "julien.fraichot@hyland.com", "publicKey": "ecdsa-koblitz-pubkey:1BPQXndcz5Uf3qZQkgnvJC87LUD5n7a2mC", "claim": {"name": "Master of Puppets", "description": "Awarded to those who rock"}}, "metadata": "{\"classOf\":\"2021\"}", "display": {"contentMediaType": "text/html", "content": "
Yo
"}, "proof": {"type": "MerkleProof2019", "created": "2024-01-30T17:36:01.833602", "proofValue": "z3hSV9ktErQhBgc5C758PsevUUCuxBPcFYGxLVGGcT4ZcWr64zeQnkUet1gJJzWmewShoTg72Yb4mZY347sPy6DYgpTfyZUdhzsLBFo8omHLYAEHPc6NAd811A1JxTViWeyZDHzoMwRtuRuehkkrwdQXLjsfTbtLEQKaCJoWug4PXyd5ab86ehRhkSumeNnZrA3GCFcT6upUUSKUfyxM6jJKfAb7YQnTxBgQzu8CuKJUKgZEHLVbYSJ2DZs1USLriQR2t91NyUHU8UhJ8GjXxMUMwT4gai4kYyurnsTZs36yQ7JmURWF1vXfEz9fJgrfJmaizLy1iTAKvSG8cp4mjFJfk8PchPVQti7gMQEMcn9LGfEQZW6ScBhAUZ4aV7eCt3X4cL5qXwBoiCg", "proofPurpose": "assertionMethod", "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1"}} \ No newline at end of file +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/blockcerts/v3" + ], + "id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", + "type": [ + "VerifiableCredential", + "BlockcertsCredential" + ], + "issuer": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json", + "validFrom": "2022-07-13T20:21:40.088Z", + "validUntil": "2039-07-13T00:00:00.000Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "name": "Julien Fraichot", + "email": "julien.fraichot@hyland.com", + "publicKey": "ecdsa-koblitz-pubkey:1BPQXndcz5Uf3qZQkgnvJC87LUD5n7a2mC", + "claim": { + "name": "Master of Puppets", + "description": "Awarded to those who rock" + } + }, + "metadata": "{\"classOf\":\"2021\"}", + "display": { + "contentMediaType": "text/html", + "content": "
Yo
" + }, + "proof": { + "type": "MerkleProof2019", + "created": "2024-01-30T17:36:01Z", + "proofValue": "z3hSV9ktErQhBgc5C758PsevUUCuxBPcFYGxLVGGcT4ZcWr64zeQnkUet1gJJzWmewShoTg72Yb4mZY347sPy6DYgpTfyZUdhzsLBFo8omHLYAEHPc6NAd811A1JxTViWeyZDHzoMwRtuRuehkkrwdQXLjsfTbtLEQKaCJoWug4PXyd5ab86ehRhkSumeNnZrA3GCFcT6upUUSKUfyxM6jJKfAb7YQnTxBgQzu8CuKJUKgZEHLVbYSJ2DZs1USLriQR2t91NyUHU8UhJ8GjXxMUMwT4gai4kYyurnsTZs36yQ7JmURWF1vXfEz9fJgrfJmaizLy1iTAKvSG8cp4mjFJfk8PchPVQti7gMQEMcn9LGfEQZW6ScBhAUZ4aV7eCt3X4cL5qXwBoiCg", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://www.blockcerts.org/samples/3.0/issuer-blockcerts.json#key-1" + } +}