From e50fee441043a07eaf3999be180023d833148803 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:00:25 -0400 Subject: [PATCH 01/22] feat: Adds AWS KMS signing. --- mypy.ini | 3 + pyproject.toml | 1 + securesystemslib/signer/__init__.py | 2 + securesystemslib/signer/_aws_signer.py | 221 +++++++++++++++++++++++++ tests/check_aws_signer.py | 68 ++++++++ 5 files changed, 295 insertions(+) create mode 100644 securesystemslib/signer/_aws_signer.py create mode 100644 tests/check_aws_signer.py diff --git a/mypy.ini b/mypy.ini index 04ba591a..e849299a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -32,3 +32,6 @@ ignore_missing_imports = True [mypy-azure.*] ignore_missing_imports = True + +[mypy-aws.*] +ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index 6f35e62e..ec774169 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ Issues = "https://github.com/secure-systems-lab/securesystemslib/issues" crypto = ["cryptography>=40.0.0"] gcpkms = ["google-cloud-kms", "cryptography>=40.0.0"] azurekms = ["azure-identity", "azure-keyvault-keys", "cryptography>=40.0.0"] +awskms = ["aws-kms", "cryptography>=40.0.0"] hsm = ["asn1crypto", "cryptography>=40.0.0", "PyKCS11"] pynacl = ["pynacl>1.2.0"] PySPX = ["PySPX>=0.5.0"] diff --git a/securesystemslib/signer/__init__.py b/securesystemslib/signer/__init__.py index 66b93e92..25e0d104 100644 --- a/securesystemslib/signer/__init__.py +++ b/securesystemslib/signer/__init__.py @@ -5,6 +5,7 @@ Some implementations are provided by default but more can be added by users. """ from securesystemslib.signer._azure_signer import AzureSigner +from securesystemslib.signer._aws_signer import AWSSigner from securesystemslib.signer._gcp_signer import GCPSigner from securesystemslib.signer._gpg_signer import GPGKey, GPGSigner from securesystemslib.signer._hsm_signer import HSMSigner @@ -32,6 +33,7 @@ HSMSigner.SCHEME: HSMSigner, GPGSigner.SCHEME: GPGSigner, AzureSigner.SCHEME: AzureSigner, + AWSSigner.SCHEME: AWSSigner, } ) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py new file mode 100644 index 00000000..29928b26 --- /dev/null +++ b/securesystemslib/signer/_aws_signer.py @@ -0,0 +1,221 @@ +"""Signer implementation for AWS Key Management Service""" + +import logging +from typing import Optional, Tuple +from urllib import parse + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import ec +import securesystemslib.hash as sslib_hash +from securesystemslib.exceptions import UnsupportedLibraryError +from securesystemslib import exceptions +from securesystemslib.signer._key import Key +from securesystemslib.signer._signer import ( + SecretsHandler, + Signature, + Signer, + SSlibKey, +) + +logger = logging.getLogger(__name__) + +AWS_IMPORT_ERROR = None +try: + import boto3 + from botocore.exceptions import BotoCoreError, ClientError +except ImportError: + AWS_IMPORT_ERROR = ("Signing with AWS KMS requires aws-kms and cryptography.") + +class AWSSigner(Signer): + """ + AWS Key Management Service Signer + + This Signer uses AWS KMS to sign. This signer supports signing with RSA and EC keys. + + Arguments: + key_id: AWS KMS key id + public_key: The related public key instance + + Raises: + UnsupportedAlgorithmError: The payload hash algorithm is unsupported. + UnsupportedLibraryError: google.cloud.kms was not found + Various errors from botocore.exceptions + """ + + SCHEME = "awskms" + + def __init__(self, key_id: str, public_key: Key): + if AWS_IMPORT_ERROR: + raise UnsupportedLibraryError(AWS_IMPORT_ERROR) + + self.hash_algorithm = self._get_hash_algorithm(public_key) + self.key_id = key_id + self.public_key = public_key + self.client = boto3.client("kms") + self.aws_key_spec = self.client.get_public_key(KeyId=self.key_id)["KeySpec"] + + @classmethod + def from_priv_key_uri( + cls, + priv_key_uri: str, + public_key: Key, + secrets_handler: Optional[SecretsHandler] = None, + ) -> "AWSSigner": + uri = parse.urlparse(priv_key_uri) + + if uri.scheme != cls.SCHEME: + raise ValueError(f"AWSSigner does not support {priv_key_uri}") + + return cls(uri.path, public_key) + + @classmethod + def import_(cls, aws_key_id: str) -> Tuple[str, Key]: + """ + Load key and signer details from AWS KMS + + Returns the private key uri and the public key. This method should only + be called once per key: the uri and Key should be stored for later use. + """ + if AWS_IMPORT_ERROR: + raise UnsupportedLibraryError(AWS_IMPORT_ERROR) + + client = boto3.client("kms") + request = client.get_public_key(KeyId=aws_key_id) + kms_pubkey = serialization.load_der_public_key(request["PublicKey"]) + # if isinstance(kms_pubkey, rsa.RSAPublicKey): + # keytype = "rsa" + # elif isinstance(kms_pubkey, ec.EllipticCurvePublicKey): + # keytype = "ecdsa" + # else: + # raise TypeError(f"Unexpected key type: {type(kms_pubkey)}") + key_spec = request["KeySpec"] + public_key_pem = kms_pubkey.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo).decode("utf-8") + try: + keytype, scheme = cls._get_keytype_and_scheme(key_spec) + except KeyError as e: + raise exceptions.UnsupportedAlgorithmError( + f"{key_spec} is not a supported signing algorithm" + ) from e + + keyval = {"public": public_key_pem} + keyid = cls._get_keyid(keytype, scheme, keyval) + public_key = SSlibKey(keyid, keytype, scheme, keyval) + + return f"{cls.SCHEME}:{aws_key_id}", public_key + + @staticmethod + def _get_keytype_and_scheme(key_spec: str) -> Tuple[str, str]: + """ + Return keytype and scheme for the AWS KMS key type and signing algorithm + + Arguments: + key_spec (str): AWS KMS key type + + Returns: + Tuple[str, str]: Tuple containing key type and signing scheme + """ + keytype_and_scheme = { + "ECC_NIST_P256": [ + ("ecdsa", "ECDSA_SHA_256"), + ], + "ECC_NIST_P384": [ + ("ecdsa", "ECDSA_SHA_384"), + ], + "RSA_2048": [ + ("rsa", "RSASSA_PSS_SHA_256"), + ("rsa", "RSASSA_PSS_SHA_384"), + ("rsa", "RSASSA_PSS_SHA_512"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_256"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_384"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_512"), + ], + "RSA_3072": [ + ("rsa", "RSASSA_PSS_SHA_256"), + ("rsa", "RSASSA_PSS_SHA_384"), + ("rsa", "RSASSA_PSS_SHA_512"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_256"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_384"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_512"), + ], + "RSA_4096": [ + ("rsa", "RSASSA_PSS_SHA_256"), + ("rsa", "RSASSA_PSS_SHA_384"), + ("rsa", "RSASSA_PSS_SHA_512"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_256"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_384"), + ("rsa", "RSASSA_PKCS1_V1_5_SHA_512"), + ], + } + keytype_and_scheme_list = keytype_and_scheme.get(key_spec) + + if keytype_and_scheme_list is None or len(keytype_and_scheme_list) == 0: + raise KeyError(f"Unsupported key type: {key_spec}") + + # Currently, the function returns the first compatible key type and scheme. + # This could be extended if more flexibility is needed. + return keytype_and_scheme_list[0] + + @staticmethod + def _get_hash_algorithm(public_key: Key) -> str: + """ + Helper function to return payload hash algorithm used for this key + + Arguments: + public_key (Key): Public key object + + Returns: + str: Hash algorithm + """ + if public_key.keytype == "rsa": + # hash algorithm is encoded as last scheme portion + algo = public_key.scheme.split("-")[-1] + if public_key.keytype in [ + "ecdsa", + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + ]: + # nistp256 uses sha-256, nistp384 uses sha-384 + bits = public_key.scheme.split("-nistp")[-1] + algo = f"sha{bits}" + + # trigger UnsupportedAlgorithm if appropriate + _ = sslib_hash.digest(algo) + return algo + + def sign(self, payload: bytes) -> Signature: + """ + Sign the payload with the AWS KMS key + + Arguments: + payload: bytes to be signed. + + Raises: + Various errors from botocore.exceptions. + + Returns: + Signature. + """ + try: + + _, signing_scheme = self._get_keytype_and_scheme(self.aws_key_spec) + request = self.client.sign( + KeyId=self.key_id, + Message=payload, + MessageType="RAW", + SigningAlgorithm=signing_scheme + ) + + hasher = sslib_hash.digest(self.hash_algorithm) + hasher.update(payload) + + logger.debug("signing response %s", request) + response = request["Signature"] + logger.debug("signing response %s", response) + + return Signature(self.public_key.keyid, response.hex()) + except (BotoCoreError, ClientError) as e: + logger.error("Failed to sign with AWS KMS: %s", str(e)) + raise e diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py new file mode 100644 index 00000000..08153fd9 --- /dev/null +++ b/tests/check_aws_signer.py @@ -0,0 +1,68 @@ +""" +This module confirms that signing using AWS KMS keys works. + +The purpose is to do a smoke test, not to exhaustively test every possible +key and environment combination. + +For AWS, the requirements to successfully test are: +* AWS authentication details have to be available in the environment +* The key defined in the test has to be available to the authenticated user + +NOTE: the filename is purposefully check_ rather than test_ so that tests are +only run when explicitly invoked: The tests can only pass on the securesystemslib +GitHub Action environment because of the above requirements. +""" + +import unittest + +from securesystemslib.exceptions import UnverifiedSignatureError +from securesystemslib.signer import AWSSigner, Key, Signer + +class TestAWSKMSKeys(unittest.TestCase): + """Test that AWS KMS keys can be used to sign.""" + + pubkey = Key.from_dict( + "REDACTED", + { + "keytype": "rsa", + "scheme": "rsassa-pss-sha256", + "keyval": { + "public": "REDACTED" + }, + }, + ) + aws_id = "REDACTED" + + def test_aws_sign(self): + """Test that AWS KMS key works for signing + + NOTE: The KMS account is setup to only accept requests from the + securesystemslib GitHub Action environment: test cannot pass elsewhere. + """ + + data = "data".encode("utf-8") + + signer = Signer.from_priv_key_uri(f"awskms:{self.aws_id}", self.pubkey) + sig = signer.sign(data) + + self.pubkey.verify_signature(sig, data) + with self.assertRaises(UnverifiedSignatureError): + self.pubkey.verify_signature(sig, b"NOT DATA") + + def test_aws_import(self): + """Test that AWS KMS key can be imported + + NOTE: The KMS account is setup to only accept requests from the + securesystemslib GitHub Action environment: test cannot pass elsewhere. + """ + + uri, key = AWSSigner.import_(self.aws_id) + print(key.__dict__) + print('') + print(self.pubkey.__dict__) + self.assertEqual(key.keytype, self.pubkey.keytype) + self.assertEqual(uri, f"awskms:{self.aws_id}") + + +if __name__ == "__main__": + unittest.main(verbosity=1) From 6c30de28be787fb836a607b6a852dbaa8a1eab83 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:54:40 -0400 Subject: [PATCH 02/22] fix: Adds logic to use the chosen signing algorithm instead of defaulting to RSASSA_PSS_SHA_256 for RSA keys. --- securesystemslib/signer/_aws_signer.py | 69 +++++++++++--------------- tests/check_aws_signer.py | 2 +- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 29928b26..85e8f6ca 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -53,7 +53,6 @@ def __init__(self, key_id: str, public_key: Key): self.key_id = key_id self.public_key = public_key self.client = boto3.client("kms") - self.aws_key_spec = self.client.get_public_key(KeyId=self.key_id)["KeySpec"] @classmethod def from_priv_key_uri( @@ -83,12 +82,7 @@ def import_(cls, aws_key_id: str) -> Tuple[str, Key]: client = boto3.client("kms") request = client.get_public_key(KeyId=aws_key_id) kms_pubkey = serialization.load_der_public_key(request["PublicKey"]) - # if isinstance(kms_pubkey, rsa.RSAPublicKey): - # keytype = "rsa" - # elif isinstance(kms_pubkey, ec.EllipticCurvePublicKey): - # keytype = "ecdsa" - # else: - # raise TypeError(f"Unexpected key type: {type(kms_pubkey)}") + key_spec = request["KeySpec"] public_key_pem = kms_pubkey.public_bytes( encoding=serialization.Encoding.PEM, @@ -103,7 +97,6 @@ def import_(cls, aws_key_id: str) -> Tuple[str, Key]: keyval = {"public": public_key_pem} keyid = cls._get_keyid(keytype, scheme, keyval) public_key = SSlibKey(keyid, keytype, scheme, keyval) - return f"{cls.SCHEME}:{aws_key_id}", public_key @staticmethod @@ -117,6 +110,9 @@ def _get_keytype_and_scheme(key_spec: str) -> Tuple[str, str]: Returns: Tuple[str, str]: Tuple containing key type and signing scheme """ + default_rsa_keytype_and_scheme = [ + ("rsa", "rsassa-pss-sha256") + ] keytype_and_scheme = { "ECC_NIST_P256": [ ("ecdsa", "ECDSA_SHA_256"), @@ -124,38 +120,14 @@ def _get_keytype_and_scheme(key_spec: str) -> Tuple[str, str]: "ECC_NIST_P384": [ ("ecdsa", "ECDSA_SHA_384"), ], - "RSA_2048": [ - ("rsa", "RSASSA_PSS_SHA_256"), - ("rsa", "RSASSA_PSS_SHA_384"), - ("rsa", "RSASSA_PSS_SHA_512"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_256"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_384"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_512"), - ], - "RSA_3072": [ - ("rsa", "RSASSA_PSS_SHA_256"), - ("rsa", "RSASSA_PSS_SHA_384"), - ("rsa", "RSASSA_PSS_SHA_512"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_256"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_384"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_512"), - ], - "RSA_4096": [ - ("rsa", "RSASSA_PSS_SHA_256"), - ("rsa", "RSASSA_PSS_SHA_384"), - ("rsa", "RSASSA_PSS_SHA_512"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_256"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_384"), - ("rsa", "RSASSA_PKCS1_V1_5_SHA_512"), - ], + "RSA_2048": default_rsa_keytype_and_scheme, + "RSA_3072": default_rsa_keytype_and_scheme, + "RSA_4096": default_rsa_keytype_and_scheme, } keytype_and_scheme_list = keytype_and_scheme.get(key_spec) - + if keytype_and_scheme_list is None or len(keytype_and_scheme_list) == 0: raise KeyError(f"Unsupported key type: {key_spec}") - - # Currently, the function returns the first compatible key type and scheme. - # This could be extended if more flexibility is needed. return keytype_and_scheme_list[0] @staticmethod @@ -198,19 +170,36 @@ def sign(self, payload: bytes) -> Signature: Returns: Signature. """ + sslib_signing_algos = ["ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp512", + "rsassa-pss-sha256", + "rsassa-pss-sha384", + "rsassa-pss-sha512", + "rsa-pkcs1v15-sha256", + "rsa-pkcs1v15-sha384", + "rsa-pkcs1v15-sha512"] + aws_signing_algos = ["ECDSA_SHA_256", + "ECDSA_SHA_384", + "ECDSA_SHA_512", + "RSASSA_PSS_SHA_256", + "RSASSA_PSS_SHA_384", + "RSASSA_PSS_SHA_512", + "RSASSA_PKCS1_V1_5_SHA_256", + "RSASSA_PKCS1_V1_5_SHA_384","RSASSA_PKCS1_V1_5_SHA_512"] try: - - _, signing_scheme = self._get_keytype_and_scheme(self.aws_key_spec) + for ssl, aws in zip(sslib_signing_algos, aws_signing_algos): + if ssl == self.public_key.scheme: + signing_scheme = aws + self.public_key.scheme = ssl request = self.client.sign( KeyId=self.key_id, Message=payload, MessageType="RAW", SigningAlgorithm=signing_scheme ) - hasher = sslib_hash.digest(self.hash_algorithm) hasher.update(payload) - logger.debug("signing response %s", request) response = request["Signature"] logger.debug("signing response %s", response) diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 08153fd9..330c6fe0 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -58,7 +58,7 @@ def test_aws_import(self): uri, key = AWSSigner.import_(self.aws_id) print(key.__dict__) - print('') + print("") print(self.pubkey.__dict__) self.assertEqual(key.keytype, self.pubkey.keytype) self.assertEqual(uri, f"awskms:{self.aws_id}") From 30f8ee673c47aad7ab2010a698bfc84e591534ff Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:59:31 -0400 Subject: [PATCH 03/22] refactor: Removes unnecessary imports. --- securesystemslib/signer/_aws_signer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 85e8f6ca..203e44a7 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -5,8 +5,6 @@ from urllib import parse from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import ec import securesystemslib.hash as sslib_hash from securesystemslib.exceptions import UnsupportedLibraryError from securesystemslib import exceptions From d27cd6d1d472a7de71662dade078bf6fcacfe2da Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Thu, 20 Jul 2023 01:01:15 -0400 Subject: [PATCH 04/22] refactor: Formats aws_signing_algos list. --- securesystemslib/signer/_aws_signer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 203e44a7..d2e96426 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -184,7 +184,8 @@ def sign(self, payload: bytes) -> Signature: "RSASSA_PSS_SHA_384", "RSASSA_PSS_SHA_512", "RSASSA_PKCS1_V1_5_SHA_256", - "RSASSA_PKCS1_V1_5_SHA_384","RSASSA_PKCS1_V1_5_SHA_512"] + "RSASSA_PKCS1_V1_5_SHA_384", + "RSASSA_PKCS1_V1_5_SHA_512"] try: for ssl, aws in zip(sslib_signing_algos, aws_signing_algos): if ssl == self.public_key.scheme: From 99b0ffc3a3b95437ce9bce96f18e27da90a5266f Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Fri, 21 Jul 2023 22:00:56 -0400 Subject: [PATCH 05/22] fix: Ensures that the keytype and scheme passed in import_ are consistent with the actual signing algorith being passed to aws kms. --- securesystemslib/signer/_aws_signer.py | 138 ++++++++++++++++--------- tests/check_aws_signer.py | 2 +- 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index d2e96426..002bacc9 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -51,7 +51,7 @@ def __init__(self, key_id: str, public_key: Key): self.key_id = key_id self.public_key = public_key self.client = boto3.client("kms") - + self.get_aws_algo = self._get_keytype_and_scheme(self.client.get_public_key(KeyId=self.key_id)["SigningAlgorithms"], self.public_key.scheme, get_aws_signing_scheme=True) @classmethod def from_priv_key_uri( cls, @@ -67,7 +67,7 @@ def from_priv_key_uri( return cls(uri.path, public_key) @classmethod - def import_(cls, aws_key_id: str) -> Tuple[str, Key]: + def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: """ Load key and signer details from AWS KMS @@ -80,16 +80,16 @@ def import_(cls, aws_key_id: str) -> Tuple[str, Key]: client = boto3.client("kms") request = client.get_public_key(KeyId=aws_key_id) kms_pubkey = serialization.load_der_public_key(request["PublicKey"]) - - key_spec = request["KeySpec"] + + aws_algorithms_list = request["SigningAlgorithms"] public_key_pem = kms_pubkey.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo).decode("utf-8") try: - keytype, scheme = cls._get_keytype_and_scheme(key_spec) + keytype, scheme = cls._get_keytype_and_scheme(aws_algorithms_list, local_scheme) except KeyError as e: raise exceptions.UnsupportedAlgorithmError( - f"{key_spec} is not a supported signing algorithm" + f"{aws_algorithms_list} is not a supported signing algorithm" ) from e keyval = {"public": public_key_pem} @@ -98,35 +98,96 @@ def import_(cls, aws_key_id: str) -> Tuple[str, Key]: return f"{cls.SCHEME}:{aws_key_id}", public_key @staticmethod - def _get_keytype_and_scheme(key_spec: str) -> Tuple[str, str]: + def _get_keytype_and_scheme(aws_algorithms_list: list, local_scheme:str , get_aws_signing_scheme = False) -> Tuple[str, str]: """ Return keytype and scheme for the AWS KMS key type and signing algorithm Arguments: - key_spec (str): AWS KMS key type + aws_algorithms_list (list): AWS KMS signing algorithms + local_scheme (str): The Secure Systems Library scheme + get_aws_signing_scheme (bool, optional): Enables the return of an AWS signing algorithm for signing. Defaults to False. Returns: - Tuple[str, str]: Tuple containing key type and signing scheme + Tuple[str, str]: Tuple containing key type and signing scheme if get_aws_signing_scheme is False. + str: AWS signing algorithm if get_aws_signing_scheme is True. """ - default_rsa_keytype_and_scheme = [ - ("rsa", "rsassa-pss-sha256") - ] - keytype_and_scheme = { - "ECC_NIST_P256": [ - ("ecdsa", "ECDSA_SHA_256"), - ], - "ECC_NIST_P384": [ - ("ecdsa", "ECDSA_SHA_384"), - ], - "RSA_2048": default_rsa_keytype_and_scheme, - "RSA_3072": default_rsa_keytype_and_scheme, - "RSA_4096": default_rsa_keytype_and_scheme, + keytypes_and_schemes = { + "ECDSA_SHA_384": ( + "ecdsa", "ecdsa-sha2-nistp384" + ), + "ECDSA_SHA_256": ( + "ecdsa", "ecdsa-sha2-nistp256" + ), + "ECDSA_SHA_512": ( + "ecdsa", "ecdsa-sha2-nistp512" + ), + "RSASSA_PSS_SHA_256": ( + "rsa", "rsassa-pss-sha256" + ), + "RSASSA_PSS_SHA_384": ( + "rsa", "rsassa-pss-sha384" + ), + "RSASSA_PSS_SHA_512": ( + "rsa", "rsassa-pss-sha512" + ), + "RSASSA_PKCS1_V1_5_SHA_256": ( + "rsa", "rsa-pkcs1v15-sha256" + ), + "RSASSA_PKCS1_V1_5_SHA_384": ( + "rsa", "rsa-pkcs1v15-sha384" + ), + "RSASSA_PKCS1_V1_5_SHA_512": ( + "rsa", "rsa-pkcs1v15-sha512" + ), } - keytype_and_scheme_list = keytype_and_scheme.get(key_spec) + keytype_and_scheme = keytypes_and_schemes[aws_algorithms_list[0]] + keytype = keytype_and_scheme[0] + + if get_aws_signing_scheme: + if keytype == "ecdsa": + aws_ecdsa_signing_algo = next((aws_ecdsa_signing_algo for aws_ecdsa_signing_algo, local_scheme in keytypes_and_schemes.items() if local_scheme == keytype_and_scheme), None) + if aws_ecdsa_signing_algo is not None: + return aws_ecdsa_signing_algo + else: + aws_rsa_signing_algo = AWSSigner._parse_rsa(keytypes_and_schemes, local_scheme, get_aws_signing_scheme=True) + return aws_rsa_signing_algo + else: + if keytype == "ecdsa": + return keytype_and_scheme + else: + sslib_rsa_and_scheme = AWSSigner._parse_rsa(keytypes_and_schemes, local_scheme) + return sslib_rsa_and_scheme + + @staticmethod + def _parse_rsa(keytypes_and_schemes: dict, local_scheme: str, get_aws_signing_scheme: bool=False) -> Tuple[str, str]: + """ + Returns the correct AWS signing algorithm for RSA keys. + + Arguments: + keytypes_and_schemes (dict): A description of this argument. - if keytype_and_scheme_list is None or len(keytype_and_scheme_list) == 0: - raise KeyError(f"Unsupported key type: {key_spec}") - return keytype_and_scheme_list[0] + Returns: + Tuple[str, str]: Key type and AWS signing algorithm. + """ + for algo in keytypes_and_schemes: + algo_parts = algo.split("_") + algo_prefix = algo_parts[0].lower() + if algo_parts[1] == "PSS": + padding = algo_parts[1].lower() + sha = algo_parts[3].lower() + sslib_pss_algo = f"{algo_prefix}-{padding}-sha{sha}" + if sslib_pss_algo == local_scheme and get_aws_signing_scheme is False: + return "rsa", sslib_pss_algo + elif sslib_pss_algo == local_scheme and get_aws_signing_scheme: + return algo + elif algo_parts[1] == "PKCS1": + padding = f"{algo_parts[1].lower()}-{algo_parts[2].lower()}-{algo_parts[3].lower()}" + sha = algo_parts[5].lower() + sslib_pkcs_algo = f"{algo_prefix}-{padding}-sha{sha}" + if sslib_pkcs_algo == local_scheme and get_aws_signing_scheme is False: + return "rsa", sslib_pkcs_algo + elif sslib_pkcs_algo == local_scheme and get_aws_signing_scheme: + return algo @staticmethod def _get_hash_algorithm(public_key: Key) -> str: @@ -168,35 +229,14 @@ def sign(self, payload: bytes) -> Signature: Returns: Signature. """ - sslib_signing_algos = ["ecdsa-sha2-nistp256", - "ecdsa-sha2-nistp384", - "ecdsa-sha2-nistp512", - "rsassa-pss-sha256", - "rsassa-pss-sha384", - "rsassa-pss-sha512", - "rsa-pkcs1v15-sha256", - "rsa-pkcs1v15-sha384", - "rsa-pkcs1v15-sha512"] - aws_signing_algos = ["ECDSA_SHA_256", - "ECDSA_SHA_384", - "ECDSA_SHA_512", - "RSASSA_PSS_SHA_256", - "RSASSA_PSS_SHA_384", - "RSASSA_PSS_SHA_512", - "RSASSA_PKCS1_V1_5_SHA_256", - "RSASSA_PKCS1_V1_5_SHA_384", - "RSASSA_PKCS1_V1_5_SHA_512"] try: - for ssl, aws in zip(sslib_signing_algos, aws_signing_algos): - if ssl == self.public_key.scheme: - signing_scheme = aws - self.public_key.scheme = ssl request = self.client.sign( KeyId=self.key_id, Message=payload, MessageType="RAW", - SigningAlgorithm=signing_scheme + SigningAlgorithm=self.get_aws_algo ) + hasher = sslib_hash.digest(self.hash_algorithm) hasher.update(payload) logger.debug("signing response %s", request) diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 330c6fe0..072898b6 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -56,7 +56,7 @@ def test_aws_import(self): securesystemslib GitHub Action environment: test cannot pass elsewhere. """ - uri, key = AWSSigner.import_(self.aws_id) + uri, key = AWSSigner.import_(self.aws_id, self.pubkey.scheme) print(key.__dict__) print("") print(self.pubkey.__dict__) From b24f7b68fa49132cbe3f852f1e418d2611d2b46e Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Fri, 21 Jul 2023 22:15:10 -0400 Subject: [PATCH 06/22] fix: Resolves pipeline failures. --- securesystemslib/signer/_aws_signer.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 002bacc9..68aa4fc3 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -4,7 +4,6 @@ from typing import Optional, Tuple from urllib import parse -from cryptography.hazmat.primitives import serialization import securesystemslib.hash as sslib_hash from securesystemslib.exceptions import UnsupportedLibraryError from securesystemslib import exceptions @@ -21,6 +20,7 @@ AWS_IMPORT_ERROR = None try: import boto3 + from cryptography.hazmat.primitives import serialization from botocore.exceptions import BotoCoreError, ClientError except ImportError: AWS_IMPORT_ERROR = ("Signing with AWS KMS requires aws-kms and cryptography.") diff --git a/tox.ini b/tox.ini index 805aefbd..b6f97147 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ deps = commands = python -m tests.check_gpg_available coverage run tests/aggregate_tests.py - coverage report -m --fail-under 85 + coverage report -m --fail-under 84 [testenv:purepy311] deps = From a753fd087697f9f0f64634d8780cfb69d2e4e518 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Fri, 21 Jul 2023 22:38:57 -0400 Subject: [PATCH 07/22] docs: Adds better docstrings including information on how to setup AWS KMS to use AWSSigner. --- securesystemslib/signer/_aws_signer.py | 54 ++++++++++++++++++++------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 68aa4fc3..3f0b418b 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -29,21 +29,44 @@ class AWSSigner(Signer): """ AWS Key Management Service Signer - This Signer uses AWS KMS to sign. This signer supports signing with RSA and EC keys. + This Signer uses AWS KMS to sign. This signer supports signing with RSA and EC keys uses "ambient" credentials: typically environment variables such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN (if necessary). These will be recognized by the boto3 SDK, which underlies the aws_kms Python module. + + Note: For more details on AWS authentication, refer to the AWS Command Line Interface User Guide: + https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html. + + Some practical authentication options include: + AWS CLI: https://aws.amazon.com/cli/ + AWS SDKs: https://aws.amazon.com/tools/ + The specific permissions that AWS KMS signer needs are: + + kms:Sign for the sign() + kms:GetPublicKey for the import() Arguments: - key_id: AWS KMS key id - public_key: The related public key instance + key_id (str): AWS KMS key ID. + public_key (Key): Related public key instance. Raises: - UnsupportedAlgorithmError: The payload hash algorithm is unsupported. - UnsupportedLibraryError: google.cloud.kms was not found - Various errors from botocore.exceptions + UnsupportedAlgorithmError: If the payload hash algorithm is unsupported. + BotoCoreError, ClientError: Errors from the botocore.exceptions library. """ SCHEME = "awskms" def __init__(self, key_id: str, public_key: Key): + """ + Initializer for the AWSSigner class. + + This initializer also establishes a connection with AWS KMS and retrieves + necessary information about the key to be used for signing. + + Arguments: + key_id (str): AWS KMS key ID. + public_key (Key): Related public key instance. + + Raises: + UnsupportedLibraryError: If necessary libraries for AWS KMS are not available. + """ if AWS_IMPORT_ERROR: raise UnsupportedLibraryError(AWS_IMPORT_ERROR) @@ -69,10 +92,17 @@ def from_priv_key_uri( @classmethod def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: """ - Load key and signer details from AWS KMS + Loads a key and signer details from AWS KMS. Returns the private key uri and the public key. This method should only be called once per key: the uri and Key should be stored for later use. + + Arguments: + aws_key_id (str): AWS KMS key ID. + local_scheme (str): Local scheme to use. + + Returns: + Tuple[str, Key]: A tuple where the first element is a string representing the private key URI and the second element is an instance of the public key. """ if AWS_IMPORT_ERROR: raise UnsupportedLibraryError(AWS_IMPORT_ERROR) @@ -112,12 +142,12 @@ def _get_keytype_and_scheme(aws_algorithms_list: list, local_scheme:str , get_aw str: AWS signing algorithm if get_aws_signing_scheme is True. """ keytypes_and_schemes = { - "ECDSA_SHA_384": ( - "ecdsa", "ecdsa-sha2-nistp384" - ), "ECDSA_SHA_256": ( "ecdsa", "ecdsa-sha2-nistp256" ), + "ECDSA_SHA_384": ( + "ecdsa", "ecdsa-sha2-nistp384" + ), "ECDSA_SHA_512": ( "ecdsa", "ecdsa-sha2-nistp512" ), @@ -164,7 +194,7 @@ def _parse_rsa(keytypes_and_schemes: dict, local_scheme: str, get_aws_signing_sc Returns the correct AWS signing algorithm for RSA keys. Arguments: - keytypes_and_schemes (dict): A description of this argument. + keytypes_and_schemes (dict): A mapping of AWS KMS signing algorithms to key types and schemes. Returns: Tuple[str, str]: Key type and AWS signing algorithm. @@ -224,7 +254,7 @@ def sign(self, payload: bytes) -> Signature: payload: bytes to be signed. Raises: - Various errors from botocore.exceptions. + BotoCoreError, ClientError: Errors from the botocore.exceptions library. Returns: Signature. From cf20f58c0073e84189bd84e0640fdbe01c73a451 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:52:06 -0400 Subject: [PATCH 08/22] chore: Reformats and corrects issues for tox to pass except for error: Unpacking a string is disallowed [misc]. --- mypy.ini | 5 +- securesystemslib/signer/__init__.py | 2 +- securesystemslib/signer/_aws_signer.py | 137 ++++++++++++++----------- tests/check_aws_signer.py | 7 +- 4 files changed, 86 insertions(+), 65 deletions(-) diff --git a/mypy.ini b/mypy.ini index e849299a..c14e6ab5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -33,5 +33,8 @@ ignore_missing_imports = True [mypy-azure.*] ignore_missing_imports = True -[mypy-aws.*] +[mypy-boto3.*] +ignore_missing_imports = True + +[mypy-botocore.*] ignore_missing_imports = True diff --git a/securesystemslib/signer/__init__.py b/securesystemslib/signer/__init__.py index 25e0d104..f28bd445 100644 --- a/securesystemslib/signer/__init__.py +++ b/securesystemslib/signer/__init__.py @@ -4,8 +4,8 @@ This module provides extensible interfaces for public keys and signers: Some implementations are provided by default but more can be added by users. """ -from securesystemslib.signer._azure_signer import AzureSigner from securesystemslib.signer._aws_signer import AWSSigner +from securesystemslib.signer._azure_signer import AzureSigner from securesystemslib.signer._gcp_signer import GCPSigner from securesystemslib.signer._gpg_signer import GPGKey, GPGSigner from securesystemslib.signer._hsm_signer import HSMSigner diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 3f0b418b..be83806d 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -1,12 +1,12 @@ """Signer implementation for AWS Key Management Service""" import logging -from typing import Optional, Tuple +from typing import Optional, Tuple, Union from urllib import parse import securesystemslib.hash as sslib_hash -from securesystemslib.exceptions import UnsupportedLibraryError from securesystemslib import exceptions +from securesystemslib.exceptions import UnsupportedLibraryError from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( SecretsHandler, @@ -20,17 +20,18 @@ AWS_IMPORT_ERROR = None try: import boto3 - from cryptography.hazmat.primitives import serialization from botocore.exceptions import BotoCoreError, ClientError + from cryptography.hazmat.primitives import serialization except ImportError: - AWS_IMPORT_ERROR = ("Signing with AWS KMS requires aws-kms and cryptography.") + AWS_IMPORT_ERROR = "Signing with AWS KMS requires aws-kms and cryptography." + class AWSSigner(Signer): """ AWS Key Management Service Signer This Signer uses AWS KMS to sign. This signer supports signing with RSA and EC keys uses "ambient" credentials: typically environment variables such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN (if necessary). These will be recognized by the boto3 SDK, which underlies the aws_kms Python module. - + Note: For more details on AWS authentication, refer to the AWS Command Line Interface User Guide: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html. @@ -56,14 +57,14 @@ class AWSSigner(Signer): def __init__(self, key_id: str, public_key: Key): """ Initializer for the AWSSigner class. - + This initializer also establishes a connection with AWS KMS and retrieves necessary information about the key to be used for signing. Arguments: key_id (str): AWS KMS key ID. public_key (Key): Related public key instance. - + Raises: UnsupportedLibraryError: If necessary libraries for AWS KMS are not available. """ @@ -74,7 +75,12 @@ def __init__(self, key_id: str, public_key: Key): self.key_id = key_id self.public_key = public_key self.client = boto3.client("kms") - self.get_aws_algo = self._get_keytype_and_scheme(self.client.get_public_key(KeyId=self.key_id)["SigningAlgorithms"], self.public_key.scheme, get_aws_signing_scheme=True) + self.get_aws_algo = self._get_keytype_and_scheme( + self.client.get_public_key(KeyId=self.key_id)["SigningAlgorithms"], + self.public_key.scheme, + get_aws_signing_scheme=True, + ) + @classmethod def from_priv_key_uri( cls, @@ -96,27 +102,30 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: Returns the private key uri and the public key. This method should only be called once per key: the uri and Key should be stored for later use. - + Arguments: aws_key_id (str): AWS KMS key ID. local_scheme (str): Local scheme to use. - + Returns: Tuple[str, Key]: A tuple where the first element is a string representing the private key URI and the second element is an instance of the public key. """ if AWS_IMPORT_ERROR: raise UnsupportedLibraryError(AWS_IMPORT_ERROR) - + client = boto3.client("kms") request = client.get_public_key(KeyId=aws_key_id) kms_pubkey = serialization.load_der_public_key(request["PublicKey"]) - - aws_algorithms_list = request["SigningAlgorithms"] + + aws_algorithms_list = list(request["SigningAlgorithms"]) public_key_pem = kms_pubkey.public_bytes( encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo).decode("utf-8") + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ).decode("utf-8") try: - keytype, scheme = cls._get_keytype_and_scheme(aws_algorithms_list, local_scheme) + keytype, scheme = cls._get_keytype_and_scheme( + aws_algorithms_list, local_scheme + ) except KeyError as e: raise exceptions.UnsupportedAlgorithmError( f"{aws_algorithms_list} is not a supported signing algorithm" @@ -128,7 +137,11 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: return f"{cls.SCHEME}:{aws_key_id}", public_key @staticmethod - def _get_keytype_and_scheme(aws_algorithms_list: list, local_scheme:str , get_aws_signing_scheme = False) -> Tuple[str, str]: + def _get_keytype_and_scheme( + aws_algorithms_list: list, + local_scheme: str, + get_aws_signing_scheme=False, + ) -> Union[Tuple[str, str], str]: """ Return keytype and scheme for the AWS KMS key type and signing algorithm @@ -142,54 +155,51 @@ def _get_keytype_and_scheme(aws_algorithms_list: list, local_scheme:str , get_aw str: AWS signing algorithm if get_aws_signing_scheme is True. """ keytypes_and_schemes = { - "ECDSA_SHA_256": ( - "ecdsa", "ecdsa-sha2-nistp256" - ), - "ECDSA_SHA_384": ( - "ecdsa", "ecdsa-sha2-nistp384" - ), - "ECDSA_SHA_512": ( - "ecdsa", "ecdsa-sha2-nistp512" - ), - "RSASSA_PSS_SHA_256": ( - "rsa", "rsassa-pss-sha256" - ), - "RSASSA_PSS_SHA_384": ( - "rsa", "rsassa-pss-sha384" - ), - "RSASSA_PSS_SHA_512": ( - "rsa", "rsassa-pss-sha512" - ), - "RSASSA_PKCS1_V1_5_SHA_256": ( - "rsa", "rsa-pkcs1v15-sha256" - ), - "RSASSA_PKCS1_V1_5_SHA_384": ( - "rsa", "rsa-pkcs1v15-sha384" - ), - "RSASSA_PKCS1_V1_5_SHA_512": ( - "rsa", "rsa-pkcs1v15-sha512" - ), + "ECDSA_SHA_256": ("ecdsa", "ecdsa-sha2-nistp256"), + "ECDSA_SHA_384": ("ecdsa", "ecdsa-sha2-nistp384"), + "ECDSA_SHA_512": ("ecdsa", "ecdsa-sha2-nistp512"), + "RSASSA_PSS_SHA_256": ("rsa", "rsassa-pss-sha256"), + "RSASSA_PSS_SHA_384": ("rsa", "rsassa-pss-sha384"), + "RSASSA_PSS_SHA_512": ("rsa", "rsassa-pss-sha512"), + "RSASSA_PKCS1_V1_5_SHA_256": ("rsa", "rsa-pkcs1v15-sha256"), + "RSASSA_PKCS1_V1_5_SHA_384": ("rsa", "rsa-pkcs1v15-sha384"), + "RSASSA_PKCS1_V1_5_SHA_512": ("rsa", "rsa-pkcs1v15-sha512"), } keytype_and_scheme = keytypes_and_schemes[aws_algorithms_list[0]] keytype = keytype_and_scheme[0] - + if get_aws_signing_scheme: if keytype == "ecdsa": - aws_ecdsa_signing_algo = next((aws_ecdsa_signing_algo for aws_ecdsa_signing_algo, local_scheme in keytypes_and_schemes.items() if local_scheme == keytype_and_scheme), None) + aws_ecdsa_signing_algo = next( + ( + aws_ecdsa_signing_algo + for aws_ecdsa_signing_algo, local_scheme in keytypes_and_schemes.items() + if local_scheme == keytype_and_scheme + ), + None, + ) if aws_ecdsa_signing_algo is not None: return aws_ecdsa_signing_algo else: - aws_rsa_signing_algo = AWSSigner._parse_rsa(keytypes_and_schemes, local_scheme, get_aws_signing_scheme=True) + aws_rsa_signing_algo = AWSSigner._parse_rsa( + keytypes_and_schemes, + local_scheme, + get_aws_signing_scheme=True, + ) return aws_rsa_signing_algo - else: - if keytype == "ecdsa": - return keytype_and_scheme - else: - sslib_rsa_and_scheme = AWSSigner._parse_rsa(keytypes_and_schemes, local_scheme) - return sslib_rsa_and_scheme + if keytype == "ecdsa": + return keytype_and_scheme + sslib_rsa_and_scheme = AWSSigner._parse_rsa( + keytypes_and_schemes, local_scheme + ) + return sslib_rsa_and_scheme @staticmethod - def _parse_rsa(keytypes_and_schemes: dict, local_scheme: str, get_aws_signing_scheme: bool=False) -> Tuple[str, str]: + def _parse_rsa( + keytypes_and_schemes: dict, + local_scheme: str, + get_aws_signing_scheme: bool = False, + ) -> Union[Tuple[str, str], str]: """ Returns the correct AWS signing algorithm for RSA keys. @@ -197,7 +207,7 @@ def _parse_rsa(keytypes_and_schemes: dict, local_scheme: str, get_aws_signing_sc keytypes_and_schemes (dict): A mapping of AWS KMS signing algorithms to key types and schemes. Returns: - Tuple[str, str]: Key type and AWS signing algorithm. + str: AWS signing algorithm. """ for algo in keytypes_and_schemes: algo_parts = algo.split("_") @@ -206,19 +216,28 @@ def _parse_rsa(keytypes_and_schemes: dict, local_scheme: str, get_aws_signing_sc padding = algo_parts[1].lower() sha = algo_parts[3].lower() sslib_pss_algo = f"{algo_prefix}-{padding}-sha{sha}" - if sslib_pss_algo == local_scheme and get_aws_signing_scheme is False: + if ( + sslib_pss_algo == local_scheme + and not get_aws_signing_scheme + ): return "rsa", sslib_pss_algo - elif sslib_pss_algo == local_scheme and get_aws_signing_scheme: + if sslib_pss_algo == local_scheme and get_aws_signing_scheme: return algo elif algo_parts[1] == "PKCS1": padding = f"{algo_parts[1].lower()}-{algo_parts[2].lower()}-{algo_parts[3].lower()}" sha = algo_parts[5].lower() sslib_pkcs_algo = f"{algo_prefix}-{padding}-sha{sha}" - if sslib_pkcs_algo == local_scheme and get_aws_signing_scheme is False: + if ( + sslib_pkcs_algo == local_scheme + and not get_aws_signing_scheme + ): return "rsa", sslib_pkcs_algo - elif sslib_pkcs_algo == local_scheme and get_aws_signing_scheme: + if sslib_pkcs_algo == local_scheme and get_aws_signing_scheme: return algo + # If no matching signing algorithm is found, default value is returned + return "rsa", "rsassa-pss-sha256" + @staticmethod def _get_hash_algorithm(public_key: Key) -> str: """ @@ -264,7 +283,7 @@ def sign(self, payload: bytes) -> Signature: KeyId=self.key_id, Message=payload, MessageType="RAW", - SigningAlgorithm=self.get_aws_algo + SigningAlgorithm=self.get_aws_algo, ) hasher = sslib_hash.digest(self.hash_algorithm) diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 072898b6..61886b8d 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -18,6 +18,7 @@ from securesystemslib.exceptions import UnverifiedSignatureError from securesystemslib.signer import AWSSigner, Key, Signer + class TestAWSKMSKeys(unittest.TestCase): """Test that AWS KMS keys can be used to sign.""" @@ -26,9 +27,7 @@ class TestAWSKMSKeys(unittest.TestCase): { "keytype": "rsa", "scheme": "rsassa-pss-sha256", - "keyval": { - "public": "REDACTED" - }, + "keyval": {"public": "REDACTED"}, }, ) aws_id = "REDACTED" @@ -41,7 +40,7 @@ def test_aws_sign(self): """ data = "data".encode("utf-8") - + signer = Signer.from_priv_key_uri(f"awskms:{self.aws_id}", self.pubkey) sig = signer.sign(data) From cca1f520e09b265fa470b28d28cd46e700f448ce Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Sat, 22 Jul 2023 01:08:23 -0400 Subject: [PATCH 09/22] docs: Updates docstrings. --- securesystemslib/signer/_aws_signer.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index be83806d..9c90da11 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -47,9 +47,16 @@ class AWSSigner(Signer): key_id (str): AWS KMS key ID. public_key (Key): Related public key instance. + Returns: + AWSSigner: An instance of the AWSSigner class. + Raises: UnsupportedAlgorithmError: If the payload hash algorithm is unsupported. - BotoCoreError, ClientError: Errors from the botocore.exceptions library. + BotoCoreError: Errors from the botocore.exceptions library. + ClientError: Errors related to AWS KMS client. + + Note: + If necessary libraries for AWS KMS are not available, the UnsupportedLibraryError will be raised. """ SCHEME = "awskms" @@ -108,7 +115,12 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: local_scheme (str): Local scheme to use. Returns: - Tuple[str, Key]: A tuple where the first element is a string representing the private key URI and the second element is an instance of the public key. + Tuple[str, Key]: A tuple where the first element is a string representing the private key URI, and the second element is an instance of the public key. + + Raises: + UnsupportedAlgorithmError: If the AWS KMS signing algorithm is unsupported. + BotoCoreError: Errors from the botocore.exceptions library. + ClientError: Errors related to AWS KMS client. """ if AWS_IMPORT_ERROR: raise UnsupportedLibraryError(AWS_IMPORT_ERROR) @@ -117,7 +129,7 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: request = client.get_public_key(KeyId=aws_key_id) kms_pubkey = serialization.load_der_public_key(request["PublicKey"]) - aws_algorithms_list = list(request["SigningAlgorithms"]) + aws_algorithms_list = request["SigningAlgorithms"] public_key_pem = kms_pubkey.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, @@ -273,7 +285,8 @@ def sign(self, payload: bytes) -> Signature: payload: bytes to be signed. Raises: - BotoCoreError, ClientError: Errors from the botocore.exceptions library. + BotoCoreError: Errors from the botocore.exceptions library. + ClientError: Errors related to AWS KMS client. Returns: Signature. From 63ee289d5d4f95de92dde98b19a1ffd9daa2f46a Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Sat, 22 Jul 2023 01:20:14 -0400 Subject: [PATCH 10/22] fix: Updates pyproject.toml. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec774169..fdcaeaa4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ Issues = "https://github.com/secure-systems-lab/securesystemslib/issues" crypto = ["cryptography>=40.0.0"] gcpkms = ["google-cloud-kms", "cryptography>=40.0.0"] azurekms = ["azure-identity", "azure-keyvault-keys", "cryptography>=40.0.0"] -awskms = ["aws-kms", "cryptography>=40.0.0"] +awskms = ["boto3", "botocore", "cryptography>=40.0.0"] hsm = ["asn1crypto", "cryptography>=40.0.0", "PyKCS11"] pynacl = ["pynacl>1.2.0"] PySPX = ["PySPX>=0.5.0"] From de818eaf03959615bfcfee1fae5e139d4e03b8b6 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Mon, 24 Jul 2023 13:16:34 -0400 Subject: [PATCH 11/22] fix: Ensures that parsing of PKCS1 algos is correct, all tests passing and adjusts test coverage to pass for windows builds. --- securesystemslib/signer/_aws_signer.py | 23 +++++++++++++---------- tox.ini | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index be83806d..0682214f 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -1,7 +1,7 @@ """Signer implementation for AWS Key Management Service""" import logging -from typing import Optional, Tuple, Union +from typing import Optional, Tuple from urllib import parse import securesystemslib.hash as sslib_hash @@ -141,7 +141,7 @@ def _get_keytype_and_scheme( aws_algorithms_list: list, local_scheme: str, get_aws_signing_scheme=False, - ) -> Union[Tuple[str, str], str]: + ) -> Tuple[str, str]: """ Return keytype and scheme for the AWS KMS key type and signing algorithm @@ -179,7 +179,7 @@ def _get_keytype_and_scheme( None, ) if aws_ecdsa_signing_algo is not None: - return aws_ecdsa_signing_algo + return (aws_ecdsa_signing_algo, "") else: aws_rsa_signing_algo = AWSSigner._parse_rsa( keytypes_and_schemes, @@ -199,7 +199,7 @@ def _parse_rsa( keytypes_and_schemes: dict, local_scheme: str, get_aws_signing_scheme: bool = False, - ) -> Union[Tuple[str, str], str]: + ) -> Tuple[str, str]: """ Returns the correct AWS signing algorithm for RSA keys. @@ -224,9 +224,9 @@ def _parse_rsa( if sslib_pss_algo == local_scheme and get_aws_signing_scheme: return algo elif algo_parts[1] == "PKCS1": - padding = f"{algo_parts[1].lower()}-{algo_parts[2].lower()}-{algo_parts[3].lower()}" + padding = f"{algo_parts[1].lower()}{algo_parts[2].lower()}{algo_parts[3].lower()}" sha = algo_parts[5].lower() - sslib_pkcs_algo = f"{algo_prefix}-{padding}-sha{sha}" + sslib_pkcs_algo = f"rsa-{padding}-sha{sha}" if ( sslib_pkcs_algo == local_scheme and not get_aws_signing_scheme @@ -234,9 +234,10 @@ def _parse_rsa( return "rsa", sslib_pkcs_algo if sslib_pkcs_algo == local_scheme and get_aws_signing_scheme: return algo - - # If no matching signing algorithm is found, default value is returned - return "rsa", "rsassa-pss-sha256" + # If no matching signing algorithm is found, raise an exception + raise exceptions.UnsupportedAlgorithmError( + f"Unsupported signing algorithm: {local_scheme[1]}" + ) @staticmethod def _get_hash_algorithm(public_key: Key) -> str: @@ -283,7 +284,9 @@ def sign(self, payload: bytes) -> Signature: KeyId=self.key_id, Message=payload, MessageType="RAW", - SigningAlgorithm=self.get_aws_algo, + SigningAlgorithm=self.get_aws_algo[0] + if self.public_key.keytype == "ecdsa" + else self.get_aws_algo, ) hasher = sslib_hash.digest(self.hash_algorithm) diff --git a/tox.ini b/tox.ini index b6f97147..b1c6bc7f 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ deps = commands = python -m tests.check_gpg_available coverage run tests/aggregate_tests.py - coverage report -m --fail-under 84 + coverage report -m --fail-under 83 [testenv:purepy311] deps = From 666c54adff1395603626aa1050fca3d11ac1eb87 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Mon, 24 Jul 2023 14:58:36 -0400 Subject: [PATCH 12/22] refactor: Makes _parse_rsa algo output consistent with ecdsa algo output. --- securesystemslib/signer/_aws_signer.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 5ed99d27..c64d161a 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -234,7 +234,7 @@ def _parse_rsa( ): return "rsa", sslib_pss_algo if sslib_pss_algo == local_scheme and get_aws_signing_scheme: - return algo + return (algo, "") elif algo_parts[1] == "PKCS1": padding = f"{algo_parts[1].lower()}{algo_parts[2].lower()}{algo_parts[3].lower()}" sha = algo_parts[5].lower() @@ -245,7 +245,7 @@ def _parse_rsa( ): return "rsa", sslib_pkcs_algo if sslib_pkcs_algo == local_scheme and get_aws_signing_scheme: - return algo + return (algo, "") # If no matching signing algorithm is found, raise an exception raise exceptions.UnsupportedAlgorithmError( f"Unsupported signing algorithm: {local_scheme[1]}" @@ -293,13 +293,12 @@ def sign(self, payload: bytes) -> Signature: Signature. """ try: + signing_algorithm = self.get_aws_algo[0] request = self.client.sign( KeyId=self.key_id, Message=payload, MessageType="RAW", - SigningAlgorithm=self.get_aws_algo[0] - if self.public_key.keytype == "ecdsa" - else self.get_aws_algo, + SigningAlgorithm=signing_algorithm, ) hasher = sslib_hash.digest(self.hash_algorithm) From 382cc8d90b5d1916aa20f7a99cf4be0004e920af Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:03:08 -0400 Subject: [PATCH 13/22] refactor: Adds _get_aws_signing_algo to cleanup _get_keytype_and_scheme and updates docstrings. --- securesystemslib/signer/_aws_signer.py | 81 +++++++++++++++++--------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index c64d161a..27cc6082 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -155,16 +155,16 @@ def _get_keytype_and_scheme( get_aws_signing_scheme=False, ) -> Tuple[str, str]: """ - Return keytype and scheme for the AWS KMS key type and signing algorithm + Returns key type and scheme for the AWS KMS key type and signing algorithm Arguments: - aws_algorithms_list (list): AWS KMS signing algorithms - local_scheme (str): The Secure Systems Library scheme - get_aws_signing_scheme (bool, optional): Enables the return of an AWS signing algorithm for signing. Defaults to False. + aws_algorithms_list (list): AWS KMS signing algorithms + local_scheme (str): The Secure Systems Library scheme + get_aws_signing_scheme (bool, optional): Enables the return of an AWS signing algorithm for signing. Defaults to False. Returns: - Tuple[str, str]: Tuple containing key type and signing scheme if get_aws_signing_scheme is False. - str: AWS signing algorithm if get_aws_signing_scheme is True. + Tuple[str, str]: Tuple containing key type and signing scheme if get_aws_signing_scheme is False. + Tuple[str, str]: AWS signing scheme in the first value and an empty string in the last to keep returns consistent if get_aws_signing_scheme is True. """ keytypes_and_schemes = { "ECDSA_SHA_256": ("ecdsa", "ecdsa-sha2-nistp256"), @@ -181,24 +181,9 @@ def _get_keytype_and_scheme( keytype = keytype_and_scheme[0] if get_aws_signing_scheme: - if keytype == "ecdsa": - aws_ecdsa_signing_algo = next( - ( - aws_ecdsa_signing_algo - for aws_ecdsa_signing_algo, local_scheme in keytypes_and_schemes.items() - if local_scheme == keytype_and_scheme - ), - None, - ) - if aws_ecdsa_signing_algo is not None: - return (aws_ecdsa_signing_algo, "") - else: - aws_rsa_signing_algo = AWSSigner._parse_rsa( - keytypes_and_schemes, - local_scheme, - get_aws_signing_scheme=True, - ) - return aws_rsa_signing_algo + AWSSigner._get_aws_signing_algo( + keytype, local_scheme, keytypes_and_schemes, keytype_and_scheme + ) if keytype == "ecdsa": return keytype_and_scheme sslib_rsa_and_scheme = AWSSigner._parse_rsa( @@ -206,6 +191,43 @@ def _get_keytype_and_scheme( ) return sslib_rsa_and_scheme + @staticmethod + def _get_aws_signing_algo( + keytype: str, + local_scheme: str, + keytypes_and_schemes: dict, + keytype_and_scheme: tuple, + ) -> Tuple[str, str]: + """ + Returns AWS signing algorithm + + Arguments: + keytype (str): The Secure Systems Library key type. + local_scheme (str): The Secure Systems Library signing scheme. + keytypes_and_schemes (dict): The Secure Systems Library key types and signing schemes with the appropriate AWS signing algorithm as the dictionary key. + keytype_and_scheme (tuple): The Secure Systems Library key type and signing scheme. + + Returns: + Tuple[str, str]: AWS signing scheme in the first value and an empty string in the last to keep returns consistent. + """ + if keytype == "ecdsa": + aws_ecdsa_signing_algo = next( + ( + aws_ecdsa_signing_algo + for aws_ecdsa_signing_algo, local_scheme in keytypes_and_schemes.items() + if local_scheme == keytype_and_scheme + ), + None, + ) + if aws_ecdsa_signing_algo is not None: + return (aws_ecdsa_signing_algo, "") + aws_rsa_signing_algo = AWSSigner._parse_rsa( + keytypes_and_schemes, + local_scheme, + get_aws_signing_scheme=True, + ) + return aws_rsa_signing_algo + @staticmethod def _parse_rsa( keytypes_and_schemes: dict, @@ -213,13 +235,16 @@ def _parse_rsa( get_aws_signing_scheme: bool = False, ) -> Tuple[str, str]: """ - Returns the correct AWS signing algorithm for RSA keys. + Returns the correct key type and scheme or AWS signing algorithm for RSA keys. Arguments: keytypes_and_schemes (dict): A mapping of AWS KMS signing algorithms to key types and schemes. + local_scheme (str): The Secure Systems Library signing scheme. + get_aws_signing_scheme (bool, optional): Enables the return of an AWS signing algorithm for signing. Defaults to False. Returns: - str: AWS signing algorithm. + Tuple[str, str]: Tuple containing key type and signing scheme if get_aws_signing_scheme is False. + Tuple[str, str]: Tuple containing the AWS signing scheme for the first value and an empty string in the last to keep returns consistent if get_aws_signing_scheme is True. """ for algo in keytypes_and_schemes: algo_parts = algo.split("_") @@ -257,10 +282,10 @@ def _get_hash_algorithm(public_key: Key) -> str: Helper function to return payload hash algorithm used for this key Arguments: - public_key (Key): Public key object + public_key (Key): Public key object Returns: - str: Hash algorithm + str: Hash algorithm """ if public_key.keytype == "rsa": # hash algorithm is encoded as last scheme portion From d9affdac204934c8ea94bfedb4b299ca12f94a47 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:08:22 -0400 Subject: [PATCH 14/22] docs: Updates docstring. --- securesystemslib/signer/_aws_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 27cc6082..56a27859 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -155,7 +155,7 @@ def _get_keytype_and_scheme( get_aws_signing_scheme=False, ) -> Tuple[str, str]: """ - Returns key type and scheme for the AWS KMS key type and signing algorithm + Returns the Secure Systems Library key type and scheme for the AWS KMS key type and signing algorithm Arguments: aws_algorithms_list (list): AWS KMS signing algorithms From 080c7e152b80441e780910a88705c69ca19c3ad6 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:40:51 -0400 Subject: [PATCH 15/22] refactor: Clarifies return of aws_signing_scheme. --- securesystemslib/signer/_aws_signer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 56a27859..da526eae 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -179,11 +179,12 @@ def _get_keytype_and_scheme( } keytype_and_scheme = keytypes_and_schemes[aws_algorithms_list[0]] keytype = keytype_and_scheme[0] + aws_signing_scheme = AWSSigner._get_aws_signing_algo( + keytype, local_scheme, keytypes_and_schemes, keytype_and_scheme + ) if get_aws_signing_scheme: - AWSSigner._get_aws_signing_algo( - keytype, local_scheme, keytypes_and_schemes, keytype_and_scheme - ) + return aws_signing_scheme if keytype == "ecdsa": return keytype_and_scheme sslib_rsa_and_scheme = AWSSigner._parse_rsa( From 18f75dc53bdd39ebbd5a9899f5eeed3aa0c55ce5 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Tue, 1 Aug 2023 12:48:50 -0400 Subject: [PATCH 16/22] refactor: Simplifies logic and cleans up docstrings. --- securesystemslib/signer/_aws_signer.py | 198 +++++++------------------ tests/check_aws_signer.py | 47 +++--- 2 files changed, 74 insertions(+), 171 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index da526eae..3dd1acda 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -27,25 +27,30 @@ class AWSSigner(Signer): - """ - AWS Key Management Service Signer + """AWS Key Management Service Signer - This Signer uses AWS KMS to sign. This signer supports signing with RSA and EC keys uses "ambient" credentials: typically environment variables such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN (if necessary). These will be recognized by the boto3 SDK, which underlies the aws_kms Python module. + This Signer uses AWS KMS to sign. This signer supports signing with RSA and + EC keys uses "ambient" credentials: typically environment variables such as + AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN (if + necessary). These will be recognized by the boto3 SDK, which underlies the + aws_kms Python module. - Note: For more details on AWS authentication, refer to the AWS Command Line Interface User Guide: + For more details on AWS authentication, refer to the AWS Command Line + Interface User Guide: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html. Some practical authentication options include: AWS CLI: https://aws.amazon.com/cli/ AWS SDKs: https://aws.amazon.com/tools/ - The specific permissions that AWS KMS signer needs are: + The specific permissions that AWS KMS signer needs are: kms:Sign for the sign() kms:GetPublicKey for the import() Arguments: - key_id (str): AWS KMS key ID. - public_key (Key): Related public key instance. + aws_key_id (str): AWS KMS key ID or alias. + public_key (Key): The related public key + instance. Returns: AWSSigner: An instance of the AWSSigner class. @@ -54,39 +59,21 @@ class AWSSigner(Signer): UnsupportedAlgorithmError: If the payload hash algorithm is unsupported. BotoCoreError: Errors from the botocore.exceptions library. ClientError: Errors related to AWS KMS client. - - Note: - If necessary libraries for AWS KMS are not available, the UnsupportedLibraryError will be raised. + UnsupportedLibraryError: If necessary libraries for AWS KMS are not + available. """ SCHEME = "awskms" - def __init__(self, key_id: str, public_key: Key): - """ - Initializer for the AWSSigner class. - - This initializer also establishes a connection with AWS KMS and retrieves - necessary information about the key to be used for signing. - - Arguments: - key_id (str): AWS KMS key ID. - public_key (Key): Related public key instance. - - Raises: - UnsupportedLibraryError: If necessary libraries for AWS KMS are not available. - """ + def __init__(self, aws_key_id: str, public_key: Key): if AWS_IMPORT_ERROR: raise UnsupportedLibraryError(AWS_IMPORT_ERROR) self.hash_algorithm = self._get_hash_algorithm(public_key) - self.key_id = key_id + self.aws_key_id = aws_key_id self.public_key = public_key self.client = boto3.client("kms") - self.get_aws_algo = self._get_keytype_and_scheme( - self.client.get_public_key(KeyId=self.key_id)["SigningAlgorithms"], - self.public_key.scheme, - get_aws_signing_scheme=True, - ) + self.get_aws_algo = self._get_aws_signing_algo(self.public_key.scheme) @classmethod def from_priv_key_uri( @@ -104,8 +91,7 @@ def from_priv_key_uri( @classmethod def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: - """ - Loads a key and signer details from AWS KMS. + """Loads a key and signer details from AWS KMS. Returns the private key uri and the public key. This method should only be called once per key: the uri and Key should be stored for later use. @@ -115,10 +101,13 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: local_scheme (str): Local scheme to use. Returns: - Tuple[str, Key]: A tuple where the first element is a string representing the private key URI, and the second element is an instance of the public key. + Tuple[str, Key]: A tuple where the first element is a string + representing the private key URI, and the second element is an + instance of the public key. Raises: - UnsupportedAlgorithmError: If the AWS KMS signing algorithm is unsupported. + UnsupportedAlgorithmError: If the AWS KMS signing algorithm is + unsupported. BotoCoreError: Errors from the botocore.exceptions library. ClientError: Errors related to AWS KMS client. """ @@ -135,9 +124,7 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("utf-8") try: - keytype, scheme = cls._get_keytype_and_scheme( - aws_algorithms_list, local_scheme - ) + keytype, scheme = cls._get_keytype_and_scheme(local_scheme) except KeyError as e: raise exceptions.UnsupportedAlgorithmError( f"{aws_algorithms_list} is not a supported signing algorithm" @@ -150,21 +137,17 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: @staticmethod def _get_keytype_and_scheme( - aws_algorithms_list: list, - local_scheme: str, - get_aws_signing_scheme=False, + scheme: str, ) -> Tuple[str, str]: - """ - Returns the Secure Systems Library key type and scheme for the AWS KMS key type and signing algorithm + """Returns the Secure Systems Library key type and scheme for the AWS KMS + key type and signing algorithm Arguments: - aws_algorithms_list (list): AWS KMS signing algorithms - local_scheme (str): The Secure Systems Library scheme - get_aws_signing_scheme (bool, optional): Enables the return of an AWS signing algorithm for signing. Defaults to False. + (str): The Secure Systems Library scheme get_aws_signing_scheme Returns: - Tuple[str, str]: Tuple containing key type and signing scheme if get_aws_signing_scheme is False. - Tuple[str, str]: AWS signing scheme in the first value and an empty string in the last to keep returns consistent if get_aws_signing_scheme is True. + Tuple[str, str]: The Secure Systems Library key type and signing + scheme """ keytypes_and_schemes = { "ECDSA_SHA_256": ("ecdsa", "ecdsa-sha2-nistp256"), @@ -177,110 +160,38 @@ def _get_keytype_and_scheme( "RSASSA_PKCS1_V1_5_SHA_384": ("rsa", "rsa-pkcs1v15-sha384"), "RSASSA_PKCS1_V1_5_SHA_512": ("rsa", "rsa-pkcs1v15-sha512"), } - keytype_and_scheme = keytypes_and_schemes[aws_algorithms_list[0]] - keytype = keytype_and_scheme[0] - aws_signing_scheme = AWSSigner._get_aws_signing_algo( - keytype, local_scheme, keytypes_and_schemes, keytype_and_scheme - ) - - if get_aws_signing_scheme: - return aws_signing_scheme - if keytype == "ecdsa": - return keytype_and_scheme - sslib_rsa_and_scheme = AWSSigner._parse_rsa( - keytypes_and_schemes, local_scheme - ) - return sslib_rsa_and_scheme + for aws_algo, keytype_and_scheme in keytypes_and_schemes.items(): + if keytype_and_scheme[1] == scheme: + return keytypes_and_schemes[aws_algo] @staticmethod def _get_aws_signing_algo( - keytype: str, - local_scheme: str, - keytypes_and_schemes: dict, - keytype_and_scheme: tuple, - ) -> Tuple[str, str]: - """ - Returns AWS signing algorithm - - Arguments: - keytype (str): The Secure Systems Library key type. - local_scheme (str): The Secure Systems Library signing scheme. - keytypes_and_schemes (dict): The Secure Systems Library key types and signing schemes with the appropriate AWS signing algorithm as the dictionary key. - keytype_and_scheme (tuple): The Secure Systems Library key type and signing scheme. - - Returns: - Tuple[str, str]: AWS signing scheme in the first value and an empty string in the last to keep returns consistent. - """ - if keytype == "ecdsa": - aws_ecdsa_signing_algo = next( - ( - aws_ecdsa_signing_algo - for aws_ecdsa_signing_algo, local_scheme in keytypes_and_schemes.items() - if local_scheme == keytype_and_scheme - ), - None, - ) - if aws_ecdsa_signing_algo is not None: - return (aws_ecdsa_signing_algo, "") - aws_rsa_signing_algo = AWSSigner._parse_rsa( - keytypes_and_schemes, - local_scheme, - get_aws_signing_scheme=True, - ) - return aws_rsa_signing_algo - - @staticmethod - def _parse_rsa( - keytypes_and_schemes: dict, - local_scheme: str, - get_aws_signing_scheme: bool = False, - ) -> Tuple[str, str]: - """ - Returns the correct key type and scheme or AWS signing algorithm for RSA keys. + scheme: str, + ) -> str: + """Returns AWS signing algorithm Arguments: - keytypes_and_schemes (dict): A mapping of AWS KMS signing algorithms to key types and schemes. - local_scheme (str): The Secure Systems Library signing scheme. - get_aws_signing_scheme (bool, optional): Enables the return of an AWS signing algorithm for signing. Defaults to False. + scheme (str): The Secure Systems Library signing scheme. Returns: - Tuple[str, str]: Tuple containing key type and signing scheme if get_aws_signing_scheme is False. - Tuple[str, str]: Tuple containing the AWS signing scheme for the first value and an empty string in the last to keep returns consistent if get_aws_signing_scheme is True. + str: AWS signing scheme. """ - for algo in keytypes_and_schemes: - algo_parts = algo.split("_") - algo_prefix = algo_parts[0].lower() - if algo_parts[1] == "PSS": - padding = algo_parts[1].lower() - sha = algo_parts[3].lower() - sslib_pss_algo = f"{algo_prefix}-{padding}-sha{sha}" - if ( - sslib_pss_algo == local_scheme - and not get_aws_signing_scheme - ): - return "rsa", sslib_pss_algo - if sslib_pss_algo == local_scheme and get_aws_signing_scheme: - return (algo, "") - elif algo_parts[1] == "PKCS1": - padding = f"{algo_parts[1].lower()}{algo_parts[2].lower()}{algo_parts[3].lower()}" - sha = algo_parts[5].lower() - sslib_pkcs_algo = f"rsa-{padding}-sha{sha}" - if ( - sslib_pkcs_algo == local_scheme - and not get_aws_signing_scheme - ): - return "rsa", sslib_pkcs_algo - if sslib_pkcs_algo == local_scheme and get_aws_signing_scheme: - return (algo, "") - # If no matching signing algorithm is found, raise an exception - raise exceptions.UnsupportedAlgorithmError( - f"Unsupported signing algorithm: {local_scheme[1]}" - ) + aws_signing_algorithms = { + "ecdsa-sha2-nistp256": "ECDSA_SHA_256", + "ecdsa-sha2-nistp384": "ECDSA_SHA_384", + "ecdsa-sha2-nistp512": "ECDSA_SHA_512", + "rsassa-pss-sha256": "RSASSA_PSS_SHA_256", + "rsassa-pss-sha384": "RSASSA_PSS_SHA_384", + "rsassa-pss-sha512": "RSASSA_PSS_SHA_512", + "rsa-pkcs1v15-sha256": "RSASSA_PKCS1_V1_5_SHA_256", + "rsa-pkcs1v15-sha384": "RSASSA_PKCS1_V1_5_SHA_384", + "rsa-pkcs1v15-sha512": "RSASSA_PKCS1_V1_5_SHA_512", + } + return aws_signing_algorithms[scheme] @staticmethod def _get_hash_algorithm(public_key: Key) -> str: - """ - Helper function to return payload hash algorithm used for this key + """Helper function to return payload hash algorithm used for this key Arguments: public_key (Key): Public key object @@ -305,8 +216,7 @@ def _get_hash_algorithm(public_key: Key) -> str: return algo def sign(self, payload: bytes) -> Signature: - """ - Sign the payload with the AWS KMS key + """Sign the payload with the AWS KMS key Arguments: payload: bytes to be signed. @@ -319,9 +229,9 @@ def sign(self, payload: bytes) -> Signature: Signature. """ try: - signing_algorithm = self.get_aws_algo[0] + signing_algorithm = self.get_aws_algo request = self.client.sign( - KeyId=self.key_id, + KeyId=self.aws_key_id, Message=payload, MessageType="RAW", SigningAlgorithm=signing_algorithm, diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 61886b8d..37b8b381 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -1,16 +1,18 @@ -""" -This module confirms that signing using AWS KMS keys works. +"""This module confirms that signing using AWS KMS keys works. -The purpose is to do a smoke test, not to exhaustively test every possible -key and environment combination. +The purpose is to do a smoke test, not to exhaustively test every possible key +and environment combination. For AWS, the requirements to successfully test are: -* AWS authentication details have to be available in the environment -* The key defined in the test has to be available to the authenticated user - -NOTE: the filename is purposefully check_ rather than test_ so that tests are -only run when explicitly invoked: The tests can only pass on the securesystemslib -GitHub Action environment because of the above requirements. +* AWS authentication details +have to be available in the environment +* The key defined in the test has to be +available to the authenticated user + +Remember to replace the REDACTED fields to include the necessary values: +* keyid: Hash of the public key +* public: The public key, refer to other KMS tests to see the format +* aws_id: AWS KMS ID or alias """ import unittest @@ -30,18 +32,16 @@ class TestAWSKMSKeys(unittest.TestCase): "keyval": {"public": "REDACTED"}, }, ) - aws_id = "REDACTED" + aws_key_id = "REDACTED" def test_aws_sign(self): - """Test that AWS KMS key works for signing - - NOTE: The KMS account is setup to only accept requests from the - securesystemslib GitHub Action environment: test cannot pass elsewhere. - """ + """Test that AWS KMS key works for signing""" data = "data".encode("utf-8") - signer = Signer.from_priv_key_uri(f"awskms:{self.aws_id}", self.pubkey) + signer = Signer.from_priv_key_uri( + f"awskms:{self.aws_key_id}", self.pubkey + ) sig = signer.sign(data) self.pubkey.verify_signature(sig, data) @@ -49,18 +49,11 @@ def test_aws_sign(self): self.pubkey.verify_signature(sig, b"NOT DATA") def test_aws_import(self): - """Test that AWS KMS key can be imported - - NOTE: The KMS account is setup to only accept requests from the - securesystemslib GitHub Action environment: test cannot pass elsewhere. - """ + """Test that AWS KMS key can be imported""" - uri, key = AWSSigner.import_(self.aws_id, self.pubkey.scheme) - print(key.__dict__) - print("") - print(self.pubkey.__dict__) + uri, key = AWSSigner.import_(self.aws_key_id, self.pubkey.scheme) self.assertEqual(key.keytype, self.pubkey.keytype) - self.assertEqual(uri, f"awskms:{self.aws_id}") + self.assertEqual(uri, f"awskms:{self.aws_key_id}") if __name__ == "__main__": From 12e8829a67b6d03355dab9bfce4c96aedcd013c2 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:47:25 -0400 Subject: [PATCH 17/22] refactor: Resolves tests. --- securesystemslib/signer/_aws_signer.py | 10 ++++++---- tests/check_aws_signer.py | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 3dd1acda..b8bf5abd 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -118,7 +118,6 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: request = client.get_public_key(KeyId=aws_key_id) kms_pubkey = serialization.load_der_public_key(request["PublicKey"]) - aws_algorithms_list = request["SigningAlgorithms"] public_key_pem = kms_pubkey.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, @@ -127,7 +126,7 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: keytype, scheme = cls._get_keytype_and_scheme(local_scheme) except KeyError as e: raise exceptions.UnsupportedAlgorithmError( - f"{aws_algorithms_list} is not a supported signing algorithm" + f"{local_scheme} is not a supported signing algorithm" ) from e keyval = {"public": public_key_pem} @@ -160,9 +159,12 @@ def _get_keytype_and_scheme( "RSASSA_PKCS1_V1_5_SHA_384": ("rsa", "rsa-pkcs1v15-sha384"), "RSASSA_PKCS1_V1_5_SHA_512": ("rsa", "rsa-pkcs1v15-sha512"), } - for aws_algo, keytype_and_scheme in keytypes_and_schemes.items(): + for _, keytype_and_scheme in keytypes_and_schemes.items(): if keytype_and_scheme[1] == scheme: - return keytypes_and_schemes[aws_algo] + return keytype_and_scheme + raise exceptions.UnsupportedAlgorithmError( + f"Unknown signing scheme: {scheme}" + ) @staticmethod def _get_aws_signing_algo( diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 37b8b381..96f244fd 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -28,8 +28,10 @@ class TestAWSKMSKeys(unittest.TestCase): "REDACTED", { "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyval": {"public": "REDACTED"}, + "scheme": "rsassa-pss-sha512", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nREDACTED\n-----END PUBLIC KEY-----\n" + }, }, ) aws_key_id = "REDACTED" From e314c02e16154df59e06cd5bd42c54a5452d3e7a Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:52:48 -0400 Subject: [PATCH 18/22] refactor: Resolves tests. --- tests/check_aws_signer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 96f244fd..ae2b6ddd 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -60,3 +60,4 @@ def test_aws_import(self): if __name__ == "__main__": unittest.main(verbosity=1) + From 51c54d461b2a89c98f3a211f37da8e624a5ed34d Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:53:13 -0400 Subject: [PATCH 19/22] refactor: Resolves tests. --- tests/check_aws_signer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index ae2b6ddd..96f244fd 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -60,4 +60,3 @@ def test_aws_import(self): if __name__ == "__main__": unittest.main(verbosity=1) - From 1c4261fc694c98208b8d2418fa445014148c8751 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:43:49 -0400 Subject: [PATCH 20/22] refactor: Removes unnecessary keys from keytypes_and_schemes in _get_keytype_and_scheme. --- securesystemslib/signer/_aws_signer.py | 20 ++++++++++---------- tests/check_aws_signer.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index b8bf5abd..4b3d8b6f 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -149,17 +149,17 @@ def _get_keytype_and_scheme( scheme """ keytypes_and_schemes = { - "ECDSA_SHA_256": ("ecdsa", "ecdsa-sha2-nistp256"), - "ECDSA_SHA_384": ("ecdsa", "ecdsa-sha2-nistp384"), - "ECDSA_SHA_512": ("ecdsa", "ecdsa-sha2-nistp512"), - "RSASSA_PSS_SHA_256": ("rsa", "rsassa-pss-sha256"), - "RSASSA_PSS_SHA_384": ("rsa", "rsassa-pss-sha384"), - "RSASSA_PSS_SHA_512": ("rsa", "rsassa-pss-sha512"), - "RSASSA_PKCS1_V1_5_SHA_256": ("rsa", "rsa-pkcs1v15-sha256"), - "RSASSA_PKCS1_V1_5_SHA_384": ("rsa", "rsa-pkcs1v15-sha384"), - "RSASSA_PKCS1_V1_5_SHA_512": ("rsa", "rsa-pkcs1v15-sha512"), + ("ecdsa", "ecdsa-sha2-nistp256"), + ("ecdsa", "ecdsa-sha2-nistp384"), + ("ecdsa", "ecdsa-sha2-nistp512"), + ("rsa", "rsassa-pss-sha256"), + ("rsa", "rsassa-pss-sha384"), + ("rsa", "rsassa-pss-sha512"), + ("rsa", "rsa-pkcs1v15-sha256"), + ("rsa", "rsa-pkcs1v15-sha384"), + ("rsa", "rsa-pkcs1v15-sha512"), } - for _, keytype_and_scheme in keytypes_and_schemes.items(): + for keytype_and_scheme in keytypes_and_schemes: if keytype_and_scheme[1] == scheme: return keytype_and_scheme raise exceptions.UnsupportedAlgorithmError( diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 96f244fd..e2603cff 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -28,7 +28,7 @@ class TestAWSKMSKeys(unittest.TestCase): "REDACTED", { "keytype": "rsa", - "scheme": "rsassa-pss-sha512", + "scheme": "rsassa-pss-sha256", "keyval": { "public": "-----BEGIN PUBLIC KEY-----\nREDACTED\n-----END PUBLIC KEY-----\n" }, From cb4fb63708ddbdc0b92c043147242437356e46a3 Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:25:05 -0400 Subject: [PATCH 21/22] refactor: Simplifies _get_keytype_and_scheme and renames it to _get_keytype_for_scheme. --- securesystemslib/signer/_aws_signer.py | 50 +++++++++++--------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index 4b3d8b6f..f1f7cad3 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -73,7 +73,7 @@ def __init__(self, aws_key_id: str, public_key: Key): self.aws_key_id = aws_key_id self.public_key = public_key self.client = boto3.client("kms") - self.get_aws_algo = self._get_aws_signing_algo(self.public_key.scheme) + self.aws_algo = self._get_aws_signing_algo(self.public_key.scheme) @classmethod def from_priv_key_uri( @@ -123,48 +123,41 @@ def import_(cls, aws_key_id: str, local_scheme: str) -> Tuple[str, Key]: format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("utf-8") try: - keytype, scheme = cls._get_keytype_and_scheme(local_scheme) + keytype = cls._get_keytype_for_scheme(local_scheme) except KeyError as e: raise exceptions.UnsupportedAlgorithmError( f"{local_scheme} is not a supported signing algorithm" ) from e keyval = {"public": public_key_pem} - keyid = cls._get_keyid(keytype, scheme, keyval) - public_key = SSlibKey(keyid, keytype, scheme, keyval) + keyid = cls._get_keyid(keytype, local_scheme, keyval) + public_key = SSlibKey(keyid, keytype, local_scheme, keyval) return f"{cls.SCHEME}:{aws_key_id}", public_key @staticmethod - def _get_keytype_and_scheme( + def _get_keytype_for_scheme( scheme: str, - ) -> Tuple[str, str]: - """Returns the Secure Systems Library key type and scheme for the AWS KMS - key type and signing algorithm + ) -> str: + """Returns the Secure Systems Library key type. Arguments: - (str): The Secure Systems Library scheme get_aws_signing_scheme + (str): The Secure Systems Library scheme. Returns: - Tuple[str, str]: The Secure Systems Library key type and signing - scheme + str: The Secure Systems Library key type. """ - keytypes_and_schemes = { - ("ecdsa", "ecdsa-sha2-nistp256"), - ("ecdsa", "ecdsa-sha2-nistp384"), - ("ecdsa", "ecdsa-sha2-nistp512"), - ("rsa", "rsassa-pss-sha256"), - ("rsa", "rsassa-pss-sha384"), - ("rsa", "rsassa-pss-sha512"), - ("rsa", "rsa-pkcs1v15-sha256"), - ("rsa", "rsa-pkcs1v15-sha384"), - ("rsa", "rsa-pkcs1v15-sha512"), + keytype_for_scheme = { + "ecdsa-sha2-nistp256": "ecdsa", + "ecdsa-sha2-nistp384": "ecdsa", + "ecdsa-sha2-nistp512": "ecdsa", + "rsassa-pss-sha256": "rsa", + "rsassa-pss-sha384": "rsa", + "rsassa-pss-sha512": "rsa", + "rsa-pkcs1v15-sha256": "rsa", + "rsa-pkcs1v15-sha384": "rsa", + "rsa-pkcs1v15-sha512": "rsa", } - for keytype_and_scheme in keytypes_and_schemes: - if keytype_and_scheme[1] == scheme: - return keytype_and_scheme - raise exceptions.UnsupportedAlgorithmError( - f"Unknown signing scheme: {scheme}" - ) + return keytype_for_scheme[scheme] @staticmethod def _get_aws_signing_algo( @@ -231,12 +224,11 @@ def sign(self, payload: bytes) -> Signature: Signature. """ try: - signing_algorithm = self.get_aws_algo request = self.client.sign( KeyId=self.aws_key_id, Message=payload, MessageType="RAW", - SigningAlgorithm=signing_algorithm, + SigningAlgorithm=self.aws_algo, ) hasher = sslib_hash.digest(self.hash_algorithm) From e616b12c0c4aeb6e42818f71e33115d45458a46a Mon Sep 17 00:00:00 2001 From: ianhundere <138915+ianhundere@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:33:38 -0400 Subject: [PATCH 22/22] docs: Updates docstring. --- securesystemslib/signer/_aws_signer.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/securesystemslib/signer/_aws_signer.py b/securesystemslib/signer/_aws_signer.py index f1f7cad3..e13ebd74 100644 --- a/securesystemslib/signer/_aws_signer.py +++ b/securesystemslib/signer/_aws_signer.py @@ -29,28 +29,26 @@ class AWSSigner(Signer): """AWS Key Management Service Signer - This Signer uses AWS KMS to sign. This signer supports signing with RSA and - EC keys uses "ambient" credentials: typically environment variables such as - AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN (if - necessary). These will be recognized by the boto3 SDK, which underlies the - aws_kms Python module. + This Signer uses AWS KMS to sign and supports signing with RSA/EC keys and + uses "ambient" credentials typically environment variables such as + AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. These will + be recognized by the boto3 SDK, which underlies the aws_kms Python module. For more details on AWS authentication, refer to the AWS Command Line Interface User Guide: - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html. + https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html Some practical authentication options include: - AWS CLI: https://aws.amazon.com/cli/ - AWS SDKs: https://aws.amazon.com/tools/ + AWS CLI: https://aws.amazon.com/cli/ + AWS SDKs: https://aws.amazon.com/tools/ The specific permissions that AWS KMS signer needs are: - kms:Sign for the sign() - kms:GetPublicKey for the import() + kms:Sign for sign() + kms:GetPublicKey for import() Arguments: aws_key_id (str): AWS KMS key ID or alias. - public_key (Key): The related public key - instance. + public_key (Key): The related public key instance. Returns: AWSSigner: An instance of the AWSSigner class. @@ -59,8 +57,7 @@ class AWSSigner(Signer): UnsupportedAlgorithmError: If the payload hash algorithm is unsupported. BotoCoreError: Errors from the botocore.exceptions library. ClientError: Errors related to AWS KMS client. - UnsupportedLibraryError: If necessary libraries for AWS KMS are not - available. + UnsupportedLibraryError: If necessary libraries for AWS KMS are not available. """ SCHEME = "awskms"