@sphereon/ssi-sdk-ext.key-utils 0.26.1-next.14 → 0.26.1-next.17
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/dist/conversion.js.map +1 -1
- package/dist/functions.d.ts +2 -2
- package/dist/functions.d.ts.map +1 -1
- package/dist/functions.js +27 -23
- package/dist/functions.js.map +1 -1
- package/dist/jwk-jcs.d.ts.map +1 -1
- package/dist/jwk-jcs.js +1 -1
- package/dist/jwk-jcs.js.map +1 -1
- package/package.json +3 -4
- package/src/conversion.ts +1 -1
- package/src/functions.ts +572 -563
- package/src/jwk-jcs.ts +3 -3
package/src/functions.ts
CHANGED
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
import {randomBytes} from '@ethersproject/random'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from '
|
|
16
|
-
import {JoseCurve, JoseSignatureAlgorithm, JWK, JwkKeyType, Loggers} from '@sphereon/ssi-types'
|
|
17
|
-
import {generateKeyPair as generateSigningKeyPair} from '@stablelib/ed25519'
|
|
18
|
-
import {IAgentContext, IKey, IKeyManager, ManagedKeyInfo, MinimalImportableKey} from '@veramo/core'
|
|
19
|
-
|
|
20
|
-
import {JsonWebKey} from 'did-resolver'
|
|
1
|
+
import { randomBytes } from '@ethersproject/random'
|
|
2
|
+
// Do not change these require statements to imports before we change to ESM. Breaks external CJS packages depending on this module
|
|
3
|
+
const { bls12_381 } = require('@noble/curves/bls12-381')
|
|
4
|
+
const { ed25519 } = require('@noble/curves/ed25519')
|
|
5
|
+
const { p256 } = require('@noble/curves/p256')
|
|
6
|
+
const { p384 } = require('@noble/curves/p384')
|
|
7
|
+
const { p521 } = require('@noble/curves/p521')
|
|
8
|
+
const { secp256k1 } = require('@noble/curves/secp256k1')
|
|
9
|
+
const { sha256, sha384, sha512 } = require('@noble/hashes/sha2')
|
|
10
|
+
import { generateRSAKeyAsPEM, hexToBase64, hexToPEM, PEMToJwk, privateKeyHexFromPEM } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
11
|
+
import { JoseCurve, JoseSignatureAlgorithm, JWK, JwkKeyType, Loggers } from '@sphereon/ssi-types'
|
|
12
|
+
import { generateKeyPair as generateSigningKeyPair } from '@stablelib/ed25519'
|
|
13
|
+
import { IAgentContext, IKey, IKeyManager, ManagedKeyInfo, MinimalImportableKey } from '@veramo/core'
|
|
14
|
+
|
|
15
|
+
import { JsonWebKey } from 'did-resolver'
|
|
21
16
|
import elliptic from 'elliptic'
|
|
22
|
-
import * as rsa from 'micro-rsa-dsa-dh/rsa.js'
|
|
17
|
+
import * as rsa from 'micro-rsa-dsa-dh/rsa.js'
|
|
23
18
|
import * as u8a from 'uint8arrays'
|
|
24
|
-
import {digestMethodParams} from './digest-methods'
|
|
25
|
-
import {validateJwk} from
|
|
19
|
+
import { digestMethodParams } from './digest-methods'
|
|
20
|
+
import { validateJwk } from './jwk-jcs'
|
|
26
21
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
ENC_KEY_ALGS,
|
|
23
|
+
IImportProvidedOrGeneratedKeyArgs,
|
|
24
|
+
JwkKeyUse,
|
|
25
|
+
KeyTypeFromCryptographicSuiteArgs,
|
|
26
|
+
SIG_KEY_ALGS,
|
|
27
|
+
SignatureAlgorithmFromKeyArgs,
|
|
28
|
+
SignatureAlgorithmFromKeyTypeArgs,
|
|
29
|
+
TKeyType,
|
|
35
30
|
} from './types'
|
|
36
31
|
|
|
37
32
|
export const logger = Loggers.DEFAULT.get('sphereon:key-utils')
|
|
@@ -43,13 +38,13 @@ export const logger = Loggers.DEFAULT.get('sphereon:key-utils')
|
|
|
43
38
|
* @param kms. Optional KMS to use. If provided will be the returned name. Otherwise the default KMS will be returned
|
|
44
39
|
*/
|
|
45
40
|
export const getKms = async (context: IAgentContext<any>, kms?: string): Promise<string> => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
if (kms) {
|
|
42
|
+
return kms
|
|
43
|
+
}
|
|
44
|
+
if (!context.agent.availableMethods().includes('keyManagerGetDefaultKeyManagementSystem')) {
|
|
45
|
+
throw Error('Cannot determine default KMS if not provided and a non Sphereon Key Manager is being used')
|
|
46
|
+
}
|
|
47
|
+
return context.agent.keyManagerGetDefaultKeyManagementSystem()
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
/**
|
|
@@ -58,41 +53,41 @@ export const getKms = async (context: IAgentContext<any>, kms?: string): Promise
|
|
|
58
53
|
* @return The private key in Hex form
|
|
59
54
|
*/
|
|
60
55
|
export const generatePrivateKeyHex = async (type: TKeyType): Promise<string> => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
default:
|
|
77
|
-
throw Error(`not_supported: Key type ${type} not yet supported for this did:jwk implementation`)
|
|
56
|
+
switch (type) {
|
|
57
|
+
case 'Ed25519': {
|
|
58
|
+
const keyPairEd25519 = generateSigningKeyPair()
|
|
59
|
+
return u8a.toString(keyPairEd25519.secretKey, 'base16')
|
|
60
|
+
}
|
|
61
|
+
// The Secp256 types use the same method to generate the key
|
|
62
|
+
case 'Secp256r1':
|
|
63
|
+
case 'Secp256k1': {
|
|
64
|
+
const privateBytes = randomBytes(32)
|
|
65
|
+
return u8a.toString(privateBytes, 'base16')
|
|
66
|
+
}
|
|
67
|
+
case 'RSA': {
|
|
68
|
+
const pem = await generateRSAKeyAsPEM('RSA-PSS', 'SHA-256', 2048)
|
|
69
|
+
return privateKeyHexFromPEM(pem)
|
|
78
70
|
}
|
|
71
|
+
default:
|
|
72
|
+
throw Error(`not_supported: Key type ${type} not yet supported for this did:jwk implementation`)
|
|
73
|
+
}
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
const keyMetaAlgorithmsFromKeyType = (type: string | TKeyType) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
switch (type) {
|
|
78
|
+
case 'Ed25519':
|
|
79
|
+
return ['Ed25519', 'EdDSA']
|
|
80
|
+
case 'ES256K':
|
|
81
|
+
case 'Secp256k1':
|
|
82
|
+
return ['ES256K', 'ES256K-R', 'eth_signTransaction', 'eth_signTypedData', 'eth_signMessage', 'eth_rawSign']
|
|
83
|
+
case 'Secp256r1':
|
|
84
|
+
return ['ES256']
|
|
85
|
+
case 'X25519':
|
|
86
|
+
return ['ECDH', 'ECDH-ES', 'ECDH-1PU']
|
|
87
|
+
case 'RSA':
|
|
88
|
+
return ['RS256', 'RS512', 'PS256', 'PS512']
|
|
89
|
+
}
|
|
90
|
+
return [type]
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
/**
|
|
@@ -103,78 +98,78 @@ const keyMetaAlgorithmsFromKeyType = (type: string | TKeyType) => {
|
|
|
103
98
|
* @private
|
|
104
99
|
*/
|
|
105
100
|
export async function importProvidedOrGeneratedKey(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
args: IImportProvidedOrGeneratedKeyArgs & {
|
|
102
|
+
kms: string
|
|
103
|
+
},
|
|
104
|
+
context: IAgentContext<IKeyManager>
|
|
110
105
|
): Promise<IKey> {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return context.agent.keyManagerCreate({
|
|
147
|
-
type,
|
|
148
|
-
kms: args.kms,
|
|
149
|
-
meta: {
|
|
150
|
-
...key?.meta,
|
|
151
|
-
algorithms: keyMetaAlgorithmsFromKeyType(type),
|
|
152
|
-
keyAlias: args.alias,
|
|
153
|
-
},
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
const type = args.options?.type ?? args.options?.key?.type ?? args.options?.keyType ?? 'Secp256r1'
|
|
108
|
+
const key = args?.options?.key
|
|
109
|
+
// Make sure x509 options are also set on the metadata as that is what the kms will look for
|
|
110
|
+
if (args.options?.x509 && key) {
|
|
111
|
+
key.meta = {
|
|
112
|
+
...key.meta,
|
|
113
|
+
x509: {
|
|
114
|
+
...args.options.x509,
|
|
115
|
+
...key.meta?.x509,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (args.options && args.options?.use === JwkKeyUse.Encryption && !ENC_KEY_ALGS.includes(type)) {
|
|
121
|
+
throw new Error(`${type} keys are not valid for encryption`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let privateKeyHex: string | undefined = undefined
|
|
125
|
+
if (key) {
|
|
126
|
+
privateKeyHex = key.privateKeyHex ?? key.meta?.x509?.privateKeyHex
|
|
127
|
+
if ((!privateKeyHex || privateKeyHex.trim() === '') && key?.meta?.x509?.privateKeyPEM) {
|
|
128
|
+
// If we do not have a privateKeyHex but do have a PEM
|
|
129
|
+
privateKeyHex = privateKeyHexFromPEM(key.meta.x509.privateKeyPEM)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (privateKeyHex) {
|
|
133
|
+
return context.agent.keyManagerImport({
|
|
134
|
+
...key,
|
|
135
|
+
kms: args.kms,
|
|
136
|
+
type,
|
|
137
|
+
privateKeyHex: privateKeyHex!,
|
|
154
138
|
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return context.agent.keyManagerCreate({
|
|
142
|
+
type,
|
|
143
|
+
kms: args.kms,
|
|
144
|
+
meta: {
|
|
145
|
+
...key?.meta,
|
|
146
|
+
algorithms: keyMetaAlgorithmsFromKeyType(type),
|
|
147
|
+
keyAlias: args.alias,
|
|
148
|
+
},
|
|
149
|
+
})
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
export const calculateJwkThumbprintForKey = (args: {
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
key: IKey | MinimalImportableKey | ManagedKeyInfo
|
|
154
|
+
digestAlgorithm?: 'sha256' | 'sha512'
|
|
160
155
|
}): string => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
156
|
+
const { key } = args
|
|
157
|
+
|
|
158
|
+
const jwk = key.publicKeyHex
|
|
159
|
+
? toJwk(key.publicKeyHex, key.type, { key: key, isPrivateKey: false })
|
|
160
|
+
: 'privateKeyHex' in key && key.privateKeyHex
|
|
161
|
+
? toJwk(key.privateKeyHex, key.type, { isPrivateKey: true })
|
|
162
|
+
: undefined
|
|
163
|
+
if (!jwk) {
|
|
164
|
+
throw Error(`Could not determine jwk from key ${key.kid}`)
|
|
165
|
+
}
|
|
166
|
+
return calculateJwkThumbprint({ jwk, digestAlgorithm: args.digestAlgorithm })
|
|
172
167
|
}
|
|
173
168
|
|
|
174
169
|
const assertJwkClaimPresent = (value: unknown, description: string) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
170
|
+
if (typeof value !== 'string' || !value) {
|
|
171
|
+
throw new Error(`${description} missing or invalid`)
|
|
172
|
+
}
|
|
178
173
|
}
|
|
179
174
|
export const toBase64url = (input: string): string => u8a.toString(u8a.fromString(input), 'base64url')
|
|
180
175
|
|
|
@@ -183,48 +178,48 @@ export const toBase64url = (input: string): string => u8a.toString(u8a.fromStrin
|
|
|
183
178
|
* @param args
|
|
184
179
|
*/
|
|
185
180
|
export const calculateJwkThumbprint = (args: { jwk: JWK; digestAlgorithm?: 'sha256' | 'sha512' }): string => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
181
|
+
const { jwk, digestAlgorithm = 'sha256' } = args
|
|
182
|
+
let components
|
|
183
|
+
switch (jwk.kty) {
|
|
184
|
+
case 'EC':
|
|
185
|
+
assertJwkClaimPresent(jwk.crv, '"crv" (Curve) Parameter')
|
|
186
|
+
assertJwkClaimPresent(jwk.x, '"x" (X Coordinate) Parameter')
|
|
187
|
+
assertJwkClaimPresent(jwk.y, '"y" (Y Coordinate) Parameter')
|
|
188
|
+
components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }
|
|
189
|
+
break
|
|
190
|
+
case 'OKP':
|
|
191
|
+
assertJwkClaimPresent(jwk.crv, '"crv" (Subtype of Key Pair) Parameter')
|
|
192
|
+
assertJwkClaimPresent(jwk.x, '"x" (Public Key) Parameter')
|
|
193
|
+
components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x }
|
|
194
|
+
break
|
|
195
|
+
case 'RSA':
|
|
196
|
+
assertJwkClaimPresent(jwk.e, '"e" (Exponent) Parameter')
|
|
197
|
+
assertJwkClaimPresent(jwk.n, '"n" (Modulus) Parameter')
|
|
198
|
+
components = { e: jwk.e, kty: jwk.kty, n: jwk.n }
|
|
199
|
+
break
|
|
200
|
+
case 'oct':
|
|
201
|
+
assertJwkClaimPresent(jwk.k, '"k" (Key Value) Parameter')
|
|
202
|
+
components = { k: jwk.k, kty: jwk.kty }
|
|
203
|
+
break
|
|
204
|
+
default:
|
|
205
|
+
throw new Error('"kty" (Key Type) Parameter missing or unsupported')
|
|
206
|
+
}
|
|
207
|
+
const data = JSON.stringify(components)
|
|
208
|
+
|
|
209
|
+
return digestAlgorithm === 'sha512'
|
|
210
|
+
? digestMethodParams('SHA-512').digestMethod(data, 'base64url')
|
|
211
|
+
: digestMethodParams('SHA-256').digestMethod(data, 'base64url')
|
|
217
212
|
}
|
|
218
213
|
|
|
219
214
|
export const toJwkFromKey = (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
215
|
+
key: IKey | MinimalImportableKey | ManagedKeyInfo,
|
|
216
|
+
opts?: {
|
|
217
|
+
use?: JwkKeyUse
|
|
218
|
+
noKidThumbprint?: boolean
|
|
219
|
+
}
|
|
225
220
|
): JWK => {
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
const isPrivateKey = 'privateKeyHex' in key
|
|
222
|
+
return toJwk(key.publicKeyHex!, key.type, { ...opts, key, isPrivateKey })
|
|
228
223
|
}
|
|
229
224
|
|
|
230
225
|
/**
|
|
@@ -235,38 +230,38 @@ export const toJwkFromKey = (
|
|
|
235
230
|
* @return The JWK
|
|
236
231
|
*/
|
|
237
232
|
export const toJwk = (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
publicKeyHex: string,
|
|
234
|
+
type: TKeyType,
|
|
235
|
+
opts?: { use?: JwkKeyUse; key?: IKey | MinimalImportableKey; isPrivateKey?: boolean; noKidThumbprint?: boolean }
|
|
241
236
|
): JWK => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
237
|
+
const { key, noKidThumbprint = false } = opts ?? {}
|
|
238
|
+
if (key && key.publicKeyHex !== publicKeyHex && opts?.isPrivateKey !== true) {
|
|
239
|
+
throw Error(`Provided key with id ${key.kid}, has a different public key hex ${key.publicKeyHex} than supplied public key ${publicKeyHex}`)
|
|
240
|
+
}
|
|
241
|
+
let jwk: JWK
|
|
242
|
+
switch (type) {
|
|
243
|
+
case 'Ed25519':
|
|
244
|
+
jwk = toEd25519OrX25519Jwk(publicKeyHex, { ...opts, crv: JoseCurve.Ed25519 })
|
|
245
|
+
break
|
|
246
|
+
case 'X25519':
|
|
247
|
+
jwk = toEd25519OrX25519Jwk(publicKeyHex, { ...opts, crv: JoseCurve.X25519 })
|
|
248
|
+
break
|
|
249
|
+
case 'Secp256k1':
|
|
250
|
+
jwk = toSecp256k1Jwk(publicKeyHex, opts)
|
|
251
|
+
break
|
|
252
|
+
case 'Secp256r1':
|
|
253
|
+
jwk = toSecp256r1Jwk(publicKeyHex, opts)
|
|
254
|
+
break
|
|
255
|
+
case 'RSA':
|
|
256
|
+
jwk = toRSAJwk(publicKeyHex, opts)
|
|
257
|
+
break
|
|
258
|
+
default:
|
|
259
|
+
throw new Error(`not_supported: Key type ${type} not yet supported for this did:jwk implementation`)
|
|
260
|
+
}
|
|
261
|
+
if (!jwk.kid && !noKidThumbprint) {
|
|
262
|
+
jwk['kid'] = calculateJwkThumbprint({ jwk })
|
|
263
|
+
}
|
|
264
|
+
return jwk
|
|
270
265
|
}
|
|
271
266
|
|
|
272
267
|
/**
|
|
@@ -276,18 +271,18 @@ export const toJwk = (
|
|
|
276
271
|
* @returns A string representing the key in raw hexadecimal format.
|
|
277
272
|
*/
|
|
278
273
|
export const jwkToRawHexKey = async (jwk: JWK): Promise<string> => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
274
|
+
// TODO: Probably makes sense to have an option to do the same for private keys
|
|
275
|
+
if (jwk.kty === 'RSA') {
|
|
276
|
+
return rsaJwkToRawHexKey(jwk)
|
|
277
|
+
} else if (jwk.kty === 'EC') {
|
|
278
|
+
return '04' + ecJwkToRawHexKey(jwk)
|
|
279
|
+
} else if (jwk.kty === 'OKP') {
|
|
280
|
+
return okpJwkToRawHexKey(jwk)
|
|
281
|
+
} else if (jwk.kty === 'oct') {
|
|
282
|
+
return octJwkToRawHexKey(jwk)
|
|
283
|
+
} else {
|
|
284
|
+
throw new Error(`Unsupported key type: ${jwk.kty}`)
|
|
285
|
+
}
|
|
291
286
|
}
|
|
292
287
|
|
|
293
288
|
/**
|
|
@@ -296,14 +291,14 @@ export const jwkToRawHexKey = async (jwk: JWK): Promise<string> => {
|
|
|
296
291
|
* @returns A string representing the RSA key in raw hexadecimal format.
|
|
297
292
|
*/
|
|
298
293
|
function rsaJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
294
|
+
if (!jwk.n || !jwk.e) {
|
|
295
|
+
throw new Error("RSA JWK must contain 'n' and 'e' properties.")
|
|
296
|
+
}
|
|
302
297
|
|
|
303
|
-
|
|
304
|
-
|
|
298
|
+
const modulus = u8a.fromString(jwk.n, 'base64url') // 'n' is the modulus
|
|
299
|
+
const exponent = u8a.fromString(jwk.e, 'base64url') // 'e' is the exponent
|
|
305
300
|
|
|
306
|
-
|
|
301
|
+
return u8a.toString(modulus, 'hex') + u8a.toString(exponent, 'hex')
|
|
307
302
|
}
|
|
308
303
|
|
|
309
304
|
/**
|
|
@@ -312,14 +307,14 @@ function rsaJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
|
312
307
|
* @returns A string representing the EC key in raw hexadecimal format.
|
|
313
308
|
*/
|
|
314
309
|
function ecJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
310
|
+
if (!jwk.x || !jwk.y) {
|
|
311
|
+
throw new Error("EC JWK must contain 'x' and 'y' properties.")
|
|
312
|
+
}
|
|
318
313
|
|
|
319
|
-
|
|
320
|
-
|
|
314
|
+
const x = u8a.fromString(jwk.x, 'base64url')
|
|
315
|
+
const y = u8a.fromString(jwk.y, 'base64url')
|
|
321
316
|
|
|
322
|
-
|
|
317
|
+
return u8a.toString(x, 'hex') + u8a.toString(y, 'hex')
|
|
323
318
|
}
|
|
324
319
|
|
|
325
320
|
/**
|
|
@@ -328,29 +323,28 @@ function ecJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
|
328
323
|
* @returns A string representing the EC key in raw hexadecimal format.
|
|
329
324
|
*/
|
|
330
325
|
function okpJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
326
|
+
if (!jwk.x) {
|
|
327
|
+
throw new Error("OKP JWK must contain 'x' property.")
|
|
328
|
+
}
|
|
334
329
|
|
|
335
|
-
|
|
330
|
+
const x = u8a.fromString(jwk.x, 'base64url')
|
|
336
331
|
|
|
337
|
-
|
|
332
|
+
return u8a.toString(x, 'hex')
|
|
338
333
|
}
|
|
339
334
|
|
|
340
|
-
|
|
341
335
|
/**
|
|
342
336
|
* Convert an octet JWK to a raw hex key.
|
|
343
337
|
* @param jwk - The octet JWK object.
|
|
344
338
|
* @returns A string representing the octet key in raw hexadecimal format.
|
|
345
339
|
*/
|
|
346
340
|
function octJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
341
|
+
if (!jwk.k) {
|
|
342
|
+
throw new Error("Octet JWK must contain 'k' property.")
|
|
343
|
+
}
|
|
350
344
|
|
|
351
|
-
|
|
345
|
+
const key = u8a.fromString(jwk.k, 'base64url')
|
|
352
346
|
|
|
353
|
-
|
|
347
|
+
return u8a.toString(key, 'hex')
|
|
354
348
|
}
|
|
355
349
|
|
|
356
350
|
/**
|
|
@@ -360,13 +354,13 @@ function octJwkToRawHexKey(jwk: JsonWebKey): string {
|
|
|
360
354
|
* @param suppliedUse A supplied use. Will be used in case it is present
|
|
361
355
|
*/
|
|
362
356
|
export const jwkDetermineUse = (type: TKeyType, suppliedUse?: JwkKeyUse): JwkKeyUse | undefined => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
357
|
+
return suppliedUse
|
|
358
|
+
? suppliedUse
|
|
359
|
+
: SIG_KEY_ALGS.includes(type)
|
|
360
|
+
? JwkKeyUse.Signature
|
|
361
|
+
: ENC_KEY_ALGS.includes(type)
|
|
362
|
+
? JwkKeyUse.Encryption
|
|
363
|
+
: undefined
|
|
370
364
|
}
|
|
371
365
|
|
|
372
366
|
/**
|
|
@@ -376,17 +370,17 @@ export const jwkDetermineUse = (type: TKeyType, suppliedUse?: JwkKeyUse): JwkKey
|
|
|
376
370
|
* @param expectedKeyLength Expected key length(s)
|
|
377
371
|
*/
|
|
378
372
|
const assertProperKeyLength = (keyHex: string, expectedKeyLength: number | number[]) => {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
} else if (keyHex.length !== expectedKeyLength) {
|
|
388
|
-
throw Error(`Invalid key length. Needs to be a hex string with length ${expectedKeyLength} instead of ${keyHex.length}. Input: ${keyHex}`)
|
|
373
|
+
if (Array.isArray(expectedKeyLength)) {
|
|
374
|
+
if (!expectedKeyLength.includes(keyHex.length)) {
|
|
375
|
+
throw Error(
|
|
376
|
+
`Invalid key length. Needs to be a hex string with length from ${JSON.stringify(expectedKeyLength)} instead of ${
|
|
377
|
+
keyHex.length
|
|
378
|
+
}. Input: ${keyHex}`
|
|
379
|
+
)
|
|
389
380
|
}
|
|
381
|
+
} else if (keyHex.length !== expectedKeyLength) {
|
|
382
|
+
throw Error(`Invalid key length. Needs to be a hex string with length ${expectedKeyLength} instead of ${keyHex.length}. Input: ${keyHex}`)
|
|
383
|
+
}
|
|
390
384
|
}
|
|
391
385
|
|
|
392
386
|
/**
|
|
@@ -396,28 +390,28 @@ const assertProperKeyLength = (keyHex: string, expectedKeyLength: number | numbe
|
|
|
396
390
|
* @return The JWK
|
|
397
391
|
*/
|
|
398
392
|
const toSecp256k1Jwk = (keyHex: string, opts?: { use?: JwkKeyUse; isPrivateKey?: boolean }): JWK => {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
393
|
+
const { use } = opts ?? {}
|
|
394
|
+
logger.debug(`toSecp256k1Jwk keyHex: ${keyHex}, length: ${keyHex.length}`)
|
|
395
|
+
if (opts?.isPrivateKey) {
|
|
396
|
+
assertProperKeyLength(keyHex, [64])
|
|
397
|
+
} else {
|
|
398
|
+
assertProperKeyLength(keyHex, [66, 130])
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const secp256k1 = new elliptic.ec('secp256k1')
|
|
402
|
+
const keyBytes = u8a.fromString(keyHex, 'base16')
|
|
403
|
+
const keyPair = opts?.isPrivateKey ? secp256k1.keyFromPrivate(keyBytes) : secp256k1.keyFromPublic(keyBytes)
|
|
404
|
+
const pubPoint = keyPair.getPublic()
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
alg: JoseSignatureAlgorithm.ES256K,
|
|
408
|
+
...(use !== undefined && { use }),
|
|
409
|
+
kty: JwkKeyType.EC,
|
|
410
|
+
crv: JoseCurve.secp256k1,
|
|
411
|
+
x: hexToBase64(pubPoint.getX().toString('hex'), 'base64url'),
|
|
412
|
+
y: hexToBase64(pubPoint.getY().toString('hex'), 'base64url'),
|
|
413
|
+
...(opts?.isPrivateKey && { d: hexToBase64(keyPair.getPrivate('hex'), 'base64url') }),
|
|
414
|
+
}
|
|
421
415
|
}
|
|
422
416
|
|
|
423
417
|
/**
|
|
@@ -427,28 +421,28 @@ const toSecp256k1Jwk = (keyHex: string, opts?: { use?: JwkKeyUse; isPrivateKey?:
|
|
|
427
421
|
* @return The JWK
|
|
428
422
|
*/
|
|
429
423
|
const toSecp256r1Jwk = (keyHex: string, opts?: { use?: JwkKeyUse; isPrivateKey?: boolean }): JWK => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
424
|
+
const { use } = opts ?? {}
|
|
425
|
+
logger.debug(`toSecp256r1Jwk keyHex: ${keyHex}, length: ${keyHex.length}`)
|
|
426
|
+
if (opts?.isPrivateKey) {
|
|
427
|
+
assertProperKeyLength(keyHex, [64])
|
|
428
|
+
} else {
|
|
429
|
+
assertProperKeyLength(keyHex, [66, 130])
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const secp256r1 = new elliptic.ec('p256')
|
|
433
|
+
const keyBytes = u8a.fromString(keyHex, 'base16')
|
|
434
|
+
logger.debug(`keyBytes length: ${keyBytes}`)
|
|
435
|
+
const keyPair = opts?.isPrivateKey ? secp256r1.keyFromPrivate(keyBytes) : secp256r1.keyFromPublic(keyBytes)
|
|
436
|
+
const pubPoint = keyPair.getPublic()
|
|
437
|
+
return {
|
|
438
|
+
alg: JoseSignatureAlgorithm.ES256,
|
|
439
|
+
...(use !== undefined && { use }),
|
|
440
|
+
kty: JwkKeyType.EC,
|
|
441
|
+
crv: JoseCurve.P_256,
|
|
442
|
+
x: hexToBase64(pubPoint.getX().toString('hex'), 'base64url'),
|
|
443
|
+
y: hexToBase64(pubPoint.getY().toString('hex'), 'base64url'),
|
|
444
|
+
...(opts?.isPrivateKey && { d: hexToBase64(keyPair.getPrivate('hex'), 'base64url') }),
|
|
445
|
+
}
|
|
452
446
|
}
|
|
453
447
|
|
|
454
448
|
/**
|
|
@@ -458,335 +452,350 @@ const toSecp256r1Jwk = (keyHex: string, opts?: { use?: JwkKeyUse; isPrivateKey?:
|
|
|
458
452
|
* @return The JWK
|
|
459
453
|
*/
|
|
460
454
|
const toEd25519OrX25519Jwk = (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
455
|
+
publicKeyHex: string,
|
|
456
|
+
opts: {
|
|
457
|
+
use?: JwkKeyUse
|
|
458
|
+
crv: JoseCurve.Ed25519 | JoseCurve.X25519
|
|
459
|
+
}
|
|
466
460
|
): JWK => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
461
|
+
assertProperKeyLength(publicKeyHex, 64)
|
|
462
|
+
const { use } = opts ?? {}
|
|
463
|
+
return {
|
|
464
|
+
alg: JoseSignatureAlgorithm.EdDSA,
|
|
465
|
+
...(use !== undefined && { use }),
|
|
466
|
+
kty: JwkKeyType.OKP,
|
|
467
|
+
crv: opts?.crv ?? JoseCurve.Ed25519,
|
|
468
|
+
x: hexToBase64(publicKeyHex, 'base64url'),
|
|
469
|
+
}
|
|
476
470
|
}
|
|
477
471
|
|
|
478
472
|
const toRSAJwk = (publicKeyHex: string, opts?: { use?: JwkKeyUse; key?: IKey | MinimalImportableKey }): JWK => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
473
|
+
const meta = opts?.key?.meta
|
|
474
|
+
if (meta?.publicKeyJwk || meta?.publicKeyPEM) {
|
|
475
|
+
if (meta?.publicKeyJwk) {
|
|
476
|
+
return meta.publicKeyJwk as JWK
|
|
477
|
+
}
|
|
478
|
+
const publicKeyPEM = meta?.publicKeyPEM ?? hexToPEM(publicKeyHex, 'public')
|
|
479
|
+
return PEMToJwk(publicKeyPEM, 'public') as JWK
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// exponent (e) is 5 chars long, rest is modulus (n)
|
|
483
|
+
// const publicKey = publicKeyHex
|
|
484
|
+
// assertProperKeyLength(publicKey, [2048, 3072, 4096])
|
|
485
|
+
const exponent = publicKeyHex.slice(-5)
|
|
486
|
+
const modulus = publicKeyHex.slice(0, -5)
|
|
487
|
+
// const modulusBitLength = (modulus.length / 2) * 8
|
|
488
|
+
|
|
489
|
+
// const alg = modulusBitLength === 2048 ? JoseSignatureAlgorithm.RS256 : modulusBitLength === 3072 ? JoseSignatureAlgorithm.RS384 : modulusBitLength === 4096 ? JoseSignatureAlgorithm.RS512 : undefined
|
|
490
|
+
return {
|
|
491
|
+
kty: 'RSA',
|
|
492
|
+
n: hexToBase64(modulus, 'base64url'),
|
|
493
|
+
e: hexToBase64(exponent, 'base64url'),
|
|
494
|
+
// ...(alg && { alg }),
|
|
495
|
+
}
|
|
503
496
|
}
|
|
504
497
|
|
|
505
498
|
export const padLeft = (args: { data: string; size?: number; padString?: string }): string => {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
499
|
+
const { data } = args
|
|
500
|
+
const size = args.size ?? 32
|
|
501
|
+
const padString = args.padString ?? '0'
|
|
502
|
+
if (data.length >= size) {
|
|
503
|
+
return data
|
|
504
|
+
}
|
|
512
505
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
506
|
+
if (padString && padString.length === 0) {
|
|
507
|
+
throw Error(`Pad string needs to have at least a length of 1`)
|
|
508
|
+
}
|
|
509
|
+
const length = padString.length
|
|
510
|
+
return padString.repeat((size - data.length) / length) + data
|
|
518
511
|
}
|
|
519
512
|
|
|
520
513
|
enum OIDType {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
514
|
+
Secp256k1,
|
|
515
|
+
Secp256r1,
|
|
516
|
+
Ed25519,
|
|
524
517
|
}
|
|
525
518
|
|
|
526
519
|
const OID: Record<OIDType, Uint8Array> = {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
520
|
+
[OIDType.Secp256k1]: new Uint8Array([0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]),
|
|
521
|
+
[OIDType.Secp256r1]: new Uint8Array([0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]),
|
|
522
|
+
[OIDType.Ed25519]: new Uint8Array([0x06, 0x03, 0x2b, 0x65, 0x70]),
|
|
530
523
|
}
|
|
531
524
|
|
|
532
525
|
const compareUint8Arrays = (a: Uint8Array, b: Uint8Array): boolean => {
|
|
533
|
-
|
|
534
|
-
|
|
526
|
+
if (a.length !== b.length) {
|
|
527
|
+
return false
|
|
528
|
+
}
|
|
529
|
+
for (let i = 0; i < a.length; i++) {
|
|
530
|
+
if (a[i] !== b[i]) {
|
|
531
|
+
return false
|
|
535
532
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return false
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return true
|
|
533
|
+
}
|
|
534
|
+
return true
|
|
542
535
|
}
|
|
543
536
|
|
|
544
537
|
const findSubarray = (haystack: Uint8Array, needle: Uint8Array): number => {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
}
|
|
538
|
+
for (let i = 0; i <= haystack.length - needle.length; i++) {
|
|
539
|
+
if (compareUint8Arrays(haystack.subarray(i, i + needle.length), needle)) {
|
|
540
|
+
return i
|
|
549
541
|
}
|
|
550
|
-
|
|
542
|
+
}
|
|
543
|
+
return -1
|
|
551
544
|
}
|
|
552
545
|
|
|
553
546
|
const getTargetOID = (keyType: TKeyType) => {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
547
|
+
switch (keyType) {
|
|
548
|
+
case 'Secp256k1':
|
|
549
|
+
return OID[OIDType.Secp256k1]
|
|
550
|
+
case 'Secp256r1':
|
|
551
|
+
return OID[OIDType.Secp256r1]
|
|
552
|
+
case 'Ed25519':
|
|
553
|
+
return OID[OIDType.Ed25519]
|
|
554
|
+
default:
|
|
555
|
+
throw new Error(`Unsupported key type: ${keyType}`)
|
|
556
|
+
}
|
|
564
557
|
}
|
|
565
558
|
|
|
566
559
|
export const isAsn1Der = (key: Uint8Array): boolean => key[0] === 0x30
|
|
567
560
|
|
|
568
561
|
export const asn1DerToRawPublicKey = (derKey: Uint8Array, keyType: TKeyType): Uint8Array => {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
562
|
+
if (!isAsn1Der(derKey)) {
|
|
563
|
+
throw new Error('Invalid DER encoding: Expected to start with sequence tag')
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
let index = 2
|
|
567
|
+
if (derKey[1] & 0x80) {
|
|
568
|
+
const lengthBytesCount = derKey[1] & 0x7f
|
|
569
|
+
index += lengthBytesCount
|
|
570
|
+
}
|
|
571
|
+
const targetOid = getTargetOID(keyType)
|
|
572
|
+
const oidIndex = findSubarray(derKey, targetOid)
|
|
573
|
+
if (oidIndex === -1) {
|
|
574
|
+
throw new Error(`OID for ${keyType} not found in DER encoding`)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
index = oidIndex + targetOid.length
|
|
578
|
+
|
|
579
|
+
while (index < derKey.length && derKey[index] !== 0x03) {
|
|
580
|
+
index++
|
|
581
|
+
}
|
|
589
582
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
583
|
+
if (index >= derKey.length) {
|
|
584
|
+
throw new Error('Invalid DER encoding: Bit string not found')
|
|
585
|
+
}
|
|
593
586
|
|
|
594
|
-
|
|
595
|
-
|
|
587
|
+
// Skip the bit string tag (0x03) and length byte
|
|
588
|
+
index += 2
|
|
596
589
|
|
|
597
|
-
|
|
598
|
-
|
|
590
|
+
// Skip the unused bits count byte
|
|
591
|
+
index++
|
|
599
592
|
|
|
600
|
-
|
|
593
|
+
return derKey.slice(index)
|
|
601
594
|
}
|
|
602
595
|
|
|
603
596
|
export const isRawCompressedPublicKey = (key: Uint8Array): boolean => key.length === 33 && (key[0] === 0x02 || key[0] === 0x03)
|
|
604
597
|
|
|
605
598
|
export const toRawCompressedHexPublicKey = (rawPublicKey: Uint8Array, keyType: TKeyType): string => {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
599
|
+
if (isRawCompressedPublicKey(rawPublicKey)) {
|
|
600
|
+
return hexStringFromUint8Array(rawPublicKey)
|
|
601
|
+
}
|
|
609
602
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
return u8a.toString(rawPublicKey, 'base16')
|
|
620
|
-
} else if (keyType === 'Ed25519') {
|
|
621
|
-
// Ed25519 keys are always in compressed form
|
|
622
|
-
return u8a.toString(rawPublicKey, 'base16')
|
|
603
|
+
if (keyType === 'Secp256k1' || keyType === 'Secp256r1') {
|
|
604
|
+
if (rawPublicKey[0] === 0x04 && rawPublicKey.length === 65) {
|
|
605
|
+
const xCoordinate = rawPublicKey.slice(1, 33)
|
|
606
|
+
const yCoordinate = rawPublicKey.slice(33)
|
|
607
|
+
const prefix = new Uint8Array([yCoordinate[31] % 2 === 0 ? 0x02 : 0x03])
|
|
608
|
+
const resultKey = hexStringFromUint8Array(new Uint8Array([...prefix, ...xCoordinate]))
|
|
609
|
+
logger.debug(`converted public key ${hexStringFromUint8Array(rawPublicKey)} to ${resultKey}`)
|
|
610
|
+
return resultKey
|
|
623
611
|
}
|
|
612
|
+
return u8a.toString(rawPublicKey, 'base16')
|
|
613
|
+
} else if (keyType === 'Ed25519') {
|
|
614
|
+
// Ed25519 keys are always in compressed form
|
|
615
|
+
return u8a.toString(rawPublicKey, 'base16')
|
|
616
|
+
}
|
|
624
617
|
|
|
625
|
-
|
|
618
|
+
throw new Error(`Unsupported key type: ${keyType}`)
|
|
626
619
|
}
|
|
627
620
|
|
|
628
621
|
export const hexStringFromUint8Array = (value: Uint8Array): string => u8a.toString(value, 'base16')
|
|
629
622
|
|
|
630
623
|
export const signatureAlgorithmFromKey = async (args: SignatureAlgorithmFromKeyArgs): Promise<JoseSignatureAlgorithm> => {
|
|
631
|
-
|
|
632
|
-
|
|
624
|
+
const { key } = args
|
|
625
|
+
return signatureAlgorithmFromKeyType({ type: key.type })
|
|
633
626
|
}
|
|
634
627
|
|
|
635
628
|
export const signatureAlgorithmFromKeyType = (args: SignatureAlgorithmFromKeyTypeArgs): JoseSignatureAlgorithm => {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
629
|
+
const { type } = args
|
|
630
|
+
switch (type) {
|
|
631
|
+
case 'Ed25519':
|
|
632
|
+
case 'X25519':
|
|
633
|
+
return JoseSignatureAlgorithm.EdDSA
|
|
634
|
+
case 'Secp256r1':
|
|
635
|
+
return JoseSignatureAlgorithm.ES256
|
|
636
|
+
case 'Secp384r1':
|
|
637
|
+
return JoseSignatureAlgorithm.ES384
|
|
638
|
+
case 'Secp521r1':
|
|
639
|
+
return JoseSignatureAlgorithm.ES512
|
|
640
|
+
case 'Secp256k1':
|
|
641
|
+
return JoseSignatureAlgorithm.ES256K
|
|
642
|
+
default:
|
|
643
|
+
throw new Error(`Key type '${type}' not supported`)
|
|
644
|
+
}
|
|
652
645
|
}
|
|
653
646
|
|
|
654
647
|
// TODO improve this conversion for jwt and jsonld, not a fan of current structure
|
|
655
648
|
export const keyTypeFromCryptographicSuite = (args: KeyTypeFromCryptographicSuiteArgs): TKeyType => {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
throw new Error(`Cryptographic suite '${crv}' not supported`)
|
|
649
|
+
const { crv, kty, alg } = args
|
|
650
|
+
|
|
651
|
+
switch (alg) {
|
|
652
|
+
case 'RSASSA-PSS':
|
|
653
|
+
case 'RS256':
|
|
654
|
+
case 'RS384':
|
|
655
|
+
case 'RS512':
|
|
656
|
+
case 'PS256':
|
|
657
|
+
case 'PS384':
|
|
658
|
+
case 'PS512':
|
|
659
|
+
return 'RSA'
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
switch (crv) {
|
|
663
|
+
case 'EdDSA':
|
|
664
|
+
case 'Ed25519':
|
|
665
|
+
case 'Ed25519Signature2018':
|
|
666
|
+
case 'Ed25519Signature2020':
|
|
667
|
+
case 'JcsEd25519Signature2020':
|
|
668
|
+
return 'Ed25519'
|
|
669
|
+
case 'JsonWebSignature2020':
|
|
670
|
+
case 'ES256':
|
|
671
|
+
case 'ECDSA':
|
|
672
|
+
case 'P-256':
|
|
673
|
+
return 'Secp256r1'
|
|
674
|
+
case 'ES384':
|
|
675
|
+
case 'P-384':
|
|
676
|
+
return 'Secp384r1'
|
|
677
|
+
case 'ES512':
|
|
678
|
+
case 'P-521':
|
|
679
|
+
return 'Secp521r1'
|
|
680
|
+
case 'EcdsaSecp256k1Signature2019':
|
|
681
|
+
case 'secp256k1':
|
|
682
|
+
case 'ES256K':
|
|
683
|
+
return 'Secp256k1'
|
|
684
|
+
}
|
|
685
|
+
if (kty) {
|
|
686
|
+
return kty as TKeyType
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
throw new Error(`Cryptographic suite '${crv}' not supported`)
|
|
698
690
|
}
|
|
699
691
|
|
|
700
692
|
export function removeNulls<T>(obj: T | any) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
693
|
+
Object.keys(obj).forEach((key) => {
|
|
694
|
+
if (obj[key] && typeof obj[key] === 'object') removeNulls(obj[key])
|
|
695
|
+
else if (obj[key] == null) delete obj[key]
|
|
696
|
+
})
|
|
697
|
+
return obj
|
|
706
698
|
}
|
|
707
699
|
|
|
708
|
-
|
|
709
700
|
/**
|
|
710
701
|
*
|
|
711
702
|
*/
|
|
712
703
|
export async function verifyRawSignature({
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
704
|
+
data,
|
|
705
|
+
signature,
|
|
706
|
+
key: inputKey,
|
|
707
|
+
opts,
|
|
708
|
+
}: {
|
|
709
|
+
data: Uint8Array
|
|
710
|
+
signature: Uint8Array
|
|
711
|
+
key: JWK
|
|
712
|
+
opts?: {
|
|
713
|
+
signatureAlg?: JoseSignatureAlgorithm
|
|
714
|
+
}
|
|
724
715
|
}) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
716
|
+
/**
|
|
717
|
+
* Converts a Base64URL-encoded JWK property to a BigInt.
|
|
718
|
+
* @param jwkProp - The Base64URL-encoded string.
|
|
719
|
+
* @returns The BigInt representation of the decoded value.
|
|
720
|
+
*/
|
|
721
|
+
function jwkPropertyToBigInt(jwkProp: string): bigint {
|
|
722
|
+
// Decode Base64URL to Uint8Array
|
|
723
|
+
const byteArray = u8a.fromString(jwkProp, 'base64url')
|
|
724
|
+
|
|
725
|
+
// Convert Uint8Array to hexadecimal string and then to BigInt
|
|
726
|
+
const hex = u8a.toString(byteArray, 'hex')
|
|
727
|
+
return BigInt(`0x${hex}`)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const key = removeNulls(inputKey)
|
|
731
|
+
validateJwk(key)
|
|
732
|
+
const keyType = keyTypeFromCryptographicSuite({ crv: key.crv, kty: key.kty, alg: key.alg })
|
|
733
|
+
const publicKeyHex = await jwkToRawHexKey(key)
|
|
734
|
+
|
|
735
|
+
// TODO: We really should look at the signature alg first if provided! From key type should be the last resort
|
|
736
|
+
switch (keyType) {
|
|
737
|
+
case 'Secp256k1':
|
|
738
|
+
return secp256k1.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true })
|
|
739
|
+
case 'Secp256r1':
|
|
740
|
+
return p256.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true })
|
|
741
|
+
case 'Secp384r1':
|
|
742
|
+
return p384.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true })
|
|
743
|
+
case 'Secp521r1':
|
|
744
|
+
return p521.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true })
|
|
745
|
+
case 'Ed25519':
|
|
746
|
+
return ed25519.verify(signature, data, u8a.fromString(publicKeyHex, 'hex'))
|
|
747
|
+
case 'Bls12381G1':
|
|
748
|
+
case 'Bls12381G2':
|
|
749
|
+
return bls12_381.verify(signature, data, u8a.fromString(publicKeyHex, 'hex'))
|
|
750
|
+
case 'RSA': {
|
|
751
|
+
const signatureAlgorithm = opts?.signatureAlg ?? JoseSignatureAlgorithm.PS256
|
|
752
|
+
const hashAlg =
|
|
753
|
+
signatureAlgorithm === (JoseSignatureAlgorithm.RS512 || JoseSignatureAlgorithm.PS512)
|
|
754
|
+
? sha512
|
|
755
|
+
: signatureAlgorithm === (JoseSignatureAlgorithm.RS384 || JoseSignatureAlgorithm.PS384)
|
|
756
|
+
? sha384
|
|
757
|
+
: sha256
|
|
758
|
+
switch (signatureAlgorithm) {
|
|
759
|
+
case JoseSignatureAlgorithm.RS256:
|
|
760
|
+
return rsa.PKCS1_SHA256.verify(
|
|
761
|
+
{
|
|
762
|
+
n: jwkPropertyToBigInt(key.n),
|
|
763
|
+
e: jwkPropertyToBigInt(key.e),
|
|
764
|
+
},
|
|
765
|
+
data,
|
|
766
|
+
signature
|
|
767
|
+
)
|
|
768
|
+
case JoseSignatureAlgorithm.RS384:
|
|
769
|
+
return rsa.PKCS1_SHA384.verify(
|
|
770
|
+
{
|
|
771
|
+
n: jwkPropertyToBigInt(key.n),
|
|
772
|
+
e: jwkPropertyToBigInt(key.e),
|
|
773
|
+
},
|
|
774
|
+
data,
|
|
775
|
+
signature
|
|
776
|
+
)
|
|
777
|
+
case JoseSignatureAlgorithm.RS512:
|
|
778
|
+
return rsa.PKCS1_SHA512.verify(
|
|
779
|
+
{
|
|
780
|
+
n: jwkPropertyToBigInt(key.n),
|
|
781
|
+
e: jwkPropertyToBigInt(key.e),
|
|
782
|
+
},
|
|
783
|
+
data,
|
|
784
|
+
signature
|
|
785
|
+
)
|
|
786
|
+
case JoseSignatureAlgorithm.PS256:
|
|
787
|
+
case JoseSignatureAlgorithm.PS384:
|
|
788
|
+
case JoseSignatureAlgorithm.PS512:
|
|
789
|
+
return rsa.PSS(hashAlg, rsa.mgf1(hashAlg)).verify(
|
|
790
|
+
{
|
|
791
|
+
n: jwkPropertyToBigInt(key.n),
|
|
792
|
+
e: jwkPropertyToBigInt(key.e),
|
|
793
|
+
},
|
|
794
|
+
data,
|
|
795
|
+
signature
|
|
796
|
+
)
|
|
797
|
+
}
|
|
790
798
|
}
|
|
791
|
-
|
|
799
|
+
}
|
|
800
|
+
throw Error(`Unsupported key type for signature validation: ${keyType}`)
|
|
792
801
|
}
|