From d190912be7757f6b1ee428d1f2e859e018a58fd6 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 2 Jul 2024 13:45:37 -0500 Subject: [PATCH 1/6] npm audit fix --- package-lock.json | 58 ++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29c0217..10b5027 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1477,9 +1477,10 @@ } }, "node_modules/@transmute/cose/node_modules/jose": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", - "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "version": "4.15.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.7.tgz", + "integrity": "sha512-L7ioP+JAuZe8v+T5+zVI9Tx8LtU8BL7NxkyDFVMv+Qr3JW0jSoYDedLtodaXwfqMpeCyx4WXFNyu9tJt4WvC1A==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -1510,9 +1511,10 @@ } }, "node_modules/@transmute/vc-jwt-sd/node_modules/jose": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", - "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "version": "4.15.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.7.tgz", + "integrity": "sha512-L7ioP+JAuZe8v+T5+zVI9Tx8LtU8BL7NxkyDFVMv+Qr3JW0jSoYDedLtodaXwfqMpeCyx4WXFNyu9tJt4WvC1A==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -2228,12 +2230,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3071,10 +3074,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3459,6 +3463,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -5135,6 +5140,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -6685,9 +6691,9 @@ }, "dependencies": { "jose": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", - "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==" + "version": "4.15.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.7.tgz", + "integrity": "sha512-L7ioP+JAuZe8v+T5+zVI9Tx8LtU8BL7NxkyDFVMv+Qr3JW0jSoYDedLtodaXwfqMpeCyx4WXFNyu9tJt4WvC1A==" } } }, @@ -6714,9 +6720,9 @@ "integrity": "sha512-bTCCiR0brj9RShibl2wirK+y99JuZBhCLXo114N7HtwjKnLa43D14X9Ay0SdIslCYhyOH6kagtMp9HhVkqyPqQ==" }, "jose": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", - "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==" + "version": "4.15.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.7.tgz", + "integrity": "sha512-L7ioP+JAuZe8v+T5+zVI9Tx8LtU8BL7NxkyDFVMv+Qr3JW0jSoYDedLtodaXwfqMpeCyx4WXFNyu9tJt4WvC1A==" } } }, @@ -7255,12 +7261,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "brorand": { @@ -7869,9 +7875,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" From 607885de809067a10f17336edd2563dbafa10f57 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 2 Jul 2024 14:46:59 -0500 Subject: [PATCH 2/6] adjust schema validation interface to enable short circuit --- src/cr1/types.ts | 4 +- src/cr1/validator/index.ts | 6 +- .../better-schema-errors.test.ts | 2 +- .../optional-schema-validation.test.ts | 100 ++++++++++++++++++ .../jwt-product-passports/integration.test.ts | 2 +- test/w3c-cr-1/3-schema.test.ts | 4 +- 6 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 test/json-schema-tests/optional-schema-validation.test.ts diff --git a/src/cr1/types.ts b/src/cr1/types.ts index 6cda8d9..5cef9e5 100644 --- a/src/cr1/types.ts +++ b/src/cr1/types.ts @@ -244,10 +244,12 @@ export type ConformanceWarningMessage = { reference: string } +export type SchemaValidation = 'succeeded' | 'failed' | 'ignored' + export type ValidationResult = { valid: boolean content: VerifiableCredential - schema: Record + schema: Record status: Record warnings: ConformanceWarningMessage[] } diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index daf8f53..ce7c5f6 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -42,6 +42,10 @@ export const validator = ({ resolver }: RequestValidator) => { id: schema.id, type: 'application/schema+json', }) + if (credentialSchema === true) { + validation.schema[schema.id] = { validation: 'ignored' } as any + continue; + } const schemaContent = decoder.decode(credentialSchema.content) const parsedSchemaContent = JSON.parse(schemaContent) let valid: any; @@ -58,7 +62,7 @@ export const validator = ({ resolver }: RequestValidator) => { } catch (e) { valid = false } - validation.schema[schema.id] = { valid } + validation.schema[schema.id] = { validation: valid ? 'succeeded' : 'failed' } if (!valid) { validation.valid = false validation.schema[schema.id].errors = compiledSchemaValidator.errors as JsonSchemaError[] diff --git a/test/json-schema-tests/better-schema-errors.test.ts b/test/json-schema-tests/better-schema-errors.test.ts index 1878277..2ca2bed 100644 --- a/test/json-schema-tests/better-schema-errors.test.ts +++ b/test/json-schema-tests/better-schema-errors.test.ts @@ -120,7 +120,7 @@ credentialSubject: expect(validation1.valid).toBe(false); expect(validation1.schema).toEqual({ "https://vendor.example/api/schemas/product-passport": { - "valid": false, + "validation": "failed", "errors": [ { "instancePath": "/credentialSubject", diff --git a/test/json-schema-tests/optional-schema-validation.test.ts b/test/json-schema-tests/optional-schema-validation.test.ts new file mode 100644 index 0000000..ae91459 --- /dev/null +++ b/test/json-schema-tests/optional-schema-validation.test.ts @@ -0,0 +1,100 @@ +import * as jose from "jose"; +import moment from "moment"; + +import * as transmute from "../../src"; + +const alg = `ES256`; +const issuer = `did:example:123`; +const baseURL = `https://vendor.example/api`; + +let publicKey: any; +let issued: any; + +beforeAll(async () => { + const privateKey = await transmute.key.generate({ + alg, + type: "application/jwk+json", + }); + publicKey = await transmute.key.publicFromPrivate({ + type: "application/jwk+json", + content: privateKey, + }); + issued = await transmute + .issuer({ + alg, + type: "application/vc+ld+json+jwt", + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign(bytes) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign( + await transmute.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey, + }) + ); + return transmute.text.encoder.encode(jws); + }, + }, + }) + .issue({ + claimset: transmute.text.encoder.encode(` +"@context": + - https://www.w3.org/ns/credentials/v2 + - ${baseURL}/context/v2 +id: ${baseURL}/credentials/3732 +type: + - VerifiableCredential + - ExampleDegreeCredential +issuer: + id: ${issuer} +name: "Example University" +validFrom: ${moment().toISOString()} +credentialSchema: + id: ${baseURL}/schemas/product-passport + type: JsonSchema +credentialSubject: + id: did:example:ebfeb1f712ebc6f1c276e12ec21 + unexpectedProperty: unexpectedValue + degree: + type: ExampleBachelorDegree + subtype: Bachelor of Science and Arts +`), + }); +}) + +it("can disable schema validation", async () => { + const validator = await transmute.validator({ + resolver: { + resolve: async (opts: any) => { + // console.log(opts) + const { id, type, content } = opts + // Resolve external resources according to verifier policy + // In this case, we return inline exampes... + if (id === `${baseURL}/schemas/product-passport`) { + return true; // resolving the special case "true" ignores validation + } + if (content != undefined && type === `application/vc+ld+json+jwt`) { + const { kid } = jose.decodeProtectedHeader( + transmute.text.decoder.decode(content) + ); + // lookup public key on a resolver + if (kid === `did:example:123#key-42`) { + return { + type: "application/jwk+json", + content: publicKey, + }; + } + } + throw new Error("Resolver option not supported."); + }, + }, + }); + const validation1 = await validator.validate({ + type: "application/vc+ld+json+jwt", + content: issued, + }); + expect(validation1.valid).toBe(true); + // console.log(JSON.stringify(validation1, null, 2)) + expect(validation1.schema['https://vendor.example/api/schemas/product-passport'].validation).toBe('ignored') +}); \ No newline at end of file diff --git a/test/jwt-product-passports/integration.test.ts b/test/jwt-product-passports/integration.test.ts index 5e429c9..8a02529 100644 --- a/test/jwt-product-passports/integration.test.ts +++ b/test/jwt-product-passports/integration.test.ts @@ -214,7 +214,7 @@ credentialSubject: content: issued, }) expect(validated.valid).toBe(true) - expect(validated.schema[`${baseURL}/schemas/product-passport`].valid).toBe(true) + expect(validated.schema[`${baseURL}/schemas/product-passport`].validation).toBe('succeeded') expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`].valid).toBe(false) expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`].valid).toBe(false) diff --git a/test/w3c-cr-1/3-schema.test.ts b/test/w3c-cr-1/3-schema.test.ts index 4829e65..512d6b4 100644 --- a/test/w3c-cr-1/3-schema.test.ts +++ b/test/w3c-cr-1/3-schema.test.ts @@ -89,7 +89,7 @@ credentialSubject: }), }) expect(validation.valid).toBe(true); - expect(validation.schema).toEqual({ 'https://issuer.example/schemas/42': { valid: true } }); + expect(validation.schema).toEqual({ 'https://issuer.example/schemas/42': { validation: 'succeeded' } }); }) it('failure', async () => { @@ -157,7 +157,7 @@ credentialSubject: expect(validation.valid).toBe(false); expect(validation.schema).toEqual({ "https://issuer.example/schemas/52": { - "valid": false, + "validation": 'failed', "errors": [ { "instancePath": "/credentialSubject/id", From fd0cbdbd80380bc1f8b8f54d0d8bea96ee2bb97b Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 2 Jul 2024 15:06:01 -0500 Subject: [PATCH 3/6] interface changes --- src/cr1/types.ts | 11 ++++++++- src/cr1/validator/index.ts | 4 +++- src/cr1/verifier/verifier.ts | 11 +++++---- .../optional-schema-validation.test.ts | 24 ++++++------------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/cr1/types.ts b/src/cr1/types.ts index 5cef9e5..acde616 100644 --- a/src/cr1/types.ts +++ b/src/cr1/types.ts @@ -158,8 +158,14 @@ export type SecuredContentType = { content: Uint8Array } +export type VerifierResolutionRequest = { + type: SupportedCredentialFormats | SupportedPresentationFormats | SupportedJwtSignatureFormats | SupportedSdJwtSignatureFormats | SupportedCoseSign1Formats + content: Uint8Array + purpose: ValidatorResolutionPurpose +} + export type VerifierResolver = { - resolve: (req: SecuredContentType) => Promise + resolve: (req: VerifierResolutionRequest) => Promise } export type RequestVerifier = { @@ -167,10 +173,13 @@ export type RequestVerifier = { } +export type ValidatorResolutionPurpose = 'schema-validation' | 'status-check' | 'verification-material' + export type ValidatorContentType = { id?: string type: any content?: Uint8Array + purpose: ValidatorResolutionPurpose } diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index ce7c5f6..c755842 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -41,6 +41,7 @@ export const validator = ({ resolver }: RequestValidator) => { // prefer to resolve this one by id, instead of content id: schema.id, type: 'application/schema+json', + purpose: 'schema-validation' }) if (credentialSchema === true) { validation.schema[schema.id] = { validation: 'ignored' } as any @@ -77,7 +78,8 @@ export const validator = ({ resolver }: RequestValidator) => { const statusListCredential = await resolver.resolve({ // prefer to resolve this one by id, instead of content id: status.statusListCredential, - type: type // we do not support mixed type credential and status lists! + type: type, // we do not support mixed type credential and status lists! + purpose: 'status-check' }) const verified = await verifier({ resolver }).verify(statusListCredential) // confirm purpose matches diff --git a/src/cr1/verifier/verifier.ts b/src/cr1/verifier/verifier.ts index 25a5d4a..0ad5e76 100644 --- a/src/cr1/verifier/verifier.ts +++ b/src/cr1/verifier/verifier.ts @@ -23,7 +23,7 @@ const acceptableAudience = (expectedAud: string, receivedAud: string | string[]) } const verifyJwt = async ({ resolver }: RequestVerifier, { type, content, audience, nonce }: RequestVerify) => { - const key = await resolver.resolve({ type, content }) + const key = await resolver.resolve({ type, content, purpose: 'verification-material' }) const publicKey = await importKeyLike(key) const jwt = decoder.decode(content) const { payload } = await jose.jwtVerify(jwt, publicKey, { @@ -46,7 +46,8 @@ const verifyCoseSign1 resolve: async () => { const key = await resolver.resolve({ type, - content + content, + purpose: 'verification-material' }) return importJWK(key) } @@ -86,7 +87,8 @@ const verifySdJwtCredential = async ({ resolver }: RequestVerifier, { type, cont resolve: async () => { const key = await resolver.resolve({ type, - content + content, + purpose: 'verification-material' }) return importJWK(key) } @@ -106,7 +108,8 @@ const verifySdJwtPresentation = async ({ resolver }: RequestVerifier, { type, co resolve: async () => { const key = await resolver.resolve({ type, - content // same a token + content, // same a token + purpose: 'verification-material' }) return importJWK(key) } diff --git a/test/json-schema-tests/optional-schema-validation.test.ts b/test/json-schema-tests/optional-schema-validation.test.ts index ae91459..cc983c4 100644 --- a/test/json-schema-tests/optional-schema-validation.test.ts +++ b/test/json-schema-tests/optional-schema-validation.test.ts @@ -66,25 +66,15 @@ credentialSubject: it("can disable schema validation", async () => { const validator = await transmute.validator({ resolver: { - resolve: async (opts: any) => { - // console.log(opts) - const { id, type, content } = opts - // Resolve external resources according to verifier policy - // In this case, we return inline exampes... - if (id === `${baseURL}/schemas/product-passport`) { + resolve: async ({ purpose }) => { + if (purpose === 'schema-validation') { return true; // resolving the special case "true" ignores validation } - if (content != undefined && type === `application/vc+ld+json+jwt`) { - const { kid } = jose.decodeProtectedHeader( - transmute.text.decoder.decode(content) - ); - // lookup public key on a resolver - if (kid === `did:example:123#key-42`) { - return { - type: "application/jwk+json", - content: publicKey, - }; - } + if (purpose === 'verification-material') { + return { + type: "application/jwk+json", + content: publicKey, + }; } throw new Error("Resolver option not supported."); }, From 8528a1d36694a4499041a73a027dc38c275c4d8f Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 2 Jul 2024 15:18:50 -0500 Subject: [PATCH 4/6] intermediate --- src/cr1/types.ts | 9 +++++- src/cr1/validator/index.ts | 6 ++-- .../jwt-product-passports/integration.test.ts | 4 +-- test/w3c-cr-1/4-status.test.ts | 31 ++++++++++++++----- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/cr1/types.ts b/src/cr1/types.ts index acde616..c0534ec 100644 --- a/src/cr1/types.ts +++ b/src/cr1/types.ts @@ -255,11 +255,18 @@ export type ConformanceWarningMessage = { export type SchemaValidation = 'succeeded' | 'failed' | 'ignored' + +export type StatusCheckResult = { + set: boolean, + purpose: string, + errors?: StatusListError[] +} & Record + export type ValidationResult = { valid: boolean content: VerifiableCredential schema: Record - status: Record + status: Record warnings: ConformanceWarningMessage[] } diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index c755842..cd33913 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -86,7 +86,9 @@ export const validator = ({ resolver }: RequestValidator) => { if (status.statusPurpose !== verified.credentialSubject.statusPurpose) { validation.valid = false validation.status[`${status.id}`] = { - valid: false, purpose: status.statusPurpose, errors: [{ + set: false, + purpose: status.statusPurpose, + errors: [{ message: 'status list purpose does not match credential status' }] } @@ -95,7 +97,7 @@ export const validator = ({ resolver }: RequestValidator) => { if (bit) { validation.valid = false } - validation.status[`${status.id}`] = { valid: bit, purpose: status.statusPurpose } + validation.status[`${status.id}`] = { set: bit, purpose: status.statusPurpose, [status.statusPurpose]: bit } } } diff --git a/test/jwt-product-passports/integration.test.ts b/test/jwt-product-passports/integration.test.ts index 8a02529..8ce4c7e 100644 --- a/test/jwt-product-passports/integration.test.ts +++ b/test/jwt-product-passports/integration.test.ts @@ -215,8 +215,8 @@ credentialSubject: }) expect(validated.valid).toBe(true) expect(validated.schema[`${baseURL}/schemas/product-passport`].validation).toBe('succeeded') - expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`].valid).toBe(false) - expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`].valid).toBe(false) + expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`]).toEqual({ "purpose": "revocation", "revocation": false, "set": false }) + expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`]).toEqual({ "purpose": "suspension", "suspension": false, "set": false }) const vp = await transmute diff --git a/test/w3c-cr-1/4-status.test.ts b/test/w3c-cr-1/4-status.test.ts index 453d667..f3e63e0 100644 --- a/test/w3c-cr-1/4-status.test.ts +++ b/test/w3c-cr-1/4-status.test.ts @@ -97,8 +97,12 @@ credentialSubject: }), }) expect(validation.valid).toBe(true); - expect(validation.status['https://example.com/credentials/status/3#94567'].valid).toBe(false); - expect(validation.status['https://example.com/credentials/status/3#94567'].purpose).toBe('revocation'); + expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ + "purpose": "revocation", + "revocation": false, + "set": false + }); + }) // failure here means REVOKED @@ -183,8 +187,11 @@ credentialSubject: }), }) expect(validation.valid).toBe(false); - expect(validation.status['https://example.com/credentials/status/3#94567'].valid).toBe(true); - expect(validation.status['https://example.com/credentials/status/3#94567'].purpose).toBe('revocation'); + expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ + "purpose": "revocation", + "revocation": true, + "set": true + }); }) }) @@ -259,8 +266,12 @@ credentialSubject: }), }) expect(validation.valid).toBe(true); - expect(validation.status['https://example.com/credentials/status/3#94567'].valid).toBe(false); - expect(validation.status['https://example.com/credentials/status/3#94567'].purpose).toBe('suspension'); + expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ + "set": false, + "purpose": "suspension", + "suspension": false + }); + }) // failure here means REVOKED @@ -345,8 +356,12 @@ credentialSubject: }), }) expect(validation.valid).toBe(false); - expect(validation.status['https://example.com/credentials/status/3#94567'].valid).toBe(true); - expect(validation.status['https://example.com/credentials/status/3#94567'].purpose).toBe('suspension'); + expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ + "set": true, + "purpose": "suspension", + "suspension": true + }); + }) }) }) From e505e8484dbd4bf98d690d92411bb0139a1a03be Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 2 Jul 2024 15:49:14 -0500 Subject: [PATCH 5/6] Update validator interface --- src/cr1/types.ts | 11 +++++------ src/cr1/validator/index.ts | 12 +++--------- .../better-schema-errors.test.ts | 2 +- test/json-schema-tests/json-schema-tests.test.ts | 4 ++-- .../optional-schema-validation.test.ts | 2 +- test/jwt-product-passports/integration.test.ts | 6 +++--- test/w3c-cr-1/3-schema.test.ts | 4 ++-- test/w3c-cr-1/4-status.test.ts | 16 ++++------------ 8 files changed, 21 insertions(+), 36 deletions(-) diff --git a/src/cr1/types.ts b/src/cr1/types.ts index c0534ec..32a35c8 100644 --- a/src/cr1/types.ts +++ b/src/cr1/types.ts @@ -255,17 +255,16 @@ export type ConformanceWarningMessage = { export type SchemaValidation = 'succeeded' | 'failed' | 'ignored' - export type StatusCheckResult = { - set: boolean, - purpose: string, errors?: StatusListError[] -} & Record +} & Record // because of enumerations. + +export type SchemaCheckResult = { validation?: SchemaValidation, errors?: JsonSchemaError[], } export type ValidationResult = { - valid: boolean + verified: boolean content: VerifiableCredential - schema: Record + schema: Record status: Record warnings: ConformanceWarningMessage[] } diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index cd33913..1c52b42 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -26,7 +26,7 @@ export const validator = ({ resolver }: RequestValidator) => { validate: async ({ type, content }: SecuredContentType) => { const verified = await verifier({ resolver }).verify({ type, content }) const validation: ValidationResult = { - valid: true, + verified: true, content: verified, schema: {}, status: {}, @@ -65,7 +65,6 @@ export const validator = ({ resolver }: RequestValidator) => { } validation.schema[schema.id] = { validation: valid ? 'succeeded' : 'failed' } if (!valid) { - validation.valid = false validation.schema[schema.id].errors = compiledSchemaValidator.errors as JsonSchemaError[] } } @@ -84,20 +83,15 @@ export const validator = ({ resolver }: RequestValidator) => { const verified = await verifier({ resolver }).verify(statusListCredential) // confirm purpose matches if (status.statusPurpose !== verified.credentialSubject.statusPurpose) { - validation.valid = false validation.status[`${status.id}`] = { - set: false, - purpose: status.statusPurpose, + [status.statusPurpose]: false, errors: [{ message: 'status list purpose does not match credential status' }] } } else { const bit = bs(verified.credentialSubject.encodedList).get(parseInt(status.statusListIndex, 10)) - if (bit) { - validation.valid = false - } - validation.status[`${status.id}`] = { set: bit, purpose: status.statusPurpose, [status.statusPurpose]: bit } + validation.status[`${status.id}`] = { [status.statusPurpose]: bit } } } diff --git a/test/json-schema-tests/better-schema-errors.test.ts b/test/json-schema-tests/better-schema-errors.test.ts index 2ca2bed..5444be7 100644 --- a/test/json-schema-tests/better-schema-errors.test.ts +++ b/test/json-schema-tests/better-schema-errors.test.ts @@ -117,7 +117,7 @@ credentialSubject: type: "application/vc+ld+json+jwt", content: issued, }); - expect(validation1.valid).toBe(false); + expect(validation1.verified).toBe(true); expect(validation1.schema).toEqual({ "https://vendor.example/api/schemas/product-passport": { "validation": "failed", diff --git a/test/json-schema-tests/json-schema-tests.test.ts b/test/json-schema-tests/json-schema-tests.test.ts index fbb7e0f..630c70c 100644 --- a/test/json-schema-tests/json-schema-tests.test.ts +++ b/test/json-schema-tests/json-schema-tests.test.ts @@ -221,12 +221,12 @@ credentialSubject: type: "application/vc+ld+json+jwt", content: issued, }); - expect(valid1.valid).toBe(true); + expect(valid1.verified).toBe(true); const valid2 = await validator.validate({ type: "application/vc+ld+json+jwt", content: issued, }); - expect(valid2.valid).toBe(true); + expect(valid2.verified).toBe(true); }); }); diff --git a/test/json-schema-tests/optional-schema-validation.test.ts b/test/json-schema-tests/optional-schema-validation.test.ts index cc983c4..7a041ca 100644 --- a/test/json-schema-tests/optional-schema-validation.test.ts +++ b/test/json-schema-tests/optional-schema-validation.test.ts @@ -84,7 +84,7 @@ it("can disable schema validation", async () => { type: "application/vc+ld+json+jwt", content: issued, }); - expect(validation1.valid).toBe(true); + expect(validation1.verified).toBe(true); // console.log(JSON.stringify(validation1, null, 2)) expect(validation1.schema['https://vendor.example/api/schemas/product-passport'].validation).toBe('ignored') }); \ No newline at end of file diff --git a/test/jwt-product-passports/integration.test.ts b/test/jwt-product-passports/integration.test.ts index 8ce4c7e..fd6d0bf 100644 --- a/test/jwt-product-passports/integration.test.ts +++ b/test/jwt-product-passports/integration.test.ts @@ -213,10 +213,10 @@ credentialSubject: type: 'application/vc+ld+json+jwt', content: issued, }) - expect(validated.valid).toBe(true) + expect(validated.verified).toBe(true) expect(validated.schema[`${baseURL}/schemas/product-passport`].validation).toBe('succeeded') - expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`]).toEqual({ "purpose": "revocation", "revocation": false, "set": false }) - expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`]).toEqual({ "purpose": "suspension", "suspension": false, "set": false }) + expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`]).toEqual({ "revocation": false, }) + expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`]).toEqual({ "suspension": false, }) const vp = await transmute diff --git a/test/w3c-cr-1/3-schema.test.ts b/test/w3c-cr-1/3-schema.test.ts index 512d6b4..068cc19 100644 --- a/test/w3c-cr-1/3-schema.test.ts +++ b/test/w3c-cr-1/3-schema.test.ts @@ -88,7 +88,7 @@ credentialSubject: `) }), }) - expect(validation.valid).toBe(true); + expect(validation.verified).toBe(true); expect(validation.schema).toEqual({ 'https://issuer.example/schemas/42': { validation: 'succeeded' } }); }) @@ -154,7 +154,7 @@ credentialSubject: `) }), }) - expect(validation.valid).toBe(false); + expect(validation.verified).toBe(true); expect(validation.schema).toEqual({ "https://issuer.example/schemas/52": { "validation": 'failed', diff --git a/test/w3c-cr-1/4-status.test.ts b/test/w3c-cr-1/4-status.test.ts index f3e63e0..20e118c 100644 --- a/test/w3c-cr-1/4-status.test.ts +++ b/test/w3c-cr-1/4-status.test.ts @@ -96,11 +96,9 @@ credentialSubject: `) }), }) - expect(validation.valid).toBe(true); + expect(validation.verified).toBe(true); expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ - "purpose": "revocation", "revocation": false, - "set": false }); }) @@ -186,11 +184,9 @@ credentialSubject: `) }), }) - expect(validation.valid).toBe(false); + expect(validation.verified).toBe(true); expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ - "purpose": "revocation", "revocation": true, - "set": true }); }) }) @@ -265,10 +261,8 @@ credentialSubject: `) }), }) - expect(validation.valid).toBe(true); + expect(validation.verified).toBe(true); expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ - "set": false, - "purpose": "suspension", "suspension": false }); @@ -355,10 +349,8 @@ credentialSubject: `) }), }) - expect(validation.valid).toBe(false); + expect(validation.verified).toBe(true); expect(validation.status['https://example.com/credentials/status/3#94567']).toEqual({ - "set": true, - "purpose": "suspension", "suspension": true }); From c6246587aa427f51f1fdedad3fbaaa8db475dae4 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 2 Jul 2024 15:55:07 -0500 Subject: [PATCH 6/6] any clean up --- src/cr1/credential/issuer.ts | 4 ++-- src/cr1/validator/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cr1/credential/issuer.ts b/src/cr1/credential/issuer.ts index 24400d9..06546b1 100644 --- a/src/cr1/credential/issuer.ts +++ b/src/cr1/credential/issuer.ts @@ -13,7 +13,7 @@ const coseSign1CredentialIssuer = (issuer: RequestCredentialIssuer) => { if (issuer.signer === undefined) { throw new Error('No signer available.') } - const claims = claimset.parse(decoder.decode(credential.claimset)) as any + const claims = claimset.parse(decoder.decode(credential.claimset)) return issuer.signer.sign(encoder.encode(JSON.stringify(claims))) } } @@ -25,7 +25,7 @@ const jwtCredentialIssuer = (issuer: RequestCredentialIssuer) => { if (issuer.signer === undefined) { throw new Error('No signer available.') } - const claims = claimset.parse(decoder.decode(credential.claimset)) as any + const claims = claimset.parse(decoder.decode(credential.claimset)) return issuer.signer.sign(encoder.encode(JSON.stringify(claims))) } } diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index 1c52b42..e022373 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -44,7 +44,7 @@ export const validator = ({ resolver }: RequestValidator) => { purpose: 'schema-validation' }) if (credentialSchema === true) { - validation.schema[schema.id] = { validation: 'ignored' } as any + validation.schema[schema.id] = { validation: 'ignored' } continue; } const schemaContent = decoder.decode(credentialSchema.content)