@sphereon/ssi-sdk.kms-rest 0.34.1-feature.IDK.11.294

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.
@@ -0,0 +1,382 @@
1
+ import type { ManagedKeyInfo, TKeyType } from '@veramo/core'
2
+ import { AbstractKeyManagementSystem } from '@veramo/key-manager'
3
+ import {
4
+ calculateJwkThumbprint,
5
+ toJwk,
6
+ x25519PublicHexFromPrivateHex,
7
+ type X509Opts
8
+ } from '@sphereon/ssi-sdk-ext.key-utils'
9
+ import {
10
+ CurveFromJSONTyped,
11
+ JwkKeyTypeFromJSONTyped,
12
+ JwkUse,
13
+ JwkUseFromJSONTyped,
14
+ KeyOperations,
15
+ KmsRestClient,
16
+ ListKeysResponseToJSONTyped,
17
+ type RestClientAuthenticationOpts,
18
+ SignatureAlgorithm,
19
+ type StoreKey
20
+ } from '@sphereon/ssi-sdk.kms-rest-client'
21
+ import {
22
+ hexToPEM,
23
+ jwkToPEM,
24
+ pemCertChainTox5c,
25
+ PEMToHex,
26
+ PEMToJwk
27
+ } from '@sphereon/ssi-sdk-ext.x509-utils'
28
+ import { JoseSignatureAlgorithm, type JWK } from '@sphereon/ssi-types'
29
+ import elliptic from 'elliptic'
30
+ // @ts-ignore
31
+ import * as u8a from 'uint8arrays'
32
+ import type {
33
+ CreateKeyArgs,
34
+ DeleteKeyArgs,
35
+ ImportKeyArgs,
36
+ MapImportKeyArgs,
37
+ MappedImportKey,
38
+ SharedSecretArgs,
39
+ SignArgs,
40
+ VerifyArgs
41
+ } from './types'
42
+
43
+ const { fromString, toString } = u8a
44
+
45
+ interface AbstractKeyManagementSystemOptions {
46
+ applicationId: string
47
+ baseUrl: string
48
+ authOpts?: RestClientAuthenticationOpts
49
+ }
50
+
51
+ export class RestKeyManagementSystem extends AbstractKeyManagementSystem {
52
+ private client: KmsRestClient
53
+ private readonly id: string
54
+
55
+ constructor(options: AbstractKeyManagementSystemOptions) {
56
+ super()
57
+
58
+ const config = {
59
+ baseUrl: options.baseUrl,
60
+ authOpts: options.authOpts
61
+ }
62
+
63
+ this.id = options.applicationId
64
+ this.client = new KmsRestClient(config)
65
+ }
66
+
67
+ async createKey(args: CreateKeyArgs): Promise<ManagedKeyInfo> {
68
+ const { type, meta } = args
69
+
70
+ const signatureAlgorithm = this.mapKeyTypeToSignatureAlgorithm(type)
71
+ const options = {
72
+ use: meta && 'keyUsage' in meta ? this.mapKeyUsage(meta.keyUsage) : JwkUse.Sig,
73
+ alg: signatureAlgorithm,
74
+ keyOperations: meta ? this.mapKeyOperations(meta.keyOperations as string[]) : [KeyOperations.Sign]
75
+ }
76
+
77
+ const key = await this.client.methods.kmsClientGenerateKey(options)
78
+
79
+ const jwk = {
80
+ ...key.keyPair.jose.publicJwk,
81
+ alg: key.keyPair.jose.publicJwk.alg ? this.mapJoseAlgorithm(key.keyPair.jose.publicJwk.alg) : undefined,
82
+ } satisfies JWK
83
+
84
+ const kid = key.keyPair.kid ?? key.keyPair.jose.publicJwk.kid
85
+ if (!kid) {
86
+ throw new Error(`No kid present in key`)
87
+ }
88
+
89
+ return {
90
+ kid,
91
+ kms: this.id,
92
+ type,
93
+ meta: {
94
+ alias: key.keyPair.kid,
95
+ algorithms: [key.keyPair.jose.publicJwk.alg ?? 'PS256'],
96
+ jwkThumbprint: calculateJwkThumbprint({
97
+ jwk,
98
+ digestAlgorithm: this.signatureAlgorithmToDigestAlgorithm(signatureAlgorithm),
99
+ }),
100
+ },
101
+ publicKeyHex: Buffer.from(key.keyPair.jose.publicJwk.toString(), 'utf8').toString('base64'),
102
+ }
103
+ }
104
+
105
+ async importKey(args: ImportKeyArgs): Promise<ManagedKeyInfo> {
106
+ const { type } = args
107
+ const signatureAlgorithm = this.mapKeyTypeToSignatureAlgorithm(type)
108
+ const importKey = this.mapImportKey(args)
109
+
110
+ const result = await this.client.methods.kmsClientStoreKey(importKey.key)
111
+
112
+ return {
113
+ kid: importKey.kid,
114
+ kms: this.id,
115
+ type,
116
+ meta: {
117
+ alias: importKey.kid,
118
+ algorithms: [result.keyInfo.key.alg ?? 'PS256'],
119
+ jwkThumbprint: calculateJwkThumbprint({
120
+ jwk: importKey.publicKeyJwk,
121
+ digestAlgorithm: this.signatureAlgorithmToDigestAlgorithm(signatureAlgorithm),
122
+ }),
123
+ },
124
+ publicKeyHex: Buffer.from(result.keyInfo.key.toString(), 'utf8').toString('base64'),
125
+ }
126
+ }
127
+
128
+ async deleteKey(args: DeleteKeyArgs): Promise<boolean> {
129
+ const { kid } = args
130
+
131
+ return await this.client.methods.kmsClientDeleteKey({ aliasOrKid: kid })
132
+ }
133
+
134
+ async listKeys(): Promise<ManagedKeyInfo[]> {
135
+ const keys = await this.client.methods.kmsClientListKeys()
136
+
137
+ return ListKeysResponseToJSONTyped(keys, false).keyInfos //ListKeysResponseFromJSONTyped
138
+ }
139
+
140
+ async sign(args: SignArgs): Promise<string> {
141
+ const { keyRef, data } = args
142
+ const key = await this.client.methods.kmsClientGetKey({ aliasOrKid: keyRef.kid })
143
+ const signingResult = await this.client.methods.kmsClientCreateRawSignature({
144
+ keyInfo: key.keyInfo,
145
+ input: toString(data, 'base64')
146
+ })
147
+
148
+ return signingResult.signature
149
+ }
150
+
151
+ async verify(args: VerifyArgs): Promise<boolean> {
152
+ const { keyRef, data, signature } = args
153
+ const key = await this.client.methods.kmsClientGetKey({ aliasOrKid: keyRef.kid })
154
+ const verification = await this.client.methods.kmsClientIsValidRawSignature({
155
+ keyInfo: key.keyInfo,
156
+ input: toString(data, 'base64'),
157
+ signature
158
+ })
159
+
160
+ return verification.isValid
161
+ }
162
+
163
+ async sharedSecret(args: SharedSecretArgs): Promise<string> {
164
+ throw new Error('sharedSecret is not implemented for REST KMS.')
165
+ }
166
+
167
+ private signatureAlgorithmToDigestAlgorithm = (signatureAlgorithm: SignatureAlgorithm): 'sha256' | 'sha512' => {
168
+ switch (signatureAlgorithm) {
169
+ case SignatureAlgorithm.EcdsaSha256:
170
+ case SignatureAlgorithm.RsaSsaPssSha256Mgf1:
171
+ case SignatureAlgorithm.EckaDhSha256:
172
+ case SignatureAlgorithm.HmacSha256:
173
+ case SignatureAlgorithm.Es256K:
174
+ return 'sha256'
175
+ case SignatureAlgorithm.EcdsaSha512:
176
+ case SignatureAlgorithm.HmacSha512:
177
+ case SignatureAlgorithm.RsaSsaPssSha512Mgf1:
178
+ return 'sha512'
179
+ default:
180
+ throw new Error(`Signature algorithm ${signatureAlgorithm} is not supported by REST KMS`)
181
+ }
182
+ }
183
+
184
+ private mapKeyUsage = (usage: string): JwkUse => {
185
+ switch (usage) {
186
+ case 'sig':
187
+ return JwkUse.Sig
188
+ case 'enc':
189
+ return JwkUse.Enc
190
+ default:
191
+ throw new Error(`Key usage ${usage} is not supported by REST KMS`)
192
+ }
193
+ }
194
+
195
+ private mapKeyTypeToSignatureAlgorithm = (type: TKeyType): SignatureAlgorithm => {
196
+ switch (type) {
197
+ case 'Secp256r1':
198
+ return SignatureAlgorithm.EcdsaSha256
199
+ case 'RSA':
200
+ return SignatureAlgorithm.RsaSsaPssSha256Mgf1
201
+ case 'X25519':
202
+ return SignatureAlgorithm.EckaDhSha256
203
+ default:
204
+ throw new Error(`Key type ${type} is not supported by REST KMS`)
205
+ }
206
+ }
207
+
208
+ private mapJoseAlgorithm = (alg: string): JoseSignatureAlgorithm => {
209
+ switch (alg) {
210
+ case 'RS256': return JoseSignatureAlgorithm.RS256;
211
+ case 'RS384': return JoseSignatureAlgorithm.RS384;
212
+ case 'RS512': return JoseSignatureAlgorithm.RS512;
213
+ case 'ES256': return JoseSignatureAlgorithm.ES256;
214
+ case 'ES256K': return JoseSignatureAlgorithm.ES256K;
215
+ case 'ES384': return JoseSignatureAlgorithm.ES384;
216
+ case 'ES512': return JoseSignatureAlgorithm.ES512;
217
+ case 'EdDSA': return JoseSignatureAlgorithm.EdDSA;
218
+ case 'HS256': return JoseSignatureAlgorithm.HS256;
219
+ case 'HS384': return JoseSignatureAlgorithm.HS384;
220
+ case 'HS512': return JoseSignatureAlgorithm.HS512;
221
+ case 'PS256': return JoseSignatureAlgorithm.PS256;
222
+ case 'PS384': return JoseSignatureAlgorithm.PS384;
223
+ case 'PS512': return JoseSignatureAlgorithm.PS512;
224
+ case 'none': return JoseSignatureAlgorithm.none;
225
+ default:
226
+ throw new Error(`Signature algorithm ${alg} is not supported by REST KMS`)
227
+ }
228
+ }
229
+
230
+ private mapKeyOperation = (operation: string): KeyOperations => {
231
+ switch (operation) {
232
+ case 'sign':
233
+ return KeyOperations.Sign
234
+ case 'verify':
235
+ return KeyOperations.Verify
236
+ case 'wrapKey':
237
+ return KeyOperations.WrapKey
238
+ case 'deriveKey':
239
+ return KeyOperations.DeriveKey
240
+ case 'unwrapKey':
241
+ return KeyOperations.UnwrapKey
242
+ case 'decrypt':
243
+ return KeyOperations.Decrypt
244
+ case 'deriveBits':
245
+ return KeyOperations.DeriveBits
246
+ case 'encrypt':
247
+ return KeyOperations.Encrypt
248
+ default:
249
+ throw new Error(`Key operation ${operation} is not supported by REST KMS`)
250
+ }
251
+ }
252
+
253
+ private mapKeyOperations = (operations: string[]): KeyOperations[] => {
254
+ return operations.map((operation) => this.mapKeyOperation(operation))
255
+ }
256
+
257
+ private mapImportRsaKey = (args: MapImportKeyArgs): MappedImportKey => {
258
+ const x509 = args.meta?.x509 as X509Opts
259
+ const privateKeyPEM = x509?.privateKeyPEM ?? (args.privateKeyHex.includes('---')
260
+ ? args.privateKeyHex
261
+ : hexToPEM(args.privateKeyHex, 'private')
262
+ ) // In case we have x509 opts, the private key hex really was a PEM already (yuck)
263
+ const publicKeyJwk = PEMToJwk(privateKeyPEM, 'public')
264
+ const privateKeyJwk = PEMToJwk(privateKeyPEM)
265
+ const publicKeyPEM = jwkToPEM(publicKeyJwk, 'public')
266
+ const publicKeyHex = PEMToHex(publicKeyPEM)
267
+
268
+ const meta = {} as any
269
+ if (x509) {
270
+ meta.x509 = {
271
+ cn: x509.cn ?? args.kid ?? publicKeyHex,
272
+ }
273
+ let certChain: string = x509.certificateChainPEM ?? ''
274
+ if (x509.certificatePEM) {
275
+ if (!certChain.includes(x509.certificatePEM)) {
276
+ certChain = `${x509.certificatePEM}\n${certChain}`
277
+ }
278
+ }
279
+ if (certChain.length > 0) {
280
+ meta.x509.certificateChainPEM = certChain
281
+ const x5c = pemCertChainTox5c(certChain)
282
+ if (!x509.certificateChainURL) {
283
+ // Do not put the chain in the JWK when the chain is hosted. We do put it in the x509 metadata
284
+ // @ts-ignore
285
+ publicKeyJwk.x5c = x5c
286
+ }
287
+ meta.x509.x5c = x5c
288
+ }
289
+ if (x509.certificateChainURL) {
290
+ // @ts-ignore
291
+ publicKeyJwk.x5u = x509.certificateChainURL
292
+ meta.x509.x5u = x509.certificateChainURL
293
+ }
294
+ }
295
+
296
+ const kid = args.kid ?? meta?.x509?.cn ?? publicKeyHex
297
+ return {
298
+ kid,
299
+ publicKeyJwk: publicKeyJwk as JWK,
300
+ key: {
301
+ keyInfo: {
302
+ key: {
303
+ ...privateKeyJwk,
304
+ kid,
305
+ kty: JwkKeyTypeFromJSONTyped(privateKeyJwk.kty, false),
306
+ use: JwkUseFromJSONTyped(privateKeyJwk.use, false),
307
+ crv: CurveFromJSONTyped(privateKeyJwk.crv, false),
308
+ },
309
+ },
310
+ certChain: meta.x509.x5c
311
+ } satisfies StoreKey
312
+ }
313
+ }
314
+
315
+ private mapImportSecp256r1Key = (args: MapImportKeyArgs): MappedImportKey => {
316
+ const { privateKeyHex } = args
317
+ const privateBytes = fromString(privateKeyHex.toLowerCase(), 'base16')
318
+ const secp256r1 = new elliptic.ec('p256')
319
+ const keyPair = secp256r1.keyFromPrivate(privateBytes, 'hex')
320
+ const publicKeyHex = keyPair.getPublic(true, 'hex')
321
+ const publicKeyJwk = toJwk(publicKeyHex, 'Secp256r1')
322
+ const privateKeyJwk = toJwk(privateKeyHex, 'Secp256r1', { isPrivateKey: true })
323
+ const kid = args.kid ?? publicKeyJwk.kid ?? publicKeyHex
324
+
325
+ return {
326
+ kid,
327
+ publicKeyJwk: publicKeyJwk as JWK,
328
+ key: {
329
+ keyInfo: {
330
+ key: {
331
+ ...privateKeyJwk,
332
+ kid,
333
+ kty: JwkKeyTypeFromJSONTyped(privateKeyJwk.kty, false),
334
+ use: JwkUseFromJSONTyped(privateKeyJwk.use, false),
335
+ crv: CurveFromJSONTyped(privateKeyJwk.crv, false),
336
+ }
337
+ }
338
+ } satisfies StoreKey
339
+ }
340
+ }
341
+
342
+ private mapImportX25519Key = (args: MapImportKeyArgs): MappedImportKey => {
343
+ const { privateKeyHex } = args
344
+ const privateKeyJwk = toJwk(privateKeyHex, 'X25519', { isPrivateKey: true })
345
+ const publicKeyHex = x25519PublicHexFromPrivateHex(privateKeyHex)
346
+ const publicKeyJwk = toJwk(publicKeyHex, 'X25519')
347
+ const kid = args.kid ?? publicKeyJwk.kid ?? publicKeyHex
348
+
349
+ return {
350
+ kid,
351
+ publicKeyJwk: publicKeyJwk as JWK,
352
+ key: {
353
+ keyInfo: {
354
+ key: {
355
+ ...privateKeyJwk,
356
+ kid,
357
+ kty: JwkKeyTypeFromJSONTyped(privateKeyJwk.kty, false),
358
+ use: JwkUseFromJSONTyped(privateKeyJwk.use, false),
359
+ crv: CurveFromJSONTyped(privateKeyJwk.crv, false),
360
+ }
361
+ }
362
+ } satisfies StoreKey
363
+ }
364
+ }
365
+
366
+ private mapImportKey = (args: MapImportKeyArgs): MappedImportKey => {
367
+ switch (args.type) {
368
+ case 'RSA': {
369
+ return this.mapImportRsaKey(args)
370
+ }
371
+ case 'Secp256r1': {
372
+ return this.mapImportSecp256r1Key(args)
373
+ }
374
+ case 'X25519': {
375
+ return this.mapImportX25519Key(args)
376
+ }
377
+ default:
378
+ throw new Error(`Key type ${args.type} is not supported by REST KMS`)
379
+ }
380
+ }
381
+
382
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { RestKeyManagementSystem } from './RestKeyManagementSystem'
2
+ export * from './types'
@@ -0,0 +1,50 @@
1
+ import type { IKey, MinimalImportableKey, TKeyType } from '@veramo/core'
2
+ import type { StoreKey } from '@sphereon/ssi-sdk.kms-rest-client'
3
+ import type { JWK } from '@sphereon/ssi-types'
4
+
5
+ export type KeyMetadata = {
6
+ algorithms?: string[]
7
+ [x: string]: any
8
+ }
9
+
10
+ export type CreateKeyArgs = {
11
+ type: TKeyType
12
+ meta?: KeyMetadata
13
+ }
14
+
15
+ export type SignArgs = {
16
+ keyRef: Pick<IKey, 'kid'>
17
+ data: Uint8Array
18
+ [x: string]: any
19
+ }
20
+
21
+ export type VerifyArgs = {
22
+ keyRef: Pick<IKey, 'kid'>
23
+ data: Uint8Array
24
+ signature: string;
25
+ [x: string]: any
26
+ }
27
+
28
+ export type SharedSecretArgs = {
29
+ myKeyRef: Pick<IKey, 'kid'>
30
+ theirKey: Pick<IKey, 'publicKeyHex' | 'type'>
31
+ }
32
+
33
+ export type ImportKeyArgs = Omit<MinimalImportableKey, 'kms'> & { privateKeyPEM?: string }
34
+
35
+ export type DeleteKeyArgs = {
36
+ kid: string
37
+ }
38
+
39
+ export type MapImportKeyArgs = {
40
+ type: TKeyType
41
+ privateKeyHex: string
42
+ meta?: KeyMetadata | null
43
+ kid?: string
44
+ }
45
+
46
+ export type MappedImportKey = {
47
+ key: StoreKey,
48
+ kid: string,
49
+ publicKeyJwk: JWK
50
+ }