@sphereon/did-auth-siop-adapter 0.14.1-next.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.
Files changed (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +13 -0
  3. package/dist/DidJwtAdapter.d.ts +17 -0
  4. package/dist/DidJwtAdapter.d.ts.map +1 -0
  5. package/dist/DidJwtAdapter.js +57 -0
  6. package/dist/DidJwtAdapter.js.map +1 -0
  7. package/dist/did/DIDResolution.d.ts +18 -0
  8. package/dist/did/DIDResolution.d.ts.map +1 -0
  9. package/dist/did/DIDResolution.js +119 -0
  10. package/dist/did/DIDResolution.js.map +1 -0
  11. package/dist/did/DidJWT.d.ts +57 -0
  12. package/dist/did/DidJWT.d.ts.map +1 -0
  13. package/dist/did/DidJWT.js +247 -0
  14. package/dist/did/DidJWT.js.map +1 -0
  15. package/dist/did/LinkedDomainValidations.d.ts +3 -0
  16. package/dist/did/LinkedDomainValidations.d.ts.map +1 -0
  17. package/dist/did/LinkedDomainValidations.js +102 -0
  18. package/dist/did/LinkedDomainValidations.js.map +1 -0
  19. package/dist/did/index.d.ts +4 -0
  20. package/dist/did/index.d.ts.map +1 -0
  21. package/dist/did/index.js +20 -0
  22. package/dist/did/index.js.map +1 -0
  23. package/dist/helpers.d.ts +5 -0
  24. package/dist/helpers.d.ts.map +1 -0
  25. package/dist/helpers.js +10 -0
  26. package/dist/helpers.js.map +1 -0
  27. package/dist/index.d.ts +5 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +21 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/types/SIOP.types.d.ts +63 -0
  32. package/dist/types/SIOP.types.d.ts.map +1 -0
  33. package/dist/types/SIOP.types.js +15 -0
  34. package/dist/types/SIOP.types.js.map +1 -0
  35. package/dist/types/SSI.types.d.ts +15 -0
  36. package/dist/types/SSI.types.d.ts.map +1 -0
  37. package/dist/types/SSI.types.js +3 -0
  38. package/dist/types/SSI.types.js.map +1 -0
  39. package/dist/types/index.d.ts +3 -0
  40. package/dist/types/index.d.ts.map +1 -0
  41. package/dist/types/index.js +19 -0
  42. package/dist/types/index.js.map +1 -0
  43. package/lib/DidJwtAdapter.ts +67 -0
  44. package/lib/did/DIDResolution.ts +117 -0
  45. package/lib/did/DidJWT.ts +273 -0
  46. package/lib/did/LinkedDomainValidations.ts +98 -0
  47. package/lib/did/index.ts +3 -0
  48. package/lib/helpers.ts +10 -0
  49. package/lib/index.ts +5 -0
  50. package/lib/types/SIOP.types.ts +77 -0
  51. package/lib/types/SSI.types.ts +16 -0
  52. package/lib/types/index.ts +2 -0
  53. package/package.json +53 -0
@@ -0,0 +1,67 @@
1
+ import {
2
+ AuthorizationRequestPayload,
3
+ IDTokenPayload,
4
+ JwtHeader,
5
+ JwtIssuerWithContext,
6
+ JwtPayload,
7
+ RequestObjectPayload,
8
+ } from '@sphereon/did-auth-siop'
9
+ import { JwtVerifier } from '@sphereon/did-auth-siop/dist/types/JwtVerifier'
10
+ import { Resolvable } from 'did-resolver'
11
+
12
+ import { getAudience, getSubDidFromPayload, signIDTokenPayload, signRequestObjectPayload, validateLinkedDomainWithDid, verifyDidJWT } from './did'
13
+ import { CheckLinkedDomain, ExternalSignature, ExternalVerification, InternalSignature, InternalVerification, SuppliedSignature } from './types'
14
+
15
+ export const verfiyDidJwtAdapter = async (
16
+ jwtVerifier: JwtVerifier,
17
+ jwt: { header: JwtHeader; payload: JwtPayload; raw: string },
18
+ options: {
19
+ verification: InternalVerification | ExternalVerification
20
+ resolver: Resolvable
21
+ },
22
+ ): Promise<boolean> => {
23
+ if (jwtVerifier.method === 'did') {
24
+ const audience = options?.verification?.resolveOpts?.jwtVerifyOpts?.audience ?? getAudience(jwt.raw)
25
+
26
+ await verifyDidJWT(jwt.raw, options.resolver, { ...options.verification?.resolveOpts?.jwtVerifyOpts, audience })
27
+
28
+ if (jwtVerifier.type === 'request-object' && (jwt.payload as JwtPayload & { client_id?: string }).client_id?.startsWith('did:')) {
29
+ const authorizationRequestPayload = jwt.payload as AuthorizationRequestPayload
30
+ if (options.verification?.checkLinkedDomain && options.verification.checkLinkedDomain != CheckLinkedDomain.NEVER) {
31
+ await validateLinkedDomainWithDid(authorizationRequestPayload.client_id, options.verification)
32
+ } else if (!options.verification?.checkLinkedDomain && options.verification.wellknownDIDVerifyCallback) {
33
+ await validateLinkedDomainWithDid(authorizationRequestPayload.client_id, options.verification)
34
+ }
35
+ }
36
+
37
+ if (jwtVerifier.type === 'id-token') {
38
+ const issuerDid = getSubDidFromPayload(jwt.payload)
39
+ if (options.verification?.checkLinkedDomain && options.verification.checkLinkedDomain != CheckLinkedDomain.NEVER) {
40
+ await validateLinkedDomainWithDid(issuerDid, options.verification)
41
+ } else if (!options.verification?.checkLinkedDomain && options.verification.wellknownDIDVerifyCallback) {
42
+ await validateLinkedDomainWithDid(issuerDid, options.verification)
43
+ }
44
+ }
45
+
46
+ return true
47
+ }
48
+
49
+ throw new Error('Invalid use of the did-auth-siop create jwt adapter')
50
+ }
51
+
52
+ export const createDidJwtAdapter = async (
53
+ signature: InternalSignature | ExternalSignature | SuppliedSignature,
54
+ jwtIssuer: JwtIssuerWithContext,
55
+ jwt: { header: JwtHeader; payload: JwtPayload },
56
+ ): Promise<string> => {
57
+ if (jwtIssuer.method === 'did') {
58
+ const issuer = jwtIssuer.didUrl.split('#')[0]
59
+ jwt.payload.issuer = issuer
60
+ if (jwtIssuer.type === 'request-object') {
61
+ return await signRequestObjectPayload(jwt.payload as RequestObjectPayload, signature)
62
+ } else if (jwtIssuer.type === 'id-token') {
63
+ return await signIDTokenPayload(jwt.payload as IDTokenPayload, signature)
64
+ }
65
+ }
66
+ throw new Error('Invalid use of the did-auth-siop create jwt adapter')
67
+ }
@@ -0,0 +1,117 @@
1
+ import { SubjectIdentifierType, SubjectSyntaxTypesSupportedValues } from '@sphereon/did-auth-siop'
2
+ import { getUniResolver, UniResolver } from '@sphereon/did-uni-client'
3
+ import { DIDResolutionOptions, DIDResolutionResult, ParsedDID, Resolvable, Resolver } from 'did-resolver'
4
+
5
+ import { DIDDocument, ResolveOpts } from '../types'
6
+
7
+ import { getMethodFromDid, toSIOPRegistrationDidMethod } from './index'
8
+
9
+ export function getResolver(opts: ResolveOpts): Resolvable {
10
+ if (opts && typeof opts.resolver === 'object') {
11
+ return opts.resolver
12
+ }
13
+ if (!opts || !opts.subjectSyntaxTypesSupported) {
14
+ if (opts?.noUniversalResolverFallback) {
15
+ throw Error(`No subject syntax types nor did methods configured for DID resolution, but fallback to universal resolver has been disabled`)
16
+ }
17
+ console.log(
18
+ `Falling back to universal resolver as no resolve opts have been provided, or no subject syntax types supported are provided. It is wise to fix this`,
19
+ )
20
+ return new UniResolver()
21
+ }
22
+
23
+ const uniResolvers: {
24
+ [p: string]: (did: string, _parsed: ParsedDID, _didResolver: Resolver, _options: DIDResolutionOptions) => Promise<DIDResolutionResult>
25
+ }[] = []
26
+ if (opts.subjectSyntaxTypesSupported.indexOf(SubjectIdentifierType.DID) === -1) {
27
+ const specificDidMethods = opts.subjectSyntaxTypesSupported.filter((sst) => sst.includes('did:'))
28
+ if (!specificDidMethods.length) {
29
+ throw new Error('No did method found.')
30
+ }
31
+ for (const didMethod of specificDidMethods) {
32
+ const uniResolver = getUniResolver(getMethodFromDid(didMethod), { resolveUrl: opts.resolveUrl })
33
+ uniResolvers.push(uniResolver)
34
+ }
35
+ return new Resolver(...uniResolvers)
36
+ } else {
37
+ if (opts?.noUniversalResolverFallback) {
38
+ throw Error(`No subject syntax types nor did methods configured for DID resolution, but fallback to universal resolver has been disabled`)
39
+ }
40
+ console.log(
41
+ `Falling back to universal resolver as no resolve opts have been provided, or no subject syntax types supported are provided. It is wise to fix this`,
42
+ )
43
+ return new UniResolver()
44
+ }
45
+ }
46
+
47
+ /**
48
+ * This method returns a resolver object in OP/RP
49
+ * If the user of this library, configures OP/RP to have a customResolver, we will use that
50
+ * If the user of this library configures OP/RP to use a custom resolver for any specific did method, we will use that
51
+ * and in the end for the rest of the did methods, configured either with calling `addDidMethod` upon building OP/RP
52
+ * (without any resolver configuration) or declaring in the subject_syntax_types_supported of the registration object
53
+ * we will use universal resolver from Sphereon's DID Universal Resolver library
54
+ * @param customResolver
55
+ * @param subjectSyntaxTypesSupported
56
+ * @param resolverMap
57
+ */
58
+ export function getResolverUnion(
59
+ customResolver: Resolvable,
60
+ subjectSyntaxTypesSupported: string[] | string,
61
+ resolverMap: Map<string, Resolvable>,
62
+ ): Resolvable {
63
+ if (customResolver) {
64
+ return customResolver
65
+ }
66
+ const fallbackResolver: Resolvable = customResolver ? customResolver : new UniResolver()
67
+ const uniResolvers: {
68
+ [p: string]: (did: string, _parsed: ParsedDID, _didResolver: Resolver, _options: DIDResolutionOptions) => Promise<DIDResolutionResult>
69
+ }[] = []
70
+ const subjectTypes: string[] = []
71
+ if (subjectSyntaxTypesSupported) {
72
+ typeof subjectSyntaxTypesSupported === 'string'
73
+ ? subjectTypes.push(subjectSyntaxTypesSupported)
74
+ : subjectTypes.push(...subjectSyntaxTypesSupported)
75
+ }
76
+ if (subjectTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1) {
77
+ return customResolver ? customResolver : new UniResolver()
78
+ }
79
+ const specificDidMethods = subjectTypes.filter((sst) => !!sst && sst.startsWith('did:'))
80
+ specificDidMethods.forEach((dm) => {
81
+ let methodResolver
82
+ if (!resolverMap.has(dm) || resolverMap.get(dm) === null) {
83
+ methodResolver = getUniResolver(getMethodFromDid(dm))
84
+ } else {
85
+ methodResolver = resolverMap.get(dm)
86
+ }
87
+ uniResolvers.push(methodResolver)
88
+ })
89
+ return subjectTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1
90
+ ? new Resolver(...{ fallbackResolver, ...uniResolvers })
91
+ : new Resolver(...uniResolvers)
92
+ }
93
+
94
+ export function mergeAllDidMethods(subjectSyntaxTypesSupported: string | string[], resolvers: Map<string, Resolvable>): string[] {
95
+ if (!Array.isArray(subjectSyntaxTypesSupported)) {
96
+ subjectSyntaxTypesSupported = [subjectSyntaxTypesSupported]
97
+ }
98
+ const unionSubjectSyntaxTypes = new Set()
99
+ subjectSyntaxTypesSupported.forEach((sst) => unionSubjectSyntaxTypes.add(sst))
100
+ resolvers.forEach((_, didMethod) => unionSubjectSyntaxTypes.add(toSIOPRegistrationDidMethod(didMethod)))
101
+ return Array.from(unionSubjectSyntaxTypes) as string[]
102
+ }
103
+
104
+ export async function resolveDidDocument(did: string, opts?: ResolveOpts): Promise<DIDDocument> {
105
+ // todo: The accept is only there because did:key used by Veramo requires it. According to the spec it is optional. It should not hurt, but let's test
106
+ const result = await getResolver({ ...opts }).resolve(did, { accept: 'application/did+ld+json' })
107
+ if (result?.didResolutionMetadata?.error) {
108
+ throw Error(result.didResolutionMetadata.error)
109
+ }
110
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
111
+ // @ts-ignore
112
+ if (!result.didDocument && result.id) {
113
+ // todo: This looks like a bug. It seems that sometimes we get back a DID document directly instead of a did resolution results
114
+ return result as unknown as DIDDocument
115
+ }
116
+ return result.didDocument
117
+ }
@@ -0,0 +1,273 @@
1
+ import { post } from '@sphereon/did-auth-siop'
2
+ import {
3
+ DEFAULT_EXPIRATION_TIME,
4
+ IDTokenPayload,
5
+ RequestObjectPayload,
6
+ ResponseIss,
7
+ SignatureResponse,
8
+ SigningAlgo,
9
+ SIOPErrors,
10
+ SIOPResonse,
11
+ VerifiedJWT,
12
+ } from '@sphereon/did-auth-siop'
13
+ import {
14
+ createJWT,
15
+ decodeJWT,
16
+ EdDSASigner,
17
+ ES256KSigner,
18
+ ES256Signer,
19
+ hexToBytes,
20
+ JWTHeader,
21
+ JWTOptions,
22
+ JWTPayload,
23
+ JWTVerifyOptions,
24
+ Signer,
25
+ verifyJWT,
26
+ } from 'did-jwt'
27
+ import { Resolvable } from 'did-resolver'
28
+
29
+ import { isExternalSignature, isInternalSignature, isSuppliedSignature } from '../helpers'
30
+ import { ExternalSignature, InternalSignature, SuppliedSignature } from '../types'
31
+
32
+ /**
33
+ * Verifies given JWT. If the JWT is valid, the promise returns an object including the JWT, the payload of the JWT,
34
+ * and the did doc of the issuer of the JWT.
35
+ *
36
+ * @example
37
+ * verifyDidJWT('did:key:example', resolver, {audience: '5A8bRWU3F7j3REx3vkJ...', callbackUrl: 'https://...'}).then(obj => {
38
+ * const did = obj.did // DIDres of signer
39
+ * const payload = obj.payload
40
+ * const doc = obj.doc // DIDres Document of signer
41
+ * const JWT = obj.JWT // JWT
42
+ * const signerKeyId = obj.signerKeyId // ID of key in DIDres document that signed JWT
43
+ * ...
44
+ * })
45
+ *
46
+ * @param {String} jwt a JSON Web Token to verify
47
+ * @param {Resolvable} resolver
48
+ * @param {JWTVerifyOptions} [options] Options
49
+ * @param {String} options.audience DID of the recipient of the JWT
50
+ * @param {String} options.callbackUrl callback url in JWT
51
+ * @return {Promise<Object, Error>} a promise which resolves with a response object or rejects with an error
52
+ */
53
+ export async function verifyDidJWT(jwt: string, resolver: Resolvable, options: JWTVerifyOptions): Promise<VerifiedJWT> {
54
+ return verifyJWT(jwt, { ...options, resolver })
55
+ }
56
+
57
+ /**
58
+ * Creates a signed JWT given an address which becomes the issuer, a signer function, and a payload for which the withSignature is over.
59
+ *
60
+ * @example
61
+ * const signer = ES256KSigner(process.env.PRIVATE_KEY)
62
+ * createJWT({address: '5A8bRWU3F7j3REx3vkJ...', signer}, {key1: 'value', key2: ..., ... }).then(JWT => {
63
+ * ...
64
+ * })
65
+ *
66
+ * @param {Object} payload payload object
67
+ * @param {Object} [options] an unsigned credential object
68
+ * @param {String} options.issuer The DID of the issuer (signer) of JWT
69
+ * @param {Signer} options.signer a `Signer` function, Please see `ES256KSigner` or `EdDSASigner`
70
+ * @param {boolean} options.canonicalize optional flag to canonicalize header and payload before signing
71
+ * @param {Object} header optional object to specify or customize the JWT header
72
+ * @return {Promise<Object, Error>} a promise which resolves with a signed JSON Web Token or rejects with an error
73
+ */
74
+ export async function createDidJWT(
75
+ payload: Partial<JWTPayload>,
76
+ { issuer, signer, expiresIn, canonicalize }: JWTOptions,
77
+ header: Partial<JWTHeader>,
78
+ ): Promise<string> {
79
+ return createJWT(payload, { issuer, signer, expiresIn, canonicalize }, header)
80
+ }
81
+
82
+ export async function signIDTokenPayload(payload: IDTokenPayload, signature: InternalSignature | ExternalSignature | SuppliedSignature) {
83
+ if (isInternalSignature(signature)) {
84
+ return signDidJwtInternal(payload, payload.issuer, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner)
85
+ } else if (isExternalSignature(signature)) {
86
+ return signDidJwtExternal(payload, signature.signatureUri, signature.authZToken, signature.alg, signature.kid)
87
+ } else if (isSuppliedSignature(signature)) {
88
+ return signDidJwtSupplied(payload, payload.issuer, signature.signature, signature.alg, signature.kid)
89
+ } else {
90
+ throw new Error(
91
+ 'Signature parameters should be internal signature with hexPrivateKey, did, and an optional kid, or external signature parameters with signatureUri, did, and optionals parameters authZToken, hexPublicKey, and kid',
92
+ )
93
+ }
94
+ }
95
+
96
+ export async function signRequestObjectPayload(payload: RequestObjectPayload, signature: InternalSignature | ExternalSignature | SuppliedSignature) {
97
+ let issuer = payload.iss
98
+ if (!issuer) {
99
+ issuer = signature.did
100
+ }
101
+ if (!issuer) {
102
+ throw Error('No issuer supplied to sign the JWT')
103
+ }
104
+ if (!payload.iss) {
105
+ payload.iss = issuer
106
+ }
107
+ if (!payload.sub) {
108
+ payload.sub = signature.did
109
+ }
110
+ if (isInternalSignature(signature)) {
111
+ return signDidJwtInternal(payload, issuer, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner)
112
+ } else if (isExternalSignature(signature)) {
113
+ return signDidJwtExternal(payload, signature.signatureUri, signature.authZToken, signature.alg, signature.kid)
114
+ } else if (isSuppliedSignature(signature)) {
115
+ return signDidJwtSupplied(payload, issuer, signature.signature, signature.alg, signature.kid)
116
+ } else {
117
+ throw new Error(
118
+ 'Signature parameters should be internal signature with hexPrivateKey, did, and an optional kid, or external signature parameters with signatureUri, did, and optionals parameters authZToken, hexPublicKey, and kid',
119
+ )
120
+ }
121
+ }
122
+
123
+ export async function signDidJwtInternal(
124
+ payload: IDTokenPayload | RequestObjectPayload,
125
+ issuer: string,
126
+ hexPrivateKey: string,
127
+ alg: SigningAlgo,
128
+ kid: string,
129
+ customJwtSigner?: Signer,
130
+ ): Promise<string> {
131
+ const signer = determineSigner(alg, hexPrivateKey, customJwtSigner)
132
+ const header = {
133
+ alg,
134
+ kid,
135
+ }
136
+ const options = {
137
+ issuer,
138
+ signer,
139
+ expiresIn: DEFAULT_EXPIRATION_TIME,
140
+ }
141
+
142
+ return await createDidJWT({ ...payload }, options, header)
143
+ }
144
+
145
+ async function signDidJwtExternal(
146
+ payload: IDTokenPayload | RequestObjectPayload,
147
+ signatureUri: string,
148
+ authZToken: string,
149
+ alg: SigningAlgo,
150
+ kid?: string,
151
+ ): Promise<string> {
152
+ const body = {
153
+ issuer: payload.iss && payload.iss.includes('did:') ? payload.iss : payload.sub,
154
+ payload,
155
+ expiresIn: DEFAULT_EXPIRATION_TIME,
156
+ alg,
157
+ selfIssued: payload.iss.includes(ResponseIss.SELF_ISSUED_V2) ? payload.iss : undefined,
158
+ kid,
159
+ }
160
+
161
+ const response: SIOPResonse<SignatureResponse> = await post(signatureUri, JSON.stringify(body), { bearerToken: authZToken })
162
+ return response.successBody.jws
163
+ }
164
+
165
+ async function signDidJwtSupplied(
166
+ payload: IDTokenPayload | RequestObjectPayload,
167
+ issuer: string,
168
+ signer: Signer,
169
+ alg: SigningAlgo,
170
+ kid: string,
171
+ ): Promise<string> {
172
+ const header = {
173
+ alg,
174
+ kid,
175
+ }
176
+ const options = {
177
+ issuer,
178
+ signer,
179
+ expiresIn: DEFAULT_EXPIRATION_TIME,
180
+ }
181
+
182
+ return await createDidJWT({ ...payload }, options, header)
183
+ }
184
+
185
+ const determineSigner = (alg: SigningAlgo, hexPrivateKey?: string, customSigner?: Signer): Signer => {
186
+ if (customSigner) {
187
+ return customSigner
188
+ } else if (!hexPrivateKey) {
189
+ throw new Error('no private key provided')
190
+ }
191
+ const privateKey = hexToBytes(hexPrivateKey.replace('0x', ''))
192
+ switch (alg) {
193
+ case SigningAlgo.EDDSA:
194
+ return EdDSASigner(privateKey)
195
+ case SigningAlgo.ES256:
196
+ return ES256Signer(privateKey)
197
+ case SigningAlgo.ES256K:
198
+ return ES256KSigner(privateKey)
199
+ case SigningAlgo.PS256:
200
+ throw Error('PS256 is not supported yet. Please provide a custom signer')
201
+ case SigningAlgo.RS256:
202
+ throw Error('RS256 is not supported yet. Please provide a custom signer')
203
+ }
204
+ }
205
+
206
+ export function getAudience(jwt: string) {
207
+ const { payload } = decodeJWT(jwt)
208
+ if (!payload) {
209
+ throw new Error(SIOPErrors.NO_AUDIENCE)
210
+ } else if (!payload.aud) {
211
+ return undefined
212
+ } else if (Array.isArray(payload.aud)) {
213
+ throw new Error(SIOPErrors.INVALID_AUDIENCE)
214
+ }
215
+
216
+ return payload.aud
217
+ }
218
+
219
+ //TODO To enable automatic registration, it cannot be a did, but HTTPS URL
220
+ function assertIssSelfIssuedOrDid(payload: JWTPayload) {
221
+ if (!payload.sub || !payload.sub.startsWith('did:') || !payload.iss || !isIssSelfIssued(payload)) {
222
+ throw new Error('Token does not have a iss DID')
223
+ }
224
+ }
225
+
226
+ export function getSubDidFromPayload(payload: JWTPayload, header?: JWTHeader): string {
227
+ assertIssSelfIssuedOrDid(payload)
228
+
229
+ if (isIssSelfIssued(payload)) {
230
+ let did
231
+ if (payload.sub && payload.sub.startsWith('did:')) {
232
+ did = payload.sub
233
+ }
234
+ if (!did && header && header.kid && header.kid.startsWith('did:')) {
235
+ did = header.kid.split('#')[0]
236
+ }
237
+ if (did) {
238
+ return did
239
+ }
240
+ }
241
+ return payload.sub
242
+ }
243
+
244
+ export function isIssSelfIssued(payload: JWTPayload): boolean {
245
+ return payload.iss.includes(ResponseIss.SELF_ISSUED_V1) || payload.iss.includes(ResponseIss.SELF_ISSUED_V2) || payload.iss === payload.sub
246
+ }
247
+
248
+ export function getMethodFromDid(did: string): string {
249
+ if (!did) {
250
+ throw new Error(SIOPErrors.BAD_PARAMS)
251
+ }
252
+ const split = did.split(':')
253
+ if (split.length == 1 && did.length > 0) {
254
+ return did
255
+ } else if (!did.startsWith('did:') || split.length < 2) {
256
+ throw new Error(SIOPErrors.BAD_PARAMS)
257
+ }
258
+
259
+ return split[1]
260
+ }
261
+
262
+ /**
263
+ * Since the OIDC SIOP spec incorrectly uses 'did:<method>:' and calls that a method, we have to fix it
264
+ * @param didOrMethod
265
+ */
266
+ export function toSIOPRegistrationDidMethod(didOrMethod: string) {
267
+ let prefix = didOrMethod
268
+ if (!didOrMethod.startsWith('did:')) {
269
+ prefix = 'did:' + didOrMethod
270
+ }
271
+ const split = prefix.split(':')
272
+ return `${split[0]}:${split[1]}`
273
+ }
@@ -0,0 +1,98 @@
1
+ import { IDomainLinkageValidation, ValidationStatusEnum, VerifyCallback, WDCErrors, WellKnownDidVerifier } from '@sphereon/wellknown-dids-client'
2
+
3
+ import { DIDDocument } from '../types/SSI.types'
4
+
5
+ import { CheckLinkedDomain, ExternalVerification, InternalVerification } from './../types/'
6
+ import { resolveDidDocument } from './DIDResolution'
7
+ import { getMethodFromDid, toSIOPRegistrationDidMethod } from './DidJWT'
8
+
9
+ function getValidationErrorMessages(validationResult: IDomainLinkageValidation): string[] {
10
+ const messages = []
11
+ if (validationResult.message) {
12
+ messages.push(validationResult.message)
13
+ }
14
+ if (validationResult?.endpointDescriptors.length) {
15
+ for (const endpointDescriptor of validationResult.endpointDescriptors) {
16
+ if (endpointDescriptor.message) {
17
+ messages.push(endpointDescriptor.message)
18
+ }
19
+ if (endpointDescriptor.resources) {
20
+ for (const resource of endpointDescriptor.resources) {
21
+ if (resource.message) {
22
+ messages.push(resource.message)
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ return messages
29
+ }
30
+
31
+ /**
32
+ * @param validationErrorMessages
33
+ * @return returns false if the messages received from wellknown-dids-client makes this invalid for CheckLinkedDomain.IF_PRESENT plus the message itself
34
+ * and true for when we can move on
35
+ */
36
+ function checkInvalidMessages(validationErrorMessages: string[]): { status: boolean; message?: string } {
37
+ if (!validationErrorMessages || !validationErrorMessages.length) {
38
+ return { status: false, message: 'linked domain is invalid.' }
39
+ }
40
+ const validMessages: string[] = [
41
+ WDCErrors.PROPERTY_LINKED_DIDS_DOES_NOT_CONTAIN_ANY_DOMAIN_LINK_CREDENTIALS.valueOf(),
42
+ WDCErrors.PROPERTY_LINKED_DIDS_NOT_PRESENT.valueOf(),
43
+ WDCErrors.PROPERTY_TYPE_NOT_CONTAIN_VALID_LINKED_DOMAIN.valueOf(),
44
+ WDCErrors.PROPERTY_SERVICE_NOT_PRESENT.valueOf(),
45
+ ]
46
+ for (const validationErrorMessage of validationErrorMessages) {
47
+ if (!validMessages.filter((vm) => validationErrorMessage.includes(vm)).pop()) {
48
+ return { status: false, message: validationErrorMessage }
49
+ }
50
+ }
51
+ return { status: true }
52
+ }
53
+
54
+ export async function validateLinkedDomainWithDid(did: string, verification: InternalVerification | ExternalVerification) {
55
+ const { checkLinkedDomain, resolveOpts, wellknownDIDVerifyCallback } = verification
56
+ if (checkLinkedDomain === CheckLinkedDomain.NEVER) {
57
+ return
58
+ }
59
+ const didDocument = await resolveDidDocument(did, {
60
+ ...resolveOpts,
61
+ subjectSyntaxTypesSupported: [toSIOPRegistrationDidMethod(getMethodFromDid(did))],
62
+ })
63
+ if (!didDocument) {
64
+ throw Error(`Could not resolve DID: ${did}`)
65
+ }
66
+ if ((!didDocument.service || !didDocument.service.find((s) => s.type === 'LinkedDomains')) && checkLinkedDomain === CheckLinkedDomain.IF_PRESENT) {
67
+ // No linked domains in DID document and it was optional. Let's cut it short here.
68
+ return
69
+ }
70
+ try {
71
+ const validationResult = await checkWellKnownDid({ didDocument, verifyCallback: wellknownDIDVerifyCallback })
72
+ if (validationResult.status === ValidationStatusEnum.INVALID) {
73
+ const validationErrorMessages = getValidationErrorMessages(validationResult)
74
+ const messageCondition: { status: boolean; message?: string } = checkInvalidMessages(validationErrorMessages)
75
+ if (checkLinkedDomain === CheckLinkedDomain.ALWAYS || (checkLinkedDomain === CheckLinkedDomain.IF_PRESENT && !messageCondition.status)) {
76
+ throw new Error(messageCondition.message ? messageCondition.message : validationErrorMessages[0])
77
+ }
78
+ }
79
+ } catch (err) {
80
+ const messageCondition: { status: boolean; message?: string } = checkInvalidMessages([err.message])
81
+ if (checkLinkedDomain === CheckLinkedDomain.ALWAYS || (checkLinkedDomain === CheckLinkedDomain.IF_PRESENT && !messageCondition.status)) {
82
+ throw new Error(err.message)
83
+ }
84
+ }
85
+ }
86
+
87
+ interface CheckWellKnownDidArgs {
88
+ didDocument: DIDDocument
89
+ verifyCallback: VerifyCallback
90
+ }
91
+
92
+ async function checkWellKnownDid(args: CheckWellKnownDidArgs): Promise<IDomainLinkageValidation> {
93
+ const verifier = new WellKnownDidVerifier({
94
+ verifySignatureCallback: args.verifyCallback,
95
+ onlyVerifyServiceDid: false,
96
+ })
97
+ return await verifier.verifyDomainLinkage({ didDocument: args.didDocument })
98
+ }
@@ -0,0 +1,3 @@
1
+ export * from './DidJWT'
2
+ export * from './DIDResolution'
3
+ export * from './LinkedDomainValidations'
package/lib/helpers.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { ExternalSignature, InternalSignature, NoSignature, SuppliedSignature } from './types/SIOP.types'
2
+
3
+ export const isInternalSignature = (object: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature): object is InternalSignature =>
4
+ 'hexPrivateKey' in object && 'did' in object
5
+
6
+ export const isExternalSignature = (object: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature): object is ExternalSignature =>
7
+ 'signatureUri' in object && 'did' in object
8
+
9
+ export const isSuppliedSignature = (object: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature): object is SuppliedSignature =>
10
+ 'signature' in object
package/lib/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './did'
2
+
3
+ export * from './types'
4
+ export * from './DidJwtAdapter'
5
+ export * from './helpers'
@@ -0,0 +1,77 @@
1
+ import { SigningAlgo } from '@sphereon/did-auth-siop'
2
+ import { VerifyCallback as WellknownDIDVerifyCallback } from '@sphereon/wellknown-dids-client'
3
+ import { JWTVerifyOptions } from 'did-jwt'
4
+ import { Resolvable } from 'did-resolver'
5
+
6
+ export enum CheckLinkedDomain {
7
+ NEVER = 'never', // We don't want to verify Linked domains
8
+ IF_PRESENT = 'if_present', // If present, did-auth-siop will check the linked domain, if exist and not valid, throws an exception
9
+ ALWAYS = 'always', // We'll always check the linked domains, if not exist or not valid, throws an exception
10
+ }
11
+
12
+ export interface InternalSignature {
13
+ hexPrivateKey: string // hex private key Only secp256k1 format
14
+ did: string
15
+
16
+ alg: SigningAlgo
17
+ kid?: string // Optional: key identifier
18
+
19
+ customJwtSigner?: Signer
20
+ }
21
+
22
+ export interface SuppliedSignature {
23
+ signature: (data: string | Uint8Array) => Promise<EcdsaSignature | string>
24
+
25
+ alg: SigningAlgo
26
+ did: string
27
+ kid: string
28
+ }
29
+
30
+ export interface NoSignature {
31
+ hexPublicKey: string // hex public key
32
+ did: string
33
+ kid?: string // Optional: key identifier
34
+ }
35
+
36
+ export interface ExternalSignature {
37
+ signatureUri: string // url to call to generate a withSignature
38
+ did: string
39
+ authZToken?: string // Optional: bearer token to use to the call
40
+ hexPublicKey?: string // Optional: hex encoded public key to compute JWK key, if not possible from DIDres Document
41
+
42
+ alg: SigningAlgo
43
+ kid?: string // Optional: key identifier. default did#keys-1
44
+ }
45
+
46
+ export enum VerificationMode {
47
+ INTERNAL,
48
+ EXTERNAL,
49
+ }
50
+
51
+ export interface EcdsaSignature {
52
+ r: string
53
+ s: string
54
+ recoveryParam?: number | null
55
+ }
56
+ export type Signer = (data: string | Uint8Array) => Promise<EcdsaSignature | string>
57
+
58
+ export interface Verification {
59
+ checkLinkedDomain?: CheckLinkedDomain
60
+ wellknownDIDVerifyCallback?: WellknownDIDVerifyCallback
61
+ resolveOpts: ResolveOpts
62
+ }
63
+
64
+ export type InternalVerification = Verification
65
+
66
+ export interface ExternalVerification extends Verification {
67
+ verifyUri: string // url to call to verify the id_token withSignature
68
+ authZToken?: string // Optional: bearer token to use to the call
69
+ }
70
+
71
+ export interface ResolveOpts {
72
+ jwtVerifyOpts?: JWTVerifyOptions
73
+ resolver?: Resolvable
74
+ resolveUrl?: string
75
+ noUniversalResolverFallback?: boolean
76
+ subjectSyntaxTypesSupported?: string[]
77
+ }
@@ -0,0 +1,16 @@
1
+ import { DIDDocument as DIFDIDDocument } from 'did-resolver'
2
+
3
+ export interface LinkedDataProof {
4
+ type: string
5
+ created: string
6
+ creator: string
7
+ nonce: string
8
+ signatureValue: string
9
+ }
10
+
11
+ export interface DIDDocument extends DIFDIDDocument {
12
+ owner?: string
13
+ created?: string
14
+ updated?: string
15
+ proof?: LinkedDataProof
16
+ }
@@ -0,0 +1,2 @@
1
+ export * from './SIOP.types'
2
+ export * from './SSI.types'