3
3
import json
4
4
import logging
5
5
import os
6
+ import random
6
7
import re
7
8
import struct
8
9
import uuid
11
12
from typing import Dict , List , Tuple
12
13
13
14
from cryptography .exceptions import InvalidSignature
14
- from cryptography .hazmat .primitives import hashes
15
+ from cryptography .hazmat .primitives import hashes , hmac
15
16
from cryptography .hazmat .primitives import serialization as crypto_serialization
16
17
from cryptography .hazmat .primitives .asymmetric import ec , padding , rsa , utils
17
18
24
25
DisabledException ,
25
26
EncryptionContextType ,
26
27
KeyMetadata ,
28
+ KMSInvalidMacException ,
27
29
KMSInvalidSignatureException ,
28
30
KMSInvalidStateException ,
31
+ MacAlgorithmSpec ,
29
32
MessageType ,
30
33
NotFoundException ,
31
34
SigningAlgorithmSpec ,
58
61
"ECC_SECG_P256K1" : ec .SECP256K1 (),
59
62
}
60
63
64
+ HMAC_RANGE_KEY_LENGTHS = {
65
+ "HMAC_224" : (28 , 64 ),
66
+ "HMAC_256" : (32 , 64 ),
67
+ "HMAC_384" : (48 , 128 ),
68
+ "HMAC_512" : (64 , 128 ),
69
+ }
70
+
61
71
62
72
class ValidationException (CommonServiceException ):
63
73
def __init__ (self , message : str ):
@@ -162,9 +172,19 @@ def __init__(self, key_spec: str):
162
172
elif key_spec .startswith ("ECC" ):
163
173
curve = ECC_CURVES .get (key_spec )
164
174
self .key = ec .generate_private_key (curve )
175
+ elif key_spec .startswith ("HMAC" ):
176
+ if key_spec not in HMAC_RANGE_KEY_LENGTHS :
177
+ raise ValidationException (
178
+ f"1 validation error detected: Value '{ key_spec } ' at 'keySpec' "
179
+ f"failed to satisfy constraint: Member must satisfy enum value set: "
180
+ f"[RSA_2048, ECC_NIST_P384, ECC_NIST_P256, ECC_NIST_P521, HMAC_384, RSA_3072, "
181
+ f"ECC_SECG_P256K1, RSA_4096, SYMMETRIC_DEFAULT, HMAC_256, HMAC_224, HMAC_512]"
182
+ )
183
+ minimum_length , maximum_length = HMAC_RANGE_KEY_LENGTHS .get (key_spec )
184
+ self .key_material = os .urandom (random .randint (minimum_length , maximum_length ))
185
+ return
165
186
else :
166
- # Currently we do not support HMAC keys - symmetric keys that are used for GenerateMac / VerifyMac.
167
- # We also do not support SM2 - asymmetric keys both suitable for ENCRYPT_DECRYPT and SIGN_VERIFY,
187
+ # We do not support SM2 - asymmetric keys both suitable for ENCRYPT_DECRYPT and SIGN_VERIFY,
168
188
# but only used in China AWS regions.
169
189
raise UnsupportedOperationException (f"KeySpec { key_spec } is not supported" )
170
190
@@ -212,6 +232,20 @@ def __init__(
212
232
def calculate_and_set_arn (self , account_id , region ):
213
233
self .metadata ["Arn" ] = kms_key_arn (self .metadata .get ("KeyId" ), account_id , region )
214
234
235
+ def generate_mac (self , msg : bytes , mac_algorithm : MacAlgorithmSpec ) -> bytes :
236
+ h = self ._get_hmac_context (mac_algorithm )
237
+ h .update (msg )
238
+ return h .finalize ()
239
+
240
+ def verify_mac (self , msg : bytes , mac : bytes , mac_algorithm : MacAlgorithmSpec ) -> bool :
241
+ h = self ._get_hmac_context (mac_algorithm )
242
+ h .update (msg )
243
+ try :
244
+ h .verify (mac )
245
+ return True
246
+ except InvalidSignature :
247
+ raise KMSInvalidMacException ()
248
+
215
249
# Encrypt is a method of KmsKey and not of KmsCryptoKey only because it requires KeyId, and KmsCryptoKeys do not
216
250
# hold KeyIds. Maybe it would be possible to remodel this better.
217
251
def encrypt (self , plaintext : bytes ) -> bytes :
@@ -254,6 +288,23 @@ def verify(
254
288
# AWS itself raises this exception without any additional message.
255
289
raise KMSInvalidSignatureException ()
256
290
291
+ def _get_hmac_context (self , mac_algorithm : MacAlgorithmSpec ) -> hmac .HMAC :
292
+ if mac_algorithm == "HMAC_SHA_224" :
293
+ h = hmac .HMAC (self .crypto_key .key_material , hashes .SHA224 ())
294
+ elif mac_algorithm == "HMAC_SHA_256" :
295
+ h = hmac .HMAC (self .crypto_key .key_material , hashes .SHA256 ())
296
+ elif mac_algorithm == "HMAC_SHA_384" :
297
+ h = hmac .HMAC (self .crypto_key .key_material , hashes .SHA384 ())
298
+ elif mac_algorithm == "HMAC_SHA_512" :
299
+ h = hmac .HMAC (self .crypto_key .key_material , hashes .SHA512 ())
300
+ else :
301
+ raise ValidationException (
302
+ f"1 validation error detected: Value '{ mac_algorithm } ' at 'macAlgorithm' "
303
+ f"failed to satisfy constraint: Member must satisfy enum value set: "
304
+ f"[HMAC_SHA_384, HMAC_SHA_256, HMAC_SHA_224, HMAC_SHA_512]"
305
+ )
306
+ return h
307
+
257
308
def _construct_sign_verify_kwargs (
258
309
self , signing_algorithm : SigningAlgorithmSpec , message_type : MessageType
259
310
) -> Dict :
@@ -318,7 +369,6 @@ def _populate_metadata(
318
369
# "DescribeKey does not return the following information: ... Tags on the KMS key."
319
370
320
371
self .metadata ["Description" ] = create_key_request .get ("Description" ) or ""
321
- self .metadata ["KeyUsage" ] = create_key_request .get ("KeyUsage" ) or "ENCRYPT_DECRYPT"
322
372
self .metadata ["MultiRegion" ] = create_key_request .get ("MultiRegion" ) or False
323
373
self .metadata ["Origin" ] = create_key_request .get ("Origin" ) or "AWS_KMS"
324
374
# https://docs.aws.amazon.com/kms/latest/APIReference/API_CreateKey.html#KMS-CreateKey-request-CustomerMasterKeySpec
@@ -330,6 +380,9 @@ def _populate_metadata(
330
380
or "SYMMETRIC_DEFAULT"
331
381
)
332
382
self .metadata ["CustomerMasterKeySpec" ] = self .metadata .get ("KeySpec" )
383
+ self .metadata ["KeyUsage" ] = self ._get_key_usage (
384
+ create_key_request .get ("KeyUsage" ), self .metadata .get ("KeySpec" )
385
+ )
333
386
334
387
# Metadata fields AWS introduces automatically
335
388
self .metadata ["AWSAccountId" ] = account_id or get_aws_account_id ()
@@ -353,6 +406,7 @@ def _populate_metadata(
353
406
self ._populate_signing_algorithms (
354
407
self .metadata .get ("KeyUsage" ), self .metadata .get ("KeySpec" )
355
408
)
409
+ self ._populate_mac_algorithms (self .metadata .get ("KeyUsage" ), self .metadata .get ("KeySpec" ))
356
410
357
411
def add_tags (self , tags : List ) -> None :
358
412
# Just in case we get None from somewhere.
@@ -430,6 +484,35 @@ def _populate_signing_algorithms(self, key_usage: str, key_spec: str) -> None:
430
484
"RSASSA_PSS_SHA_512" ,
431
485
]
432
486
487
+ def _populate_mac_algorithms (self , key_usage : str , key_spec : str ) -> None :
488
+ if key_usage != "GENERATE_VERIFY_MAC" :
489
+ return
490
+ if key_spec == "HMAC_224" :
491
+ self .metadata ["MacAlgorithms" ] = ["HMAC_SHA_224" ]
492
+ elif key_spec == "HMAC_256" :
493
+ self .metadata ["MacAlgorithms" ] = ["HMAC_SHA_256" ]
494
+ elif key_spec == "HMAC_384" :
495
+ self .metadata ["MacAlgorithms" ] = ["HMAC_SHA_384" ]
496
+ elif key_spec == "HMAC_512" :
497
+ self .metadata ["MacAlgorithms" ] = ["HMAC_SHA_512" ]
498
+
499
+ def _get_key_usage (self , request_key_usage : str , key_spec : str ) -> str :
500
+ if key_spec in HMAC_RANGE_KEY_LENGTHS :
501
+ if request_key_usage is None :
502
+ raise ValidationException (
503
+ "You must specify a KeyUsage value for all KMS keys except for symmetric encryption keys."
504
+ )
505
+ elif request_key_usage != "GENERATE_VERIFY_MAC" :
506
+ raise ValidationException (
507
+ f"1 validation error detected: Value '{ request_key_usage } ' at 'keyUsage' "
508
+ f"failed to satisfy constraint: Member must satisfy enum value set: "
509
+ f"[ENCRYPT_DECRYPT, SIGN_VERIFY, GENERATE_VERIFY_MAC]"
510
+ )
511
+ else :
512
+ return "GENERATE_VERIFY_MAC"
513
+ else :
514
+ return request_key_usage or "ENCRYPT_DECRYPT"
515
+
433
516
434
517
class KmsGrant :
435
518
# AWS documentation doesn't seem to mention any metadata object for grants like it does mention KeyMetadata for
0 commit comments