@sphereon/ssi-sdk-ext.key-utils 0.26.1-next.11 → 0.26.1-next.14

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