@opengovsg/mockpass 4.0.7 → 4.0.9
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/.github/workflows/ci.yml
CHANGED
|
@@ -8,20 +8,13 @@ jobs:
|
|
|
8
8
|
name: CI
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
|
-
- uses: actions/checkout@
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
12
|
- name: Use Node.js
|
|
13
|
-
uses: actions/setup-node@
|
|
13
|
+
uses: actions/setup-node@v3
|
|
14
14
|
with:
|
|
15
|
-
node-version: '
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
with:
|
|
19
|
-
# npm cache files are stored in `~/.npm` on Linux/macOS
|
|
20
|
-
path: ~/.npm
|
|
21
|
-
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
22
|
-
restore-keys: |
|
|
23
|
-
${{ runner.OS }}-node-
|
|
24
|
-
${{ runner.OS }}-
|
|
15
|
+
node-version: 'lts/*'
|
|
16
|
+
cache: 'npm'
|
|
17
|
+
cache-dependency-path: '**/package-lock.json'
|
|
25
18
|
- run: npm ci
|
|
26
19
|
- run: npx lockfile-lint --type npm --path package-lock.json --validate-https --allowed-hosts npm
|
|
27
20
|
- run: npm run lint
|
|
@@ -5,7 +5,7 @@ const path = require('path')
|
|
|
5
5
|
const express = require('express')
|
|
6
6
|
const { pick, partition } = require('lodash')
|
|
7
7
|
|
|
8
|
-
const jose = require('
|
|
8
|
+
const jose = require('jose')
|
|
9
9
|
const jwt = require('jsonwebtoken')
|
|
10
10
|
|
|
11
11
|
const assertions = require('../../assertions')
|
|
@@ -31,16 +31,27 @@ module.exports =
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const encryptPersona = async (persona) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
/*
|
|
35
|
+
* We sign and encrypt the persona. It's important to note that although a signature is
|
|
36
|
+
* usually derived from the payload hash and is thus much smaller than the payload itself,
|
|
37
|
+
* we're specifically contructeding a JWT, which contains the original payload.
|
|
38
|
+
*
|
|
39
|
+
* We then construct a JWE and provide two headers specifying the encryption algorithms used.
|
|
40
|
+
* You can read about them here: https://www.rfc-editor.org/rfc/inline-errata/rfc7518.html
|
|
41
|
+
*
|
|
42
|
+
* These values weren't picked arbitrarily; they were the defaults used by a library we
|
|
43
|
+
* formerly used: node-jose. We opted to continue using them for backwards compatibility.
|
|
44
|
+
*/
|
|
45
|
+
const privateKey = await jose.importPKCS8(MOCKPASS_PRIVATE_KEY.toString())
|
|
46
|
+
const sign = await new jose.SignJWT(persona)
|
|
47
|
+
.setProtectedHeader({ alg: 'RS256' })
|
|
48
|
+
.sign(privateKey)
|
|
49
|
+
const publicKey = await jose.importX509(serviceProvider.cert.toString())
|
|
50
|
+
const encryptedAndSignedPersona = await new jose.CompactEncrypt(
|
|
51
|
+
Buffer.from(sign),
|
|
41
52
|
)
|
|
42
|
-
.
|
|
43
|
-
.
|
|
53
|
+
.setProtectedHeader({ alg: 'RSA-OAEP', enc: 'A128CBC-HS256' })
|
|
54
|
+
.encrypt(publicKey)
|
|
44
55
|
return encryptedAndSignedPersona
|
|
45
56
|
}
|
|
46
57
|
|
|
@@ -142,7 +153,6 @@ module.exports =
|
|
|
142
153
|
redirect_uri,
|
|
143
154
|
})
|
|
144
155
|
: {}
|
|
145
|
-
|
|
146
156
|
if (!tokenTemplate) {
|
|
147
157
|
res.status(400).send({
|
|
148
158
|
code: 400,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const express = require('express')
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const { render } = require('mustache')
|
|
7
|
-
const jose = require('
|
|
7
|
+
const jose = require('jose')
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
10
|
const assertions = require('../../assertions')
|
|
@@ -32,6 +32,81 @@ const rpPublic = fs.readFileSync(
|
|
|
32
32
|
path.resolve(__dirname, '../../../static/certs/oidc-v2-rp-public.json'),
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
+
const singpass_token_endpoint_auth_signing_alg_values_supported = [
|
|
36
|
+
'ES256',
|
|
37
|
+
'ES384',
|
|
38
|
+
'ES512',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const corppass_token_endpoint_auth_signing_alg_values_supported = ['ES256']
|
|
42
|
+
|
|
43
|
+
const token_endpoint_auth_signing_alg_values_supported = {
|
|
44
|
+
singPass: singpass_token_endpoint_auth_signing_alg_values_supported,
|
|
45
|
+
corpPass: corppass_token_endpoint_auth_signing_alg_values_supported,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const singpass_id_token_encryption_alg_values_supported = [
|
|
49
|
+
'ECDH-ES+A256KW',
|
|
50
|
+
'ECDH-ES+A192KW',
|
|
51
|
+
'ECDH-ES+A128KW',
|
|
52
|
+
'RSA-OAEP-256',
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
const corppass_id_token_encryption_alg_values_supported = ['ECDH-ES+A256KW']
|
|
56
|
+
|
|
57
|
+
const id_token_encryption_alg_values_supported = {
|
|
58
|
+
singPass: singpass_id_token_encryption_alg_values_supported,
|
|
59
|
+
corpPass: corppass_id_token_encryption_alg_values_supported,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findEncryptionKey(jwks) {
|
|
63
|
+
let encryptionKey = jwks.keys.find(
|
|
64
|
+
(item) =>
|
|
65
|
+
item.use === 'enc' &&
|
|
66
|
+
item.kty === 'EC' &&
|
|
67
|
+
item.crv === 'P-521' &&
|
|
68
|
+
(item.alg === 'ECDH-ES+A256KW' || !item.alg),
|
|
69
|
+
)
|
|
70
|
+
if (encryptionKey) {
|
|
71
|
+
return { ...encryptionKey, alg: 'ECDH-ES+A256KW' }
|
|
72
|
+
}
|
|
73
|
+
if (!encryptionKey) {
|
|
74
|
+
encryptionKey = jwks.keys.find(
|
|
75
|
+
(item) =>
|
|
76
|
+
item.use === 'enc' &&
|
|
77
|
+
item.kty === 'EC' &&
|
|
78
|
+
item.crv === 'P-384' &&
|
|
79
|
+
(item.alg === 'ECDH-ES+A192KW' || !item.alg),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
if (encryptionKey) {
|
|
83
|
+
return { ...encryptionKey, alg: 'ECDH-ES+A192KW' }
|
|
84
|
+
}
|
|
85
|
+
if (!encryptionKey) {
|
|
86
|
+
encryptionKey = jwks.keys.find(
|
|
87
|
+
(item) =>
|
|
88
|
+
item.use === 'enc' &&
|
|
89
|
+
item.kty === 'EC' &&
|
|
90
|
+
item.crv === 'P-256' &&
|
|
91
|
+
(item.alg === 'ECDH-ES+A128KW' || !item.alg),
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
if (encryptionKey) {
|
|
95
|
+
return { ...encryptionKey, alg: 'ECDH-ES+A128KW' }
|
|
96
|
+
}
|
|
97
|
+
if (!encryptionKey) {
|
|
98
|
+
encryptionKey = jwks.keys.find(
|
|
99
|
+
(item) =>
|
|
100
|
+
item.use === 'enc' &&
|
|
101
|
+
item.kty === 'RSA' &&
|
|
102
|
+
(item.alg === 'RSA-OAEP-256' || !item.alg),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
if (encryptionKey) {
|
|
106
|
+
return { ...encryptionKey, alg: 'RSA-OAEP-256' }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
35
110
|
function config(app, { showLoginPage }) {
|
|
36
111
|
for (const idp of ['singPass', 'corpPass']) {
|
|
37
112
|
const profiles = assertions.oidc[idp]
|
|
@@ -146,18 +221,21 @@ function config(app, { showLoginPage }) {
|
|
|
146
221
|
|
|
147
222
|
// Only SP requires client_id
|
|
148
223
|
if (idp === 'singPass' && !client_id) {
|
|
224
|
+
console.error('Missing client_id')
|
|
149
225
|
return res.status(400).send({
|
|
150
226
|
error: 'invalid_request',
|
|
151
227
|
error_description: 'Missing client_id',
|
|
152
228
|
})
|
|
153
229
|
}
|
|
154
230
|
if (!redirectURI) {
|
|
231
|
+
console.error('Missing redirect_uri')
|
|
155
232
|
return res.status(400).send({
|
|
156
233
|
error: 'invalid_request',
|
|
157
234
|
error_description: 'Missing redirect_uri',
|
|
158
235
|
})
|
|
159
236
|
}
|
|
160
237
|
if (grant_type !== 'authorization_code') {
|
|
238
|
+
console.error('Unknown grant_type', grant_type)
|
|
161
239
|
return res.status(400).send({
|
|
162
240
|
error: 'unsupported_grant_type',
|
|
163
241
|
error_description: `Unknown grant_type ${grant_type}`,
|
|
@@ -173,12 +251,14 @@ function config(app, { showLoginPage }) {
|
|
|
173
251
|
client_assertion_type !==
|
|
174
252
|
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
|
175
253
|
) {
|
|
254
|
+
console.error('Unknown client_assertion_type', client_assertion_type)
|
|
176
255
|
return res.status(400).send({
|
|
177
256
|
error: 'invalid_request',
|
|
178
257
|
error_description: `Unknown client_assertion_type ${client_assertion_type}`,
|
|
179
258
|
})
|
|
180
259
|
}
|
|
181
260
|
if (!clientAssertion) {
|
|
261
|
+
console.error('Missing client_assertion')
|
|
182
262
|
return res.status(400).send({
|
|
183
263
|
error: 'invalid_request',
|
|
184
264
|
error_description: 'Missing client_assertion',
|
|
@@ -195,10 +275,19 @@ function config(app, { showLoginPage }) {
|
|
|
195
275
|
|
|
196
276
|
if (rpJwksEndpoint) {
|
|
197
277
|
try {
|
|
198
|
-
|
|
278
|
+
const rpKeysetResponse = await fetch(rpJwksEndpoint, {
|
|
199
279
|
method: 'GET',
|
|
200
|
-
})
|
|
280
|
+
})
|
|
281
|
+
rpKeysetString = await rpKeysetResponse.text()
|
|
282
|
+
if (!rpKeysetResponse.ok) {
|
|
283
|
+
throw new Error(rpKeysetString)
|
|
284
|
+
}
|
|
201
285
|
} catch (e) {
|
|
286
|
+
console.error(
|
|
287
|
+
'Failed to fetch RP JWKS from',
|
|
288
|
+
rpJwksEndpoint,
|
|
289
|
+
e.message,
|
|
290
|
+
)
|
|
202
291
|
return res.status(400).send({
|
|
203
292
|
error: 'invalid_client',
|
|
204
293
|
error_description: `Failed to fetch RP JWKS from specified endpoint: ${e.message}`,
|
|
@@ -213,40 +302,70 @@ function config(app, { showLoginPage }) {
|
|
|
213
302
|
try {
|
|
214
303
|
rpKeysetJson = JSON.parse(rpKeysetString)
|
|
215
304
|
} catch (e) {
|
|
305
|
+
console.error('Unable to parse RP keyset', e.message)
|
|
216
306
|
return res.status(400).send({
|
|
217
307
|
error: 'invalid_client',
|
|
218
308
|
error_description: `Unable to parse RP keyset: ${e.message}`,
|
|
219
309
|
})
|
|
220
310
|
}
|
|
221
311
|
|
|
222
|
-
const rpKeyset =
|
|
223
|
-
|
|
312
|
+
const rpKeyset = jose.createLocalJWKSet(rpKeysetJson)
|
|
224
313
|
// Step 0.5: Verify client assertion with RP signing key
|
|
225
|
-
let
|
|
314
|
+
let clientAssertionResult
|
|
226
315
|
try {
|
|
227
|
-
|
|
316
|
+
clientAssertionResult = await jose.jwtVerify(
|
|
317
|
+
clientAssertion,
|
|
228
318
|
rpKeyset,
|
|
229
|
-
)
|
|
319
|
+
)
|
|
230
320
|
} catch (e) {
|
|
231
|
-
|
|
321
|
+
console.error(
|
|
322
|
+
'Unable to verify client_assertion',
|
|
323
|
+
e.message,
|
|
324
|
+
clientAssertion,
|
|
325
|
+
)
|
|
326
|
+
return res.status(401).send({
|
|
232
327
|
error: 'invalid_client',
|
|
233
328
|
error_description: `Unable to verify client_assertion: ${e.message}`,
|
|
234
329
|
})
|
|
235
330
|
}
|
|
236
331
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
332
|
+
const { payload: clientAssertionClaims, protectedHeader } =
|
|
333
|
+
clientAssertionResult
|
|
334
|
+
console.debug(
|
|
335
|
+
'Received client_assertion',
|
|
336
|
+
clientAssertionClaims,
|
|
337
|
+
protectedHeader,
|
|
338
|
+
)
|
|
339
|
+
if (
|
|
340
|
+
!token_endpoint_auth_signing_alg_values_supported[idp].some(
|
|
341
|
+
(item) => item === protectedHeader.alg,
|
|
342
|
+
)
|
|
343
|
+
) {
|
|
344
|
+
console.warn(
|
|
345
|
+
'The client_assertion alg',
|
|
346
|
+
protectedHeader.alg,
|
|
347
|
+
'does not meet required token_endpoint_auth_signing_alg_values_supported',
|
|
348
|
+
token_endpoint_auth_signing_alg_values_supported[idp],
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!protectedHeader.typ) {
|
|
353
|
+
console.error('The client_assertion typ should be set')
|
|
354
|
+
return res.status(401).send({
|
|
242
355
|
error: 'invalid_client',
|
|
243
|
-
error_description:
|
|
356
|
+
error_description: 'The client_assertion typ should be set',
|
|
244
357
|
})
|
|
245
358
|
}
|
|
246
359
|
|
|
247
360
|
if (idp === 'singPass') {
|
|
248
361
|
if (clientAssertionClaims['sub'] !== client_id) {
|
|
249
|
-
|
|
362
|
+
console.error(
|
|
363
|
+
'Incorrect sub in client_assertion claims. Found',
|
|
364
|
+
clientAssertionClaims['sub'],
|
|
365
|
+
'but should be',
|
|
366
|
+
client_id,
|
|
367
|
+
)
|
|
368
|
+
return res.status(401).send({
|
|
250
369
|
error: 'invalid_client',
|
|
251
370
|
error_description: 'Incorrect sub in client_assertion claims',
|
|
252
371
|
})
|
|
@@ -255,7 +374,8 @@ function config(app, { showLoginPage }) {
|
|
|
255
374
|
// Since client_id is not given for corpPass, sub claim is required in
|
|
256
375
|
// order to get aud for id_token.
|
|
257
376
|
if (!clientAssertionClaims['sub']) {
|
|
258
|
-
|
|
377
|
+
console.error('Missing sub in client_assertion claims')
|
|
378
|
+
return res.status(401).send({
|
|
259
379
|
error: 'invalid_client',
|
|
260
380
|
error_description: 'Missing sub in client_assertion claims',
|
|
261
381
|
})
|
|
@@ -268,7 +388,13 @@ function config(app, { showLoginPage }) {
|
|
|
268
388
|
)}/${idp.toLowerCase()}/v2`
|
|
269
389
|
|
|
270
390
|
if (clientAssertionClaims['aud'] !== iss) {
|
|
271
|
-
|
|
391
|
+
console.error(
|
|
392
|
+
'Incorrect aud in client_assertion claims. Found',
|
|
393
|
+
clientAssertionClaims['aud'],
|
|
394
|
+
'but should be',
|
|
395
|
+
iss,
|
|
396
|
+
)
|
|
397
|
+
return res.status(401).send({
|
|
272
398
|
error: 'invalid_client',
|
|
273
399
|
error_description: 'Incorrect aud in client_assertion claims',
|
|
274
400
|
})
|
|
@@ -279,38 +405,65 @@ function config(app, { showLoginPage }) {
|
|
|
279
405
|
|
|
280
406
|
// Step 2: Get ID token
|
|
281
407
|
const aud = clientAssertionClaims['sub']
|
|
282
|
-
console.
|
|
283
|
-
|
|
284
|
-
|
|
408
|
+
console.debug('Received token request', {
|
|
409
|
+
code: authCode,
|
|
410
|
+
client_id: aud,
|
|
411
|
+
redirect_uri: redirectURI,
|
|
412
|
+
})
|
|
285
413
|
|
|
286
414
|
const { idTokenClaims, accessToken } = await assertions.oidc.create[
|
|
287
415
|
idp
|
|
288
416
|
](profile, iss, aud, nonce)
|
|
289
417
|
|
|
290
418
|
// Step 3: Sign ID token with ASP signing key
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
419
|
+
const aspKeyset = JSON.parse(aspSecret)
|
|
420
|
+
const aspSigningKey = aspKeyset.keys.find(
|
|
421
|
+
(item) =>
|
|
422
|
+
item.use === 'sig' && item.kty === 'EC' && item.crv === 'P-256',
|
|
423
|
+
)
|
|
424
|
+
if (!aspSigningKey) {
|
|
425
|
+
console.error('No suitable signing key found', aspKeyset.keys)
|
|
426
|
+
return res.status(400).send({
|
|
427
|
+
error: 'invalid_request',
|
|
428
|
+
error_description: 'No suitable signing key found',
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
const signingKey = await jose.importJWK(aspSigningKey, 'ES256')
|
|
432
|
+
const signedProtectedHeader = {
|
|
433
|
+
alg: 'ES256',
|
|
434
|
+
typ: 'JWT',
|
|
435
|
+
kid: signingKey.kid,
|
|
436
|
+
}
|
|
437
|
+
const signedIdToken = await new jose.CompactSign(
|
|
438
|
+
new TextEncoder().encode(JSON.stringify(idTokenClaims)),
|
|
298
439
|
)
|
|
299
|
-
.
|
|
300
|
-
.
|
|
440
|
+
.setProtectedHeader(signedProtectedHeader)
|
|
441
|
+
.sign(signingKey)
|
|
301
442
|
|
|
302
443
|
// Step 4: Encrypt ID token with RP encryption key
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
444
|
+
const rpEncryptionKey = findEncryptionKey(rpKeysetJson)
|
|
445
|
+
if (!rpEncryptionKey) {
|
|
446
|
+
console.error('No suitable encryption key found', rpKeysetJson.keys)
|
|
447
|
+
return res.status(400).send({
|
|
448
|
+
error: 'invalid_request',
|
|
449
|
+
error_description: 'No suitable encryption key found',
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
console.debug('Using encryption key', rpEncryptionKey)
|
|
453
|
+
const encryptedProtectedHeader = {
|
|
454
|
+
alg: rpEncryptionKey.alg,
|
|
455
|
+
typ: 'JWT',
|
|
456
|
+
kid: rpEncryptionKey.kid,
|
|
457
|
+
enc: 'A256CBC-HS512',
|
|
458
|
+
cty: 'JWT',
|
|
459
|
+
}
|
|
460
|
+
const idToken = await new jose.CompactEncrypt(
|
|
461
|
+
new TextEncoder().encode(signedIdToken),
|
|
310
462
|
)
|
|
311
|
-
.
|
|
312
|
-
.
|
|
463
|
+
.setProtectedHeader(encryptedProtectedHeader)
|
|
464
|
+
.encrypt(await jose.importJWK(rpEncryptionKey, rpEncryptionKey.alg))
|
|
313
465
|
|
|
466
|
+
console.debug('ID Token', idToken)
|
|
314
467
|
// Step 5: Send token
|
|
315
468
|
res.status(200).send({
|
|
316
469
|
access_token: accessToken,
|
|
@@ -342,20 +495,23 @@ function config(app, { showLoginPage }) {
|
|
|
342
495
|
grant_types_supported: ['authorization_code'],
|
|
343
496
|
token_endpoint: `${baseUrl}/token`,
|
|
344
497
|
token_endpoint_auth_methods_supported: ['private_key_jwt'],
|
|
345
|
-
token_endpoint_auth_signing_alg_values_supported:
|
|
498
|
+
token_endpoint_auth_signing_alg_values_supported:
|
|
499
|
+
token_endpoint_auth_signing_alg_values_supported[idp],
|
|
346
500
|
id_token_signing_alg_values_supported: ['ES256'],
|
|
347
|
-
id_token_encryption_alg_values_supported:
|
|
501
|
+
id_token_encryption_alg_values_supported:
|
|
502
|
+
id_token_encryption_alg_values_supported[idp],
|
|
348
503
|
id_token_encryption_enc_values_supported: ['A256CBC-HS512'],
|
|
349
504
|
}
|
|
350
505
|
|
|
351
506
|
if (idp === 'corpPass') {
|
|
352
|
-
data['claims_supported']
|
|
507
|
+
data['claims_supported'] = [
|
|
508
|
+
...data['claims_supported'],
|
|
353
509
|
'userInfo',
|
|
354
|
-
'
|
|
510
|
+
'EntityInfo',
|
|
355
511
|
'rt_hash',
|
|
356
512
|
'at_hash',
|
|
357
513
|
'amr',
|
|
358
|
-
]
|
|
514
|
+
]
|
|
359
515
|
// Omit authorization-info_endpoint for CP
|
|
360
516
|
}
|
|
361
517
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengovsg/mockpass",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.9",
|
|
4
4
|
"description": "A mock SingPass/CorpPass server for dev purposes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"dotenv": "^16.0.0",
|
|
42
42
|
"expiry-map": "^2.0.0",
|
|
43
43
|
"express": "^4.16.3",
|
|
44
|
+
"jose": "^4.14.4",
|
|
44
45
|
"jsonwebtoken": "^9.0.0",
|
|
45
46
|
"lodash": "^4.17.11",
|
|
46
47
|
"morgan": "^1.9.1",
|
|
47
48
|
"mustache": "^4.2.0",
|
|
48
49
|
"node-jose": "^2.0.0",
|
|
49
|
-
"uuid": "^9.0.0"
|
|
50
|
-
"xpath": "0.0.32"
|
|
50
|
+
"uuid": "^9.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@commitlint/cli": "^17.1.2",
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
"kid": "sig-1655709297",
|
|
8
8
|
"x": "AWuSHLkeP89DOkPaTs6MUDTFX1oL_Nr2rsJxCUyWL9x4LDEwtGXxWmw5-KhJSKauwJL2fAiNribZa2E0EZ-A4DzL",
|
|
9
9
|
"y": "AHoghl5OGyp7Vejt2sqYW7z2G_gTGBDR9q-ylLjnERpKd7-kHybLEutkwp5tmkhhlOysCcXE7vpTcnwxeQPa3zN0"
|
|
10
|
-
}
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"kty": "EC",
|
|
13
|
+
"use": "sig",
|
|
14
|
+
"crv": "P-256",
|
|
15
|
+
"kid": "ndi_mock_01",
|
|
16
|
+
"x": "ZyAP_T3GS6tzdEfIKgj7Z_TkKWQ9AQAU7LNTSV_JICQ",
|
|
17
|
+
"y": "gxQgPvGD8ASZT7DT41pgWP4ZHiZ_7HGcMoDM0NEOfO8",
|
|
18
|
+
"alg": "ES256"
|
|
19
|
+
}
|
|
11
20
|
]
|
|
12
21
|
}
|
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
"kid": "sig-1655709297",
|
|
9
9
|
"x": "AWuSHLkeP89DOkPaTs6MUDTFX1oL_Nr2rsJxCUyWL9x4LDEwtGXxWmw5-KhJSKauwJL2fAiNribZa2E0EZ-A4DzL",
|
|
10
10
|
"y": "AHoghl5OGyp7Vejt2sqYW7z2G_gTGBDR9q-ylLjnERpKd7-kHybLEutkwp5tmkhhlOysCcXE7vpTcnwxeQPa3zN0"
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"kty": "EC",
|
|
14
|
+
"d": "_nXJySWym8zFj_jL3skM2zf0wxL8GQo10WgC3nrx3vw",
|
|
15
|
+
"use": "sig",
|
|
16
|
+
"crv": "P-256",
|
|
17
|
+
"kid": "ndi_mock_01",
|
|
18
|
+
"x": "ZyAP_T3GS6tzdEfIKgj7Z_TkKWQ9AQAU7LNTSV_JICQ",
|
|
19
|
+
"y": "gxQgPvGD8ASZT7DT41pgWP4ZHiZ_7HGcMoDM0NEOfO8"
|
|
20
|
+
}
|
|
12
21
|
]
|
|
13
22
|
}
|