@sphereon/ssi-sdk.kms-rest 0.36.1-next.11 → 0.36.1-next.115

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.
@@ -1,4 +1,16 @@
1
- import { calculateJwkThumbprint, toJwk, x25519PublicHexFromPrivateHex, type X509Opts } from '@sphereon/ssi-sdk-ext.key-utils'
1
+ import {
2
+ base64ToBase64Url,
3
+ calculateJwkThumbprint,
4
+ isHashString,
5
+ joseAlgorithmToDigest,
6
+ jwkToRawHexKey,
7
+ shaHasher,
8
+ signatureAlgorithmFromKeyType,
9
+ signatureAlgorithmToJoseAlgorithm,
10
+ toJwk,
11
+ x25519PublicHexFromPrivateHex,
12
+ type X509Opts,
13
+ } from '@sphereon/ssi-sdk-ext.key-utils'
2
14
  import { hexToPEM, jwkToPEM, pemCertChainTox5c, PEMToHex, PEMToJwk } from '@sphereon/ssi-sdk-ext.x509-utils'
3
15
  import type { ManagedKeyInfo as RestManagedKeyInfo } from '@sphereon/ssi-sdk.kms-rest-client'
4
16
  import {
@@ -27,13 +39,17 @@ interface KeyManagementSystemOptions {
27
39
  applicationId: string
28
40
  baseUrl: string
29
41
  providerId?: string
42
+ tenantId?: string
43
+ userId?: string
30
44
  authOpts?: RestClientAuthenticationOpts
31
45
  }
32
46
 
33
47
  export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
34
48
  private client: KmsRestClient
35
49
  private readonly id: string
36
- private providerId: string | undefined
50
+ private readonly providerId: string | undefined
51
+ private readonly tenantId: string | undefined
52
+ private readonly userId: string | undefined
37
53
 
38
54
  constructor(options: KeyManagementSystemOptions) {
39
55
  super()
@@ -45,18 +61,26 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
45
61
 
46
62
  this.id = options.applicationId
47
63
  this.providerId = options.providerId
64
+ this.tenantId = options.tenantId
65
+ this.userId = options.userId
48
66
  this.client = new KmsRestClient(config)
49
67
  }
50
68
 
51
69
  async createKey(args: CreateKeyArgs): Promise<ManagedKeyInfo> {
52
70
  const { type, meta } = args
53
71
 
54
- const signatureAlgorithm = this.mapKeyTypeToSignatureAlgorithm(type)
72
+ const joseAlg = signatureAlgorithmFromKeyType({
73
+ type,
74
+ algorithms: meta?.algorithms as string[] | undefined,
75
+ })
76
+ const signatureAlgorithm = this.mapJoseToRestSignatureAlgorithm(joseAlg)
55
77
  const options = {
56
78
  use: meta && 'keyUsage' in meta ? this.mapKeyUsage(meta.keyUsage) : JwkUse.Sig,
57
79
  alg: signatureAlgorithm,
58
80
  keyOperations: meta && meta.keyOperations ? this.mapKeyOperations(meta.keyOperations as string[]) : [KeyOperations.Sign],
59
81
  ...(meta && 'keyAlias' in meta && meta.keyAlias ? { alias: meta.keyAlias } : {}),
82
+ ...(this.tenantId && { tenantId: this.tenantId }),
83
+ ...(this.userId && { userId: this.userId }),
60
84
  }
61
85
 
62
86
  const key = this.providerId
@@ -68,7 +92,7 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
68
92
 
69
93
  const jwk = {
70
94
  ...key.keyPair.jose.publicJwk,
71
- alg: key.keyPair.jose.publicJwk.alg ? this.mapJoseAlgorithm(key.keyPair.jose.publicJwk.alg) : undefined,
95
+ alg: key.keyPair.jose.publicJwk.alg ? signatureAlgorithmToJoseAlgorithm(key.keyPair.jose.publicJwk.alg) : undefined,
72
96
  } satisfies JWK
73
97
 
74
98
  const kid = key.keyPair.kid ?? key.keyPair.jose.publicJwk.kid
@@ -85,7 +109,7 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
85
109
  algorithms: [key.keyPair.jose.publicJwk.alg ?? 'PS256'],
86
110
  jwkThumbprint: calculateJwkThumbprint({
87
111
  jwk,
88
- digestAlgorithm: this.signatureAlgorithmToDigestAlgorithm(signatureAlgorithm),
112
+ digestAlgorithm: jwk.alg ? joseAlgorithmToDigest(jwk.alg) : 'sha256',
89
113
  }),
90
114
  },
91
115
  publicKeyHex: Buffer.from(key.keyPair.jose.publicJwk.toString(), 'utf8').toString('base64'),
@@ -94,15 +118,20 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
94
118
 
95
119
  async importKey(args: ImportKeyArgs): Promise<ManagedKeyInfo> {
96
120
  const { type } = args
97
- const signatureAlgorithm = this.mapKeyTypeToSignatureAlgorithm(type)
98
121
  const importKey = this.mapImportKey(args)
99
122
 
100
123
  const result = this.providerId
101
124
  ? await this.client.methods.kmsClientProviderStoreKey({
102
125
  ...importKey.key,
103
126
  providerId: this.providerId,
127
+ ...(this.tenantId && { tenantId: this.tenantId }),
128
+ ...(this.userId && { userId: this.userId }),
129
+ })
130
+ : await this.client.methods.kmsClientStoreKey({
131
+ ...importKey.key,
132
+ ...(this.tenantId && { tenantId: this.tenantId }),
133
+ ...(this.userId && { userId: this.userId }),
104
134
  })
105
- : await this.client.methods.kmsClientStoreKey(importKey.key)
106
135
 
107
136
  return {
108
137
  kid: importKey.kid,
@@ -113,7 +142,7 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
113
142
  algorithms: [result.keyInfo.key.alg ?? 'PS256'],
114
143
  jwkThumbprint: calculateJwkThumbprint({
115
144
  jwk: importKey.publicKeyJwk,
116
- digestAlgorithm: this.signatureAlgorithmToDigestAlgorithm(signatureAlgorithm),
145
+ digestAlgorithm: importKey.publicKeyJwk.alg ? joseAlgorithmToDigest(importKey.publicKeyJwk.alg) : 'sha256',
117
146
  }),
118
147
  },
119
148
  publicKeyHex: Buffer.from(result.keyInfo.key.toString(), 'utf8').toString('base64'),
@@ -127,53 +156,68 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
127
156
  ? await this.client.methods.kmsClientProviderDeleteKey({
128
157
  aliasOrKid: kid,
129
158
  providerId: this.providerId,
159
+ ...(this.tenantId && { tenantId: this.tenantId }),
160
+ ...(this.userId && { userId: this.userId }),
161
+ })
162
+ : await this.client.methods.kmsClientDeleteKey({
163
+ aliasOrKid: kid,
164
+ ...(this.tenantId && { tenantId: this.tenantId }),
165
+ ...(this.userId && { userId: this.userId }),
130
166
  })
131
- : await this.client.methods.kmsClientDeleteKey({ aliasOrKid: kid })
132
167
  }
133
168
 
134
169
  async listKeys(): Promise<ManagedKeyInfo[]> {
135
170
  const keys = this.providerId
136
- ? await this.client.methods.kmsClientProviderListKeys({ providerId: this.providerId })
137
- : await this.client.methods.kmsClientListKeys()
171
+ ? await this.client.methods.kmsClientProviderListKeys({
172
+ providerId: this.providerId,
173
+ ...(this.tenantId && { tenantId: this.tenantId }),
174
+ ...(this.userId && { userId: this.userId }),
175
+ })
176
+ : await this.client.methods.kmsClientListKeys({
177
+ ...(this.tenantId && { tenantId: this.tenantId }),
178
+ ...(this.userId && { userId: this.userId }),
179
+ })
138
180
 
139
181
  const restKeys = ListKeysResponseToJSONTyped(keys, false).keyInfos
140
182
 
141
- return restKeys.map((restKey: RestManagedKeyInfo) => {
142
- const jwk = restKey.key
143
- let publicKeyHex = ''
144
-
145
- // Derive publicKeyHex from JWK based on key type
146
- if (jwk.kty === 'EC') {
147
- publicKeyHex = jwk.x || ''
148
- } else if (jwk.kty === 'RSA') {
149
- publicKeyHex = jwk.n || ''
150
- } else if (jwk.kty === 'OKP') {
151
- publicKeyHex = jwk.x || ''
152
- }
153
-
154
- const keyType = this.mapRestKeyTypeToTKeyType(restKey.keyType)
155
-
156
- return {
157
- kid: restKey.kid || restKey.alias,
158
- kms: this.id,
159
- type: keyType,
160
- publicKeyHex,
161
- meta: {
162
- algorithms: restKey.signatureAlgorithm ? [restKey.signatureAlgorithm] : undefined,
163
- jwk,
164
- jwkThumbprint: calculateJwkThumbprint({
165
- jwk: jwk as JWK,
166
- digestAlgorithm: restKey.signatureAlgorithm ? this.signatureAlgorithmToDigestAlgorithm(restKey.signatureAlgorithm) : 'sha256',
167
- }),
168
- alias: restKey.alias,
169
- providerId: restKey.providerId,
170
- x5c: restKey.x5c,
171
- keyVisibility: restKey.keyVisibility,
172
- keyEncoding: restKey.keyEncoding,
173
- ...restKey.opts,
174
- },
175
- } satisfies ManagedKeyInfo
176
- })
183
+ const results = await Promise.allSettled(
184
+ restKeys.map(async (restKey: RestManagedKeyInfo) => {
185
+ const jwk = restKey.key
186
+ const publicKeyHex = await jwkToRawHexKey(jwk as JWK)
187
+ const keyType = this.mapRestKeyTypeToTKeyType(restKey.keyType)
188
+
189
+ return {
190
+ kid: restKey.kid || restKey.alias,
191
+ kms: this.id,
192
+ type: keyType,
193
+ publicKeyHex,
194
+ meta: {
195
+ algorithms: restKey.signatureAlgorithm ? [restKey.signatureAlgorithm] : undefined,
196
+ jwk,
197
+ jwkThumbprint: calculateJwkThumbprint({
198
+ jwk: jwk as JWK,
199
+ digestAlgorithm: restKey.key.alg ? joseAlgorithmToDigest(restKey.key.alg) : 'sha256',
200
+ }),
201
+ alias: restKey.alias,
202
+ providerId: restKey.providerId,
203
+ x5c: restKey.x5c,
204
+ keyVisibility: restKey.keyVisibility,
205
+ keyEncoding: restKey.keyEncoding,
206
+ ...restKey.opts,
207
+ },
208
+ } satisfies ManagedKeyInfo
209
+ }),
210
+ )
211
+
212
+ return results
213
+ .filter((result): result is PromiseFulfilledResult<ManagedKeyInfo> => {
214
+ if (result.status === 'rejected') {
215
+ console.warn('Failed to process key in listKeys:', result.reason)
216
+ return false
217
+ }
218
+ return true
219
+ })
220
+ .map((result) => result.value)
177
221
  }
178
222
 
179
223
  private mapRestKeyTypeToTKeyType(keyType: string | undefined): TKeyType {
@@ -195,35 +239,84 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
195
239
  }
196
240
 
197
241
  async sign(args: SignArgs): Promise<string> {
198
- const { keyRef, data } = args
242
+ const { keyRef, data, algorithm = 'SHA-256' } = args
199
243
  const key = this.providerId
200
244
  ? await this.client.methods.kmsClientProviderGetKey({
201
245
  aliasOrKid: keyRef.kid,
202
246
  providerId: this.providerId,
247
+ ...(this.tenantId && { tenantId: this.tenantId }),
248
+ ...(this.userId && { userId: this.userId }),
249
+ })
250
+ : await this.client.methods.kmsClientGetKey({
251
+ aliasOrKid: keyRef.kid,
252
+ ...(this.tenantId && { tenantId: this.tenantId }),
253
+ ...(this.userId && { userId: this.userId }),
203
254
  })
204
- : await this.client.methods.kmsClientGetKey({ aliasOrKid: keyRef.kid })
255
+
256
+ // Check if this is an EdDSA/Ed25519 key - these algorithms MUST sign the raw message, not a hash
257
+ const keyAlg = key.keyInfo.key.alg
258
+ const isEdDSA = keyAlg === 'EdDSA' || keyAlg === 'ED25519' || key.keyInfo.key.crv === 'Ed25519'
259
+
260
+ let dataToBeSigned: Uint8Array
261
+ if (isEdDSA) {
262
+ // EdDSA signatures are computed over the raw message (PureEdDSA)
263
+ // The algorithm internally handles hashing with SHA-512
264
+ dataToBeSigned = data
265
+ } else {
266
+ // For other algorithms (RSA, ECDSA), hash the data before signing
267
+ // with remote signing we are not going to send the whole data over the network, we need to hash it (unless we already get a hash)
268
+ dataToBeSigned = isHashString(data)
269
+ ? data
270
+ : shaHasher(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength), algorithm)
271
+ }
205
272
 
206
273
  const signingResult = await this.client.methods.kmsClientCreateRawSignature({
207
274
  keyInfo: key.keyInfo,
208
- input: toString(data, 'base64'),
275
+ input: toString(dataToBeSigned, 'base64'),
276
+ ...(this.tenantId && { tenantId: this.tenantId }),
277
+ ...(this.userId && { userId: this.userId }),
209
278
  })
210
279
 
211
- return signingResult.signature
280
+ return base64ToBase64Url(signingResult.signature)
212
281
  }
213
282
 
214
283
  async verify(args: VerifyArgs): Promise<boolean> {
215
- const { keyRef, data, signature } = args
284
+ const { keyRef, data, signature, algorithm = 'SHA-256' } = args
216
285
  const key = this.providerId
217
286
  ? await this.client.methods.kmsClientProviderGetKey({
218
287
  aliasOrKid: keyRef.kid,
219
288
  providerId: this.providerId,
289
+ ...(this.tenantId && { tenantId: this.tenantId }),
290
+ ...(this.userId && { userId: this.userId }),
220
291
  })
221
- : await this.client.methods.kmsClientGetKey({ aliasOrKid: keyRef.kid })
292
+ : await this.client.methods.kmsClientGetKey({
293
+ aliasOrKid: keyRef.kid,
294
+ ...(this.tenantId && { tenantId: this.tenantId }),
295
+ ...(this.userId && { userId: this.userId }),
296
+ })
297
+
298
+ // Check if this is an EdDSA/Ed25519 key - these algorithms MUST verify the raw message, not a hash
299
+ const keyAlg = key.keyInfo.key.alg
300
+ const isEdDSA = keyAlg === 'EdDSA' || keyAlg === 'ED25519' || key.keyInfo.key.crv === 'Ed25519'
301
+
302
+ let dataToBeVerified: Uint8Array
303
+ if (isEdDSA) {
304
+ // EdDSA signatures are verified over the raw message (PureEdDSA)
305
+ dataToBeVerified = data
306
+ } else {
307
+ // For other algorithms (RSA, ECDSA), hash the data before verifying
308
+ // with remote signing we are not going to send the whole data over the network, we need to hash it (unless we already get a hash)
309
+ dataToBeVerified = isHashString(data)
310
+ ? data
311
+ : shaHasher(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength), algorithm)
312
+ }
222
313
 
223
314
  const verification = await this.client.methods.kmsClientIsValidRawSignature({
224
315
  keyInfo: key.keyInfo,
225
- input: toString(data, 'base64'),
316
+ input: toString(dataToBeVerified, 'base64'),
226
317
  signature,
318
+ ...(this.tenantId && { tenantId: this.tenantId }),
319
+ ...(this.userId && { userId: this.userId }),
227
320
  })
228
321
 
229
322
  return verification.isValid
@@ -233,23 +326,6 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
233
326
  throw new Error('sharedSecret is not implemented for REST KMS.')
234
327
  }
235
328
 
236
- private signatureAlgorithmToDigestAlgorithm = (signatureAlgorithm: SignatureAlgorithm): 'sha256' | 'sha512' => {
237
- switch (signatureAlgorithm) {
238
- case SignatureAlgorithm.EcdsaSha256:
239
- case SignatureAlgorithm.RsaSsaPssSha256Mgf1:
240
- case SignatureAlgorithm.EckaDhSha256:
241
- case SignatureAlgorithm.HmacSha256:
242
- case SignatureAlgorithm.Es256K:
243
- return 'sha256'
244
- case SignatureAlgorithm.EcdsaSha512:
245
- case SignatureAlgorithm.HmacSha512:
246
- case SignatureAlgorithm.RsaSsaPssSha512Mgf1:
247
- return 'sha512'
248
- default:
249
- throw new Error(`Signature algorithm ${signatureAlgorithm} is not supported by REST KMS`)
250
- }
251
- }
252
-
253
329
  private mapKeyUsage = (usage: string): JwkUse => {
254
330
  switch (usage) {
255
331
  case 'sig':
@@ -261,53 +337,32 @@ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
261
337
  }
262
338
  }
263
339
 
264
- private mapKeyTypeToSignatureAlgorithm = (type: TKeyType): SignatureAlgorithm => {
265
- switch (type) {
266
- case 'Secp256r1':
267
- return SignatureAlgorithm.EcdsaSha256
268
- case 'RSA':
269
- return SignatureAlgorithm.RsaSsaPssSha256Mgf1
270
- case 'X25519':
271
- return SignatureAlgorithm.EckaDhSha256
272
- default:
273
- throw new Error(`Key type ${type} is not supported by REST KMS`)
274
- }
275
- }
276
-
277
- private mapJoseAlgorithm = (alg: string): JoseSignatureAlgorithm => {
340
+ private mapJoseToRestSignatureAlgorithm = (alg: JoseSignatureAlgorithm): SignatureAlgorithm => {
278
341
  switch (alg) {
279
- case 'RS256':
280
- return JoseSignatureAlgorithm.RS256
281
- case 'RS384':
282
- return JoseSignatureAlgorithm.RS384
283
- case 'RS512':
284
- return JoseSignatureAlgorithm.RS512
285
- case 'ES256':
286
- return JoseSignatureAlgorithm.ES256
287
- case 'ES256K':
288
- return JoseSignatureAlgorithm.ES256K
289
- case 'ES384':
290
- return JoseSignatureAlgorithm.ES384
291
- case 'ES512':
292
- return JoseSignatureAlgorithm.ES512
293
- case 'EdDSA':
294
- return JoseSignatureAlgorithm.EdDSA
295
- case 'HS256':
296
- return JoseSignatureAlgorithm.HS256
297
- case 'HS384':
298
- return JoseSignatureAlgorithm.HS384
299
- case 'HS512':
300
- return JoseSignatureAlgorithm.HS512
301
- case 'PS256':
302
- return JoseSignatureAlgorithm.PS256
303
- case 'PS384':
304
- return JoseSignatureAlgorithm.PS384
305
- case 'PS512':
306
- return JoseSignatureAlgorithm.PS512
307
- case 'none':
308
- return JoseSignatureAlgorithm.none
342
+ case JoseSignatureAlgorithm.RS256:
343
+ return SignatureAlgorithm.RsaSha256
344
+ case JoseSignatureAlgorithm.RS384:
345
+ return SignatureAlgorithm.RsaSha384
346
+ case JoseSignatureAlgorithm.RS512:
347
+ return SignatureAlgorithm.RsaSha512
348
+ case JoseSignatureAlgorithm.PS256:
349
+ return SignatureAlgorithm.RsaSsaPssSha256Mgf1
350
+ case JoseSignatureAlgorithm.PS384:
351
+ return SignatureAlgorithm.RsaSsaPssSha384Mgf1
352
+ case JoseSignatureAlgorithm.PS512:
353
+ return SignatureAlgorithm.RsaSsaPssSha512Mgf1
354
+ case JoseSignatureAlgorithm.ES256:
355
+ return SignatureAlgorithm.EcdsaSha256
356
+ case JoseSignatureAlgorithm.ES384:
357
+ return SignatureAlgorithm.EcdsaSha384
358
+ case JoseSignatureAlgorithm.ES512:
359
+ return SignatureAlgorithm.EcdsaSha512
360
+ case JoseSignatureAlgorithm.ES256K:
361
+ return SignatureAlgorithm.Es256K
362
+ case JoseSignatureAlgorithm.EdDSA:
363
+ return SignatureAlgorithm.Ed25519
309
364
  default:
310
- throw new Error(`Signature algorithm ${alg} is not supported by REST KMS`)
365
+ throw new Error(`JOSE algorithm ${alg} not supported by REST KMS`)
311
366
  }
312
367
  }
313
368
 
@@ -15,6 +15,7 @@ export type CreateKeyArgs = {
15
15
  export type SignArgs = {
16
16
  keyRef: Pick<IKey, 'kid'>
17
17
  data: Uint8Array
18
+ algorithm?: string
18
19
  [x: string]: any
19
20
  }
20
21
 
@@ -22,6 +23,7 @@ export type VerifyArgs = {
22
23
  keyRef: Pick<IKey, 'kid'>
23
24
  data: Uint8Array
24
25
  signature: string
26
+ algorithm?: string
25
27
  [x: string]: any
26
28
  }
27
29