@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.
- package/LICENSE +201 -0
- package/README.md +34 -0
- package/dist/agent/CredentialValidation.d.ts +20 -0
- package/dist/agent/CredentialValidation.d.ts.map +1 -0
- package/dist/agent/CredentialValidation.js +227 -0
- package/dist/agent/CredentialValidation.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/ssi-sdk.credential-validation.d.ts +101 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/types/ICredentialValidation.d.ts +59 -0
- package/dist/types/ICredentialValidation.d.ts.map +1 -0
- package/dist/types/ICredentialValidation.js +10 -0
- package/dist/types/ICredentialValidation.js.map +1 -0
- package/package.json +58 -0
- package/plugin.schema.json +1580 -0
- package/src/agent/CredentialValidation.ts +244 -0
- package/src/index.ts +7 -0
- package/src/types/ICredentialValidation.ts +68 -0
|
@@ -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,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>
|