Skip to content

Commit

Permalink
add a status test
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Feb 5, 2024
1 parent 22e2dcc commit 43832e8
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 13 deletions.
10 changes: 10 additions & 0 deletions src/cr1/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,13 @@ export type CredentialSchema = {
id: string
type: 'JsonSchema'
}

export type CredentialStatus = {
id: string
type: 'BitstringStatusListEntry'
statusPurpose: 'revocation' | 'suspension'
statusListIndex: string
statusListCredential: string
}


26 changes: 21 additions & 5 deletions src/cr1/validator/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import Ajv from 'ajv'

import { RequestValidator, SecuredContentType, CredentialSchema } from "../types"
import { RequestValidator, SecuredContentType, CredentialSchema, CredentialStatus } from "../types"

import { verifier } from "../verifier"

import { decoder, encoder } from "../text"

import { bs } from '../../cr1/status-list'

const ajv = new Ajv({
strict: false,
})

export const validator = ({ resolver }: RequestValidator) => {

return {
validate: async ({ type, content }: SecuredContentType) => {
const verified = await verifier({ resolver }).verify({ type, content })
Expand All @@ -21,16 +22,16 @@ export const validator = ({ resolver }: RequestValidator) => {
schema: {},
status: {}
}
const { credentialSchema } = verified
const { credentialSchema, credentialStatus } = verified
if (credentialSchema) {
const schemas = (Array.isArray(credentialSchema) ? verified.credentialSchema : [credentialSchema]) as CredentialSchema[]
for (const schema of schemas) {
if (schema.type === 'JsonSchema') {
const resolvedSchema = await resolver.resolve({
const credentialSchema = await resolver.resolve({
type: 'application/schema+json',
content: encoder.encode(schema.id)
})
const schemaContent = decoder.decode(resolvedSchema.content)
const schemaContent = decoder.decode(credentialSchema.content)
const parsedSchemaContent = JSON.parse(schemaContent)
const compiledSchemaValidator = ajv.compile(parsedSchemaContent)
const valid = compiledSchemaValidator(verified)
Expand All @@ -42,6 +43,21 @@ export const validator = ({ resolver }: RequestValidator) => {
}
}
}
if (credentialStatus) {
const statuses = (Array.isArray(credentialStatus) ? verified.credentialStatus : [credentialStatus]) as CredentialStatus[]
for (const status of statuses) {
if (status.type === 'BitstringStatusListEntry') {
const statusListCredential = await resolver.resolve({
type: type, // we do not support mixed type credential and status lists!
content: encoder.encode(status.statusListCredential)
})
// TODO create type for bitstring status list instead of ANY here...
const verified = await verifier({ resolver }).verify<any>(statusListCredential)
const bit = bs(verified.credentialSubject.encodedList).get(parseInt(status.statusListIndex, 10))
validation.status[`${status.id}`] = { [`${status.statusPurpose}`]: bit, statusListCredential }
}
}
}
return validation
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/cr1/verifier/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ const verifyJwt = async ({ resolver }: RequestVerifier, { type, content, audienc
}

const verifyCoseSign1
= async ({ resolver }: RequestVerifier, { content, audience, nonce }: RequestVerify) => {
= async ({ resolver }: RequestVerifier, { type, content, audience, nonce }: RequestVerify) => {
const verifier = cose.attached.verifier({
resolver: {
resolve: async () => {
const key = await resolver.resolve({
type: 'application/vc+ld+json+sd-jwt',
type,
content
})
return importJWK(key)
Expand Down Expand Up @@ -80,12 +80,12 @@ export const verifyUnsecuredPresentation = async ({ resolver }: RequestVerifier,
return dataModel
}

const verifySdJwtCredential = async ({ resolver }: RequestVerifier, { content, audience, nonce }: RequestVerify) => {
const verifySdJwtCredential = async ({ resolver }: RequestVerifier, { type, content, audience, nonce }: RequestVerify) => {
const verifier = sd.verifier({
resolver: {
resolve: async () => {
const key = await resolver.resolve({
type: 'application/vc+ld+json+sd-jwt',
type,
content
})
return importJWK(key)
Expand All @@ -100,12 +100,12 @@ const verifySdJwtCredential = async ({ resolver }: RequestVerifier, { content, a
return verified.claimset
}

const verifySdJwtPresentation = async ({ resolver }: RequestVerifier, { content, audience, nonce }: RequestVerify) => {
const verifySdJwtPresentation = async ({ resolver }: RequestVerifier, { type, content, audience, nonce }: RequestVerify) => {
const verifier = sd.verifier({
resolver: {
resolve: async () => {
const key = await resolver.resolve({
type: 'application/vp+ld+json+sd-jwt',
type,
content // same a token
})
return importJWK(key)
Expand Down
2 changes: 1 addition & 1 deletion test/w3c-cr-1/3-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('JSON Schema Validator for W3C Verifiable Credentials', () => {
.validator({
resolver: {
resolve: async ({ type }) => {
if (type === 'application/vc+ld+json+sd-jwt') {
if (type === 'application/vc+ld+json+cose') {
return {
type: privateKeyType,
content: publicKeyContent
Expand Down
99 changes: 98 additions & 1 deletion test/w3c-cr-1/4-status.test.ts
Original file line number Diff line number Diff line change
@@ -1 +1,98 @@
it.todo('status tests')
import fs from 'fs'
import * as cose from '@transmute/cose'
import * as transmute from '../../src'

const privateKeyType = 'application/jwk+json'
const privateKeyContent = fs.readFileSync('./src/cr1/__fixtures__/issuer-0-private-key.json')
const publicKeyContent = fs.readFileSync('./src/cr1/__fixtures__/issuer-0-public-key.json')

const coseSign1 = {
sign: async (bytes: Uint8Array) => {
const signer = cose.attached.signer({
remote: cose.crypto.signer({
secretKeyJwk: await transmute.key.importJWK({
type: privateKeyType,
content: privateKeyContent
})
})
})
const signature = await signer.sign({
protectedHeader: new Map([[1, -35]]),
unprotectedHeader: new Map(),
payload: bytes
})
return new Uint8Array(signature)
}
}

describe('Bitstring Status List Credential Validator for W3C Verifiable Credentials', () => {
it('single schema', async () => {
const validation = await transmute
.validator({
resolver: {
resolve: async ({ type, content }) => {
// it would be nice to be able to pass back a URL
// instead of content for some cases...
const statusList = transmute.text.decoder.decode(content)
if (statusList === 'https://example.com/credentials/status/3') {
return {
type: `application/vc+ld+json+cose`,
content: await transmute
.issuer({
alg: 'ES384',
type: 'application/vc+ld+json+cose',
signer: coseSign1
})
.issue({
claimset: transmute.text.encoder.encode(
await transmute.status.create({
issuer: "https://issuer.example",
"validFrom": "2021-04-05T14:27:40Z",
"id": "https://example.com/status/3#list",
"purpose": "revocation",
})
)
})
}
}
// public key for credential with status
if (type === 'application/vc+ld+json+cose') {
return {
type: privateKeyType,
content: publicKeyContent
}
}
throw new Error('Unsupported resolver content')
}
}
})
.validate({
type: 'application/vc+ld+json+cose',
content: await transmute
.issuer({
alg: 'ES384',
type: 'application/vc+ld+json+cose',
signer: coseSign1
})
.issue({
claimset: transmute.text.encoder.encode(`
"@context":
- https://www.w3.org/ns/credentials/v2
type:
- VerifiableCredential
issuer: https://issuer.example
credentialStatus:
- id: https://example.com/credentials/status/3#94567
type: BitstringStatusListEntry
statusPurpose: revocation
statusListIndex: "94567"
statusListCredential: "https://example.com/credentials/status/3"
credentialSubject:
id: https://issuer.example/issuers/57
`)
}),
})
expect(validation.valid).toBe(true);
expect(validation.status['https://example.com/credentials/status/3#94567'].revocation).toBe(false);
})
})

0 comments on commit 43832e8

Please sign in to comment.