From 549bf773efb041f39bd9d5a7eb96934c6af1862c Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 25 Apr 2024 13:56:33 +0200 Subject: [PATCH] Add basic VaultSigner (WIP) VaultSigner.import_ already works for ed25519 keys, and can be tested via tox -e local-vault TODO: - sign - from_priv_key_uri - test on CI Signed-off-by: Lukas Puehringer --- securesystemslib/signer/__init__.py | 1 + securesystemslib/signer/_vault_signer.py | 51 ++++++++++++++++++++++++ tests/check_vault_signer.py | 40 +++++++++++++++++++ tox.ini | 12 +++--- 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 securesystemslib/signer/_vault_signer.py create mode 100644 tests/check_vault_signer.py diff --git a/securesystemslib/signer/__init__.py b/securesystemslib/signer/__init__.py index e210090f..65abd2ca 100644 --- a/securesystemslib/signer/__init__.py +++ b/securesystemslib/signer/__init__.py @@ -11,6 +11,7 @@ from securesystemslib.signer._gcp_signer import GCPSigner from securesystemslib.signer._gpg_signer import GPGKey, GPGSigner from securesystemslib.signer._hsm_signer import HSMSigner +from securesystemslib.signer._vault_signer import VaultSigner from securesystemslib.signer._key import KEY_FOR_TYPE_AND_SCHEME, Key, SSlibKey from securesystemslib.signer._signature import Signature from securesystemslib.signer._signer import ( diff --git a/securesystemslib/signer/_vault_signer.py b/securesystemslib/signer/_vault_signer.py new file mode 100644 index 00000000..887e59a8 --- /dev/null +++ b/securesystemslib/signer/_vault_signer.py @@ -0,0 +1,51 @@ +"""Signer implementation for HashiCorp Vault (Transit secrets engine)""" + +from typing import Tuple +from base64 import b64decode + +from securesystemslib.exceptions import UnsupportedLibraryError +from securesystemslib.signer._key import Key, SSlibKey +from securesystemslib.signer._signer import SecretsHandler, Signature, Signer +from securesystemslib.signer._utils import compute_default_keyid + + +VAULT_IMPORT_ERROR = None +try: + import hvac + from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PublicKey, + ) + +except ImportError: + VAULT_IMPORT_ERROR = "Signing with HashiCorp Vault requires hvac and cryptography." + + +class VaultSigner(Signer): + """HashiCorp Vault Signer (Transit secrets engine) """ + SCHEME = "hv" + + @classmethod + def import_(cls, hv_key_name: str) -> Tuple[str, Key]: + """Load key and signer details from vault. + + Supported keytypes: + * ed25519 + + """ + if VAULT_IMPORT_ERROR: + raise UnsupportedLibraryError(VAULT_IMPORT_ERROR) + + client = hvac.Client() + + resp = client.secrets.transit.read_key(hv_key_name) + + # Extract "newest" key from response + pub_b64 = sorted(resp["data"]["keys"].items())[-1][1]["public_key"] + pub_raw = b64decode(pub_b64) + pub_crypto = Ed25519PublicKey.from_public_bytes(pub_raw) + + pub = SSlibKey.from_crypto(pub_crypto) + uri = f"{VaultSigner.SCHEME}:{hv_key_name}" + + return uri, pub + diff --git a/tests/check_vault_signer.py b/tests/check_vault_signer.py new file mode 100644 index 00000000..88f989da --- /dev/null +++ b/tests/check_vault_signer.py @@ -0,0 +1,40 @@ +"""Test AWSSigner + +""" + +import unittest + +from securesystemslib.signer import VaultSigner + + +class TestVaultSigner(unittest.TestCase): + """Test VaultSigner""" + + def test_vault_import_sign_verify(self): + # Test full signer flow with vault + # - see tests/scripts/init-vault.sh for how keys are created + # - see tox.ini for how credentials etc. are passed via env vars + keys_and_schemes = [ + ("test-key-ed25519", "ed25519") + ] + for hv_key_name, scheme in keys_and_schemes: + # Test import + uri, public_key = VaultSigner.import_(hv_key_name) + self.assertEqual(uri, f"{VaultSigner.SCHEME}:{hv_key_name}") + self.assertEqual(scheme, public_key.scheme) + + # # Test load + # signer = Signer.from_priv_key_uri(uri, public_key) + # self.assertIsInstance(signer, VaultSigner) + + # # Test sign and verify + # signature = signer.sign(b"DATA") + # self.assertIsNone( + # public_key.verify_signature(signature, b"DATA") + # ) + # with self.assertRaises(UnverifiedSignatureError): + # public_key.verify_signature(signature, b"NOT DATA") + + +if __name__ == "__main__": + unittest.main(verbosity=1) diff --git a/tox.ini b/tox.ini index 13b4ffbc..b85f94ae 100644 --- a/tox.ini +++ b/tox.ini @@ -107,10 +107,12 @@ commands_post = localstack stop -# Requires docker running +# Requires `vault` +# https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-install [testenv:local-vault] -; deps = -; hvac +deps = + -r{toxinidir}/requirements-pinned.txt + hvac allowlist_externals = bash @@ -123,9 +125,7 @@ commands_pre = bash {toxinidir}/tests/scripts/init-vault.sh commands = - ; TODO: Test AWSSigner.import_, from_priv_key_uri and sign here - ; curl --header "X-Vault-Token: test-root-token" \ - ; http://127.0.0.1:8200/v1/transit/export/public-key/test-key-ed25519 + python -m tests.check_vault_signer commands_post = bash {toxinidir}/tests/scripts/stop-vault.sh