@sphereon/ssi-sdk-ext.jwt-service 0.24.1-unstable.92 → 0.25.0
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/README.md +73 -374
- package/dist/agent/JwtService.d.ts +3 -0
- package/dist/agent/JwtService.d.ts.map +1 -1
- package/dist/agent/JwtService.js +78 -1
- package/dist/agent/JwtService.js.map +1 -1
- package/dist/functions/JWE.d.ts +75 -0
- package/dist/functions/JWE.d.ts.map +1 -0
- package/dist/functions/JWE.js +280 -0
- package/dist/functions/JWE.js.map +1 -0
- package/dist/functions/index.d.ts +19 -5
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +155 -19
- package/dist/functions/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types/IJwtService.d.ts +149 -20
- package/dist/types/IJwtService.d.ts.map +1 -1
- package/dist/types/IJwtService.js +54 -1
- package/dist/types/IJwtService.js.map +1 -1
- package/package.json +15 -13
- package/plugin.schema.json +4212 -282
- package/src/agent/JwtService.ts +103 -39
- package/src/functions/JWE.ts +360 -0
- package/src/functions/index.ts +184 -26
- package/src/index.ts +4 -0
- package/src/types/IJwtService.ts +272 -55
package/src/functions/index.ts
CHANGED
|
@@ -1,45 +1,70 @@
|
|
|
1
|
+
import { jwkTtoPublicKeyHex } from '@sphereon/ssi-sdk-ext.did-utils'
|
|
1
2
|
import {
|
|
3
|
+
ensureManagedIdentifierResult,
|
|
4
|
+
ExternalIdentifierDidOpts,
|
|
5
|
+
ExternalIdentifierX5cOpts,
|
|
6
|
+
IIdentifierResolution,
|
|
2
7
|
isManagedIdentifierDidResult,
|
|
3
8
|
isManagedIdentifierX5cResult,
|
|
4
9
|
ManagedIdentifierMethod,
|
|
5
10
|
ManagedIdentifierResult,
|
|
6
|
-
|
|
11
|
+
resolveExternalJwkIdentifier,
|
|
7
12
|
} from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
8
|
-
import {
|
|
13
|
+
import { keyTypeFromCryptographicSuite, verifySignatureWithSubtle } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
14
|
+
import { contextHasPlugin } from '@sphereon/ssi-sdk.agent-config'
|
|
15
|
+
import { JWK } from '@sphereon/ssi-types'
|
|
16
|
+
import { IAgentContext } from '@veramo/core'
|
|
17
|
+
import { bytesToBase64url, decodeJoseBlob, encodeJoseBlob } from '@veramo/utils'
|
|
18
|
+
import { base64ToBytes } from '@veramo/utils'
|
|
9
19
|
import * as u8a from 'uint8arrays'
|
|
10
20
|
import {
|
|
11
21
|
CreateJwsCompactArgs,
|
|
12
22
|
CreateJwsFlattenedArgs,
|
|
13
23
|
CreateJwsJsonArgs,
|
|
14
|
-
|
|
24
|
+
IJwsValidationResult,
|
|
15
25
|
IRequiredContext,
|
|
26
|
+
isJwsCompact,
|
|
27
|
+
isJwsJsonFlattened,
|
|
28
|
+
isJwsJsonGeneral,
|
|
29
|
+
Jws,
|
|
16
30
|
JwsCompact,
|
|
31
|
+
JwsIdentifierMode,
|
|
17
32
|
JwsJsonFlattened,
|
|
18
33
|
JwsJsonGeneral,
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
JwsJsonGeneralWithIdentifiers,
|
|
35
|
+
JwsJsonSignature, JwsJsonSignatureWithIdentifier,
|
|
36
|
+
JwsHeader,
|
|
37
|
+
JwsPayload,
|
|
21
38
|
PreparedJwsObject,
|
|
39
|
+
VerifyJwsArgs, JweHeader,
|
|
22
40
|
} from '../types/IJwtService'
|
|
23
41
|
|
|
42
|
+
const payloadToBytes = (payload: string | JwsPayload | Uint8Array): Uint8Array => {
|
|
43
|
+
const isBytes = payload instanceof Uint8Array
|
|
44
|
+
const isString = typeof payload === 'string'
|
|
45
|
+
return isBytes ? payload : isString ? u8a.fromString(payload, 'base64url') : u8a.fromString(JSON.stringify(payload), 'utf-8')
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
export const prepareJwsObject = async (args: CreateJwsJsonArgs, context: IRequiredContext): Promise<PreparedJwsObject> => {
|
|
25
|
-
const { existingSignatures, protectedHeader, unprotectedHeader, issuer, payload, mode = 'auto' } = args
|
|
49
|
+
const { existingSignatures, protectedHeader, unprotectedHeader, issuer, payload, mode = 'auto', clientId, clientIdScheme } = args
|
|
26
50
|
|
|
27
51
|
const { noIdentifierInHeader = false } = issuer
|
|
28
|
-
const combinedHeader = { ...unprotectedHeader, ...protectedHeader }
|
|
29
|
-
if (!combinedHeader.alg) {
|
|
30
|
-
return Promise.reject(`No 'alg' key present in the JWS header`)
|
|
31
|
-
}
|
|
32
52
|
const identifier = await ensureManagedIdentifierResult(issuer, context)
|
|
33
|
-
await
|
|
34
|
-
|
|
53
|
+
await checkAndUpdateJwsHeader({ mode, identifier, noIdentifierInHeader, header: protectedHeader }, context)
|
|
35
54
|
const isBytes = payload instanceof Uint8Array
|
|
36
55
|
const isString = typeof payload === 'string'
|
|
37
56
|
if (!isBytes && !isString) {
|
|
38
57
|
if (issuer.noIssPayloadUpdate !== true && !payload.iss && identifier.issuer) {
|
|
39
58
|
payload.iss = identifier.issuer
|
|
40
59
|
}
|
|
60
|
+
if (clientIdScheme && !payload.client_id_scheme) {
|
|
61
|
+
payload.client_id_scheme = clientIdScheme
|
|
62
|
+
}
|
|
63
|
+
if (clientId && !payload.client_id) {
|
|
64
|
+
payload.client_id = clientId
|
|
65
|
+
}
|
|
41
66
|
}
|
|
42
|
-
const payloadBytes =
|
|
67
|
+
const payloadBytes = payloadToBytes(payload)
|
|
43
68
|
const base64urlHeader = encodeJoseBlob(protectedHeader)
|
|
44
69
|
const base64urlPayload = bytesToBase64url(payloadBytes)
|
|
45
70
|
|
|
@@ -113,31 +138,31 @@ export const createJwsJsonGeneral = async (args: CreateJwsJsonArgs, context: IRe
|
|
|
113
138
|
* @param context
|
|
114
139
|
*/
|
|
115
140
|
|
|
116
|
-
export const
|
|
141
|
+
export const checkAndUpdateJwsHeader = async (
|
|
117
142
|
{
|
|
118
143
|
mode = 'auto',
|
|
119
144
|
identifier,
|
|
120
145
|
header,
|
|
121
146
|
noIdentifierInHeader = false,
|
|
122
147
|
}: {
|
|
123
|
-
mode?:
|
|
148
|
+
mode?: JwsIdentifierMode
|
|
124
149
|
identifier: ManagedIdentifierResult
|
|
125
150
|
noIdentifierInHeader?: boolean
|
|
126
|
-
header:
|
|
151
|
+
header: JwsHeader
|
|
127
152
|
},
|
|
128
153
|
context: IRequiredContext
|
|
129
154
|
) => {
|
|
130
|
-
if (
|
|
155
|
+
if (isIdentifierMode(mode, identifier.method, 'did')) {
|
|
131
156
|
// kid is VM of the DID
|
|
132
157
|
// @see https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4
|
|
133
158
|
await checkAndUpdateDidHeader({ header, identifier, noIdentifierInHeader }, context)
|
|
134
|
-
} else if (
|
|
159
|
+
} else if (isIdentifierMode(mode, identifier.method, 'x5c')) {
|
|
135
160
|
// Include the x5c in the header. No kid
|
|
136
161
|
// @see https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
|
|
137
162
|
await checkAndUpdateX5cHeader({ header, identifier, noIdentifierInHeader }, context)
|
|
138
|
-
} else if (
|
|
163
|
+
} else if (isIdentifierMode(mode, identifier.method, 'kid', false)) {
|
|
139
164
|
await checkAndUpdateKidHeader({ header, identifier, noIdentifierInHeader }, context)
|
|
140
|
-
} else if (
|
|
165
|
+
} else if (isIdentifierMode(mode, identifier.method, 'jwk', false)) {
|
|
141
166
|
// Include the JWK in the header as well as its kid if present
|
|
142
167
|
// @see https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.3
|
|
143
168
|
// @see https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4
|
|
@@ -154,7 +179,7 @@ const checkAndUpdateX5cHeader = async (
|
|
|
154
179
|
identifier,
|
|
155
180
|
noIdentifierInHeader = false,
|
|
156
181
|
}: {
|
|
157
|
-
header:
|
|
182
|
+
header: JwsHeader | JweHeader
|
|
158
183
|
identifier: ManagedIdentifierResult
|
|
159
184
|
noIdentifierInHeader?: boolean
|
|
160
185
|
},
|
|
@@ -183,7 +208,7 @@ const checkAndUpdateDidHeader = async (
|
|
|
183
208
|
identifier,
|
|
184
209
|
noIdentifierInHeader = false,
|
|
185
210
|
}: {
|
|
186
|
-
header:
|
|
211
|
+
header: JwsHeader | JweHeader
|
|
187
212
|
identifier: ManagedIdentifierResult
|
|
188
213
|
noIdentifierInHeader?: boolean
|
|
189
214
|
},
|
|
@@ -212,7 +237,7 @@ const checkAndUpdateJwkHeader = async (
|
|
|
212
237
|
identifier,
|
|
213
238
|
noIdentifierInHeader = false,
|
|
214
239
|
}: {
|
|
215
|
-
header:
|
|
240
|
+
header: JwsHeader | JweHeader
|
|
216
241
|
identifier: ManagedIdentifierResult
|
|
217
242
|
noIdentifierInHeader?: boolean
|
|
218
243
|
},
|
|
@@ -221,7 +246,7 @@ const checkAndUpdateJwkHeader = async (
|
|
|
221
246
|
const { jwk } = header
|
|
222
247
|
if (jwk) {
|
|
223
248
|
// let's resolve the provided x5c to be sure
|
|
224
|
-
const jwkIdentifier = await context.agent.identifierManagedGetByJwk({ identifier: jwk })
|
|
249
|
+
const jwkIdentifier = await context.agent.identifierManagedGetByJwk({ identifier: jwk as JWK })
|
|
225
250
|
if (jwkIdentifier.kmsKeyRef !== identifier.kmsKeyRef) {
|
|
226
251
|
return Promise.reject(Error(`A jwk header was present, but its value did not match the provided signing jwk or kid!`))
|
|
227
252
|
}
|
|
@@ -240,7 +265,7 @@ const checkAndUpdateKidHeader = async (
|
|
|
240
265
|
identifier,
|
|
241
266
|
noIdentifierInHeader = false,
|
|
242
267
|
}: {
|
|
243
|
-
header:
|
|
268
|
+
header: JwsHeader | JweHeader
|
|
244
269
|
identifier: ManagedIdentifierResult
|
|
245
270
|
noIdentifierInHeader?: boolean
|
|
246
271
|
},
|
|
@@ -262,7 +287,7 @@ const checkAndUpdateKidHeader = async (
|
|
|
262
287
|
}
|
|
263
288
|
}
|
|
264
289
|
|
|
265
|
-
const
|
|
290
|
+
const isIdentifierMode = (mode: JwsIdentifierMode, identifierMethod: ManagedIdentifierMethod, checkMode: JwsIdentifierMode, loose = true) => {
|
|
266
291
|
if (loose && (checkMode === 'jwk' || checkMode === 'kid')) {
|
|
267
292
|
// we always have the kid and jwk at hand no matter the identifier method, so we are okay with that
|
|
268
293
|
// todo: check the impact on the above expressions, as this will now always return true for the both of them
|
|
@@ -277,3 +302,136 @@ const isMode = (mode: CreateJwsMode, identifierMethod: ManagedIdentifierMethod,
|
|
|
277
302
|
// we always have the kid and jwk at hand no matter the identifier method, so we are okay with that
|
|
278
303
|
return mode === 'auto' && identifierMethod === checkMode
|
|
279
304
|
}
|
|
305
|
+
|
|
306
|
+
export const verifyJws = async (args: VerifyJwsArgs, context: IAgentContext<IIdentifierResolution>): Promise<IJwsValidationResult> => {
|
|
307
|
+
const jws = await toJwsJsonGeneralWithIdentifiers(args, context)
|
|
308
|
+
|
|
309
|
+
let errorMessages: string[] = []
|
|
310
|
+
let index = 0
|
|
311
|
+
await Promise.all(
|
|
312
|
+
jws.signatures.map(async (sigWithId) => {
|
|
313
|
+
// If we have a specific KMS agent plugin that can do the verification prefer that over the generic verification
|
|
314
|
+
index++
|
|
315
|
+
let valid: boolean
|
|
316
|
+
const data = u8a.fromString(`${sigWithId.protected}.${jws.payload}`, 'utf-8')
|
|
317
|
+
const jwkInfo = sigWithId.identifier.jwks[0]
|
|
318
|
+
if (sigWithId.header?.alg === 'RSA' && contextHasPlugin(context, 'keyManagerVerify')) {
|
|
319
|
+
const publicKeyHex = jwkTtoPublicKeyHex(jwkInfo.jwk)
|
|
320
|
+
valid = await context.agent.keyManagerVerify({
|
|
321
|
+
signature: sigWithId.signature,
|
|
322
|
+
data,
|
|
323
|
+
publicKeyHex,
|
|
324
|
+
type: keyTypeFromCryptographicSuite({ suite: jwkInfo.jwk.crv ?? 'ES256' }),
|
|
325
|
+
// no kms arg, as the current key manager needs a bit more work
|
|
326
|
+
})
|
|
327
|
+
} else {
|
|
328
|
+
const signature = base64ToBytes(sigWithId.signature)
|
|
329
|
+
valid = await verifySignatureWithSubtle({ data, signature, key: jwkInfo.jwk })
|
|
330
|
+
}
|
|
331
|
+
if (!valid) {
|
|
332
|
+
errorMessages.push(`Signature ${index} was not valid`)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
sigWithId,
|
|
337
|
+
valid,
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
)
|
|
341
|
+
const error = errorMessages.length !== 0
|
|
342
|
+
return {
|
|
343
|
+
name: 'jws',
|
|
344
|
+
jws,
|
|
345
|
+
error,
|
|
346
|
+
critical: error,
|
|
347
|
+
message: error ? errorMessages.join(', ') : 'Signature validated',
|
|
348
|
+
verificationTime: new Date(),
|
|
349
|
+
} satisfies IJwsValidationResult
|
|
350
|
+
}
|
|
351
|
+
export const toJwsJsonGeneral = async ({ jws }: { jws: Jws }, context: IAgentContext<any>): Promise<JwsJsonGeneral> => {
|
|
352
|
+
let payload: string
|
|
353
|
+
let signatures: JwsJsonSignature[] = []
|
|
354
|
+
|
|
355
|
+
if (isJwsCompact(jws)) {
|
|
356
|
+
const split = jws.split('.')
|
|
357
|
+
payload = split[1]
|
|
358
|
+
signatures[0] = {
|
|
359
|
+
protected: split[0],
|
|
360
|
+
signature: split[2],
|
|
361
|
+
} satisfies JwsJsonSignature
|
|
362
|
+
} else if (isJwsJsonGeneral(jws)) {
|
|
363
|
+
payload = jws.payload
|
|
364
|
+
signatures = jws.signatures
|
|
365
|
+
} else if (isJwsJsonFlattened(jws)) {
|
|
366
|
+
const { payload: _payload, ...signature } = jws
|
|
367
|
+
payload = _payload
|
|
368
|
+
signatures = [signature]
|
|
369
|
+
} else {
|
|
370
|
+
return Promise.reject(Error(`Invalid JWS supplied`))
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
payload,
|
|
374
|
+
signatures,
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function resolveExternalIdentifierFromJwsHeader(
|
|
379
|
+
protectedHeader: JwsHeader,
|
|
380
|
+
context: IAgentContext<IIdentifierResolution>,
|
|
381
|
+
args: {
|
|
382
|
+
jws: Jws
|
|
383
|
+
opts?: { x5c?: Omit<ExternalIdentifierX5cOpts, 'identifier'>; did?: Omit<ExternalIdentifierDidOpts, 'identifier'> }
|
|
384
|
+
}
|
|
385
|
+
) {
|
|
386
|
+
if (protectedHeader.x5c) {
|
|
387
|
+
const x5c = protectedHeader.x5c
|
|
388
|
+
return await context.agent.identifierExternalResolveByX5c({
|
|
389
|
+
...args.opts?.x5c,
|
|
390
|
+
identifier: x5c,
|
|
391
|
+
verify: true,
|
|
392
|
+
})
|
|
393
|
+
} else if (protectedHeader.jwk) {
|
|
394
|
+
const jwk = protectedHeader.jwk
|
|
395
|
+
const x5c = jwk.x5c // todo resolve x5u
|
|
396
|
+
return await context.agent.identifierExternalResolveByJwk({
|
|
397
|
+
identifier: protectedHeader.jwk,
|
|
398
|
+
...(x5c && {
|
|
399
|
+
x5c: {
|
|
400
|
+
...args?.opts?.x5c,
|
|
401
|
+
identifier: x5c,
|
|
402
|
+
},
|
|
403
|
+
}),
|
|
404
|
+
})
|
|
405
|
+
} else if (protectedHeader.kid && protectedHeader.kid.startsWith('did:')) {
|
|
406
|
+
return await context.agent.identifierExternalResolveByDid({ ...args?.opts?.did, identifier: protectedHeader.kid })
|
|
407
|
+
} else if (protectedHeader.alg === 'none') {
|
|
408
|
+
return undefined
|
|
409
|
+
} else {
|
|
410
|
+
return Promise.reject(Error(`We can only process DIDs, X.509 certificate chains and JWKs for signature validation at present`))
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export const toJwsJsonGeneralWithIdentifiers = async (
|
|
415
|
+
args: {
|
|
416
|
+
jws: Jws
|
|
417
|
+
jwk?: JWK
|
|
418
|
+
opts?: { x5c?: Omit<ExternalIdentifierX5cOpts, 'identifier'>; did?: Omit<ExternalIdentifierDidOpts, 'identifier'> }
|
|
419
|
+
},
|
|
420
|
+
context: IAgentContext<IIdentifierResolution>
|
|
421
|
+
): Promise<JwsJsonGeneralWithIdentifiers> => {
|
|
422
|
+
const jws = await toJwsJsonGeneral(args, context)
|
|
423
|
+
const signatures = (await Promise.all(
|
|
424
|
+
jws.signatures.map(async (signature) => {
|
|
425
|
+
const protectedHeader: JwsHeader = decodeJoseBlob(signature.protected)
|
|
426
|
+
const identifier = args.jwk
|
|
427
|
+
? await resolveExternalJwkIdentifier({ identifier: args.jwk }, context)
|
|
428
|
+
: await resolveExternalIdentifierFromJwsHeader(protectedHeader, context, args)
|
|
429
|
+
if (identifier !== undefined) {
|
|
430
|
+
return { ...signature, identifier }
|
|
431
|
+
}
|
|
432
|
+
return undefined
|
|
433
|
+
})
|
|
434
|
+
)) as Array<JwsJsonSignatureWithIdentifier>
|
|
435
|
+
|
|
436
|
+
return { payload: jws.payload, signatures }
|
|
437
|
+
}
|
package/src/index.ts
CHANGED