@sphereon/ssi-sdk.credential-validation 0.30.2-unstable.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,244 @@
1
+ import { IAgentPlugin, IVerifyCredentialArgs, W3CVerifiableCredential as VeramoW3CVerifiableCredential } from '@veramo/core'
2
+ import {
3
+ CredentialVerificationError,
4
+ ICredentialValidation,
5
+ RequiredContext,
6
+ schema,
7
+ SchemaValidation,
8
+ ValidateSchemaArgs,
9
+ VerificationResult,
10
+ VerificationSubResult,
11
+ VerifyCredentialArgs,
12
+ VerifyMdocCredentialArgs,
13
+ VerifySDJWTCredentialArgs,
14
+ } from '../index'
15
+ import {
16
+ CredentialMapper,
17
+ ICoseKeyJson,
18
+ ICredentialSchemaType,
19
+ IVerifyResult,
20
+ OriginalVerifiableCredential,
21
+ WrappedVerifiableCredential,
22
+ } from '@sphereon/ssi-types'
23
+ import fetch from 'cross-fetch'
24
+ import Ajv2020 from 'ajv/dist/2020'
25
+ import addFormats from 'ajv-formats'
26
+ import { com } from '@sphereon/kmp-mdl-mdoc'
27
+ import { IVerifySdJwtVcResult } from '@sphereon/ssi-sdk.sd-jwt'
28
+ import decodeFrom = com.sphereon.kmp.decodeFrom
29
+ import IssuerSignedCbor = com.sphereon.mdoc.data.device.IssuerSignedCbor
30
+ import IVerifySignatureResult = com.sphereon.crypto.IVerifySignatureResult
31
+
32
+ // Exposing the methods here for any REST implementation
33
+ export const credentialValidationMethods: Array<string> = [
34
+ 'cvVerifyCredential',
35
+ 'cvVerifySchema',
36
+ 'cvVerifyMdoc',
37
+ 'cvVerifySDJWTCredential',
38
+ 'cvVerifyW3CCredential',
39
+ ]
40
+
41
+ /**
42
+ * {@inheritDoc ICredentialValidation}
43
+ */
44
+ export class CredentialValidation implements IAgentPlugin {
45
+ readonly schema = schema.ICredentialValidation
46
+ readonly methods: ICredentialValidation = {
47
+ cvVerifyCredential: this.cvVerifyCredential.bind(this),
48
+ cvVerifySchema: this.cvVerifySchema.bind(this),
49
+ cvVerifyMdoc: this.cvVerifyMdoc.bind(this),
50
+ cvVerifySDJWTCredential: this.cvVerifySDJWTCredential.bind(this),
51
+ cvVerifyW3CCredential: this.cvVerifyW3CCredential.bind(this),
52
+ }
53
+
54
+ private detectSchemas(wrappedVC: WrappedVerifiableCredential): ICredentialSchemaType[] | undefined {
55
+ if ('credential' in wrappedVC) {
56
+ const { credential } = wrappedVC
57
+
58
+ if ('credentialSchema' in credential) {
59
+ const { credentialSchema } = credential
60
+
61
+ if (Array.isArray(credentialSchema)) {
62
+ return credentialSchema
63
+ } else if (credentialSchema) {
64
+ return [credentialSchema]
65
+ }
66
+ }
67
+ }
68
+
69
+ return undefined
70
+ }
71
+
72
+ private async cvVerifyCredential(args: VerifyCredentialArgs, context: RequiredContext): Promise<VerificationResult> {
73
+ const { credential, hasher, policies } = args
74
+ // defaulting the schema validation to when_present
75
+ const schemaResult = await this.cvVerifySchema({ credential, validationPolicy: policies?.schemaValidation ?? SchemaValidation.WHEN_PRESENT })
76
+ if (!schemaResult.result) {
77
+ return schemaResult
78
+ }
79
+ if (CredentialMapper.isMsoMdocOid4VPEncoded(credential)) {
80
+ return await this.cvVerifyMdoc({ credential }, context)
81
+ } else if (CredentialMapper.isSdJwtEncoded(credential)) {
82
+ return await this.cvVerifySDJWTCredential({ credential, hasher }, context)
83
+ } else {
84
+ return await this.cvVerifyW3CCredential({ ...args, credential: credential as VeramoW3CVerifiableCredential }, context)
85
+ }
86
+ }
87
+
88
+ private async cvVerifySchema(args: ValidateSchemaArgs): Promise<VerificationResult> {
89
+ const { credential, hasher, validationPolicy } = args
90
+ const wrappedCredential: WrappedVerifiableCredential = CredentialMapper.toWrappedVerifiableCredential(credential, { hasher })
91
+ if (validationPolicy === SchemaValidation.NEVER) {
92
+ return {
93
+ result: true,
94
+ source: wrappedCredential,
95
+ subResults: [],
96
+ }
97
+ }
98
+ return this.validateSchema(wrappedCredential, validationPolicy)
99
+ }
100
+
101
+ private async validateSchema(wrappedVC: WrappedVerifiableCredential, validationPolicy?: SchemaValidation): Promise<VerificationResult> {
102
+ const schemas: ICredentialSchemaType[] | undefined = this.detectSchemas(wrappedVC)
103
+ if (!schemas) {
104
+ return validationPolicy === SchemaValidation.ALWAYS
105
+ ? {
106
+ result: false,
107
+ source: wrappedVC,
108
+ subResults: [],
109
+ }
110
+ : {
111
+ result: true,
112
+ source: wrappedVC,
113
+ subResults: [],
114
+ }
115
+ }
116
+
117
+ const subResults: VerificationSubResult[] = await Promise.all(schemas.map((schema) => this.verifyCredentialAgainstSchema(wrappedVC, schema)))
118
+
119
+ return {
120
+ result: subResults.every((subResult) => subResult.result),
121
+ source: wrappedVC,
122
+ subResults,
123
+ }
124
+ }
125
+
126
+ private async fetchSchema(uri: string) {
127
+ const response = await fetch(uri)
128
+ if (!response.ok) {
129
+ throw new Error(`Unable to fetch schema from ${uri}`)
130
+ }
131
+ return response.json()
132
+ }
133
+
134
+ private async verifyCredentialAgainstSchema(wrappedVC: WrappedVerifiableCredential, schema: ICredentialSchemaType): Promise<VerificationSubResult> {
135
+ const schemaUrl: string = typeof schema === 'string' ? schema : schema.id
136
+ let schemaValue
137
+ try {
138
+ schemaValue = await this.fetchSchema(schemaUrl)
139
+ } catch (e) {
140
+ return {
141
+ result: false,
142
+ error: e,
143
+ }
144
+ }
145
+
146
+ const ajv = new Ajv2020({ loadSchema: this.fetchSchema })
147
+ addFormats(ajv)
148
+
149
+ const validate = await ajv.compileAsync(schemaValue)
150
+ const valid = validate(wrappedVC.credential)
151
+ return {
152
+ result: valid,
153
+ }
154
+ }
155
+
156
+ private async cvVerifyMdoc(args: VerifyMdocCredentialArgs, context: RequiredContext): Promise<VerificationResult> {
157
+ const { credential } = args
158
+
159
+ const issuerSigned = IssuerSignedCbor.Static.cborDecode(decodeFrom(credential, com.sphereon.kmp.Encoding.BASE64URL))
160
+
161
+ const verification = await context.agent.mdocVerifyIssuerSigned({ input: issuerSigned.toJson().issuerAuth }).catch((error: Error) => {
162
+ return {
163
+ name: 'mdoc',
164
+ critical: true,
165
+ error: true,
166
+ message: error.message ?? 'SD-JWT VC could not be verified',
167
+ } satisfies IVerifySignatureResult<ICoseKeyJson>
168
+ })
169
+
170
+ return {
171
+ source: CredentialMapper.toWrappedVerifiableCredential(credential as OriginalVerifiableCredential),
172
+ result: !verification.error ?? true,
173
+ subResults: [],
174
+ ...(verification.error && {
175
+ error: verification.message ?? `Could not verify mdoc from issuer`,
176
+ }),
177
+ }
178
+ }
179
+
180
+ private async cvVerifyW3CCredential(args: IVerifyCredentialArgs, context: RequiredContext): Promise<VerificationResult> {
181
+ // We also allow/add boolean, because 4.x Veramo returns a boolean for JWTs. 5.X will return better results
182
+ const { credential, policies } = args
183
+
184
+ const result: IVerifyResult | boolean = (await context.agent.verifyCredential(args)) as IVerifyResult | boolean
185
+
186
+ if (typeof result === 'boolean') {
187
+ return {
188
+ // FIXME the source is never used, need to start using this as the source of truth
189
+ source: CredentialMapper.toWrappedVerifiableCredential(args.credential as OriginalVerifiableCredential),
190
+ result,
191
+ ...(!result && {
192
+ error: 'Invalid JWT VC',
193
+ errorDetails: `JWT VC was not valid with policies: ${JSON.stringify(policies)}`,
194
+ }),
195
+ subResults: [],
196
+ }
197
+ } else {
198
+ // TODO look at what this is doing and make it simple and readable
199
+ let error: string | undefined
200
+ let errorDetails: string | undefined
201
+ const subResults: Array<VerificationSubResult> = []
202
+ if (result.error) {
203
+ error = result.error?.message ?? ''
204
+ errorDetails = result.error?.details?.code ?? ''
205
+ errorDetails = (errorDetails !== '' ? `${errorDetails}, ` : '') + (result.error?.details?.url ?? '')
206
+ if (result.error?.errors) {
207
+ error = (error !== '' ? `${error}, ` : '') + result.error?.errors?.map((error) => error.message ?? error.name).join(', ')
208
+ errorDetails =
209
+ (errorDetails !== '' ? `${errorDetails}, ` : '') +
210
+ result.error?.errors?.map((error) => (error?.details?.code ? `${error.details.code}, ` : '') + (error?.details?.url ?? '')).join(', ')
211
+ }
212
+ }
213
+
214
+ return {
215
+ source: CredentialMapper.toWrappedVerifiableCredential(credential as OriginalVerifiableCredential),
216
+ result: result.verified,
217
+ subResults,
218
+ error,
219
+ errorDetails,
220
+ }
221
+ }
222
+ }
223
+
224
+ private async cvVerifySDJWTCredential(args: VerifySDJWTCredentialArgs, context: RequiredContext): Promise<VerificationResult> {
225
+ const { credential, hasher } = args
226
+
227
+ const verification: IVerifySdJwtVcResult | CredentialVerificationError = await context.agent
228
+ .verifySdJwtVc({ credential })
229
+ .catch((error: Error): CredentialVerificationError => {
230
+ return {
231
+ error: 'Invalid SD-JWT VC',
232
+ errorDetails: error.message ?? 'SD-JWT VC could not be verified',
233
+ }
234
+ })
235
+
236
+ const result = 'header' in verification && 'payload' in verification
237
+ return {
238
+ source: CredentialMapper.toWrappedVerifiableCredential(credential as OriginalVerifiableCredential, { hasher }),
239
+ result,
240
+ subResults: [],
241
+ ...(!result && { ...verification }),
242
+ }
243
+ }
244
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @public
3
+ */
4
+ const schema = require('../plugin.schema.json')
5
+ export { schema }
6
+ export { CredentialValidation, credentialValidationMethods } from './agent/CredentialValidation'
7
+ export * from './types/ICredentialValidation'
@@ -0,0 +1,68 @@
1
+ import { IAgentContext, ICredentialVerifier, IPluginMethodMap } from '@veramo/core'
2
+ import { Hasher, WrappedVerifiableCredential, WrappedVerifiablePresentation } from '@sphereon/ssi-types'
3
+ import { ImDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc'
4
+ import { OriginalVerifiableCredential } from '@sphereon/ssi-types/dist'
5
+
6
+ export interface ICredentialValidation extends IPluginMethodMap {
7
+ cvVerifyCredential(args: VerifyCredentialArgs, context: RequiredContext): Promise<VerificationResult>
8
+ cvVerifySchema(args: ValidateSchemaArgs): Promise<VerificationResult>
9
+ cvVerifyMdoc(args: VerifyMdocCredentialArgs, context: RequiredContext): Promise<VerificationResult>
10
+ cvVerifySDJWTCredential(args: VerifySDJWTCredentialArgs, context: RequiredContext): Promise<VerificationResult>
11
+ cvVerifyW3CCredential(args: VerifyW3CCredentialArgs, context: RequiredContext): Promise<VerificationResult>
12
+ }
13
+
14
+ export enum SchemaValidation {
15
+ ALWAYS = 'ALWAYS',
16
+ NEVER = 'NEVER',
17
+ WHEN_PRESENT = 'WHEN_PRESENT',
18
+ }
19
+
20
+ export type VerifyCredentialArgs = {
21
+ credential: OriginalVerifiableCredential
22
+ hasher?: Hasher
23
+ fetchRemoteContexts: boolean
24
+ policies?: VerificationPolicies
25
+ }
26
+
27
+ export type VerificationPolicies = {
28
+ schemaValidation?: SchemaValidation
29
+ credentialStatus?: boolean
30
+ expirationDate: boolean
31
+ issuanceDate: boolean
32
+ }
33
+
34
+ export type ValidateSchemaArgs = {
35
+ credential: OriginalVerifiableCredential
36
+ validationPolicy?: SchemaValidation
37
+ hasher?: Hasher
38
+ }
39
+
40
+ export type VerifyMdocCredentialArgs = { credential: string }
41
+
42
+ export type VerifySDJWTCredentialArgs = {
43
+ credential: string
44
+ hasher?: Hasher
45
+ }
46
+
47
+ export type VerifyW3CCredentialArgs = {}
48
+
49
+ export type VerificationResult = {
50
+ result: boolean
51
+ source: WrappedVerifiableCredential | WrappedVerifiablePresentation
52
+ subResults: Array<VerificationSubResult>
53
+ error?: string | undefined
54
+ errorDetails?: string
55
+ }
56
+
57
+ export type VerificationSubResult = {
58
+ result: boolean
59
+ error?: string
60
+ errorDetails?: string
61
+ }
62
+
63
+ export type CredentialVerificationError = {
64
+ error?: string
65
+ errorDetails?: string
66
+ }
67
+
68
+ export type RequiredContext = IAgentContext<ImDLMdoc & ICredentialVerifier>