From f6ffa19af93c1224658cd1dc9ad2ccbe5611b872 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 23 May 2023 19:40:33 -0600 Subject: [PATCH 01/31] start adding azure signer implementation Signed-off-by: Meredith Lancaster --- securesystemslib/signer/__init__.py | 2 + securesystemslib/signer/_azure_signer.py | 87 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 securesystemslib/signer/_azure_signer.py diff --git a/securesystemslib/signer/__init__.py b/securesystemslib/signer/__init__.py index 3f43c3c7..bd83cc64 100644 --- a/securesystemslib/signer/__init__.py +++ b/securesystemslib/signer/__init__.py @@ -4,6 +4,7 @@ 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._gcp_signer import GCPSigner from securesystemslib.signer._gpg_signer import GPGKey, GPGSigner from securesystemslib.signer._hsm_signer import HSMSigner @@ -25,6 +26,7 @@ GCPSigner.SCHEME: GCPSigner, HSMSigner.SCHEME: HSMSigner, GPGSigner.SCHEME: GPGSigner, + AzureSigner.SCHEME: AzureSigner, } ) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py new file mode 100644 index 00000000..46d4b9a0 --- /dev/null +++ b/securesystemslib/signer/_azure_signer.py @@ -0,0 +1,87 @@ +"""Signer implementation for Azure Key Vault""" + +from azure.identity import DefaultAzureCredential +from azure.keyvault.keys import ( + KeyClient, + KeyVaultKey +) +from azure.keyvault.keys.crypto import SignatureAlgorithm + +class AzureSigner(Signer): + """Azure Key Vault Signer + + This Signer uses Azure Key Vault to sign. + + The specific permissions that AzureSigner needs are: + * todo:add roles + + Arguments: + az_keyvaultid: Fully qualified Azure Key Vault name, like + azurekms://.vault.azure.net + az_keyid: Fully qualified Azure Key Vault key name, like + azurekms://.vault.azure.net/ + + Raises: + Various errors from azure.identity + Various errors from azure.keyvault.keys + """ + + SCHEME = "azurekms" + + def __init__(self, az_keyvaultid: str, az_keyid: str): + self.az_keyid = az_keyid + self.public_key = public_key + + credential = DefaultAzureCredential() + key_client = KeyClient(vault_url=az_keyvaultid, credential=credential) + self.key_client = key_client + + key_vault_key = key_client.get_key(az_keyid) + crypto_client = CryptographyClient(key_vault_key, credential=credential) + + self.crypto_client = crypto_client + self.signature_algorithm = self._get_signature_algorithm(key_vault_key) + + @staticmethod + def _get_signature_algorithm(key: KeyVaultKey) -> SignatureAlgorithm: + key_curve_name = keyVaultKey.key.crv + match key_curve_name: + case KeyCurveName.p_256: + return SignatureAlgorithm.es256 + case KeyCurveName.p_384: + return SignatureAlgorithm.es384 + case KeyCurveName.p_521: + return SignatureAlgorithm.es512 + case _: + print("unsupported curve supplied") + + @classmethod + def from_priv_key_uri( + cls, + priv_key_uri: str, + public_key: Key, + secrets_handler: Optional[SecretsHandler] = None, + ) -> "AzureSigner": + uri = parse.urlparse(priv_key_uri) + + if uri.scheme != cls.SCHEME: + raise ValueError(f"AzureSigner does not support {priv_key_uri}") + + return cls(uri.path, public_key) + + def sign(self, payload: bytes) -> Signature: + """Signs payload with Azure Key Vault. + + Arguments: + payload: bytes to be signed. + + Raises: + Various errors from azure.keyvault.keys. + + Returns: + Signature. + """ + + result = crypto_client.sign(SignatureAlgorithm.es256, payload) + + return Signature(result.keyid, result.signature.hex()) From 618b4de23fd9b7dae31394a9b3b6d3f34c01801a Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Wed, 24 May 2023 15:02:40 +0200 Subject: [PATCH 02/31] Added packages to requirements and a small bug fix Signed-off-by: Fredrik Skogman --- requirements-kms.txt | 2 ++ securesystemslib/signer/_azure_signer.py | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/requirements-kms.txt b/requirements-kms.txt index 78d6fb9f..a046edc5 100644 --- a/requirements-kms.txt +++ b/requirements-kms.txt @@ -1 +1,3 @@ google-cloud-kms +azure-identity +azure-keyvault-keys diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 46d4b9a0..feb56f09 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -38,7 +38,7 @@ def __init__(self, az_keyvaultid: str, az_keyid: str): key_vault_key = key_client.get_key(az_keyid) crypto_client = CryptographyClient(key_vault_key, credential=credential) - + self.crypto_client = crypto_client self.signature_algorithm = self._get_signature_algorithm(key_vault_key) @@ -64,6 +64,8 @@ def from_priv_key_uri( ) -> "AzureSigner": uri = parse.urlparse(priv_key_uri) + print("fsn test test") + if uri.scheme != cls.SCHEME: raise ValueError(f"AzureSigner does not support {priv_key_uri}") @@ -82,6 +84,6 @@ def sign(self, payload: bytes) -> Signature: Signature. """ - result = crypto_client.sign(SignatureAlgorithm.es256, payload) + result = self.crypto_client.sign(SignatureAlgorithm.es256, payload) return Signature(result.keyid, result.signature.hex()) From a5317d1b0bb8f06a7dd83af6cdc1e0131982e2c7 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Wed, 24 May 2023 15:07:24 +0200 Subject: [PATCH 03/31] Removed debub print Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index feb56f09..afb2330f 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -64,8 +64,6 @@ def from_priv_key_uri( ) -> "AzureSigner": uri = parse.urlparse(priv_key_uri) - print("fsn test test") - if uri.scheme != cls.SCHEME: raise ValueError(f"AzureSigner does not support {priv_key_uri}") From c88c7f98562afb17451e211f1c0ca9132dbdf7bc Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Wed, 24 May 2023 15:23:07 +0200 Subject: [PATCH 04/31] Added missing imports Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index afb2330f..e84bfc33 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -1,10 +1,19 @@ """Signer implementation for Azure Key Vault""" +from typing import Optional +from urllib import parse + from azure.identity import DefaultAzureCredential from azure.keyvault.keys import ( KeyClient, KeyVaultKey ) +from securesystemslib.signer._key import Key +from securesystemslib.signer._signer import ( + SecretsHandler, + Signature, + Signer, +) from azure.keyvault.keys.crypto import SignatureAlgorithm class AzureSigner(Signer): @@ -30,7 +39,7 @@ class AzureSigner(Signer): def __init__(self, az_keyvaultid: str, az_keyid: str): self.az_keyid = az_keyid - self.public_key = public_key + #self.public_key = public_key credential = DefaultAzureCredential() key_client = KeyClient(vault_url=az_keyvaultid, credential=credential) From ba237b86dcadd5e4a380b50c5ab0570b45fdebce Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Wed, 24 May 2023 15:38:31 +0200 Subject: [PATCH 05/31] Use the keyvault uri --- securesystemslib/signer/_azure_signer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index e84bfc33..87c14266 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -39,10 +39,12 @@ class AzureSigner(Signer): def __init__(self, az_keyvaultid: str, az_keyid: str): self.az_keyid = az_keyid - #self.public_key = public_key - credential = DefaultAzureCredential() - key_client = KeyClient(vault_url=az_keyvaultid, credential=credential) + + # az vault is on form: azurekms:// but key client expects https:// + vault_url = az_keyvaultid.replace("azurekms:", "https:") + + key_client = KeyClient(vault_url=vault_url, credential=credential) self.key_client = key_client key_vault_key = key_client.get_key(az_keyid) @@ -76,7 +78,7 @@ def from_priv_key_uri( if uri.scheme != cls.SCHEME: raise ValueError(f"AzureSigner does not support {priv_key_uri}") - return cls(uri.path, public_key) + return cls(priv_key_uri, public_key) def sign(self, payload: bytes) -> Signature: """Signs payload with Azure Key Vault. From 8fe44e706daaeaec8aee029458b98d8d4b2954d7 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Wed, 24 May 2023 08:09:57 -0600 Subject: [PATCH 06/31] replace match with if else Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 87c14266..c52a515b 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -54,17 +54,16 @@ def __init__(self, az_keyvaultid: str, az_keyid: str): self.signature_algorithm = self._get_signature_algorithm(key_vault_key) @staticmethod - def _get_signature_algorithm(key: KeyVaultKey) -> SignatureAlgorithm: - key_curve_name = keyVaultKey.key.crv - match key_curve_name: - case KeyCurveName.p_256: - return SignatureAlgorithm.es256 - case KeyCurveName.p_384: - return SignatureAlgorithm.es384 - case KeyCurveName.p_521: - return SignatureAlgorithm.es512 - case _: - print("unsupported curve supplied") + def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: + key_curve_name = kvk.key.crv + if key_curve_name == KeyCurveName.p_256: + return SignatureAlgorithm.es256 + elif KeyCurveName.p_384: + return SignatureAlgorithm.es384 + elif KeyCurveName.p_521: + return SignatureAlgorithm.es512 + else: + print("unsupported curve supplied") @classmethod def from_priv_key_uri( From 3239a7ece8820c519df3549dd00551ddf05ceb47 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Wed, 24 May 2023 12:43:21 -0600 Subject: [PATCH 07/31] working signer creation and signing Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 43 +++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index c52a515b..2f0a1d97 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -3,11 +3,15 @@ from typing import Optional from urllib import parse +import logging +from azure.core.exceptions import HttpResponseError from azure.identity import DefaultAzureCredential from azure.keyvault.keys import ( KeyClient, - KeyVaultKey + KeyVaultKey, + KeyCurveName ) +from azure.keyvault.keys.crypto import CryptographyClient from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( SecretsHandler, @@ -16,6 +20,8 @@ ) from azure.keyvault.keys.crypto import SignatureAlgorithm +logger = logging.getLogger(__name__) + class AzureSigner(Signer): """Azure Key Vault Signer @@ -40,19 +46,32 @@ class AzureSigner(Signer): def __init__(self, az_keyvaultid: str, az_keyid: str): self.az_keyid = az_keyid credential = DefaultAzureCredential() - + # az vault is on form: azurekms:// but key client expects https:// vault_url = az_keyvaultid.replace("azurekms:", "https:") + self.key_vault_key = self._create_key_vault_key(self.az_keyid, vault_url, credential) + self.signature_algorithm = self._get_signature_algorithm(self.key_vault_key) + self.crypto_client = self._create_crypto_client(credential, self.key_vault_key) - key_client = KeyClient(vault_url=vault_url, credential=credential) - self.key_client = key_client - - key_vault_key = key_client.get_key(az_keyid) - crypto_client = CryptographyClient(key_vault_key, credential=credential) - - self.crypto_client = crypto_client - self.signature_algorithm = self._get_signature_algorithm(key_vault_key) - + @staticmethod + def _create_key_vault_key(az_keyid: str, vault_url: str, cred: DefaultAzureCredential) -> KeyVaultKey: + try: + key_client = KeyClient(vault_url=vault_url, credential=cred) + return key_client.get_key(az_keyid) + except ( + HttpResponseError, + ) as e: + logger.info("Key %s failed to create key client from key: %s", az_keyid, str(e)) + + @staticmethod + def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> CryptographyClient: + try: + return CryptographyClient(kv_key, credential=cred) + except ( + HttpResponseError, + ) as e: + logger.info("Key %s failed to create key client from key: %s", az_keyid, str(e)) + @staticmethod def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: key_curve_name = kvk.key.crv @@ -94,4 +113,4 @@ def sign(self, payload: bytes) -> Signature: result = self.crypto_client.sign(SignatureAlgorithm.es256, payload) - return Signature(result.keyid, result.signature.hex()) + return Signature(result.key_id, result.signature.hex()) From d7437bdd792d6d00cf54515a2662d1aa6d0e46a0 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Wed, 24 May 2023 12:50:30 -0600 Subject: [PATCH 08/31] clean up functions Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 2f0a1d97..a056aa99 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -11,14 +11,16 @@ KeyVaultKey, KeyCurveName ) -from azure.keyvault.keys.crypto import CryptographyClient +from azure.keyvault.keys.crypto import ( + CryptographyClient, + SignatureAlgorithm +) from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( SecretsHandler, Signature, Signer, ) -from azure.keyvault.keys.crypto import SignatureAlgorithm logger = logging.getLogger(__name__) @@ -44,24 +46,23 @@ class AzureSigner(Signer): SCHEME = "azurekms" def __init__(self, az_keyvaultid: str, az_keyid: str): - self.az_keyid = az_keyid credential = DefaultAzureCredential() - # az vault is on form: azurekms:// but key client expects https:// vault_url = az_keyvaultid.replace("azurekms:", "https:") - self.key_vault_key = self._create_key_vault_key(self.az_keyid, vault_url, credential) - self.signature_algorithm = self._get_signature_algorithm(self.key_vault_key) - self.crypto_client = self._create_crypto_client(credential, self.key_vault_key) + + key_vault_key = self._create_key_vault_key(credential, az_keyid, vault_url) + self.signature_algorithm = self._get_signature_algorithm(key_vault_key) + self.crypto_client = self._create_crypto_client(credential, key_vault_key) @staticmethod - def _create_key_vault_key(az_keyid: str, vault_url: str, cred: DefaultAzureCredential) -> KeyVaultKey: + def _create_key_vault_key(cred: DefaultAzureCredential, az_keyid: str, vault_url: str) -> KeyVaultKey: try: key_client = KeyClient(vault_url=vault_url, credential=cred) return key_client.get_key(az_keyid) except ( HttpResponseError, ) as e: - logger.info("Key %s failed to create key client from key: %s", az_keyid, str(e)) + logger.info("Key %s failed to create key client from credentials, key ID, and Vault URL: %s", az_keyid, str(e)) @staticmethod def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> CryptographyClient: @@ -70,7 +71,7 @@ def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> except ( HttpResponseError, ) as e: - logger.info("Key %s failed to create key client from key: %s", az_keyid, str(e)) + logger.info("Key %s failed to create crypto client from credentials and KeyVaultKey: %s", az_keyid, str(e)) @staticmethod def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: @@ -111,6 +112,6 @@ def sign(self, payload: bytes) -> Signature: Signature. """ - result = self.crypto_client.sign(SignatureAlgorithm.es256, payload) + response = self.crypto_client.sign(self.signature_algorithm, payload) - return Signature(result.key_id, result.signature.hex()) + return Signature(response.key_id, response.signature.hex()) From df9b5c249187bf9d07879bae6390be893f988855 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Thu, 25 May 2023 13:41:25 +0200 Subject: [PATCH 09/31] Make sure to calculated the digest using the correct hash function. The CryptographySigner expectes the digest when it performs a signing operation. It's a nicer API to let the AzureSigner calculate the digest. --- securesystemslib/signer/_azure_signer.py | 27 ++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index a056aa99..a832b821 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -15,6 +15,7 @@ CryptographyClient, SignatureAlgorithm ) +import securesystemslib.hash as sslib_hash from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( SecretsHandler, @@ -49,9 +50,10 @@ def __init__(self, az_keyvaultid: str, az_keyid: str): credential = DefaultAzureCredential() # az vault is on form: azurekms:// but key client expects https:// vault_url = az_keyvaultid.replace("azurekms:", "https:") - + key_vault_key = self._create_key_vault_key(credential, az_keyid, vault_url) self.signature_algorithm = self._get_signature_algorithm(key_vault_key) + self.hash_algorithm = self._get_hash_algorithm(key_vault_key) self.crypto_client = self._create_crypto_client(credential, key_vault_key) @staticmethod @@ -63,7 +65,7 @@ def _create_key_vault_key(cred: DefaultAzureCredential, az_keyid: str, vault_url HttpResponseError, ) as e: logger.info("Key %s failed to create key client from credentials, key ID, and Vault URL: %s", az_keyid, str(e)) - + @staticmethod def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> CryptographyClient: try: @@ -72,7 +74,7 @@ def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> HttpResponseError, ) as e: logger.info("Key %s failed to create crypto client from credentials and KeyVaultKey: %s", az_keyid, str(e)) - + @staticmethod def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: key_curve_name = kvk.key.crv @@ -85,6 +87,20 @@ def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: else: print("unsupported curve supplied") + @staticmethod + def _get_hash_algorithm(kvk: KeyVaultKey) -> str: + key_curve_name = kvk.key.crv + if key_curve_name == KeyCurveName.p_256: + return "sha256" + elif KeyCurveName.p_384: + return "sha384" + elif KeyCurveName.p_521: + return "sha512" + else: + print("unsupported curve supplied") + # trigger UnsupportedAlgorithm if appropriate + _ = sslib_hash.digest("") + @classmethod def from_priv_key_uri( cls, @@ -112,6 +128,9 @@ def sign(self, payload: bytes) -> Signature: Signature. """ - response = self.crypto_client.sign(self.signature_algorithm, payload) + hasher = sslib_hash.digest(self.hash_algorithm) + hasher.update(payload) + digest = hasher.digest() + response = self.crypto_client.sign(self.signature_algorithm, digest) return Signature(response.key_id, response.signature.hex()) From a820bcfc87491886ff0e9c351654728929a05e7f Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Thu, 25 May 2023 14:42:55 +0200 Subject: [PATCH 10/31] Make sure signature is in ASN.1 format Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 25 +++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index a832b821..a167de21 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -1,5 +1,7 @@ """Signer implementation for Azure Key Vault""" +import binascii + from typing import Optional from urllib import parse @@ -15,6 +17,9 @@ CryptographyClient, SignatureAlgorithm ) +from cryptography.hazmat.primitives.asymmetric.utils import ( + encode_dss_signature, +) import securesystemslib.hash as sslib_hash from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( @@ -133,4 +138,22 @@ def sign(self, payload: bytes) -> Signature: digest = hasher.digest() response = self.crypto_client.sign(self.signature_algorithm, digest) - return Signature(response.key_id, response.signature.hex()) + # This code is copied from: + # https://github.com/secure-systems-lab/securesystemslib/blob/135567fa04f10d0c6a4cd32eb45ce736e1f50a93/securesystemslib/signer/_hsm_signer.py#L379 + # + # The PKCS11 signature octets correspond to the concatenation of the + # ECDSA values r and s, both represented as an octet string of equal + # length of at most nLen with the most significant byte first (i.e. + # big endian) + # https://docs.oasis-open.org/pkcs11/pkcs11-curr/v3.0/cs01/pkcs11-curr-v3.0-cs01.html#_Toc30061178 + r_s_len = int(len(response.signature) / 2) + r = int.from_bytes(response.signature[:r_s_len], byteorder="big") + s = int.from_bytes(response.signature[r_s_len:], byteorder="big") + + # Create an ASN.1 encoded Dss-Sig-Value to be used with + # pyca/cryptography + dss_sig_value = binascii.hexlify(encode_dss_signature(r, s)).decode( + "ascii" + ) + + return Signature(response.key_id, dss_sig_value) From 041be38388f856bd802681572ba72d88e05754b8 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Thu, 25 May 2023 08:15:01 -0600 Subject: [PATCH 11/31] add comments and exceptions around only supporting ec keys Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 39 +++++++++++++++--------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index a167de21..3e68836d 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -11,7 +11,8 @@ from azure.keyvault.keys import ( KeyClient, KeyVaultKey, - KeyCurveName + KeyCurveName, + KeyType, ) from azure.keyvault.keys.crypto import ( CryptographyClient, @@ -30,19 +31,20 @@ logger = logging.getLogger(__name__) +class UnsupportedKeyType(Exception): + pass + class AzureSigner(Signer): """Azure Key Vault Signer This Signer uses Azure Key Vault to sign. - - The specific permissions that AzureSigner needs are: - * todo:add roles + Currently this signer only supports signing with EC keys. + RSA support will be added in a separate pull request. Arguments: az_keyvaultid: Fully qualified Azure Key Vault name, like azurekms://.vault.azure.net - az_keyid: Fully qualified Azure Key Vault key name, like - azurekms://.vault.azure.net/ + az_keyid: Azure Key Vault key name Raises: Various errors from azure.identity @@ -52,14 +54,17 @@ class AzureSigner(Signer): SCHEME = "azurekms" def __init__(self, az_keyvaultid: str, az_keyid: str): - credential = DefaultAzureCredential() - # az vault is on form: azurekms:// but key client expects https:// - vault_url = az_keyvaultid.replace("azurekms:", "https:") + try: + credential = DefaultAzureCredential() + # az vault is on form: azurekms:// but key client expects https:// + vault_url = az_keyvaultid.replace("azurekms:", "https:") - key_vault_key = self._create_key_vault_key(credential, az_keyid, vault_url) - self.signature_algorithm = self._get_signature_algorithm(key_vault_key) - self.hash_algorithm = self._get_hash_algorithm(key_vault_key) - self.crypto_client = self._create_crypto_client(credential, key_vault_key) + key_vault_key = self._create_key_vault_key(credential, az_keyid, vault_url) + self.signature_algorithm = self._get_signature_algorithm(key_vault_key) + self.hash_algorithm = self._get_hash_algorithm(key_vault_key) + self.crypto_client = self._create_crypto_client(credential, key_vault_key) + except UnsupportedKeyType as e: + logger.info("Key %s has unsupported key type or unsupported elliptic curve") @staticmethod def _create_key_vault_key(cred: DefaultAzureCredential, az_keyid: str, vault_url: str) -> KeyVaultKey: @@ -82,6 +87,10 @@ def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> @staticmethod def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: + key_type = kvk.key.kty + if key_type != KeyType.ec and key_type != KeyType.ec_hsm: + logger.info("only EC keys are supported for now") + raise UnsupportedKeyType("Supplied key must be an EC key") key_curve_name = kvk.key.crv if key_curve_name == KeyCurveName.p_256: return SignatureAlgorithm.es256 @@ -90,7 +99,7 @@ def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: elif KeyCurveName.p_521: return SignatureAlgorithm.es512 else: - print("unsupported curve supplied") + raise UnsupportedKeyType("Unsupported curve supplied by key") @staticmethod def _get_hash_algorithm(kvk: KeyVaultKey) -> str: @@ -102,7 +111,7 @@ def _get_hash_algorithm(kvk: KeyVaultKey) -> str: elif KeyCurveName.p_521: return "sha512" else: - print("unsupported curve supplied") + logger.info("unsupported curve supplied") # trigger UnsupportedAlgorithm if appropriate _ = sslib_hash.digest("") From 473648cb80e4bb29337e1ac11fffdfc9114648a3 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Fri, 26 May 2023 12:43:07 +0200 Subject: [PATCH 12/31] Added an import method with returns the public key in the expected format. Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 149 ++++++++++++++++++----- 1 file changed, 118 insertions(+), 31 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 3e68836d..92aa04ef 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -2,33 +2,47 @@ import binascii -from typing import Optional +from typing import Optional, Tuple from urllib import parse import logging -from azure.core.exceptions import HttpResponseError -from azure.identity import DefaultAzureCredential -from azure.keyvault.keys import ( - KeyClient, - KeyVaultKey, - KeyCurveName, - KeyType, -) -from azure.keyvault.keys.crypto import ( - CryptographyClient, - SignatureAlgorithm -) from cryptography.hazmat.primitives.asymmetric.utils import ( encode_dss_signature, ) +from cryptography.hazmat.primitives.asymmetric import ( + ec, +) +from cryptography.hazmat.primitives.serialization import ( + Encoding, PublicFormat +) import securesystemslib.hash as sslib_hash from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( SecretsHandler, Signature, Signer, + SSlibKey, ) +AZURE_IMPORT_ERROR = None +try: + from azure.core.exceptions import HttpResponseError + from azure.identity import DefaultAzureCredential + from azure.keyvault.keys import ( + KeyClient, + KeyVaultKey, + KeyCurveName, + KeyType, + ) + from azure.keyvault.keys.crypto import ( + CryptographyClient, + SignatureAlgorithm + ) +except ImportError: + AZURE_IMPORT_ERROR = ( + "azure-cloud-kms library required to sign with Azure Cloud keys." + ) + logger = logging.getLogger(__name__) class UnsupportedKeyType(Exception): @@ -42,9 +56,9 @@ class AzureSigner(Signer): RSA support will be added in a separate pull request. Arguments: - az_keyvaultid: Fully qualified Azure Key Vault name, like - azurekms://.vault.azure.net - az_keyid: Azure Key Vault key name + az_key_uri: Fully qualified Azure Key Vault name, like + azurekms://.vault.azure.net/ + public_key: Azure Key Vault key name Raises: Various errors from azure.identity @@ -53,21 +67,40 @@ class AzureSigner(Signer): SCHEME = "azurekms" - def __init__(self, az_keyvaultid: str, az_keyid: str): + def __init__(self, az_key_uri: str, public_key: str): + if AZURE_IMPORT_ERROR: + raise exceptions.UnsupportedLibraryError(AZURE_IMPORT_ERROR) + try: credential = DefaultAzureCredential() - # az vault is on form: azurekms:// but key client expects https:// - vault_url = az_keyvaultid.replace("azurekms:", "https:") - - key_vault_key = self._create_key_vault_key(credential, az_keyid, vault_url) + vault_url, key_name = self._vault_url_and_key(az_key_uri) + key_vault_key = self._get_key_vault_key(credential, key_name, vault_url) self.signature_algorithm = self._get_signature_algorithm(key_vault_key) self.hash_algorithm = self._get_hash_algorithm(key_vault_key) self.crypto_client = self._create_crypto_client(credential, key_vault_key) except UnsupportedKeyType as e: logger.info("Key %s has unsupported key type or unsupported elliptic curve") + raise e @staticmethod - def _create_key_vault_key(cred: DefaultAzureCredential, az_keyid: str, vault_url: str) -> KeyVaultKey: + + + @staticmethod + def _vault_url_and_key(az_key_uri: str) -> Tuple[str, str]: + # Extract the vault uri and key name. + # Format is: azurekms://.vault.azure.net/ + (az_vault_uri, az_key_name) = az_key_uri.rsplit("/", 1) + + if not az_vault_uri.startswith("azurekms://"): + raise ValueError(f"Invalid key URI {az_key_uri}") + + # az vault is on form: azurekms:// but key client expects https:// + vault_url = az_vault_uri.replace("azurekms:", "https:") + + return vault_url, az_key_name + + @staticmethod + def _get_key_vault_key(cred: DefaultAzureCredential, az_keyid: str, vault_url: str) -> KeyVaultKey: try: key_client = KeyClient(vault_url=vault_url, credential=cred) return key_client.get_key(az_keyid) @@ -91,30 +124,41 @@ def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: if key_type != KeyType.ec and key_type != KeyType.ec_hsm: logger.info("only EC keys are supported for now") raise UnsupportedKeyType("Supplied key must be an EC key") - key_curve_name = kvk.key.crv - if key_curve_name == KeyCurveName.p_256: + crv = kvk.key.crv + if crv == KeyCurveName.p_256: return SignatureAlgorithm.es256 - elif KeyCurveName.p_384: + elif crv == KeyCurveName.p_384: return SignatureAlgorithm.es384 - elif KeyCurveName.p_521: + elif crv == KeyCurveName.p_521: return SignatureAlgorithm.es512 else: raise UnsupportedKeyType("Unsupported curve supplied by key") @staticmethod def _get_hash_algorithm(kvk: KeyVaultKey) -> str: - key_curve_name = kvk.key.crv - if key_curve_name == KeyCurveName.p_256: + crv = kvk.key.crv + if crv == KeyCurveName.p_256: return "sha256" - elif KeyCurveName.p_384: + elif crv == KeyCurveName.p_384: return "sha384" - elif KeyCurveName.p_521: + elif crv == KeyCurveName.p_521: return "sha512" else: - logger.info("unsupported curve supplied") + logger.info(f"unsupported curve supplied {kvk.key.crv}") # trigger UnsupportedAlgorithm if appropriate _ = sslib_hash.digest("") + @staticmethod + def _get_keytype_and_scheme(crv: str) -> Tuple[str, str]: + if crv == KeyCurveName.p_256: + return "ecdsa", "ecdsa-sha2-nistp256" + elif crv == KeyCurveName.p_384: + return "ecdsa", "ecdsa-sha2-nistp384" + elif crv == KeyCurveName.p_521: + return "ecdsa", "ecdsa-sha2-nistp521" + else: + raise UnsupportedKeyType("Unsupported curve supplied by key") + @classmethod def from_priv_key_uri( cls, @@ -129,6 +173,49 @@ def from_priv_key_uri( return cls(priv_key_uri, public_key) + @classmethod + def import_(cls, az_key_uri: str) -> Tuple[str, Key]: + """Load key and signer details from 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 AZURE_IMPORT_ERROR: + raise exceptions.UnsupportedLibraryError(AZURE_IMPORT_ERROR) + + vault_url, key_name = cls._vault_url_and_key(az_key_uri) + credential = DefaultAzureCredential() + key_vault_key = cls._get_key_vault_key(credential, key_name, vault_url) + + if key_vault_key.key.kty != "EC-HSM": + raise UnsupportedKeyType(f"Unsupported key type {key_vault_key.key.kty}") + + if key_vault_key.key.crv == KeyCurveName.p_256: + crv = ec.SECP256R1() + elif key_vault_key.key.crv == KeyCurveName.p_384: + crv = ec.SECP384R1() + elif key_vault_key.key.crv == KeyCurveName.p_521: + crv = ec.SECP521R1() + else: + raise UnsupportedKeyType(f"Unsupported curve type {kvk.key.crv}") + + # Key is in JWK format, create a curve from it with the parameters + x = int.from_bytes(key_vault_key.key.x, byteorder='big') + y = int.from_bytes(key_vault_key.key.y, byteorder='big') + + cpub = ec.EllipticCurvePublicNumbers(x, y, crv) + pub_key = cpub.public_key() + pem = pub_key.public_bytes(Encoding.PEM, + PublicFormat.SubjectPublicKeyInfo) + + keytype, scheme = cls._get_keytype_and_scheme(key_vault_key.key.crv) + keyval = {"public": pem.decode("utf-8")} + keyid = cls._get_keyid(keytype, scheme, keyval) + public_key = SSlibKey(keyid, keytype, scheme, keyval) + + return az_key_uri, public_key + + def sign(self, payload: bytes) -> Signature: """Signs payload with Azure Key Vault. From 41a89ae40db321d4de87455d7b9cf57f4198d2ed Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Fri, 26 May 2023 12:44:11 +0200 Subject: [PATCH 13/31] Updated comment to be correct on second ctor parameter Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 92aa04ef..02f92ee0 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -58,7 +58,7 @@ class AzureSigner(Signer): Arguments: az_key_uri: Fully qualified Azure Key Vault name, like azurekms://.vault.azure.net/ - public_key: Azure Key Vault key name + public_key: public key object Raises: Various errors from azure.identity From 4184d0b86d13e22b7a930f059c2836e1da86d664 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Fri, 26 May 2023 08:34:41 -0600 Subject: [PATCH 14/31] remove extra staticmethod Signed-off-by: Meredith Lancaster --- azure-test-runner.py | 19 +++++++++++++++++++ securesystemslib/signer/_azure_signer.py | 3 --- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 azure-test-runner.py diff --git a/azure-test-runner.py b/azure-test-runner.py new file mode 100644 index 00000000..570b8fb7 --- /dev/null +++ b/azure-test-runner.py @@ -0,0 +1,19 @@ +from securesystemslib.exceptions import UnverifiedSignatureError +from securesystemslib.signer import AzureSigner, Key, Signer + + +def main(): + azure_key_vault = "azurekms://tsa-staging.vault.azure.net" + azure_key_id = "tsa-leaf" + + data = ("data" * 8).encode("utf-8") + signer = AzureSigner.from_priv_key_uri( + azure_key_vault, + azure_key_id + ) + sig = signer.sign(data) + + print(sig) + +if __name__ == "__main__": + main() diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 02f92ee0..8ea54072 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -82,9 +82,6 @@ def __init__(self, az_key_uri: str, public_key: str): logger.info("Key %s has unsupported key type or unsupported elliptic curve") raise e - @staticmethod - - @staticmethod def _vault_url_and_key(az_key_uri: str) -> Tuple[str, str]: # Extract the vault uri and key name. From f2c354fbe66939a48e0cb0989d6712356005d592 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Fri, 26 May 2023 10:51:23 -0600 Subject: [PATCH 15/31] move cryptography imports Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 8ea54072..44fb1dcf 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -6,15 +6,6 @@ from urllib import parse import logging -from cryptography.hazmat.primitives.asymmetric.utils import ( - encode_dss_signature, -) -from cryptography.hazmat.primitives.asymmetric import ( - ec, -) -from cryptography.hazmat.primitives.serialization import ( - Encoding, PublicFormat -) import securesystemslib.hash as sslib_hash from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( @@ -38,6 +29,15 @@ CryptographyClient, SignatureAlgorithm ) + from cryptography.hazmat.primitives.asymmetric.utils import ( + encode_dss_signature, + ) + from cryptography.hazmat.primitives.asymmetric import ( + ec, + ) + from cryptography.hazmat.primitives.serialization import ( + Encoding, PublicFormat + ) except ImportError: AZURE_IMPORT_ERROR = ( "azure-cloud-kms library required to sign with Azure Cloud keys." From fe326b04708bbdcf14c16791f9592e9db41c62f1 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 29 May 2023 14:03:07 +0300 Subject: [PATCH 16/31] lint: Run black and isort Specifically $ black . $ isort . --- azure-test-runner.py | 6 +- securesystemslib/signer/_azure_signer.py | 90 ++++++++++++++---------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/azure-test-runner.py b/azure-test-runner.py index 570b8fb7..dbb6e94c 100644 --- a/azure-test-runner.py +++ b/azure-test-runner.py @@ -7,13 +7,11 @@ def main(): azure_key_id = "tsa-leaf" data = ("data" * 8).encode("utf-8") - signer = AzureSigner.from_priv_key_uri( - azure_key_vault, - azure_key_id - ) + signer = AzureSigner.from_priv_key_uri(azure_key_vault, azure_key_id) sig = signer.sign(data) print(sig) + if __name__ == "__main__": main() diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 44fb1dcf..15b56504 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -1,11 +1,10 @@ """Signer implementation for Azure Key Vault""" import binascii - +import logging from typing import Optional, Tuple from urllib import parse -import logging import securesystemslib.hash as sslib_hash from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( @@ -21,22 +20,21 @@ from azure.identity import DefaultAzureCredential from azure.keyvault.keys import ( KeyClient, - KeyVaultKey, KeyCurveName, KeyType, + KeyVaultKey, ) from azure.keyvault.keys.crypto import ( CryptographyClient, - SignatureAlgorithm + SignatureAlgorithm, ) + from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( encode_dss_signature, ) - from cryptography.hazmat.primitives.asymmetric import ( - ec, - ) from cryptography.hazmat.primitives.serialization import ( - Encoding, PublicFormat + Encoding, + PublicFormat, ) except ImportError: AZURE_IMPORT_ERROR = ( @@ -45,9 +43,11 @@ logger = logging.getLogger(__name__) + class UnsupportedKeyType(Exception): pass + class AzureSigner(Signer): """Azure Key Vault Signer @@ -74,46 +74,62 @@ def __init__(self, az_key_uri: str, public_key: str): try: credential = DefaultAzureCredential() vault_url, key_name = self._vault_url_and_key(az_key_uri) - key_vault_key = self._get_key_vault_key(credential, key_name, vault_url) - self.signature_algorithm = self._get_signature_algorithm(key_vault_key) + key_vault_key = self._get_key_vault_key( + credential, key_name, vault_url + ) + self.signature_algorithm = self._get_signature_algorithm( + key_vault_key + ) self.hash_algorithm = self._get_hash_algorithm(key_vault_key) - self.crypto_client = self._create_crypto_client(credential, key_vault_key) + self.crypto_client = self._create_crypto_client( + credential, key_vault_key + ) except UnsupportedKeyType as e: - logger.info("Key %s has unsupported key type or unsupported elliptic curve") + logger.info( + "Key %s has unsupported key type or unsupported elliptic curve" + ) raise e @staticmethod def _vault_url_and_key(az_key_uri: str) -> Tuple[str, str]: - # Extract the vault uri and key name. - # Format is: azurekms://.vault.azure.net/ - (az_vault_uri, az_key_name) = az_key_uri.rsplit("/", 1) + # Extract the vault uri and key name. + # Format is: azurekms://.vault.azure.net/ + (az_vault_uri, az_key_name) = az_key_uri.rsplit("/", 1) - if not az_vault_uri.startswith("azurekms://"): - raise ValueError(f"Invalid key URI {az_key_uri}") + if not az_vault_uri.startswith("azurekms://"): + raise ValueError(f"Invalid key URI {az_key_uri}") - # az vault is on form: azurekms:// but key client expects https:// - vault_url = az_vault_uri.replace("azurekms:", "https:") + # az vault is on form: azurekms:// but key client expects https:// + vault_url = az_vault_uri.replace("azurekms:", "https:") - return vault_url, az_key_name + return vault_url, az_key_name @staticmethod - def _get_key_vault_key(cred: DefaultAzureCredential, az_keyid: str, vault_url: str) -> KeyVaultKey: + def _get_key_vault_key( + cred: DefaultAzureCredential, az_keyid: str, vault_url: str + ) -> KeyVaultKey: try: key_client = KeyClient(vault_url=vault_url, credential=cred) return key_client.get_key(az_keyid) - except ( - HttpResponseError, - ) as e: - logger.info("Key %s failed to create key client from credentials, key ID, and Vault URL: %s", az_keyid, str(e)) + except (HttpResponseError,) as e: + logger.info( + "Key %s failed to create key client from credentials, key ID, and Vault URL: %s", + az_keyid, + str(e), + ) @staticmethod - def _create_crypto_client(cred: DefaultAzureCredential, kv_key: KeyVaultKey) -> CryptographyClient: + def _create_crypto_client( + cred: DefaultAzureCredential, kv_key: KeyVaultKey + ) -> CryptographyClient: try: return CryptographyClient(kv_key, credential=cred) - except ( - HttpResponseError, - ) as e: - logger.info("Key %s failed to create crypto client from credentials and KeyVaultKey: %s", az_keyid, str(e)) + except (HttpResponseError,) as e: + logger.info( + "Key %s failed to create crypto client from credentials and KeyVaultKey: %s", + az_keyid, + str(e), + ) @staticmethod def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: @@ -185,7 +201,9 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: key_vault_key = cls._get_key_vault_key(credential, key_name, vault_url) if key_vault_key.key.kty != "EC-HSM": - raise UnsupportedKeyType(f"Unsupported key type {key_vault_key.key.kty}") + raise UnsupportedKeyType( + f"Unsupported key type {key_vault_key.key.kty}" + ) if key_vault_key.key.crv == KeyCurveName.p_256: crv = ec.SECP256R1() @@ -197,13 +215,14 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: raise UnsupportedKeyType(f"Unsupported curve type {kvk.key.crv}") # Key is in JWK format, create a curve from it with the parameters - x = int.from_bytes(key_vault_key.key.x, byteorder='big') - y = int.from_bytes(key_vault_key.key.y, byteorder='big') + x = int.from_bytes(key_vault_key.key.x, byteorder="big") + y = int.from_bytes(key_vault_key.key.y, byteorder="big") cpub = ec.EllipticCurvePublicNumbers(x, y, crv) pub_key = cpub.public_key() - pem = pub_key.public_bytes(Encoding.PEM, - PublicFormat.SubjectPublicKeyInfo) + pem = pub_key.public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo + ) keytype, scheme = cls._get_keytype_and_scheme(key_vault_key.key.crv) keyval = {"public": pem.decode("utf-8")} @@ -212,7 +231,6 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: return az_key_uri, public_key - def sign(self, payload: bytes) -> Signature: """Signs payload with Azure Key Vault. From a692a5e295d7fd36b9ffbc75cc14c1a324746d96 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 29 May 2023 14:25:24 +0300 Subject: [PATCH 17/31] AzureSigner: Fix a bunch of lint issues These are the trivial looking issues: there are still some left that require actual decisions (both in pylint and mypy) --- mypy.ini | 3 ++ securesystemslib/signer/_azure_signer.py | 41 ++++++++++++------------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/mypy.ini b/mypy.ini index 52661c3f..04ba591a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -29,3 +29,6 @@ ignore_missing_imports = True [mypy-pyspx.*] ignore_missing_imports = True + +[mypy-azure.*] +ignore_missing_imports = True diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 15b56504..31776a35 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -6,6 +6,7 @@ from urllib import parse import securesystemslib.hash as sslib_hash +from securesystemslib.exceptions import UnsupportedLibraryError from securesystemslib.signer._key import Key from securesystemslib.signer._signer import ( SecretsHandler, @@ -67,9 +68,9 @@ class AzureSigner(Signer): SCHEME = "azurekms" - def __init__(self, az_key_uri: str, public_key: str): + def __init__(self, az_key_uri: str, _: str): if AZURE_IMPORT_ERROR: - raise exceptions.UnsupportedLibraryError(AZURE_IMPORT_ERROR) + raise UnsupportedLibraryError(AZURE_IMPORT_ERROR) try: credential = DefaultAzureCredential() @@ -134,43 +135,43 @@ def _create_crypto_client( @staticmethod def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: key_type = kvk.key.kty - if key_type != KeyType.ec and key_type != KeyType.ec_hsm: + if key_type not in (KeyType.ec, KeyType.ec_hsm): logger.info("only EC keys are supported for now") raise UnsupportedKeyType("Supplied key must be an EC key") crv = kvk.key.crv if crv == KeyCurveName.p_256: return SignatureAlgorithm.es256 - elif crv == KeyCurveName.p_384: + if crv == KeyCurveName.p_384: return SignatureAlgorithm.es384 - elif crv == KeyCurveName.p_521: + if crv == KeyCurveName.p_521: return SignatureAlgorithm.es512 - else: - raise UnsupportedKeyType("Unsupported curve supplied by key") + + raise UnsupportedKeyType("Unsupported curve supplied by key") @staticmethod def _get_hash_algorithm(kvk: KeyVaultKey) -> str: crv = kvk.key.crv if crv == KeyCurveName.p_256: return "sha256" - elif crv == KeyCurveName.p_384: + if crv == KeyCurveName.p_384: return "sha384" - elif crv == KeyCurveName.p_521: + if crv == KeyCurveName.p_521: return "sha512" - else: - logger.info(f"unsupported curve supplied {kvk.key.crv}") - # trigger UnsupportedAlgorithm if appropriate - _ = sslib_hash.digest("") + + logger.info("unsupported curve supplied %s", kvk.key.crv) + # trigger UnsupportedAlgorithm if appropriate + _ = sslib_hash.digest("") @staticmethod def _get_keytype_and_scheme(crv: str) -> Tuple[str, str]: if crv == KeyCurveName.p_256: return "ecdsa", "ecdsa-sha2-nistp256" - elif crv == KeyCurveName.p_384: + if crv == KeyCurveName.p_384: return "ecdsa", "ecdsa-sha2-nistp384" - elif crv == KeyCurveName.p_521: + if crv == KeyCurveName.p_521: return "ecdsa", "ecdsa-sha2-nistp521" - else: - raise UnsupportedKeyType("Unsupported curve supplied by key") + + raise UnsupportedKeyType("Unsupported curve supplied by key") @classmethod def from_priv_key_uri( @@ -194,7 +195,7 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: be called once per key: the uri and Key should be stored for later use. """ if AZURE_IMPORT_ERROR: - raise exceptions.UnsupportedLibraryError(AZURE_IMPORT_ERROR) + raise UnsupportedLibraryError(AZURE_IMPORT_ERROR) vault_url, key_name = cls._vault_url_and_key(az_key_uri) credential = DefaultAzureCredential() @@ -206,13 +207,13 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: ) if key_vault_key.key.crv == KeyCurveName.p_256: - crv = ec.SECP256R1() + crv: ec.EllipticCurve = ec.SECP256R1() elif key_vault_key.key.crv == KeyCurveName.p_384: crv = ec.SECP384R1() elif key_vault_key.key.crv == KeyCurveName.p_521: crv = ec.SECP521R1() else: - raise UnsupportedKeyType(f"Unsupported curve type {kvk.key.crv}") + raise UnsupportedKeyType(f"Unsupported curve type {crv}") # Key is in JWK format, create a curve from it with the parameters x = int.from_bytes(key_vault_key.key.x, byteorder="big") From fa4f443e0ab5c5cf9d85f64cd46466685c5f17af Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 29 May 2023 14:43:44 +0300 Subject: [PATCH 18/31] AzureSigner: Use "stringized" annotations where needed To allow the _azure_signer module import when azure dependency modules are not installed, use stringized annotations (this allows method definitions to happen without the types existing, while still letting the static type checkers do their work). --- securesystemslib/signer/_azure_signer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 31776a35..ec48200b 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -107,8 +107,8 @@ def _vault_url_and_key(az_key_uri: str) -> Tuple[str, str]: @staticmethod def _get_key_vault_key( - cred: DefaultAzureCredential, az_keyid: str, vault_url: str - ) -> KeyVaultKey: + cred: "DefaultAzureCredential", az_keyid: str, vault_url: str + ) -> "KeyVaultKey": try: key_client = KeyClient(vault_url=vault_url, credential=cred) return key_client.get_key(az_keyid) @@ -121,8 +121,8 @@ def _get_key_vault_key( @staticmethod def _create_crypto_client( - cred: DefaultAzureCredential, kv_key: KeyVaultKey - ) -> CryptographyClient: + cred: "DefaultAzureCredential", kv_key: "KeyVaultKey" + ) -> "CryptographyClient": try: return CryptographyClient(kv_key, credential=cred) except (HttpResponseError,) as e: @@ -133,7 +133,7 @@ def _create_crypto_client( ) @staticmethod - def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: + def _get_signature_algorithm(kvk: "KeyVaultKey") -> "SignatureAlgorithm": key_type = kvk.key.kty if key_type not in (KeyType.ec, KeyType.ec_hsm): logger.info("only EC keys are supported for now") @@ -149,7 +149,7 @@ def _get_signature_algorithm(kvk: KeyVaultKey) -> SignatureAlgorithm: raise UnsupportedKeyType("Unsupported curve supplied by key") @staticmethod - def _get_hash_algorithm(kvk: KeyVaultKey) -> str: + def _get_hash_algorithm(kvk: "KeyVaultKey") -> str: crv = kvk.key.crv if crv == KeyCurveName.p_256: return "sha256" From 64a536e6169fc35026c7c0e7149907f6e5e707d1 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Mon, 29 May 2023 16:23:41 +0200 Subject: [PATCH 19/31] Cleaned up the usage of uris. It's clear now wheter it's a private uri (azurekms) or an azure uri (https) Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 46 ++++++++++-------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index ec48200b..aa42ac4e 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -58,7 +58,7 @@ class AzureSigner(Signer): Arguments: az_key_uri: Fully qualified Azure Key Vault name, like - azurekms://.vault.azure.net/ + https://.vault.azure.net/ public_key: public key object Raises: @@ -74,10 +74,7 @@ def __init__(self, az_key_uri: str, _: str): try: credential = DefaultAzureCredential() - vault_url, key_name = self._vault_url_and_key(az_key_uri) - key_vault_key = self._get_key_vault_key( - credential, key_name, vault_url - ) + key_vault_key = self._get_key_vault_key(credential, az_key_uri) self.signature_algorithm = self._get_signature_algorithm( key_vault_key ) @@ -91,37 +88,28 @@ def __init__(self, az_key_uri: str, _: str): ) raise e - @staticmethod - def _vault_url_and_key(az_key_uri: str) -> Tuple[str, str]: - # Extract the vault uri and key name. - # Format is: azurekms://.vault.azure.net/ - (az_vault_uri, az_key_name) = az_key_uri.rsplit("/", 1) - - if not az_vault_uri.startswith("azurekms://"): - raise ValueError(f"Invalid key URI {az_key_uri}") - - # az vault is on form: azurekms:// but key client expects https:// - vault_url = az_vault_uri.replace("azurekms:", "https:") - - return vault_url, az_key_name - @staticmethod def _get_key_vault_key( - cred: "DefaultAzureCredential", az_keyid: str, vault_url: str + cred: "DefaultAzureCredential", + az_key_uri: str, ) -> "KeyVaultKey": + # Format is: https://.vault.azure.net/ + (vault_url, key_name) = az_key_uri.rsplit("/", 1) + try: key_client = KeyClient(vault_url=vault_url, credential=cred) - return key_client.get_key(az_keyid) + return key_client.get_key(key_name) except (HttpResponseError,) as e: logger.info( "Key %s failed to create key client from credentials, key ID, and Vault URL: %s", - az_keyid, + az_key_uri, str(e), ) @staticmethod def _create_crypto_client( - cred: "DefaultAzureCredential", kv_key: "KeyVaultKey" + cred: "DefaultAzureCredential", + kv_key: "KeyVaultKey", ) -> "CryptographyClient": try: return CryptographyClient(kv_key, credential=cred) @@ -185,10 +173,11 @@ def from_priv_key_uri( if uri.scheme != cls.SCHEME: raise ValueError(f"AzureSigner does not support {priv_key_uri}") - return cls(priv_key_uri, public_key) + az_key_uri = priv_key_uri.replace("azurekms:", "https:") + return cls(az_key_uri, public_key) @classmethod - def import_(cls, az_key_uri: str) -> Tuple[str, Key]: + def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: """Load key and signer details from KMS Returns the private key uri and the public key. This method should only @@ -197,9 +186,10 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: if AZURE_IMPORT_ERROR: raise UnsupportedLibraryError(AZURE_IMPORT_ERROR) - vault_url, key_name = cls._vault_url_and_key(az_key_uri) + priv_key_uri = f"azurekms://{az_vault_name}.vault.azure.net/{az_key_name}" + az_key_uri = priv_key_uri.replace("azurekms:", "https:") credential = DefaultAzureCredential() - key_vault_key = cls._get_key_vault_key(credential, key_name, vault_url) + key_vault_key = cls._get_key_vault_key(credential, az_key_uri) if key_vault_key.key.kty != "EC-HSM": raise UnsupportedKeyType( @@ -230,7 +220,7 @@ def import_(cls, az_key_uri: str) -> Tuple[str, Key]: keyid = cls._get_keyid(keytype, scheme, keyval) public_key = SSlibKey(keyid, keytype, scheme, keyval) - return az_key_uri, public_key + return priv_key_uri, public_key def sign(self, payload: bytes) -> Signature: """Signs payload with Azure Key Vault. From d6e60b6254b37248568851a12697dbae19a0ea45 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 30 May 2023 06:09:11 -0600 Subject: [PATCH 20/31] remove test runner Signed-off-by: Meredith Lancaster --- azure-test-runner.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 azure-test-runner.py diff --git a/azure-test-runner.py b/azure-test-runner.py deleted file mode 100644 index 570b8fb7..00000000 --- a/azure-test-runner.py +++ /dev/null @@ -1,19 +0,0 @@ -from securesystemslib.exceptions import UnverifiedSignatureError -from securesystemslib.signer import AzureSigner, Key, Signer - - -def main(): - azure_key_vault = "azurekms://tsa-staging.vault.azure.net" - azure_key_id = "tsa-leaf" - - data = ("data" * 8).encode("utf-8") - signer = AzureSigner.from_priv_key_uri( - azure_key_vault, - azure_key_id - ) - sig = signer.sign(data) - - print(sig) - -if __name__ == "__main__": - main() From 80bf9a2c50cb073e4a99330b7c970982b2ae24a0 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Tue, 30 May 2023 14:17:42 +0200 Subject: [PATCH 21/31] Update the import method to return a key id that contains the version too Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 69 +++++++++++++----------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index aa42ac4e..fb579296 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -58,7 +58,7 @@ class AzureSigner(Signer): Arguments: az_key_uri: Fully qualified Azure Key Vault name, like - https://.vault.azure.net/ + https://.vault.azure.net/keys// public_key: public key object Raises: @@ -68,20 +68,20 @@ class AzureSigner(Signer): SCHEME = "azurekms" - def __init__(self, az_key_uri: str, _: str): + def __init__(self, az_key_uri: str, public_key: Key): if AZURE_IMPORT_ERROR: raise UnsupportedLibraryError(AZURE_IMPORT_ERROR) try: - credential = DefaultAzureCredential() - key_vault_key = self._get_key_vault_key(credential, az_key_uri) - self.signature_algorithm = self._get_signature_algorithm( - key_vault_key + cred = DefaultAzureCredential() + self.crypto_client = CryptographyClient( + az_key_uri, + credential=cred, ) - self.hash_algorithm = self._get_hash_algorithm(key_vault_key) - self.crypto_client = self._create_crypto_client( - credential, key_vault_key + self.signature_algorithm = self._get_signature_algorithm( + public_key, ) + self.hash_algorithm = self._get_hash_algorithm(public_key) except UnsupportedKeyType as e: logger.info( "Key %s has unsupported key type or unsupported elliptic curve" @@ -91,18 +91,19 @@ def __init__(self, az_key_uri: str, _: str): @staticmethod def _get_key_vault_key( cred: "DefaultAzureCredential", - az_key_uri: str, + vault_name: str, + key_name: str, ) -> "KeyVaultKey": - # Format is: https://.vault.azure.net/ - (vault_url, key_name) = az_key_uri.rsplit("/", 1) + vault_url = f"https://{vault_name}.vault.azure.net/" try: key_client = KeyClient(vault_url=vault_url, credential=cred) return key_client.get_key(key_name) except (HttpResponseError,) as e: logger.info( - "Key %s failed to create key client from credentials, key ID, and Vault URL: %s", - az_key_uri, + "Key %s/%s failed to create key client from credentials, key ID, and Vault URL: %s", + vault_name, + key_name, str(e), ) @@ -121,34 +122,39 @@ def _create_crypto_client( ) @staticmethod - def _get_signature_algorithm(kvk: "KeyVaultKey") -> "SignatureAlgorithm": - key_type = kvk.key.kty - if key_type not in (KeyType.ec, KeyType.ec_hsm): + def _get_signature_algorithm(public_key: "Key") -> "SignatureAlgorithm": + if public_key.keytype != "ecdsa": logger.info("only EC keys are supported for now") raise UnsupportedKeyType("Supplied key must be an EC key") - crv = kvk.key.crv - if crv == KeyCurveName.p_256: + # Format is "ecdsa-sha2-nistp256" + comps = public_key.scheme.split("-") + if len(comps) != 3: + raise UnsupportedKeyType("Invalid scheme found") + + if comps[2] == "nistp256": return SignatureAlgorithm.es256 - if crv == KeyCurveName.p_384: + if comps[2] == "nistp384": return SignatureAlgorithm.es384 - if crv == KeyCurveName.p_521: + if comps[2] == "nistp521": return SignatureAlgorithm.es512 raise UnsupportedKeyType("Unsupported curve supplied by key") @staticmethod - def _get_hash_algorithm(kvk: "KeyVaultKey") -> str: - crv = kvk.key.crv - if crv == KeyCurveName.p_256: + def _get_hash_algorithm(public_key: "Key") -> str: + # Format is "ecdsa-sha2-nistp256" + comps = public_key.scheme.split("-") + if len(comps) != 3: + raise UnsupportedKeyType("Invalid scheme found") + + if comps[2] == "nistp256": return "sha256" - if crv == KeyCurveName.p_384: + if comps[2] == "nistp384": return "sha384" - if crv == KeyCurveName.p_521: + if comps[2] == "nistp521": return "sha512" - logger.info("unsupported curve supplied %s", kvk.key.crv) - # trigger UnsupportedAlgorithm if appropriate - _ = sslib_hash.digest("") + raise UnsupportedKeyType("Unsupported curve supplied by key") @staticmethod def _get_keytype_and_scheme(crv: str) -> Tuple[str, str]: @@ -186,10 +192,8 @@ def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: if AZURE_IMPORT_ERROR: raise UnsupportedLibraryError(AZURE_IMPORT_ERROR) - priv_key_uri = f"azurekms://{az_vault_name}.vault.azure.net/{az_key_name}" - az_key_uri = priv_key_uri.replace("azurekms:", "https:") credential = DefaultAzureCredential() - key_vault_key = cls._get_key_vault_key(credential, az_key_uri) + key_vault_key = cls._get_key_vault_key(credential, az_vault_name, az_key_name) if key_vault_key.key.kty != "EC-HSM": raise UnsupportedKeyType( @@ -219,6 +223,7 @@ def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: keyval = {"public": pem.decode("utf-8")} keyid = cls._get_keyid(keytype, scheme, keyval) public_key = SSlibKey(keyid, keytype, scheme, keyval) + priv_key_uri = key_vault_key.key.kid.replace("https:", "azurekms:") return priv_key_uri, public_key From 9232e444edf8ede26c91d6987dce024ebf31d8af Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 30 May 2023 06:59:24 -0600 Subject: [PATCH 22/31] fix linting issues Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index fb579296..0e429c0c 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -19,12 +19,7 @@ try: from azure.core.exceptions import HttpResponseError from azure.identity import DefaultAzureCredential - from azure.keyvault.keys import ( - KeyClient, - KeyCurveName, - KeyType, - KeyVaultKey, - ) + from azure.keyvault.keys import KeyClient, KeyCurveName, KeyVaultKey from azure.keyvault.keys.crypto import ( CryptographyClient, SignatureAlgorithm, @@ -117,7 +112,7 @@ def _create_crypto_client( except (HttpResponseError,) as e: logger.info( "Key %s failed to create crypto client from credentials and KeyVaultKey: %s", - az_keyid, + kv_key, str(e), ) @@ -193,7 +188,9 @@ def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: raise UnsupportedLibraryError(AZURE_IMPORT_ERROR) credential = DefaultAzureCredential() - key_vault_key = cls._get_key_vault_key(credential, az_vault_name, az_key_name) + key_vault_key = cls._get_key_vault_key( + credential, az_vault_name, az_key_name + ) if key_vault_key.key.kty != "EC-HSM": raise UnsupportedKeyType( From 960b741839eb2cca4cd4804d39a65100c1a57de1 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 30 May 2023 07:08:57 -0600 Subject: [PATCH 23/31] linter fix, raise error after logging Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 0e429c0c..08185533 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -101,6 +101,7 @@ def _get_key_vault_key( key_name, str(e), ) + raise e @staticmethod def _create_crypto_client( @@ -115,6 +116,7 @@ def _create_crypto_client( kv_key, str(e), ) + raise e @staticmethod def _get_signature_algorithm(public_key: "Key") -> "SignatureAlgorithm": From 688555e0751c2aa3327a117507082806caf63591 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 30 May 2023 07:23:19 -0600 Subject: [PATCH 24/31] add docstrings Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 08185533..708798d4 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -89,6 +89,7 @@ def _get_key_vault_key( vault_name: str, key_name: str, ) -> "KeyVaultKey": + """Return KeyVaultKey created from the Vault name and key name""" vault_url = f"https://{vault_name}.vault.azure.net/" try: @@ -108,6 +109,7 @@ def _create_crypto_client( cred: "DefaultAzureCredential", kv_key: "KeyVaultKey", ) -> "CryptographyClient": + """Return CryptographyClient created Azure credentials and a KeyVaultKey""" try: return CryptographyClient(kv_key, credential=cred) except (HttpResponseError,) as e: @@ -120,6 +122,7 @@ def _create_crypto_client( @staticmethod def _get_signature_algorithm(public_key: "Key") -> "SignatureAlgorithm": + """Return SignatureAlgorithm after parsing the public key""" if public_key.keytype != "ecdsa": logger.info("only EC keys are supported for now") raise UnsupportedKeyType("Supplied key must be an EC key") @@ -139,6 +142,7 @@ def _get_signature_algorithm(public_key: "Key") -> "SignatureAlgorithm": @staticmethod def _get_hash_algorithm(public_key: "Key") -> str: + """Return the hash algorithm used by the public key""" # Format is "ecdsa-sha2-nistp256" comps = public_key.scheme.split("-") if len(comps) != 3: From c00d3df2267fc4463b2873023950d6eda4897dcb Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 30 May 2023 07:27:20 -0600 Subject: [PATCH 25/31] add pylint disable comment for too-many-locals Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 708798d4..18ac89b8 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -184,6 +184,7 @@ def from_priv_key_uri( return cls(az_key_uri, public_key) @classmethod + # pylint: disable=too-many-locals def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: """Load key and signer details from KMS @@ -210,7 +211,9 @@ def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: elif key_vault_key.key.crv == KeyCurveName.p_521: crv = ec.SECP521R1() else: - raise UnsupportedKeyType(f"Unsupported curve type {crv}") + raise UnsupportedKeyType( + f"Unsupported curve type {key_vault_key.key.crv}" + ) # Key is in JWK format, create a curve from it with the parameters x = int.from_bytes(key_vault_key.key.x, byteorder="big") From 3c99967859b7f1e543a2fe31198fa60d563f4896 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Wed, 31 May 2023 14:25:19 +0200 Subject: [PATCH 26/31] Support all EC keys, not just HSM Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 18ac89b8..5e4968cd 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -199,7 +199,7 @@ def import_(cls, az_vault_name: str, az_key_name: str) -> Tuple[str, Key]: credential, az_vault_name, az_key_name ) - if key_vault_key.key.kty != "EC-HSM": + if not key_vault_key.key.kty.startswith("EC"): raise UnsupportedKeyType( f"Unsupported key type {key_vault_key.key.kty}" ) From 2d7c05bf500963ee75f40079fded8150359920ae Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Wed, 31 May 2023 07:21:03 -0600 Subject: [PATCH 27/31] More actionable error message Co-authored-by: Jussi Kukkonen --- securesystemslib/signer/_azure_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 5e4968cd..526a4b24 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -34,7 +34,7 @@ ) except ImportError: AZURE_IMPORT_ERROR = ( - "azure-cloud-kms library required to sign with Azure Cloud keys." + "Signing with Azure Key Vault requires azure-identity, azure-keyvault-keys and cryptography." ) logger = logging.getLogger(__name__) From f5400905c309408a78978215b488b549deb117ea Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Wed, 31 May 2023 07:26:57 -0600 Subject: [PATCH 28/31] pr feedback, don't need to stringify Key Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 526a4b24..736dc749 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -121,7 +121,7 @@ def _create_crypto_client( raise e @staticmethod - def _get_signature_algorithm(public_key: "Key") -> "SignatureAlgorithm": + def _get_signature_algorithm(public_key: Key) -> "SignatureAlgorithm": """Return SignatureAlgorithm after parsing the public key""" if public_key.keytype != "ecdsa": logger.info("only EC keys are supported for now") From e905b2709ce8c4bc84e317350aa4e10c0ed32ecc Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Wed, 31 May 2023 08:29:12 -0600 Subject: [PATCH 29/31] pr feedback, add section with roles needed by the azure signer, simplify signature creation Signed-off-by: Meredith Lancaster --- securesystemslib/signer/_azure_signer.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 736dc749..7692c099 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -1,6 +1,5 @@ """Signer implementation for Azure Key Vault""" -import binascii import logging from typing import Optional, Tuple from urllib import parse @@ -33,9 +32,7 @@ PublicFormat, ) except ImportError: - AZURE_IMPORT_ERROR = ( - "Signing with Azure Key Vault requires azure-identity, azure-keyvault-keys and cryptography." - ) + AZURE_IMPORT_ERROR = "Signing with Azure Key Vault requires azure-identity, azure-keyvault-keys and cryptography." logger = logging.getLogger(__name__) @@ -51,6 +48,12 @@ class AzureSigner(Signer): Currently this signer only supports signing with EC keys. RSA support will be added in a separate pull request. + The specific permissions that AzureSigner needs are: + * "Key Vault Crypto User" for import() and sign() + + See https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-guide?tabs=azure-cli + for a list of all built-in Azure Key Vault roles + Arguments: az_key_uri: Fully qualified Azure Key Vault name, like https://.vault.azure.net/keys// @@ -265,8 +268,6 @@ def sign(self, payload: bytes) -> Signature: # Create an ASN.1 encoded Dss-Sig-Value to be used with # pyca/cryptography - dss_sig_value = binascii.hexlify(encode_dss_signature(r, s)).decode( - "ascii" - ) + dss_sig_value = encode_dss_signature(r, s).hex() return Signature(response.key_id, dss_sig_value) From ca604896e01d8633c56fa936926cdfe044150951 Mon Sep 17 00:00:00 2001 From: Fredrik Skogman Date: Thu, 1 Jun 2023 12:44:23 +0200 Subject: [PATCH 30/31] Fixed a bug that sets the wrong key id on the returned signature. Added smoke tests for Azure KMS sign. Signed-off-by: Fredrik Skogman --- securesystemslib/signer/_azure_signer.py | 3 +- tests/check_azure_signer.py | 66 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tests/check_azure_signer.py diff --git a/securesystemslib/signer/_azure_signer.py b/securesystemslib/signer/_azure_signer.py index 7692c099..dbe5d6b5 100644 --- a/securesystemslib/signer/_azure_signer.py +++ b/securesystemslib/signer/_azure_signer.py @@ -85,6 +85,7 @@ def __init__(self, az_key_uri: str, public_key: Key): "Key %s has unsupported key type or unsupported elliptic curve" ) raise e + self.public_key = public_key @staticmethod def _get_key_vault_key( @@ -270,4 +271,4 @@ def sign(self, payload: bytes) -> Signature: # pyca/cryptography dss_sig_value = encode_dss_signature(r, s).hex() - return Signature(response.key_id, dss_sig_value) + return Signature(self.public_key.keyid, dss_sig_value) diff --git a/tests/check_azure_signer.py b/tests/check_azure_signer.py new file mode 100644 index 00000000..e21da188 --- /dev/null +++ b/tests/check_azure_signer.py @@ -0,0 +1,66 @@ +""" +This module confirms that signing using Azure KMS keys works. + +The purpose is to do a smoke test, not to exhaustively test every possible +key and environment combination. + +For Azure, the requirements to successfully test are: +* Azure 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. +""" + +import unittest + +from securesystemslib.exceptions import UnverifiedSignatureError +from securesystemslib.signer import AzureSigner, Key, Signer + + +class TestAzureKeys(unittest.TestCase): + """Test that KMS keys can be used to sign.""" + + azure_pubkey = Key.from_dict( + "8b4af6aec66518bc66718474aa15c8becd3286e8e2b958c497a60a828d591d04", + { + "keytype": "ecdsa", + "scheme": "ecdsa-sha2-nistp256", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE95qxD+/kX6oCace7hrfChtz2IYGK\nHNBmUwtf3wXH0VEdLPWVoFgGITonvA7vxqYrF8ZzAeeZYNyEBbod7SEeaw==\n-----END PUBLIC KEY-----\n" + }, + }, + ) + azure_id = "azurekms://fsn-vault-1.vault.azure.net/keys/ec-key-1/b1089bbf068742d483970282f02090de" + + def test_azure_sign(self): + """Test that Azure KMS key works for signing + + Note that this test requires valid credentials available. + """ + + data = "data".encode("utf-8") + + signer = Signer.from_priv_key_uri(self.azure_id, self.azure_pubkey) + sig = signer.sign(data) + + print(sig.signature) + + self.azure_pubkey.verify_signature(sig, data) + with self.assertRaises(UnverifiedSignatureError): + self.azure_pubkey.verify_signature(sig, b"NOT DATA") + + def test_azure_import(self): + """Test that Azure KMS key works for signing + + Note that this test requires valid credentials available. + """ + + uri, pubkey = AzureSigner.import_("fsn-vault-1", "ec-key-1") + + self.assertEqual(pubkey, self.azure_pubkey) + self.assertEqual(uri, self.azure_id) + + +if __name__ == "__main__": + unittest.main(verbosity=1, buffer=True) From 36b65a436ff8fb21d9c9d5020e9f0ef2ba8e703a Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Thu, 1 Jun 2023 18:57:09 +0300 Subject: [PATCH 31/31] requirements-kms: Remove azure packages requirements-kms.txt lists the requirements to run tests.check_kms_signers (which is run on CI): Azure is not currently part of that test set so the requirements are not needed either. --- requirements-kms.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements-kms.txt b/requirements-kms.txt index a046edc5..78d6fb9f 100644 --- a/requirements-kms.txt +++ b/requirements-kms.txt @@ -1,3 +1 @@ google-cloud-kms -azure-identity -azure-keyvault-keys