solana-kms-signer 0.1.0 → 1.0.2

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,22 +1,58 @@
1
- import { vi, beforeEach, describe, it, expect } from 'vitest';
2
- import { KmsClient } from './client.js';
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
2
  import { KmsClientError } from '../errors/index.js';
4
3
  import type { KmsConfig } from '../types/index.js';
4
+ import { KmsClient } from './client.js';
5
5
 
6
6
  /**
7
7
  * Mock DER-encoded ED25519 public key matching AWS KMS GetPublicKey format.
8
8
  * Reuses the same structure as publicKey.test.ts for consistency.
9
9
  */
10
10
  const MOCK_DER_PUBLIC_KEY = new Uint8Array([
11
- 0x30, 0x2a, // SEQUENCE, 42 bytes
12
- 0x30, 0x05, // AlgorithmIdentifier SEQUENCE, 5 bytes
13
- 0x06, 0x03, 0x2b, 0x65, 0x70, // OID: 1.3.101.112 (Ed25519)
14
- 0x03, 0x21, 0x00, // BIT STRING, 33 bytes (32 + 1 padding)
15
- // Mock 32-byte ED25519 public key
16
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
17
- 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
18
- 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
19
- 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
11
+ 0x30,
12
+ 0x2a, // SEQUENCE, 42 bytes
13
+ 0x30,
14
+ 0x05, // AlgorithmIdentifier SEQUENCE, 5 bytes
15
+ 0x06,
16
+ 0x03,
17
+ 0x2b,
18
+ 0x65,
19
+ 0x70, // OID: 1.3.101.112 (Ed25519)
20
+ 0x03,
21
+ 0x21,
22
+ 0x00, // BIT STRING, 33 bytes (32 + 1 padding)
23
+ // Mock 32-byte ED25519 public key
24
+ 0x01,
25
+ 0x02,
26
+ 0x03,
27
+ 0x04,
28
+ 0x05,
29
+ 0x06,
30
+ 0x07,
31
+ 0x08,
32
+ 0x09,
33
+ 0x0a,
34
+ 0x0b,
35
+ 0x0c,
36
+ 0x0d,
37
+ 0x0e,
38
+ 0x0f,
39
+ 0x10,
40
+ 0x11,
41
+ 0x12,
42
+ 0x13,
43
+ 0x14,
44
+ 0x15,
45
+ 0x16,
46
+ 0x17,
47
+ 0x18,
48
+ 0x19,
49
+ 0x1a,
50
+ 0x1b,
51
+ 0x1c,
52
+ 0x1d,
53
+ 0x1e,
54
+ 0x1f,
55
+ 0x20,
20
56
  ]);
21
57
 
22
58
  /**
@@ -29,257 +65,284 @@ const MOCK_SIGNATURE = new Uint8Array(64).fill(0xab);
29
65
  * Mock KMS configuration for testing
30
66
  */
31
67
  const MOCK_KMS_CONFIG: KmsConfig = {
32
- region: 'us-east-1',
33
- keyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012',
68
+ region: 'us-east-1',
69
+ keyId:
70
+ 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012',
34
71
  };
35
72
 
36
73
  // Mock AWS SDK client and commands
37
74
  const mockSend = vi.fn();
38
75
 
39
76
  vi.mock('@aws-sdk/client-kms', () => {
40
- return {
41
- KMSClient: class {
42
- send = mockSend;
43
- },
44
- GetPublicKeyCommand: class {
45
- constructor(public params: any) {}
46
- },
47
- SignCommand: class {
48
- constructor(public params: any) {}
49
- },
50
- };
77
+ return {
78
+ KMSClient: class {
79
+ send = mockSend;
80
+ },
81
+ GetPublicKeyCommand: class {
82
+ constructor(public params: unknown) {}
83
+ },
84
+ SignCommand: class {
85
+ constructor(public params: unknown) {}
86
+ },
87
+ };
51
88
  });
52
89
 
53
90
  describe('KmsClient', () => {
54
- beforeEach(() => {
55
- // Reset mock state before each test
56
- mockSend.mockReset();
57
- });
58
-
59
- describe('Happy Path', () => {
60
- it('should successfully retrieve public key as DER-encoded Uint8Array', async () => {
61
- // given: KmsClient instance and mocked successful KMS response
62
- const client = new KmsClient(MOCK_KMS_CONFIG);
63
- mockSend.mockResolvedValueOnce({
64
- PublicKey: MOCK_DER_PUBLIC_KEY,
65
- });
66
-
67
- // when: Getting public key from KMS
68
- const publicKey = await client.getPublicKey();
69
-
70
- // then: Should return DER-encoded public key as Uint8Array
71
- expect(publicKey).toBeInstanceOf(Uint8Array);
72
- expect(publicKey.length).toBe(MOCK_DER_PUBLIC_KEY.length);
73
- expect(publicKey).toEqual(MOCK_DER_PUBLIC_KEY);
74
-
75
- // then: Should have called KMS with correct parameters
76
- expect(mockSend).toHaveBeenCalledTimes(1);
77
- });
78
-
79
- it('should successfully sign message and return 64-byte signature', async () => {
80
- // given: KmsClient instance and message to sign
81
- const client = new KmsClient(MOCK_KMS_CONFIG);
82
- const message = new Uint8Array([1, 2, 3, 4, 5]);
83
-
84
- mockSend.mockResolvedValueOnce({
85
- Signature: MOCK_SIGNATURE,
86
- });
87
-
88
- // when: Signing message with KMS
89
- const signature = await client.sign(message);
90
-
91
- // then: Should return 64-byte signature
92
- expect(signature).toBeInstanceOf(Uint8Array);
93
- expect(signature.length).toBe(64);
94
- expect(signature).toEqual(MOCK_SIGNATURE);
95
-
96
- // then: Should have called KMS with correct SigningAlgorithm
97
- expect(mockSend).toHaveBeenCalledTimes(1);
98
- const signCommand = mockSend.mock.calls[0][0];
99
- expect(signCommand.params.SigningAlgorithm).toBe('ED25519_SHA_512');
100
- expect(signCommand.params.MessageType).toBe('RAW');
101
- expect(signCommand.params.Message).toEqual(message);
102
- });
103
- });
104
-
105
- describe('Failure Paths', () => {
106
- it('should throw KmsClientError when getPublicKey receives AccessDeniedException', async () => {
107
- // given: KmsClient and mocked AccessDeniedException
108
- const client = new KmsClient(MOCK_KMS_CONFIG);
109
- const awsError = new Error('User: arn:aws:iam::123456789012:user/test is not authorized');
110
- awsError.name = 'AccessDeniedException';
111
- mockSend.mockRejectedValueOnce(awsError);
112
-
113
- // when: Getting public key with insufficient permissions
114
- const getPublicKeyPromise = client.getPublicKey();
115
-
116
- // then: Should throw KmsClientError with cause
117
- await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
118
- await expect(getPublicKeyPromise).rejects.toThrow('Failed to get public key from KMS');
119
-
120
- // then: Verify cause is preserved in error
121
- mockSend.mockRejectedValueOnce(awsError);
122
- try {
123
- await client.getPublicKey();
124
- } catch (error) {
125
- expect(error).toBeInstanceOf(KmsClientError);
126
- expect((error as KmsClientError).cause).toBe(awsError);
127
- }
128
- });
129
-
130
- it('should throw KmsClientError when getPublicKey receives KeyNotFoundException', async () => {
131
- // given: KmsClient and mocked KeyNotFoundException
132
- const client = new KmsClient(MOCK_KMS_CONFIG);
133
- const awsError = new Error('Key arn:aws:kms:us-east-1:123456789012:key/nonexistent is not found');
134
- awsError.name = 'KeyNotFoundException';
135
- mockSend.mockRejectedValueOnce(awsError);
136
-
137
- // when: Getting public key for non-existent key
138
- const getPublicKeyPromise = client.getPublicKey();
139
-
140
- // then: Should throw KmsClientError with cause
141
- await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
142
- await expect(getPublicKeyPromise).rejects.toThrow('Failed to get public key from KMS');
143
- });
144
-
145
- it('should throw KmsClientError when getPublicKey receives InvalidKeyUsageException', async () => {
146
- // given: KmsClient and mocked InvalidKeyUsageException
147
- const client = new KmsClient(MOCK_KMS_CONFIG);
148
- const awsError = new Error('The request is not valid for the specified key usage');
149
- awsError.name = 'InvalidKeyUsageException';
150
- mockSend.mockRejectedValueOnce(awsError);
151
-
152
- // when: Getting public key for key with wrong usage (e.g., ENCRYPT_DECRYPT)
153
- const getPublicKeyPromise = client.getPublicKey();
154
-
155
- // then: Should throw KmsClientError with cause
156
- await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
157
- await expect(getPublicKeyPromise).rejects.toThrow('Failed to get public key from KMS');
158
- });
159
-
160
- it('should throw KmsClientError when sign receives ThrottlingException', async () => {
161
- // given: KmsClient and mocked ThrottlingException
162
- const client = new KmsClient(MOCK_KMS_CONFIG);
163
- const message = new Uint8Array([1, 2, 3]);
164
- const awsError = new Error('Rate exceeded');
165
- awsError.name = 'ThrottlingException';
166
- mockSend.mockRejectedValueOnce(awsError);
167
-
168
- // when: Signing message with rate limit exceeded
169
- const signPromise = client.sign(message);
170
-
171
- // then: Should throw KmsClientError with cause
172
- await expect(signPromise).rejects.toThrow(KmsClientError);
173
- await expect(signPromise).rejects.toThrow('Failed to sign message with KMS');
174
- });
175
-
176
- it('should throw KmsClientError when sign receives KeyUnavailableException', async () => {
177
- // given: KmsClient and mocked KeyUnavailableException
178
- const client = new KmsClient(MOCK_KMS_CONFIG);
179
- const message = new Uint8Array([1, 2, 3]);
180
- const awsError = new Error('The request was rejected because the specified KMS key is not available');
181
- awsError.name = 'KeyUnavailableException';
182
- mockSend.mockRejectedValueOnce(awsError);
183
-
184
- // when: Signing message with unavailable key
185
- const signPromise = client.sign(message);
186
-
187
- // then: Should throw KmsClientError with cause
188
- await expect(signPromise).rejects.toThrow(KmsClientError);
189
- await expect(signPromise).rejects.toThrow('Failed to sign message with KMS');
190
- });
191
-
192
- it('should handle signing empty message appropriately', async () => {
193
- // given: KmsClient and empty message
194
- const client = new KmsClient(MOCK_KMS_CONFIG);
195
- const emptyMessage = new Uint8Array(0);
196
-
197
- mockSend.mockResolvedValueOnce({
198
- Signature: MOCK_SIGNATURE,
199
- });
200
-
201
- // when: Signing empty message
202
- const signature = await client.sign(emptyMessage);
203
-
204
- // then: Should successfully return signature (AWS KMS allows empty messages)
205
- expect(signature).toBeInstanceOf(Uint8Array);
206
- expect(signature.length).toBe(64);
207
- expect(mockSend).toHaveBeenCalledTimes(1);
208
- });
209
-
210
- it('should throw KmsClientError on network timeout simulation', async () => {
211
- // given: KmsClient and mocked network timeout
212
- const client = new KmsClient(MOCK_KMS_CONFIG);
213
- const message = new Uint8Array([1, 2, 3]);
214
- const timeoutError = new Error('Connection timeout after 10000ms');
215
- timeoutError.name = 'TimeoutError';
216
- mockSend.mockRejectedValueOnce(timeoutError);
217
-
218
- // when: Signing message with network timeout
219
- const signPromise = client.sign(message);
220
-
221
- // then: Should throw KmsClientError with timeout cause
222
- await expect(signPromise).rejects.toThrow(KmsClientError);
223
- await expect(signPromise).rejects.toThrow('Failed to sign message with KMS');
224
-
225
- // then: Verify cause is preserved in error
226
- mockSend.mockRejectedValueOnce(timeoutError);
227
- try {
228
- await client.sign(message);
229
- } catch (error) {
230
- expect(error).toBeInstanceOf(KmsClientError);
231
- expect((error as KmsClientError).cause).toBe(timeoutError);
232
- }
233
- });
234
- });
235
-
236
- describe('Edge Cases', () => {
237
- it('should throw KmsClientError when getPublicKey response has null PublicKey', async () => {
238
- // given: KmsClient and response with null PublicKey
239
- const client = new KmsClient(MOCK_KMS_CONFIG);
240
- mockSend.mockResolvedValueOnce({
241
- PublicKey: null,
242
- });
243
-
244
- // when: Getting public key with null response
245
- const getPublicKeyPromise = client.getPublicKey();
246
-
247
- // then: Should throw KmsClientError
248
- await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
249
- await expect(getPublicKeyPromise).rejects.toThrow('GetPublicKey response missing PublicKey field');
250
- });
251
-
252
- it('should throw KmsClientError when sign response has null Signature', async () => {
253
- // given: KmsClient and response with null Signature
254
- const client = new KmsClient(MOCK_KMS_CONFIG);
255
- const message = new Uint8Array([1, 2, 3]);
256
- mockSend.mockResolvedValueOnce({
257
- Signature: null,
258
- });
259
-
260
- // when: Signing message with null response
261
- const signPromise = client.sign(message);
262
-
263
- // then: Should throw KmsClientError
264
- await expect(signPromise).rejects.toThrow(KmsClientError);
265
- await expect(signPromise).rejects.toThrow('Sign response missing Signature field');
266
- });
267
-
268
- it('should throw KmsClientError when signature length is not 64 bytes', async () => {
269
- // given: KmsClient and invalid signature length (e.g., 32 bytes)
270
- const client = new KmsClient(MOCK_KMS_CONFIG);
271
- const message = new Uint8Array([1, 2, 3]);
272
- const invalidSignature = new Uint8Array(32); // Wrong length
273
- mockSend.mockResolvedValueOnce({
274
- Signature: invalidSignature,
275
- });
276
-
277
- // when: Receiving signature with wrong length
278
- const signPromise = client.sign(message);
279
-
280
- // then: Should throw KmsClientError with length validation message
281
- await expect(signPromise).rejects.toThrow(KmsClientError);
282
- await expect(signPromise).rejects.toThrow('Invalid signature length: expected 64 bytes, got 32 bytes');
283
- });
284
- });
91
+ beforeEach(() => {
92
+ // Reset mock state before each test
93
+ mockSend.mockReset();
94
+ });
95
+
96
+ describe('Happy Path', () => {
97
+ it('should successfully retrieve public key as DER-encoded Uint8Array', async () => {
98
+ // given: KmsClient instance and mocked successful KMS response
99
+ const client = new KmsClient(MOCK_KMS_CONFIG);
100
+ mockSend.mockResolvedValueOnce({
101
+ PublicKey: MOCK_DER_PUBLIC_KEY,
102
+ });
103
+
104
+ // when: Getting public key from KMS
105
+ const publicKey = await client.getPublicKey();
106
+
107
+ // then: Should return DER-encoded public key as Uint8Array
108
+ expect(publicKey).toBeInstanceOf(Uint8Array);
109
+ expect(publicKey.length).toBe(MOCK_DER_PUBLIC_KEY.length);
110
+ expect(publicKey).toEqual(MOCK_DER_PUBLIC_KEY);
111
+
112
+ // then: Should have called KMS with correct parameters
113
+ expect(mockSend).toHaveBeenCalledTimes(1);
114
+ });
115
+
116
+ it('should successfully sign message and return 64-byte signature', async () => {
117
+ // given: KmsClient instance and message to sign
118
+ const client = new KmsClient(MOCK_KMS_CONFIG);
119
+ const message = new Uint8Array([1, 2, 3, 4, 5]);
120
+
121
+ mockSend.mockResolvedValueOnce({
122
+ Signature: MOCK_SIGNATURE,
123
+ });
124
+
125
+ // when: Signing message with KMS
126
+ const signature = await client.sign(message);
127
+
128
+ // then: Should return 64-byte signature
129
+ expect(signature).toBeInstanceOf(Uint8Array);
130
+ expect(signature.length).toBe(64);
131
+ expect(signature).toEqual(MOCK_SIGNATURE);
132
+
133
+ // then: Should have called KMS with correct SigningAlgorithm
134
+ expect(mockSend).toHaveBeenCalledTimes(1);
135
+ const signCommand = mockSend.mock.calls[0][0];
136
+ expect(signCommand.params.SigningAlgorithm).toBe('ED25519_SHA_512');
137
+ expect(signCommand.params.MessageType).toBe('RAW');
138
+ expect(signCommand.params.Message).toEqual(message);
139
+ });
140
+ });
141
+
142
+ describe('Failure Paths', () => {
143
+ it('should throw KmsClientError when getPublicKey receives AccessDeniedException', async () => {
144
+ // given: KmsClient and mocked AccessDeniedException
145
+ const client = new KmsClient(MOCK_KMS_CONFIG);
146
+ const awsError = new Error(
147
+ 'User: arn:aws:iam::123456789012:user/test is not authorized',
148
+ );
149
+ awsError.name = 'AccessDeniedException';
150
+ mockSend.mockRejectedValueOnce(awsError);
151
+
152
+ // when: Getting public key with insufficient permissions
153
+ const getPublicKeyPromise = client.getPublicKey();
154
+
155
+ // then: Should throw KmsClientError with cause
156
+ await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
157
+ await expect(getPublicKeyPromise).rejects.toThrow(
158
+ 'Failed to get public key from KMS',
159
+ );
160
+
161
+ // then: Verify cause is preserved in error
162
+ mockSend.mockRejectedValueOnce(awsError);
163
+ try {
164
+ await client.getPublicKey();
165
+ } catch (error) {
166
+ expect(error).toBeInstanceOf(KmsClientError);
167
+ expect((error as KmsClientError).cause).toBe(awsError);
168
+ }
169
+ });
170
+
171
+ it('should throw KmsClientError when getPublicKey receives KeyNotFoundException', async () => {
172
+ // given: KmsClient and mocked KeyNotFoundException
173
+ const client = new KmsClient(MOCK_KMS_CONFIG);
174
+ const awsError = new Error(
175
+ 'Key arn:aws:kms:us-east-1:123456789012:key/nonexistent is not found',
176
+ );
177
+ awsError.name = 'KeyNotFoundException';
178
+ mockSend.mockRejectedValueOnce(awsError);
179
+
180
+ // when: Getting public key for non-existent key
181
+ const getPublicKeyPromise = client.getPublicKey();
182
+
183
+ // then: Should throw KmsClientError with cause
184
+ await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
185
+ await expect(getPublicKeyPromise).rejects.toThrow(
186
+ 'Failed to get public key from KMS',
187
+ );
188
+ });
189
+
190
+ it('should throw KmsClientError when getPublicKey receives InvalidKeyUsageException', async () => {
191
+ // given: KmsClient and mocked InvalidKeyUsageException
192
+ const client = new KmsClient(MOCK_KMS_CONFIG);
193
+ const awsError = new Error(
194
+ 'The request is not valid for the specified key usage',
195
+ );
196
+ awsError.name = 'InvalidKeyUsageException';
197
+ mockSend.mockRejectedValueOnce(awsError);
198
+
199
+ // when: Getting public key for key with wrong usage (e.g., ENCRYPT_DECRYPT)
200
+ const getPublicKeyPromise = client.getPublicKey();
201
+
202
+ // then: Should throw KmsClientError with cause
203
+ await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
204
+ await expect(getPublicKeyPromise).rejects.toThrow(
205
+ 'Failed to get public key from KMS',
206
+ );
207
+ });
208
+
209
+ it('should throw KmsClientError when sign receives ThrottlingException', async () => {
210
+ // given: KmsClient and mocked ThrottlingException
211
+ const client = new KmsClient(MOCK_KMS_CONFIG);
212
+ const message = new Uint8Array([1, 2, 3]);
213
+ const awsError = new Error('Rate exceeded');
214
+ awsError.name = 'ThrottlingException';
215
+ mockSend.mockRejectedValueOnce(awsError);
216
+
217
+ // when: Signing message with rate limit exceeded
218
+ const signPromise = client.sign(message);
219
+
220
+ // then: Should throw KmsClientError with cause
221
+ await expect(signPromise).rejects.toThrow(KmsClientError);
222
+ await expect(signPromise).rejects.toThrow(
223
+ 'Failed to sign message with KMS',
224
+ );
225
+ });
226
+
227
+ it('should throw KmsClientError when sign receives KeyUnavailableException', async () => {
228
+ // given: KmsClient and mocked KeyUnavailableException
229
+ const client = new KmsClient(MOCK_KMS_CONFIG);
230
+ const message = new Uint8Array([1, 2, 3]);
231
+ const awsError = new Error(
232
+ 'The request was rejected because the specified KMS key is not available',
233
+ );
234
+ awsError.name = 'KeyUnavailableException';
235
+ mockSend.mockRejectedValueOnce(awsError);
236
+
237
+ // when: Signing message with unavailable key
238
+ const signPromise = client.sign(message);
239
+
240
+ // then: Should throw KmsClientError with cause
241
+ await expect(signPromise).rejects.toThrow(KmsClientError);
242
+ await expect(signPromise).rejects.toThrow(
243
+ 'Failed to sign message with KMS',
244
+ );
245
+ });
246
+
247
+ it('should handle signing empty message appropriately', async () => {
248
+ // given: KmsClient and empty message
249
+ const client = new KmsClient(MOCK_KMS_CONFIG);
250
+ const emptyMessage = new Uint8Array(0);
251
+
252
+ mockSend.mockResolvedValueOnce({
253
+ Signature: MOCK_SIGNATURE,
254
+ });
255
+
256
+ // when: Signing empty message
257
+ const signature = await client.sign(emptyMessage);
258
+
259
+ // then: Should successfully return signature (AWS KMS allows empty messages)
260
+ expect(signature).toBeInstanceOf(Uint8Array);
261
+ expect(signature.length).toBe(64);
262
+ expect(mockSend).toHaveBeenCalledTimes(1);
263
+ });
264
+
265
+ it('should throw KmsClientError on network timeout simulation', async () => {
266
+ // given: KmsClient and mocked network timeout
267
+ const client = new KmsClient(MOCK_KMS_CONFIG);
268
+ const message = new Uint8Array([1, 2, 3]);
269
+ const timeoutError = new Error('Connection timeout after 10000ms');
270
+ timeoutError.name = 'TimeoutError';
271
+ mockSend.mockRejectedValueOnce(timeoutError);
272
+
273
+ // when: Signing message with network timeout
274
+ const signPromise = client.sign(message);
275
+
276
+ // then: Should throw KmsClientError with timeout cause
277
+ await expect(signPromise).rejects.toThrow(KmsClientError);
278
+ await expect(signPromise).rejects.toThrow(
279
+ 'Failed to sign message with KMS',
280
+ );
281
+
282
+ // then: Verify cause is preserved in error
283
+ mockSend.mockRejectedValueOnce(timeoutError);
284
+ try {
285
+ await client.sign(message);
286
+ } catch (error) {
287
+ expect(error).toBeInstanceOf(KmsClientError);
288
+ expect((error as KmsClientError).cause).toBe(timeoutError);
289
+ }
290
+ });
291
+ });
292
+
293
+ describe('Edge Cases', () => {
294
+ it('should throw KmsClientError when getPublicKey response has null PublicKey', async () => {
295
+ // given: KmsClient and response with null PublicKey
296
+ const client = new KmsClient(MOCK_KMS_CONFIG);
297
+ mockSend.mockResolvedValueOnce({
298
+ PublicKey: null,
299
+ });
300
+
301
+ // when: Getting public key with null response
302
+ const getPublicKeyPromise = client.getPublicKey();
303
+
304
+ // then: Should throw KmsClientError
305
+ await expect(getPublicKeyPromise).rejects.toThrow(KmsClientError);
306
+ await expect(getPublicKeyPromise).rejects.toThrow(
307
+ 'GetPublicKey response missing PublicKey field',
308
+ );
309
+ });
310
+
311
+ it('should throw KmsClientError when sign response has null Signature', async () => {
312
+ // given: KmsClient and response with null Signature
313
+ const client = new KmsClient(MOCK_KMS_CONFIG);
314
+ const message = new Uint8Array([1, 2, 3]);
315
+ mockSend.mockResolvedValueOnce({
316
+ Signature: null,
317
+ });
318
+
319
+ // when: Signing message with null response
320
+ const signPromise = client.sign(message);
321
+
322
+ // then: Should throw KmsClientError
323
+ await expect(signPromise).rejects.toThrow(KmsClientError);
324
+ await expect(signPromise).rejects.toThrow(
325
+ 'Sign response missing Signature field',
326
+ );
327
+ });
328
+
329
+ it('should throw KmsClientError when signature length is not 64 bytes', async () => {
330
+ // given: KmsClient and invalid signature length (e.g., 32 bytes)
331
+ const client = new KmsClient(MOCK_KMS_CONFIG);
332
+ const message = new Uint8Array([1, 2, 3]);
333
+ const invalidSignature = new Uint8Array(32); // Wrong length
334
+ mockSend.mockResolvedValueOnce({
335
+ Signature: invalidSignature,
336
+ });
337
+
338
+ // when: Receiving signature with wrong length
339
+ const signPromise = client.sign(message);
340
+
341
+ // then: Should throw KmsClientError with length validation message
342
+ await expect(signPromise).rejects.toThrow(KmsClientError);
343
+ await expect(signPromise).rejects.toThrow(
344
+ 'Invalid signature length: expected 64 bytes, got 32 bytes',
345
+ );
346
+ });
347
+ });
285
348
  });