@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/dist/conversion.d.ts.map +1 -1
- package/dist/conversion.js +3 -2
- package/dist/conversion.js.map +1 -1
- package/dist/functions.d.ts +16 -4
- package/dist/functions.d.ts.map +1 -1
- package/dist/functions.js +213 -29
- package/dist/functions.js.map +1 -1
- package/dist/jwk-jcs.d.ts +9 -0
- package/dist/jwk-jcs.d.ts.map +1 -1
- package/dist/jwk-jcs.js +5 -8
- package/dist/jwk-jcs.js.map +1 -1
- package/dist/types/key-util-types.d.ts +5 -3
- package/dist/types/key-util-types.d.ts.map +1 -1
- package/package.json +7 -3
- package/src/conversion.ts +5 -4
- package/src/functions.ts +620 -423
- package/src/jwk-jcs.ts +7 -8
- package/src/types/key-util-types.ts +5 -3
package/src/functions.ts
CHANGED
|
@@ -1,22 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import {
|
|
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 {
|
|
24
|
+
import {digestMethodParams} from './digest-methods'
|
|
25
|
+
import {validateJwk} from "./jwk-jcs";
|
|
11
26
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
args: IImportProvidedOrGeneratedKeyArgs & {
|
|
107
|
+
kms: string
|
|
108
|
+
},
|
|
109
|
+
context: IAgentContext<IKeyManager>
|
|
95
110
|
): Promise<IKey> {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
144
|
-
|
|
158
|
+
key: IKey | MinimalImportableKey | ManagedKeyInfo
|
|
159
|
+
digestAlgorithm?: 'sha256' | 'sha512'
|
|
145
160
|
}): string => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
key: IKey | MinimalImportableKey | ManagedKeyInfo,
|
|
221
|
+
opts?: {
|
|
222
|
+
use?: JwkKeyUse
|
|
223
|
+
noKidThumbprint?: boolean
|
|
224
|
+
}
|
|
210
225
|
): JWK => {
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
238
|
+
publicKeyHex: string,
|
|
239
|
+
type: TKeyType,
|
|
240
|
+
opts?: { use?: JwkKeyUse; key?: IKey | MinimalImportableKey; isPrivateKey?: boolean; noKidThumbprint?: boolean }
|
|
226
241
|
): JWK => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
461
|
+
publicKeyHex: string,
|
|
462
|
+
opts: {
|
|
463
|
+
use?: JwkKeyUse
|
|
464
|
+
crv: JoseCurve.Ed25519 | JoseCurve.X25519
|
|
465
|
+
}
|
|
367
466
|
): JWK => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
521
|
+
Secp256k1,
|
|
522
|
+
Secp256r1,
|
|
523
|
+
Ed25519,
|
|
411
524
|
}
|
|
412
525
|
|
|
413
526
|
const OID: Record<OIDType, Uint8Array> = {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
421
|
-
|
|
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
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
478
|
-
throw new Error('Invalid DER encoding: Bit string not found')
|
|
479
|
-
}
|
|
584
|
+
index = oidIndex + targetOid.length
|
|
480
585
|
|
|
481
|
-
|
|
482
|
-
|
|
586
|
+
while (index < derKey.length && derKey[index] !== 0x03) {
|
|
587
|
+
index++
|
|
588
|
+
}
|
|
483
589
|
|
|
484
|
-
|
|
485
|
-
|
|
590
|
+
if (index >= derKey.length) {
|
|
591
|
+
throw new Error('Invalid DER encoding: Bit string not found')
|
|
592
|
+
}
|
|
486
593
|
|
|
487
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
606
|
+
if (isRawCompressedPublicKey(rawPublicKey)) {
|
|
607
|
+
return hexStringFromUint8Array(rawPublicKey)
|
|
608
|
+
}
|
|
496
609
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
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
|
-
|
|
519
|
-
|
|
631
|
+
const {key} = args
|
|
632
|
+
return signatureAlgorithmFromKeyType({type: key.type})
|
|
520
633
|
}
|
|
521
634
|
|
|
522
635
|
export const signatureAlgorithmFromKeyType = (args: SignatureAlgorithmFromKeyTypeArgs): JoseSignatureAlgorithm => {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
}
|