diff --git a/pyproject.toml b/pyproject.toml index 4d4ca204..d4f28b68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,6 @@ gcpkms = ["google-cloud-kms", "cryptography>=40.0.0"] azurekms = ["azure-identity", "azure-keyvault-keys", "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"] sigstore = ["sigstore~=2.0"] diff --git a/requirements-pinned.txt b/requirements-pinned.txt index d908dc9c..9eef3930 100644 --- a/requirements-pinned.txt +++ b/requirements-pinned.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile --output-file=requirements-pinned.txt requirements.txt @@ -9,7 +9,6 @@ asn1crypto==1.5.1 cffi==1.16.0 # via # cryptography - # pynacl # pyspx cryptography==42.0.5 # via -r requirements.txt @@ -17,7 +16,5 @@ pycparser==2.22 # via cffi pykcs11==1.5.14 # via -r requirements.txt -pynacl==1.5.0 - # via -r requirements.txt pyspx==0.5.0 ; platform_system != "Windows" # via -r requirements.txt diff --git a/requirements.txt b/requirements.txt index 7bf2c867..b9e800e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ # 'requirements-pinned.txt' is updated on GitHub with Dependabot, which # triggers CI/CD builds to automatically test against updated dependencies. cryptography >= 37.0.0 -pynacl PySPX; platform_system != 'Windows' PyKCS11 asn1crypto diff --git a/securesystemslib/ecdsa_keys.py b/securesystemslib/ecdsa_keys.py deleted file mode 100755 index c3d7c9b8..00000000 --- a/securesystemslib/ecdsa_keys.py +++ /dev/null @@ -1,520 +0,0 @@ -""" - - ecdsa_keys.py - - - Vladimir Diaz - - - November 22, 2016. - - - See LICENSE for licensing information. - - - The goal of this module is to support ECDSA keys and signatures. ECDSA is an - elliptic-curve digital signature algorithm. It grants a similar level of - security as RSA, but uses smaller keys. No subexponential-time algorithm is - known for the elliptic curve discrete logarithm problem. - - https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm - - 'securesystemslib.ecdsa_keys.py' calls the 'cryptography' library to perform - all of the ecdsa-related operations. - - The ecdsa-related functions included here are generate(), create_signature() - and verify_signature(). The 'cryptography' library is used by ecdsa_keys.py - to perform the actual ECDSA computations, and the functions listed above can - be viewed as an easy-to-use public interface. - """ - -import logging - -# Import cryptography modules to support ecdsa keys and signatures. -CRYPTO = True -NO_CRYPTO_MSG = "ECDSA key support requires the cryptography library" -try: - from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.hazmat.primitives.serialization import ( - load_pem_private_key, - load_pem_public_key, - ) - - _SCHEME_HASHER = { - "ecdsa-sha2-nistp256": ec.ECDSA(hashes.SHA256()), - "ecdsa-sha2-nistp384": ec.ECDSA(hashes.SHA384()), - } - -except ImportError: - CRYPTO = False - -# Perform object format-checking and add ability to handle/raise exceptions. -from securesystemslib import ( # pylint: disable=wrong-import-position - exceptions, - formats, -) - -_SUPPORTED_ECDSA_SCHEMES = ["ecdsa-sha2-nistp256"] - -logger = logging.getLogger(__name__) - - -def generate_public_and_private(scheme="ecdsa-sha2-nistp256"): - """ - - Generate a pair of ECDSA public and private keys with one of the supported, - external cryptography libraries. The public and private keys returned - conform to 'securesystemslib.formats.PEMECDSA_SCHEMA' and - 'securesystemslib.formats.PEMECDSA_SCHEMA', respectively. - - The public ECDSA public key has the PEM format: - TODO: should we encrypt the private keys returned here? Should the - create_signature() accept encrypted keys? - - '-----BEGIN PUBLIC KEY----- - - ... - - '-----END PUBLIC KEY-----' - - - - The private ECDSA private key has the PEM format: - - '-----BEGIN EC PRIVATE KEY----- - - ... - - -----END EC PRIVATE KEY-----' - - >>> public, private = generate_public_and_private() - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) - True - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) - True - - - scheme: - A string indicating which algorithm to use for the generation of the - public and private ECDSA keys. 'ecdsa-sha2-nistp256' is the only - currently supported ECDSA algorithm, which is supported by OpenSSH and - specified in RFC 5656 (https://tools.ietf.org/html/rfc5656). - - - securesystemslib.exceptions.FormatError, if 'algorithm' is improperly - formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is an - unsupported algorithm. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - None. - - - A (public, private) tuple that conform to - 'securesystemslib.formats.PEMECDSA_SCHEMA' and - 'securesystemslib.formats.PEMECDSA_SCHEMA', respectively. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does 'scheme' have the correct format? - # Verify that 'scheme' is of the correct type, and that it's one of the - # supported ECDSA . It must conform to - # 'securesystemslib.formats.ECDSA_SCHEME_SCHEMA'. Raise - # 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - - public_key = None - private_key = None - - # An if-clause is strictly not needed, since 'ecdsa_sha2-nistp256' is the - # only currently supported ECDSA signature scheme. Nevertheness, include the - # conditional statement to accomodate any schemes that might be added. - if scheme == "ecdsa-sha2-nistp256": - private_key = ec.generate_private_key(ec.SECP256R1, default_backend()) - public_key = private_key.public_key() - - # The ECDSA_SCHEME_SCHEMA.check_match() above should have detected any - # invalid 'scheme'. This is a defensive check. - else: # pragma: no cover - raise exceptions.UnsupportedAlgorithmError( - "An unsupported" - " scheme specified: " + repr(scheme) + ".\n Supported" - " algorithms: " + repr(_SUPPORTED_ECDSA_SCHEMES) - ) - - private_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ).strip() - - public_pem = public_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ).strip() - - return public_pem.decode("utf-8"), private_pem.decode("utf-8") - - -def create_signature( - public_key, private_key, data, scheme="ecdsa-sha2-nistp256" -): - """ - - Return a (signature, scheme) tuple. - - >>> requested_scheme = 'ecdsa-sha2-nistp256' - >>> public, private = generate_public_and_private(requested_scheme) - >>> data = b'The quick brown fox jumps over the lazy dog' - >>> signature, scheme = create_signature(public, private, data, requested_scheme) - >>> securesystemslib.formats.ECDSASIGNATURE_SCHEMA.matches(signature) - True - >>> requested_scheme == scheme - True - - - public: - The ECDSA public key in PEM format. - - private: - The ECDSA private key in PEM format. - - data: - Byte data used by create_signature() to generate the signature returned. - - scheme: - The signature scheme used to generate the signature. For example: - 'ecdsa-sha2-nistp256'. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if a signature cannot be created. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is not - one of the supported signature schemes. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - None. - - - A signature dictionary conformat to - 'securesystemslib.format.SIGNATURE_SCHEMA'. ECDSA signatures are XX bytes, - however, the hexlified signature is stored in the dictionary returned. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Do 'public_key' and 'private_key' have the correct format? - # This check will ensure that the arguments conform to - # 'securesystemslib.formats.PEMECDSA_SCHEMA'. Raise - # 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMECDSA_SCHEMA.check_match(public_key) - - # Is 'private_key' properly formatted? - formats.PEMECDSA_SCHEMA.check_match(private_key) - - # Is 'scheme' properly formatted? - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - - # 'ecdsa-sha2-nistp256' is the only currently supported ECDSA scheme, so this - # if-clause isn't strictly needed. Nevertheless, the conditional statement - # is included to accommodate multiple schemes that can potentially be added - # in the future. - if scheme == "ecdsa-sha2-nistp256": - try: - private_key = load_pem_private_key( - private_key.encode("utf-8"), - password=None, - backend=default_backend(), - ) - - signature = private_key.sign(data, ec.ECDSA(hashes.SHA256())) - - except TypeError as e: - raise exceptions.CryptoError( - "Could not create" " signature: " + str(e) - ) - - # A defensive check for an invalid 'scheme'. The - # ECDSA_SCHEME_SCHEMA.check_match() above should have already validated it. - else: # pragma: no cover - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " signature scheme is specified: " + repr(scheme) - ) - - return signature, scheme - - -def verify_signature(public_key, scheme, signature, data): - """ - - Verify that 'signature' was produced by the private key associated with - 'public_key'. - - >>> scheme = 'ecdsa-sha2-nistp256' - >>> public, private = generate_public_and_private(scheme) - >>> data = b'The quick brown fox jumps over the lazy dog' - >>> signature, scheme = create_signature(public, private, data, scheme) - >>> verify_signature(public, scheme, signature, data) - True - >>> verify_signature(public, scheme, signature, b'bad data') - False - - - public_key: - The ECDSA public key in PEM format. The public key is needed to verify - 'signature'. - - scheme: - The signature scheme used to generate 'signature'. For example: - 'ecdsa-sha2-nistp256'. - - signature: - The signature to be verified, which should have been generated by - the private key associated with 'public_key'. 'data'. - - data: - Byte data that was used by create_signature() to generate 'signature'. - - - securesystemslib.exceptions.FormatError, if any of the arguments are - improperly formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is - not one of the supported signature schemes. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - None. - - - Boolean, indicating whether the 'signature' of data was generated by - the private key associated with 'public_key'. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Are the arguments properly formatted? - # If not, raise 'securesystemslib.exceptions.FormatError'. - formats.PEMECDSA_SCHEMA.check_match(public_key) - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - formats.ECDSASIGNATURE_SCHEMA.check_match(signature) - - try: - ecdsa_key = load_pem_public_key( - public_key.encode("utf-8"), backend=default_backend() - ) - except ValueError as e: - raise exceptions.FormatError( - f"Failed to load PEM key {public_key}" - ) from e - - if not isinstance(ecdsa_key, ec.EllipticCurvePublicKey): - raise exceptions.FormatError( - "Invalid ECDSA public" " key: " + repr(public_key) - ) - logger.debug("Loaded a valid ECDSA public key.") - - # verify() raises an 'InvalidSignature' exception if 'signature' - # is invalid. - try: - ecdsa_key.verify(signature, data, _SCHEME_HASHER[scheme]) - return True - - except (TypeError, InvalidSignature): - return False - - -def create_ecdsa_public_and_private_from_pem(pem, password=None): - """ - - Create public and private ECDSA keys from a private 'pem'. The public and - private keys are strings in PEM format: - - public: '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----', - private: '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----'}} - - >>> junk, private = generate_public_and_private() - >>> public, private = create_ecdsa_public_and_private_from_pem(private) - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) - True - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) - True - >>> passphrase = 'secret' - >>> encrypted_pem = create_ecdsa_encrypted_pem(private, passphrase) - >>> public, private = create_ecdsa_public_and_private_from_pem(encrypted_pem, passphrase) - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) - True - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) - True - - - pem: - A string in PEM format. The private key is extracted and returned in - an ecdsakey object. - - password: (optional) - The password, or passphrase, to decrypt the private part of the ECDSA key - if it is encrypted. 'password' is not used directly as the encryption - key, a stronger encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if the ECDSA key - pair could not be extracted, possibly due to an unsupported algorithm. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - None. - - - A dictionary containing the ECDSA keys and other identifying information. - Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does 'pem' have the correct format? - # This check will ensure 'pem' conforms to - # 'securesystemslib.formats.ECDSARSA_SCHEMA'. - formats.PEMECDSA_SCHEMA.check_match(pem) - - if password is not None: - formats.PASSWORD_SCHEMA.check_match(password) - password = password.encode("utf-8") - - else: - logger.debug( - "The password/passphrase is unset. The PEM is expected" - " to be unencrypted." - ) - - public = None - private = None - - # Generate the public and private ECDSA keys. The pyca/cryptography library - # performs the actual import operation. - try: - private = load_pem_private_key( - pem.encode("utf-8"), password=password, backend=default_backend() - ) - - except (ValueError, UnsupportedAlgorithm) as e: - raise exceptions.CryptoError( - "Could not import private" " PEM.\n" + str(e) - ) - - public = private.public_key() - - # Serialize public and private keys to PEM format. - private = private.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ).strip() - - public = public.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ).strip() - - return public.decode("utf-8"), private.decode("utf-8") - - -def create_ecdsa_encrypted_pem(private_pem, passphrase): - """ - - Return a string in PEM format, where the private part of the ECDSA key is - encrypted. The private part of the ECDSA key is encrypted as done by - pyca/cryptography: "Encrypt using the best available encryption for a given - key's backend. This is a curated encryption choice and the algorithm may - change over time." - - >>> junk, private = generate_public_and_private() - >>> passphrase = 'secret' - >>> encrypted_pem = create_ecdsa_encrypted_pem(private, passphrase) - >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(encrypted_pem) - True - - - private_pem: - The private ECDSA key string in PEM format. - - passphrase: - The passphrase, or password, to encrypt the private part of the ECDSA - key. 'passphrase' is not used directly as the encryption key, a stronger - encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if an ECDSA key in encrypted PEM - format cannot be created. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - None. - - - A string in PEM format, where the private RSA portion is encrypted. - Conforms to 'securesystemslib.formats.PEMECDSA_SCHEMA'. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does 'private_key' have the correct format? - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(private_pem) - - # Does 'passphrase' have the correct format? - formats.PASSWORD_SCHEMA.check_match(passphrase) - - private = load_pem_private_key( - private_pem.encode("utf-8"), password=None, backend=default_backend() - ) - - encrypted_private_pem = private.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.BestAvailableEncryption( - passphrase.encode("utf-8") - ), - ).strip() - - return encrypted_private_pem - - -if __name__ == "__main__": - # The interactive sessions of the documentation strings can - # be tested by running 'ecdsa_keys.py' as a standalone module. - # python -B ecdsa_keys.py - import doctest - - doctest.testmod() diff --git a/securesystemslib/ed25519_keys.py b/securesystemslib/ed25519_keys.py deleted file mode 100755 index b4ad62e9..00000000 --- a/securesystemslib/ed25519_keys.py +++ /dev/null @@ -1,363 +0,0 @@ -""" - - ed25519_keys.py - - - Vladimir Diaz - - - September 24, 2013. - - - See LICENSE for licensing information. - - - The goal of this module is to support Ed25519 signatures. Ed25519 is an - elliptic-curve public key signature scheme, its main strength being small - signatures (64 bytes) and small public keys (32 bytes). - http://ed25519.cr.yp.to/ - - 'securesystemslib/ed25519_keys.py' calls 'ed25519.py', which is the pure - Python implementation of ed25519 optimized for a faster runtime. The Python - reference implementation is concise, but very slow (verifying signatures - takes ~9 seconds on an Intel core 2 duo @ 2.2 ghz x 2). The optimized - version can verify signatures in ~2 seconds. - - http://ed25519.cr.yp.to/software.html - https://github.com/pyca/ed25519 - - Optionally, ed25519 cryptographic operations may be executed by PyNaCl, which - is a Python binding to the NaCl library and is faster than the pure python - implementation. Verifying signatures can take approximately 0.0009 seconds. - PyNaCl relies on the libsodium C library. PyNaCl is required for key and - signature generation. Verifying signatures may be done in pure Python. - - https://github.com/pyca/pynacl - https://github.com/jedisct1/libsodium - http://nacl.cr.yp.to/ - https://github.com/pyca/ed25519 - - The ed25519-related functions included here are generate(), create_signature() - and verify_signature(). The 'ed25519' and PyNaCl (i.e., 'nacl') modules used - by ed25519_keys.py perform the actual ed25519 computations and the functions - listed above can be viewed as an easy-to-use public interface. - """ - -# 'os' required to generate OS-specific randomness (os.urandom) suitable for -# cryptographic use. -# http://docs.python.org/2/library/os.html#miscellaneous-functions -import os - -# Import the python implementation of the ed25519 algorithm provided by pyca, -# which is an optimized version of the one provided by ed25519's authors. -# Note: The pure Python version does not include protection against side-channel -# attacks. Verifying signatures can take approximately 2 seconds on an intel -# core 2 duo @ 2.2 ghz x 2). Optionally, the PyNaCl module may be used to -# speed up ed25519 cryptographic operations. -# http://ed25519.cr.yp.to/software.html -# https://github.com/pyca/ed25519 -# https://github.com/pyca/pynacl -# -# Import the PyNaCl library, if available. It is recommended this library be -# used over the pure python implementation of Ed25519, due to its speedier -# routines and side-channel protections available in the libsodium library. -NACL = True -NO_NACL_MSG = "ed25519 key support requires the nacl library" -try: - # avoid conflicts with own exceptions of same name - from nacl import exceptions as nacl_exceptions - from nacl.encoding import RawEncoder - from nacl.signing import SigningKey, VerifyKey -except ImportError: - NACL = False - -# pylint: disable=wrong-import-position -from securesystemslib import exceptions, formats - -# The optimized pure Python implementation of Ed25519. If -# PyNaCl cannot be imported and an attempt to use is made in this module, a -# 'securesystemslib.exceptions.UnsupportedLibraryError' exception is raised. -from securesystemslib._vendor.ed25519 import ed25519 as python_ed25519 - -# pylint: enable=wrong-import-position - -# Supported ed25519 signing schemes: 'ed25519'. The pure Python implementation -# (i.e., ed25519') and PyNaCl (i.e., 'nacl', libsodium + Python bindings) -# modules are currently supported in the creation of 'ed25519' signatures. -# Previously, a distinction was made between signatures made by the pure Python -# implementation and PyNaCl. -_SUPPORTED_ED25519_SIGNING_SCHEMES = ["ed25519"] - - -def generate_public_and_private(): - """ - - Generate a pair of ed25519 public and private keys with PyNaCl. The public - and private keys returned conform to - 'securesystemslib.formats.ED25519PUBLIC_SCHEMA' and - 'securesystemslib.formats.ED25519SEED_SCHEMA', respectively. - - An ed25519 seed key is a random 32-byte string. Public keys are also 32 - bytes. - - >>> public, private = generate_public_and_private() - >>> securesystemslib.formats.ED25519PUBLIC_SCHEMA.matches(public) - True - >>> securesystemslib.formats.ED25519SEED_SCHEMA.matches(private) - True - - - None. - - - securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl') - module is unavailable. - - NotImplementedError, if a randomness source is not found by 'os.urandom'. - - - The ed25519 keys are generated by first creating a random 32-byte seed - with os.urandom() and then calling PyNaCl's nacl.signing.SigningKey(). - - - A (public, private) tuple that conform to - 'securesystemslib.formats.ED25519PUBLIC_SCHEMA' and - 'securesystemslib.formats.ED25519SEED_SCHEMA', respectively. - """ - - if not NACL: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_NACL_MSG) - - # Generate ed25519's seed key by calling os.urandom(). The random bytes - # returned should be suitable for cryptographic use and is OS-specific. - # Raise 'NotImplementedError' if a randomness source is not found. - # ed25519 seed keys are fixed at 32 bytes (256-bit keys). - # http://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ - seed = os.urandom(32) - public = None - - # Generate the public key. PyNaCl (i.e., 'nacl' module) performs the actual - # key generation. - nacl_key = SigningKey(seed) - public = nacl_key.verify_key.encode(encoder=RawEncoder()) - - return public, seed - - -def create_signature(public_key, private_key, data, scheme): - """ - - Return a (signature, scheme) tuple, where the signature scheme is 'ed25519' - and is always generated by PyNaCl (i.e., 'nacl'). The signature returned - conforms to 'securesystemslib.formats.ED25519SIGNATURE_SCHEMA', and has the - form: - - '\xae\xd7\x9f\xaf\x95{bP\x9e\xa8YO Z\x86\x9d...' - - A signature is a 64-byte string. - - >>> public, private = generate_public_and_private() - >>> data = b'The quick brown fox jumps over the lazy dog' - >>> scheme = 'ed25519' - >>> signature, scheme = \ - create_signature(public, private, data, scheme) - >>> securesystemslib.formats.ED25519SIGNATURE_SCHEMA.matches(signature) - True - >>> scheme == 'ed25519' - True - >>> signature, scheme = \ - create_signature(public, private, data, scheme) - >>> securesystemslib.formats.ED25519SIGNATURE_SCHEMA.matches(signature) - True - >>> scheme == 'ed25519' - True - - - public: - The ed25519 public key, which is a 32-byte string. - - private: - The ed25519 private key, which is a 32-byte string. - - data: - Data object used by create_signature() to generate the signature. - - scheme: - The signature scheme used to generate the signature. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if a signature cannot be created. - - securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl') - module is unavailable. - - - nacl.signing.SigningKey.sign() called to generate the actual signature. - - - A signature dictionary conformat to - 'securesystemslib.format.SIGNATURE_SCHEMA'. ed25519 signatures are 64 - bytes, however, the hexlified signature is stored in the dictionary - returned. - """ - - if not NACL: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_NACL_MSG) - - # Does 'public_key' have the correct format? - # This check will ensure 'public_key' conforms to - # 'securesystemslib.formats.ED25519PUBLIC_SCHEMA', which must have length 32 - # bytes. Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ED25519PUBLIC_SCHEMA.check_match(public_key) - - # Is 'private_key' properly formatted? - formats.ED25519SEED_SCHEMA.check_match(private_key) - - # Is 'scheme' properly formatted? - formats.ED25519_SIG_SCHEMA.check_match(scheme) - - # Signing the 'data' object requires a seed and public key. - # nacl.signing.SigningKey.sign() generates the signature. - signature = None - - # An if-clause is not strictly needed here, since 'ed25519' is the only - # currently supported scheme. Nevertheless, include the conditional - # statement to accommodate schemes that might be added in the future. - if scheme == "ed25519": - try: - nacl_key = SigningKey(private_key) - nacl_sig = nacl_key.sign(data) - signature = nacl_sig.signature - - except (ValueError, TypeError, nacl_exceptions.CryptoError) as e: - raise exceptions.CryptoError( - 'An "ed25519" signature' - " could not be created with PyNaCl." + str(e) - ) - - # This is a defensive check for a valid 'scheme', which should have already - # been validated in the check_match() above. - else: # pragma: no cover - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " signature scheme is specified: " + repr(scheme) - ) - - return signature, scheme - - -def verify_signature(public_key, scheme, signature, data): - """ - - Determine whether the private key corresponding to 'public_key' produced - 'signature'. verify_signature() will use the public key, the 'scheme' and - 'sig', and 'data' arguments to complete the verification. - - >>> public, private = generate_public_and_private() - >>> data = b'The quick brown fox jumps over the lazy dog' - >>> scheme = 'ed25519' - >>> signature, scheme = \ - create_signature(public, private, data, scheme) - >>> verify_signature(public, scheme, signature, data) - True - >>> bad_data = b'The sly brown fox jumps over the lazy dog' - >>> bad_signature, scheme = \ - create_signature(public, private, bad_data, scheme) - >>> verify_signature(public, scheme, bad_signature, data) - False - - - public_key: - The public key is a 32-byte string. - - scheme: - 'ed25519' signature scheme used by either the pure python - implementation (i.e., ed25519.py) or PyNacl (i.e., 'nacl'). - - signature: - The signature is a 64-byte string. - - data: - Data object used by securesystemslib.ed25519_keys.create_signature() to - generate 'signature'. 'data' is needed here to verify the signature. - - - securesystemslib.exceptions.UnsupportedAlgorithmError. Raised if the - signature scheme 'scheme' is not one supported by - securesystemslib.ed25519_keys.create_signature(). - - securesystemslib.exceptions.FormatError. Raised if the arguments are - improperly formatted. - - - nacl.signing.VerifyKey.verify() called if available, otherwise - securesystemslib._vendor.ed25519.ed25519.checkvalid() called to do the - verification. - - - Boolean. True if the signature is valid, False otherwise. - """ - - # Does 'public_key' have the correct format? - # This check will ensure 'public_key' conforms to - # 'securesystemslib.formats.ED25519PUBLIC_SCHEMA', which must have length 32 - # bytes. Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ED25519PUBLIC_SCHEMA.check_match(public_key) - - # Is 'scheme' properly formatted? - formats.ED25519_SIG_SCHEMA.check_match(scheme) - - # Is 'signature' properly formatted? - formats.ED25519SIGNATURE_SCHEMA.check_match(signature) - - # Verify 'signature'. Before returning the Boolean result, ensure 'ed25519' - # was used as the signature scheme. - public = public_key - valid_signature = False - - if scheme in _SUPPORTED_ED25519_SIGNING_SCHEMES: - if NACL: - try: - nacl_verify_key = VerifyKey(public) - nacl_verify_key.verify(data, signature) - valid_signature = True - - except nacl_exceptions.BadSignatureError: - pass - - # Verify 'ed25519' signature with the pure Python implementation. - else: - try: - python_ed25519.checkvalid(signature, data, public) - valid_signature = True - - # The pure Python implementation raises 'Exception' if 'signature' is - # invalid. - except Exception: # pylint: disable=broad-except # nosec - pass - - # This is a defensive check for a valid 'scheme', which should have already - # been validated in the ED25519_SIG_SCHEMA.check_match(scheme) above. - else: # pragma: no cover - message = ( - "Unsupported ed25519 signature scheme: " - + repr(scheme) - + ".\n" - + "Supported schemes: " - + repr(_SUPPORTED_ED25519_SIGNING_SCHEMES) - + "." - ) - raise exceptions.UnsupportedAlgorithmError(message) - - return valid_signature - - -if __name__ == "__main__": - # The interactive sessions of the documentation strings can - # be tested by running 'ed25519_keys.py' as a standalone module. - # python -B ed25519_keys.py - import doctest - - doctest.testmod() diff --git a/securesystemslib/exceptions.py b/securesystemslib/exceptions.py index 7e4bc892..c1eb8914 100755 --- a/securesystemslib/exceptions.py +++ b/securesystemslib/exceptions.py @@ -22,93 +22,18 @@ class Error(Exception): """Indicate a generic error.""" -class Warning(Warning): # pylint: disable=redefined-builtin - """Generic warning. It is used by the 'warnings' module.""" - - class FormatError(Error): """Indicate an error while validating an object's format.""" -class InvalidMetadataJSONError(FormatError): - """Indicate that a metadata file is not valid JSON.""" - - def __init__(self, exception): # pylint: disable=super-init-not-called - # Store the original exception. - self.exception = exception - - def __str__(self): - # Show the original exception. - return repr(self.exception) - - class UnsupportedAlgorithmError(Error): """Indicate an error while trying to identify a user-specified algorithm.""" -class BadHashError(Error): - """Indicate an error while checking the value a hash object.""" - - def __init__( - self, expected_hash, observed_hash - ): # pylint: disable=super-init-not-called - self.expected_hash = expected_hash - self.observed_hash = observed_hash - - def __str__(self): - return ( - "Observed hash (" - + repr(self.observed_hash) - + ") != expected hash (" - + repr(self.expected_hash) - + ")" - ) - - -class BadPasswordError(Error): - """Indicate an error after encountering an invalid password.""" - - -class CryptoError(Error): - """Indicate any cryptography-related errors.""" - - -class BadSignatureError(CryptoError): - """Indicate that some metadata has a bad signature.""" - - def __init__( - self, metadata_role_name - ): # pylint: disable=super-init-not-called - self.metadata_role_name = metadata_role_name - - def __str__(self): - return repr(self.metadata_role_name) + " metadata has bad signature." - - -class UnknownMethodError(CryptoError): - """Indicate that a user-specified cryptograpthic method is unknown.""" - - class UnsupportedLibraryError(Error): """Indicate that a supported library could not be located or imported.""" -class InvalidNameError(Error): - """Indicate an error while trying to validate any type of named object.""" - - -class NotFoundError(Error): - """If a required configuration or resource is not found.""" - - -class URLMatchesNoPatternError(Error): - """If a URL does not match a user-specified regular expression.""" - - -class InvalidConfigurationError(Error): - """If a configuration object does not match the expected format.""" - - class StorageError(Error): """Indicate an error occured during interaction with an abstracted storage backend.""" @@ -121,11 +46,3 @@ class UnverifiedSignatureError(Error): class VerificationError(UnverifiedSignatureError): """Signature could not be verified because something failed in the process""" - - -class SerializationError(Error): - """Error during serialization.""" - - -class DeserializationError(Error): - """Error during deserialization.""" diff --git a/securesystemslib/hash.py b/securesystemslib/hash.py index a8ae0f52..59aaace4 100755 --- a/securesystemslib/hash.py +++ b/securesystemslib/hash.py @@ -64,8 +64,7 @@ class PycaDiggestWrapper( algorithm: - Specific for `cryptography.hazmat.primitives.hashes.Hash` object, but - needed for `rsa_keys.py` + Specific for `cryptography.hazmat.primitives.hashes.Hash` object. digest_size: Returns original's object digest size. @@ -394,8 +393,7 @@ def digest_from_rsa_scheme(scheme, hash_library=DEFAULT_HASH_LIBRARY): scheme: A string that indicates the signature scheme used to generate - 'signature'. Currently supported RSA schemes are defined in - `securesystemslib.keys.RSA_SIGNATURE_SCHEMES` + 'signature'. hash_library: The crypto library to use for the given hash algorithm (e.g., 'hashlib'). diff --git a/securesystemslib/interface.py b/securesystemslib/interface.py deleted file mode 100644 index 3afb4fdd..00000000 --- a/securesystemslib/interface.py +++ /dev/null @@ -1,1091 +0,0 @@ -""" - - interface.py - - - Vladimir Diaz - - - January 5, 2017. - - - See LICENSE for licensing information. - - - Provide an interface to the cryptography functions available in - securesystemslib. The interface can be used with the Python interpreter in - interactive mode, or imported directly into a Python module. See - 'securesystemslib/README' for the complete guide to using 'interface.py'. -""" - -import getpass -import json -import logging -import os -import sys -import tempfile - -from securesystemslib import ( - KEY_TYPE_ECDSA, - KEY_TYPE_ED25519, - KEY_TYPE_RSA, - exceptions, - formats, - keys, - settings, - util, -) -from securesystemslib.storage import FilesystemBackend - -logger = logging.getLogger(__name__) - -# Recommended RSA key sizes: -# https://en.wikipedia.org/wiki/Key_size#Asymmetric_algorithm_key_lengths -# Based on the above, RSA keys of size 3072 bits are expected to provide -# security through 2031 and beyond. -DEFAULT_RSA_KEY_BITS = 3072 - - -def get_password(prompt="Password: ", confirm=False): - """Prompts user to enter a password. - - Arguments: - prompt (optional): A text displayed on the prompt (stderr). - confirm (optional): A boolean indicating if the user needs to enter the - same password twice. - - Returns: - The password entered on the prompt. - - """ - formats.TEXT_SCHEMA.check_match(prompt) - formats.BOOLEAN_SCHEMA.check_match(confirm) - - while True: - # getpass() prompts the user for a password without echoing - # the user input. - password = getpass.getpass(prompt, sys.stderr) - - if not confirm: - return password - password2 = getpass.getpass("Confirm: ", sys.stderr) - - if password == password2: # pylint: disable=no-else-return - return password - - else: - print("Mismatch; try again.") - - -def _get_key_file_encryption_password(password, prompt, path): - """Encryption password helper for `_generate_and_write_*_keypair` functions. - - Combinations of 'password' and 'prompt' -> result (explanation) - ---------------------------------------------------------------- - None False -> return None (clear non-encryption desire) - "" False -> return password (clear encryption desire) - False -> raise (bad pw type, unclear encryption desire) - True -> raise (unclear password/prompt precedence) - None True -> prompt and return password if entered and None - otherwise (users on the prompt can only - indicate desire to not encrypt by entering no - password) - """ - formats.BOOLEAN_SCHEMA.check_match(prompt) - - # We don't want to decide which takes precedence so we fail - if password is not None and prompt: - raise ValueError("passing 'password' and 'prompt=True' is not allowed") - - # Prompt user for password and confirmation - if prompt: - password = get_password( - "enter password to encrypt private key file " - "'" + str(path) + "' (leave empty if key " - "should not be encrypted): ", - confirm=True, - ) - - # Treat empty password as no password. A user on the prompt can only - # indicate the desire to not encrypt by entering no password. - if not len(password): # pylint: disable=use-implicit-booleaness-not-len - return None - - if password is not None: - formats.PASSWORD_SCHEMA.check_match(password) - - # Fail on empty passed password. A caller should pass None to indicate the - # desire to not encrypt. - if not len(password): # pylint: disable=use-implicit-booleaness-not-len - raise ValueError( - "encryption password must be 1 or more characters long" - ) - - return password - - -def _get_key_file_decryption_password(password, prompt, path): - """Decryption password helper for `import_*_privatekey_from_file` functions. - - Combinations of 'password' and 'prompt' -> result (explanation) - ---------------------------------------------------------------- - None False -> return None (clear non-decryption desire) - "" False -> return password (clear decryption desire) - False -> raise (bad pw type, unclear decryption desire) - True -> raise (unclear password/prompt precedence) - None True -> prompt and return password if entered and None - otherwise (users on the prompt can only indicate - desire to not decrypt by entering no password) - - """ - formats.BOOLEAN_SCHEMA.check_match(prompt) - - # We don't want to decide which takes precedence so we fail - if password is not None and prompt: - raise ValueError("passing 'password' and 'prompt=True' is not allowed") - - # Prompt user for password - if prompt: - password = get_password( - "enter password to decrypt private key file " - "'" + str(path) + "' " - "(leave empty if key not encrypted): ", - confirm=False, - ) - - # Treat empty password as no password. A user on the prompt can only - # indicate the desire to not decrypt by entering no password. - if not len(password): # pylint: disable=use-implicit-booleaness-not-len - return None - - if password is not None: - formats.PASSWORD_SCHEMA.check_match(password) - # No additional vetting needed. Decryption will show if it was correct. - - return password - - -def _generate_and_write_rsa_keypair( - filepath=None, bits=DEFAULT_RSA_KEY_BITS, password=None, prompt=False -): - """Generates RSA key pair and writes PEM-encoded keys to disk. - - If a password is passed or entered on the prompt, the private key is - encrypted. According to the documentation of the used pyca/cryptography - library, encryption is performed "using the best available encryption for a - given key's backend", which "is a curated encryption choice and the algorithm - may change over time." The private key is written in PKCS#1 and the public - key in X.509 SubjectPublicKeyInfo format. - - NOTE: A signing scheme can be assigned on key import (see import functions). - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - bits (optional): The number of bits of the generated RSA key. - password (optional): An encryption password. - prompt (optional): A boolean indicating if the user should be prompted - for an encryption password. If the user enters an empty password, the - key is not encrypted. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: An empty string is passed as 'password', or both a 'password' - is passed and 'prompt' is true. - StorageError: Key files cannot be written. - - Side Effects: - Prompts user for a password if 'prompt' is True. - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - formats.RSAKEYBITS_SCHEMA.check_match(bits) - - # Generate private RSA key and extract public and private both in PEM - rsa_key = keys.generate_rsa_key(bits) - public = rsa_key["keyval"]["public"] - private = rsa_key["keyval"]["private"] - - # Use passed 'filepath' or keyid as file name - if not filepath: - filepath = os.path.join(os.getcwd(), rsa_key["keyid"]) - - formats.PATH_SCHEMA.check_match(filepath) - - password = _get_key_file_encryption_password(password, prompt, filepath) - - # Encrypt the private key if a 'password' was passed or entered on the prompt - if password is not None: - private = keys.create_rsa_encrypted_pem(private, password) - - # Create intermediate directories as required - util.ensure_parent_dir(filepath) - - # Write PEM-encoded public key to .pub - file_object = tempfile.TemporaryFile() - file_object.write(public.encode("utf-8")) - util.persist_temp_file(file_object, filepath + ".pub") - - # Write PEM-encoded private key to - file_object = tempfile.TemporaryFile() - file_object.write(private.encode("utf-8")) - util.persist_temp_file(file_object, filepath, restrict=True) - - return filepath - - -def generate_and_write_rsa_keypair( - password, filepath=None, bits=DEFAULT_RSA_KEY_BITS -): - """Generates RSA key pair and writes PEM-encoded keys to disk. - - The private key is encrypted using the best available encryption algorithm - chosen by 'pyca/cryptography', which may change over time. The private key is - written in PKCS#1 and the public key in X.509 SubjectPublicKeyInfo format. - - NOTE: A signing scheme can be assigned on key import (see import functions). - - Arguments: - password: An encryption password. - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - bits (optional): The number of bits of the generated RSA key. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: An empty string is passed as 'password'. - StorageError: Key files cannot be written. - - Side Effects: - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - formats.PASSWORD_SCHEMA.check_match(password) - return _generate_and_write_rsa_keypair( - filepath=filepath, bits=bits, password=password, prompt=False - ) - - -def generate_and_write_rsa_keypair_with_prompt( - filepath=None, bits=DEFAULT_RSA_KEY_BITS -): - """Generates RSA key pair and writes PEM-encoded keys to disk. - - The private key is encrypted with a password entered on the prompt, using the - best available encryption algorithm chosen by 'pyca/cryptography', which may - change over time. The private key is written in PKCS#1 and the public key in - X.509 SubjectPublicKeyInfo format. - - NOTE: A signing scheme can be assigned on key import (see import functions). - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - bits (optional): The number of bits of the generated RSA key. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key files cannot be written. - - Side Effects: - Prompts user for a password. - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - return _generate_and_write_rsa_keypair( - filepath=filepath, bits=bits, password=None, prompt=True - ) - - -def generate_and_write_unencrypted_rsa_keypair( - filepath=None, bits=DEFAULT_RSA_KEY_BITS -): - """Generates RSA key pair and writes PEM-encoded keys to disk. - - The private key is written in PKCS#1 and the public key in X.509 - SubjectPublicKeyInfo format. - - NOTE: A signing scheme can be assigned on key import (see import functions). - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - bits (optional): The number of bits of the generated RSA key. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key files cannot be written. - - Side Effects: - Writes unencrypted key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - return _generate_and_write_rsa_keypair( - filepath=filepath, bits=bits, password=None, prompt=False - ) - - -def import_rsa_privatekey_from_file( - filepath, - password=None, - scheme="rsassa-pss-sha256", - prompt=False, - storage_backend=None, -): - """Imports PEM-encoded RSA private key from file storage. - - The expected key format is PKCS#1. If a password is passed or entered on the - prompt, the private key is decrypted, otherwise it is treated as unencrypted. - - Arguments: - filepath: The path to read the file from. - password (optional): A password to decrypt the key. - scheme (optional): The signing scheme assigned to the returned key object. - See RSA_SCHEME_SCHEMA for available signing schemes. - prompt (optional): A boolean indicating if the user should be prompted - for a decryption password. If the user enters an empty password, the - key is not decrypted. - storage_backend (optional): An object implementing StorageBackendInterface. - If not passed a default FilesystemBackend will be used. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: Both a 'password' is passed and 'prompt' is true. - StorageError: Key file cannot be read. - CryptoError: Key cannot be parsed. - - Returns: - An RSA private key object conformant with 'RSAKEY_SCHEMA'. - - """ - formats.PATH_SCHEMA.check_match(filepath) - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - password = _get_key_file_decryption_password(password, prompt, filepath) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - with storage_backend.get(filepath) as file_object: - pem_key = file_object.read().decode("utf-8") - - # Optionally decrypt and convert PEM-encoded key to 'RSAKEY_SCHEMA' format - rsa_key = keys.import_rsakey_from_private_pem(pem_key, scheme, password) - - return rsa_key - - -def import_rsa_publickey_from_file( - filepath, scheme="rsassa-pss-sha256", storage_backend=None -): - """Imports PEM-encoded RSA public key from file storage. - - The expected key format is X.509 SubjectPublicKeyInfo. - - Arguments: - filepath: The path to read the file from. - scheme (optional): The signing scheme assigned to the returned key object. - See RSA_SCHEME_SCHEMA for available signing schemes. - storage_backend (optional): An object implementing StorageBackendInterface. - If not passed a default FilesystemBackend will be used. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key file cannot be read. - Error: Public key is malformed. - - Returns: - An RSA public key object conformant with 'RSAKEY_SCHEMA'. - - """ - formats.PATH_SCHEMA.check_match(filepath) - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - with storage_backend.get(filepath) as file_object: - rsa_pubkey_pem = file_object.read().decode("utf-8") - - # Convert PEM-encoded key to 'RSAKEY_SCHEMA' format - try: - rsakey_dict = keys.import_rsakey_from_public_pem(rsa_pubkey_pem, scheme) - - except exceptions.FormatError as e: - raise exceptions.Error( - "Cannot import improperly formatted" " PEM file." + repr(str(e)) - ) - - return rsakey_dict - - -def _generate_and_write_ed25519_keypair( - filepath=None, password=None, prompt=False -): - """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. - - If a password is passed or entered on the prompt, the private key is - encrypted using AES-256 in CTR mode, with the password strengthened in - PBKDF2-HMAC-SHA256. - - NOTE: The custom key format includes 'ed25519' as signing scheme. - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - password (optional): An encryption password. - prompt (optional): A boolean indicating if the user should be prompted - for an encryption password. If the user enters an empty password, the - key is not encrypted. - - Raises: - UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: An empty string is passed as 'password', or both a 'password' - is passed and 'prompt' is true. - StorageError: Key files cannot be written. - - Side Effects: - Prompts user for a password if 'prompt' is True. - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - ed25519_key = keys.generate_ed25519_key() - - # Use passed 'filepath' or keyid as file name - if not filepath: - filepath = os.path.join(os.getcwd(), ed25519_key["keyid"]) - - formats.PATH_SCHEMA.check_match(filepath) - - password = _get_key_file_encryption_password(password, prompt, filepath) - - # Create intermediate directories as required - util.ensure_parent_dir(filepath) - - # Use custom JSON format for ed25519 keys on-disk - keytype = ed25519_key["keytype"] - keyval = ed25519_key["keyval"] - scheme = ed25519_key["scheme"] - ed25519key_metadata_format = keys.format_keyval_to_metadata( - keytype, scheme, keyval, private=False - ) - - # Write public key to .pub - file_object = tempfile.TemporaryFile() - file_object.write(json.dumps(ed25519key_metadata_format).encode("utf-8")) - util.persist_temp_file(file_object, filepath + ".pub") - - # Encrypt private key if we have a password, store as JSON string otherwise - if password is not None: - ed25519_key = keys.encrypt_key(ed25519_key, password) - else: - ed25519_key = json.dumps(ed25519_key) - - # Write private key to - file_object = tempfile.TemporaryFile() - file_object.write(ed25519_key.encode("utf-8")) - util.persist_temp_file(file_object, filepath, restrict=True) - - return filepath - - -def generate_and_write_ed25519_keypair(password, filepath=None): - """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. - - The private key is encrypted using AES-256 in CTR mode, with the passed - password strengthened in PBKDF2-HMAC-SHA256. - - NOTE: The custom key format includes 'ed25519' as signing scheme. - - Arguments: - password: An encryption password. - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - - Raises: - UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: An empty string is passed as 'password'. - StorageError: Key files cannot be written. - - Side Effects: - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - formats.PASSWORD_SCHEMA.check_match(password) - return _generate_and_write_ed25519_keypair( - filepath=filepath, password=password, prompt=False - ) - - -def generate_and_write_ed25519_keypair_with_prompt(filepath=None): - """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. - - The private key is encrypted using AES-256 in CTR mode, with the password - entered on the prompt strengthened in PBKDF2-HMAC-SHA256. - - NOTE: The custom key format includes 'ed25519' as signing scheme. - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - - Raises: - UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key files cannot be written. - - Side Effects: - Prompts user for a password. - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - return _generate_and_write_ed25519_keypair( - filepath=filepath, password=None, prompt=True - ) - - -def generate_and_write_unencrypted_ed25519_keypair(filepath=None): - """Generates ed25519 key pair and writes custom JSON-formatted keys to disk. - - NOTE: The custom key format includes 'ed25519' as signing scheme. - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - - Raises: - UnsupportedLibraryError: pyca/pynacl or pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key files cannot be written. - - Side Effects: - Writes unencrypted key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - return _generate_and_write_ed25519_keypair( - filepath=filepath, password=None, prompt=False - ) - - -def import_ed25519_publickey_from_file(filepath): - """Imports custom JSON-formatted ed25519 public key from disk. - - NOTE: The signing scheme is set at key generation (see generate function). - - Arguments: - filepath: The path to read the file from. - - Raises: - FormatError: Argument is malformed. - StorageError: Key file cannot be read. - Error: Public key is malformed. - - Returns: - An ed25519 public key object conformant with 'ED25519KEY_SCHEMA'. - - """ - formats.PATH_SCHEMA.check_match(filepath) - - # Load custom on-disk JSON formatted key and convert to its custom in-memory - # dict key representation - ed25519_key_metadata = util.load_json_file(filepath) - ed25519_key, _ = keys.format_metadata_to_key(ed25519_key_metadata) - - # Check that the generic loading functions indeed loaded an ed25519 key - if ed25519_key["keytype"] != "ed25519": - message = "Invalid key type loaded: " + repr(ed25519_key["keytype"]) - raise exceptions.FormatError(message) - - return ed25519_key - - -def import_ed25519_privatekey_from_file( - filepath, password=None, prompt=False, storage_backend=None -): - """Imports custom JSON-formatted ed25519 private key from file storage. - - If a password is passed or entered on the prompt, the private key is - decrypted, otherwise it is treated as unencrypted. - - NOTE: The signing scheme is set at key generation (see generate function). - - Arguments: - filepath: The path to read the file from. - password (optional): A password to decrypt the key. - prompt (optional): A boolean indicating if the user should be prompted - for a decryption password. If the user enters an empty password, the - key is not decrypted. - storage_backend (optional): An object implementing StorageBackendInterface. - If not passed a default FilesystemBackend will be used. - - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: Both a 'password' is passed and 'prompt' is true. - StorageError: Key file cannot be read. - Error, CryptoError: Key cannot be parsed. - - - Returns: - An ed25519 private key object conformant with 'ED25519KEY_SCHEMA'. - - """ - formats.PATH_SCHEMA.check_match(filepath) - password = _get_key_file_decryption_password(password, prompt, filepath) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - with storage_backend.get(filepath) as file_object: - json_str = file_object.read() - - # Load custom on-disk JSON formatted key and convert to its custom - # in-memory dict key representation, decrypting it if password is not None - return keys.import_ed25519key_from_private_json( - json_str, password=password - ) - - -def _generate_and_write_ecdsa_keypair( - filepath=None, password=None, prompt=False -): - """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. - - If a password is passed or entered on the prompt, the private key is - encrypted using AES-256 in CTR mode, with the password strengthened in - PBKDF2-HMAC-SHA256. - - NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - password (optional): An encryption password. - prompt (optional): A boolean indicating if the user should be prompted - for an encryption password. If the user enters an empty password, the - key is not encrypted. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: An empty string is passed as 'password', or both a 'password' - is passed and 'prompt' is true. - StorageError: Key files cannot be written. - - Side Effects: - Prompts user for a password if 'prompt' is True. - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - ecdsa_key = keys.generate_ecdsa_key() - - # Use passed 'filepath' or keyid as file name - if not filepath: - filepath = os.path.join(os.getcwd(), ecdsa_key["keyid"]) - - formats.PATH_SCHEMA.check_match(filepath) - - password = _get_key_file_encryption_password(password, prompt, filepath) - - # Create intermediate directories as required - util.ensure_parent_dir(filepath) - - # Use custom JSON format for ecdsa keys on-disk - keytype = ecdsa_key["keytype"] - keyval = ecdsa_key["keyval"] - scheme = ecdsa_key["scheme"] - ecdsakey_metadata_format = keys.format_keyval_to_metadata( - keytype, scheme, keyval, private=False - ) - - # Write public key to .pub - file_object = tempfile.TemporaryFile() - file_object.write(json.dumps(ecdsakey_metadata_format).encode("utf-8")) - util.persist_temp_file(file_object, filepath + ".pub") - - # Encrypt private key if we have a password, store as JSON string otherwise - if password is not None: - ecdsa_key = keys.encrypt_key(ecdsa_key, password) - else: - ecdsa_key = json.dumps(ecdsa_key) - - # Write private key to - file_object = tempfile.TemporaryFile() - file_object.write(ecdsa_key.encode("utf-8")) - util.persist_temp_file(file_object, filepath, restrict=True) - - return filepath - - -def generate_and_write_ecdsa_keypair(password, filepath=None): - """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. - - The private key is encrypted using AES-256 in CTR mode, with the passed - password strengthened in PBKDF2-HMAC-SHA256. - - NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. - - Arguments: - password: An encryption password. - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: An empty string is passed as 'password'. - StorageError: Key files cannot be written. - - Side Effects: - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - formats.PASSWORD_SCHEMA.check_match(password) - return _generate_and_write_ecdsa_keypair( - filepath=filepath, password=password, prompt=False - ) - - -def generate_and_write_ecdsa_keypair_with_prompt(filepath=None): - """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. - - The private key is encrypted using AES-256 in CTR mode, with the password - entered on the prompt strengthened in PBKDF2-HMAC-SHA256. - - NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key files cannot be written. - - Side Effects: - Prompts user for a password. - Writes key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - return _generate_and_write_ecdsa_keypair( - filepath=filepath, password=None, prompt=True - ) - - -def generate_and_write_unencrypted_ecdsa_keypair(filepath=None): - """Generates ecdsa key pair and writes custom JSON-formatted keys to disk. - - NOTE: The custom key format includes 'ecdsa-sha2-nistp256' as signing scheme. - - Arguments: - filepath (optional): The path to write the private key to. If not passed, - the key is written to CWD using the keyid as filename. The public key - is written to the same path as the private key using the suffix '.pub'. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - StorageError: Key files cannot be written. - - Side Effects: - Writes unencrypted key files to disk. - Overwrites files if they already exist. - - Returns: - The private key filepath. - - """ - return _generate_and_write_ecdsa_keypair( - filepath=filepath, password=None, prompt=False - ) - - -def import_ecdsa_publickey_from_file(filepath): - """Imports custom JSON-formatted ecdsa public key from disk. - - NOTE: The signing scheme is set at key generation (see generate function). - - Arguments: - filepath: The path to read the file from. - - Raises: - FormatError: Argument is malformed. - StorageError: Key file cannot be read. - Error: Public key is malformed. - - Returns: - An ecdsa public key object conformant with 'ECDSAKEY_SCHEMA'. - - """ - formats.PATH_SCHEMA.check_match(filepath) - - # Load custom on-disk JSON formatted key and convert to its custom in-memory - # dict key representation - ecdsa_key_metadata = util.load_json_file(filepath) - ecdsa_key, _ = keys.format_metadata_to_key(ecdsa_key_metadata) - - return ecdsa_key - - -def import_ecdsa_privatekey_from_file( - filepath, password=None, prompt=False, storage_backend=None -): - """Imports custom JSON-formatted ecdsa private key from file storage. - - If a password is passed or entered on the prompt, the private key is - decrypted, otherwise it is treated as unencrypted. - - NOTE: The signing scheme is set at key generation (see generate function). - - Arguments: - filepath: The path to read the file from. - password (optional): A password to decrypt the key. - prompt (optional): A boolean indicating if the user should be prompted - for a decryption password. If the user enters an empty password, the - key is not decrypted. - storage_backend (optional): An object implementing StorageBackendInterface. - If not passed a default FilesystemBackend will be used. - - Raises: - UnsupportedLibraryError: pyca/cryptography is not available. - FormatError: Arguments are malformed. - ValueError: Both a 'password' is passed and 'prompt' is true. - StorageError: Key file cannot be read. - Error, CryptoError: Key cannot be parsed. - - Returns: - An ecdsa private key object conformant with 'ED25519KEY_SCHEMA'. - - """ - formats.PATH_SCHEMA.check_match(filepath) - - password = _get_key_file_decryption_password(password, prompt, filepath) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - with storage_backend.get(filepath) as file_object: - key_data = file_object.read().decode("utf-8") - - # Decrypt private key if we have a password, directly load JSON otherwise - if password is not None: - key_object = keys.decrypt_key(key_data, password) - else: - key_object = util.load_json_string(key_data) - - # Raise an exception if an unexpected key type is imported. - # NOTE: we support keytype's of ecdsa-sha2-nistp256 and ecdsa-sha2-nistp384 - # in order to support key files generated with older versions of - # securesystemslib. At some point this backwards compatibility should be - # removed. - if key_object["keytype"] not in [ - "ecdsa", - "ecdsa-sha2-nistp256", - "ecdsa-sha2-nistp384", - ]: - message = "Invalid key type loaded: " + repr(key_object["keytype"]) - raise exceptions.FormatError(message) - - # Add "keyid_hash_algorithms" so that equal ecdsa keys with different keyids - # can be associated using supported keyid_hash_algorithms. - key_object["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return key_object - - -def import_publickeys_from_file(filepaths, key_types=None): - """Imports multiple public keys from files. - - NOTE: The default signing scheme 'rsassa-pss-sha256' is assigned to RSA keys. - Use 'import_rsa_publickey_from_file' to specify any other than the default - signing scheme for an RSA key. ed25519 and ecdsa keys have the signing scheme - included in the custom key format (see generate functions). - - Arguments: - filepaths: A list of paths to public key files. - key_types (optional): A list of types of keys to be imported associated - with filepaths by index. Must be one of KEY_TYPE_RSA, KEY_TYPE_ED25519 - or KEY_TYPE_ECDSA. If not specified, all keys are assumed to be - KEY_TYPE_RSA. - - Raises: - TypeError: filepaths or 'key_types' (if passed) is not iterable. - FormatError: Argument are malformed, or 'key_types' is passed and does not - have the same length as 'filepaths' or contains an unsupported type. - UnsupportedLibraryError: pyca/cryptography is not available. - StorageError: Key file cannot be read. - Error: Public key is malformed. - - Returns: - A dict of public keys in KEYDICT_SCHEMA format. - - """ - if key_types is None: - key_types = [KEY_TYPE_RSA] * len(filepaths) - - if len(key_types) != len(filepaths): - raise exceptions.FormatError( - "Pass equal amount of 'filepaths' (got {}) and 'key_types (got {}), " # pylint: disable=consider-using-f-string - "or no 'key_types' at all to default to '{}'.".format( - len(filepaths), len(key_types), KEY_TYPE_RSA - ) - ) - - key_dict = {} - for idx, filepath in enumerate(filepaths): - if key_types[idx] == KEY_TYPE_ED25519: - key = import_ed25519_publickey_from_file(filepath) - - elif key_types[idx] == KEY_TYPE_RSA: - key = import_rsa_publickey_from_file(filepath) - - elif key_types[idx] == KEY_TYPE_ECDSA: - key = import_ecdsa_publickey_from_file(filepath) - - else: - raise exceptions.FormatError( - "Unsupported key type '{}'. Must be '{}', '{}' or '{}'.".format( # pylint: disable=consider-using-f-string - key_types[idx], - KEY_TYPE_RSA, - KEY_TYPE_ED25519, - KEY_TYPE_ECDSA, - ) - ) - - key_dict[key["keyid"]] = key - - return key_dict - - -def import_privatekey_from_file( - filepath, key_type=None, password=None, prompt=False -): - """Imports private key from file. - - If a password is passed or entered on the prompt, the private key is - decrypted, otherwise it is treated as unencrypted. - - NOTE: The default signing scheme 'rsassa-pss-sha256' is assigned to RSA keys. - Use 'import_rsa_privatekey_from_file' to specify any other than the default - signing scheme for an RSA key. ed25519 and ecdsa keys have the signing scheme - included in the custom key format (see generate functions). - - Arguments: - filepath: The path to read the file from. - key_type (optional): One of KEY_TYPE_RSA, KEY_TYPE_ED25519 or - KEY_TYPE_ECDSA. Default is KEY_TYPE_RSA. - password (optional): A password to decrypt the key. - prompt (optional): A boolean indicating if the user should be prompted - for a decryption password. If the user enters an empty password, the - key is not decrypted. - - Raises: - FormatError: Arguments are malformed or 'key_type' is not supported. - ValueError: Both a 'password' is passed and 'prompt' is true. - UnsupportedLibraryError: pyca/cryptography is not available. - StorageError: Key file cannot be read. - Error, CryptoError: Key cannot be parsed. - - Returns: - A private key object conformant with one of 'ED25519KEY_SCHEMA', - 'RSAKEY_SCHEMA' or 'ECDSAKEY_SCHEMA'. - - """ - if key_type is None: - key_type = KEY_TYPE_RSA - - if key_type == KEY_TYPE_ED25519: # pylint: disable=no-else-return - return import_ed25519_privatekey_from_file( - filepath, password=password, prompt=prompt - ) - - elif key_type == KEY_TYPE_RSA: - return import_rsa_privatekey_from_file( - filepath, password=password, prompt=prompt - ) - - elif key_type == KEY_TYPE_ECDSA: - return import_ecdsa_privatekey_from_file( - filepath, password=password, prompt=prompt - ) - - else: - raise exceptions.FormatError( - "Unsupported key type '{}'. Must be '{}', '{}' or '{}'.".format( # pylint: disable=consider-using-f-string - key_type, KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA - ) - ) - - -if __name__ == "__main__": - # The interactive sessions of the documentation strings can - # be tested by running interface.py as a standalone module: - # $ python interface.py. - import doctest - - doctest.testmod() diff --git a/securesystemslib/keys.py b/securesystemslib/keys.py deleted file mode 100755 index 1c6f9243..00000000 --- a/securesystemslib/keys.py +++ /dev/null @@ -1,1862 +0,0 @@ -""" - - keys.py - - - Vladimir Diaz - - - October 4, 2013. - - - See LICENSE for licensing information. - - - The goal of this module is to centralize cryptographic key routines and their - supported operations (e.g., creating and verifying signatures). This module - is designed to support multiple public-key algorithms, such as RSA, Ed25519, - and ECDSA, and multiple cryptography libraries. Which cryptography library - to use is determined by the default, or user modified, values set in - 'settings.py' - - https://en.wikipedia.org/wiki/RSA_(algorithm) - http://ed25519.cr.yp.to/ - https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm - - The (RSA, ECDSA and Ed25519)-related functions provided include - generate_rsa_key(), generate_ed25519_key(), generate_ecdsa_key(), - create_signature(), and verify_signature(). The cryptography libraries - called by 'securesystemslib.keys.py' generate the actual keys and the - functions listed above can be viewed as the easy-to-use public interface. - - Additional functions contained here include format_keyval_to_metadata() and - format_metadata_to_key(). These last two functions produce or use keys - compatible with the key structures listed in Metadata files. The key - generation functions return a dictionary containing all the information needed - of keys, such as public & private keys, and a keyID. create_signature() - and verify_signature() are supplemental functions needed for generating - signatures and verifying them. - - Key IDs are used as identifiers for keys (e.g., RSA key). They are the - hexadecimal representation of the hash of the key object (specifically, the - key object containing only the public key). Review the '_get_keyid()' - function of this module to see precisely how keyids are generated. One may - get the keyid of a key object by simply accessing the dictionary's 'keyid' - key (i.e., rsakey['keyid']). - """ - -# Required for hexadecimal conversions. Signatures and public/private keys are -# hexlified. -import binascii -import logging - -from securesystemslib import ( - ecdsa_keys, - ed25519_keys, - exceptions, - formats, - rsa_keys, - settings, - util, -) -from securesystemslib.hash import digest - -# The hash algorithm to use in the generation of keyids. -_KEY_ID_HASH_ALGORITHM = "sha256" - -# Recommended RSA key sizes: -# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 -# According to the document above, revised May 6, 2003, RSA keys of -# size 3072 provide security through 2031 and beyond. -_DEFAULT_RSA_KEY_BITS = 3072 - - -RSA_SIGNATURE_SCHEMES = [ - "rsassa-pss-sha224", - "rsassa-pss-sha256", - "rsassa-pss-sha384", - "rsassa-pss-sha512", - "rsa-pkcs1v15-sha224", - "rsa-pkcs1v15-sha256", - "rsa-pkcs1v15-sha384", - "rsa-pkcs1v15-sha512", -] - -logger = logging.getLogger(__name__) - - -def generate_rsa_key(bits=_DEFAULT_RSA_KEY_BITS, scheme="rsassa-pss-sha256"): - """ - - Generate public and private RSA keys, with modulus length 'bits'. In - addition, a keyid identifier for the RSA key is generated. The object - returned conforms to 'securesystemslib.formats.RSAKEY_SCHEMA' and has the - form: - - {'keytype': 'rsa', - 'scheme': 'rsassa-pss-sha256', - 'keyid': keyid, - 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', - 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} - - The public and private keys are strings in PEM format. - - Although the PyCA cryptography library and/or its crypto backend might set - a minimum key size, generate() enforces a minimum key size of 2048 bits. - If 'bits' is unspecified, a 3072-bit RSA key is generated, which is the key - size recommended by securesystemslib. These key size restrictions are only - enforced for keys generated within securesystemslib. RSA keys with sizes - lower than what we recommended may still be imported (e.g., with - import_rsakey_from_pem(). - - >>> rsa_key = generate_rsa_key(bits=2048) - >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) - True - - >>> public = rsa_key['keyval']['public'] - >>> private = rsa_key['keyval']['private'] - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public) - True - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private) - True - - - bits: - The key size, or key length, of the RSA key. 'bits' must be 2048, or - greater, and a multiple of 256. - - scheme: - The signature scheme used by the key. It must be one from the list - `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. - - - securesystemslib.exceptions.FormatError, if 'bits' is improperly or invalid - (i.e., not an integer and not at least 2048). - - ValueError, if an exception occurs after calling the RSA key generation - routine. The 'ValueError' exception is raised by the key generation - function of the cryptography library called. - - - None. - - - A dictionary containing the RSA keys and other identifying information. - Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. - """ - - # Does 'bits' have the correct format? This check will ensure 'bits' - # conforms to 'securesystemslib.formats.RSAKEYBITS_SCHEMA'. 'bits' must be - # an integer object, with a minimum value of 2048. Raise - # 'securesystemslib.exceptions.FormatError' if the check fails. - formats.RSAKEYBITS_SCHEMA.check_match(bits) - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - # Begin building the RSA key dictionary. - rsakey_dict = {} - keytype = "rsa" - public = None - private = None - - # Generate the public and private RSA keys. The pyca/cryptography module is - # used to generate the actual key. Raise 'ValueError' if 'bits' is less than - # 1024, although a 2048-bit minimum is enforced by - # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). - public, private = rsa_keys.generate_rsa_public_and_private(bits) - - # When loading in PEM keys, extract_pem() is called, which strips any - # leading or trailing new line characters. Do the same here before generating - # the keyid. - public = extract_pem(public, private_pem=False) - private = extract_pem(private, private_pem=True) - - # Generate the keyid of the RSA key. Note: The private key material is not - # included in the generation of the 'keyid' identifier. Convert any '\r\n' - # (e.g., Windows) newline characters to '\n' so that a consistent keyid is - # generated. - key_value = {"public": public.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - # Build the 'rsakey_dict' dictionary. Update 'key_value' with the RSA - # private key prior to adding 'key_value' to 'rsakey_dict'. - key_value["private"] = private - - rsakey_dict["keytype"] = keytype - rsakey_dict["scheme"] = scheme - rsakey_dict["keyid"] = keyid - rsakey_dict["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - rsakey_dict["keyval"] = key_value - - return rsakey_dict - - -def generate_ecdsa_key(scheme="ecdsa-sha2-nistp256"): - """ - - Generate public and private ECDSA keys, with NIST P-256 + SHA256 (for - hashing) being the default scheme. In addition, a keyid identifier for the - ECDSA key is generated. The object returned conforms to - 'securesystemslib.formats.ECDSAKEY_SCHEMA' and has the form: - - {'keytype': 'ecdsa', - 'scheme', 'ecdsa-sha2-nistp256', - 'keyid': keyid, - 'keyval': {'public': '', - 'private': ''}} - - The public and private keys are strings in TODO format. - - >>> ecdsa_key = generate_ecdsa_key(scheme='ecdsa-sha2-nistp256') - >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) - True - - - scheme: - The ECDSA signature scheme. By default, ECDSA NIST P-256 is used, with - SHA256 for hashing. - - - securesystemslib.exceptions.FormatError, if 'scheme' is improperly - formatted or invalid (i.e., not one of the supported ECDSA signature - schemes). - - - None. - - - A dictionary containing the ECDSA keys and other identifying information. - Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. - """ - - # Does 'scheme' have the correct format? - # This check will ensure 'scheme' is properly formatted and is a supported - # ECDSA signature scheme. Raise 'securesystemslib.exceptions.FormatError' if - # the check fails. - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - - # Begin building the ECDSA key dictionary. - ecdsa_key = {} - keytype = "ecdsa" - public = None - private = None - - # Generate the public and private ECDSA keys with one of the supported - # libraries. - public, private = ecdsa_keys.generate_public_and_private(scheme) - - # Generate the keyid of the Ed25519 key. 'key_value' corresponds to the - # 'keyval' entry of the 'Ed25519KEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a - # consistent keyid is generated. - key_value = {"public": public.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - # Build the 'ed25519_key' dictionary. Update 'key_value' with the Ed25519 - # private key prior to adding 'key_value' to 'ed25519_key'. - - key_value["private"] = private - - ecdsa_key["keytype"] = keytype - ecdsa_key["scheme"] = scheme - ecdsa_key["keyid"] = keyid - ecdsa_key["keyval"] = key_value - - # Add "keyid_hash_algorithms" so that equal ECDSA keys with different keyids - # can be associated using supported keyid_hash_algorithms. - ecdsa_key["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return ecdsa_key - - -def generate_ed25519_key(scheme="ed25519"): - """ - - Generate public and private ED25519 keys, both of length 32-bytes, although - they are hexlified to 64 bytes. In addition, a keyid identifier generated - for the returned ED25519 object. The object returned conforms to - 'securesystemslib.formats.ED25519KEY_SCHEMA' and has the form: - - {'keytype': 'ed25519', - 'scheme': 'ed25519', - 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', - 'keyval': {'public': '9ccf3f02b17f82febf5dd3bab878b767d8408...', - 'private': 'ab310eae0e229a0eceee3947b6e0205dfab3...'}} - - >>> ed25519_key = generate_ed25519_key() - >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(ed25519_key) - True - >>> len(ed25519_key['keyval']['public']) - 64 - >>> len(ed25519_key['keyval']['private']) - 64 - - - scheme: - The signature scheme used by the generated Ed25519 key. - - - None. - - - The ED25519 keys are generated by calling either the optimized pure Python - implementation of ed25519, or the ed25519 routines provided by 'pynacl'. - - - A dictionary containing the ED25519 keys and other identifying information. - Conforms to 'securesystemslib.formats.ED25519KEY_SCHEMA'. - """ - - # Are the arguments properly formatted? If not, raise an - # 'securesystemslib.exceptions.FormatError' exceptions. - formats.ED25519_SIG_SCHEMA.check_match(scheme) - - # Begin building the Ed25519 key dictionary. - ed25519_key = {} - keytype = "ed25519" - public = None - private = None - - # Generate the public and private Ed25519 key with the 'pynacl' library. - # Unlike in the verification of Ed25519 signatures, do not fall back to the - # optimized, pure python implementation provided by PyCA. Ed25519 should - # always be generated with a backend like libsodium to prevent side-channel - # attacks. - public, private = ed25519_keys.generate_public_and_private() - - # Generate the keyid of the Ed25519 key. 'key_value' corresponds to the - # 'keyval' entry of the 'Ed25519KEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - key_value = {"public": binascii.hexlify(public).decode(), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - # Build the 'ed25519_key' dictionary. Update 'key_value' with the Ed25519 - # private key prior to adding 'key_value' to 'ed25519_key'. - key_value["private"] = binascii.hexlify(private).decode() - - ed25519_key["keytype"] = keytype - ed25519_key["scheme"] = scheme - ed25519_key["keyid"] = keyid - ed25519_key["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - ed25519_key["keyval"] = key_value - - return ed25519_key - - -def format_keyval_to_metadata(keytype, scheme, key_value, private=False): - """ - - Return a dictionary conformant to 'securesystemslib.formats.KEY_SCHEMA'. - If 'private' is True, include the private key. The dictionary - returned has the form: - - {'keytype': keytype, - 'scheme' : scheme, - 'keyval': {'public': '...', - 'private': '...'}} - - or if 'private' is False: - - {'keytype': keytype, - 'scheme': scheme, - 'keyval': {'public': '...', - 'private': ''}} - - >>> ed25519_key = generate_ed25519_key() - >>> key_val = ed25519_key['keyval'] - >>> keytype = ed25519_key['keytype'] - >>> scheme = ed25519_key['scheme'] - >>> ed25519_metadata = \ - format_keyval_to_metadata(keytype, scheme, key_val, private=True) - >>> securesystemslib.formats.KEY_SCHEMA.matches(ed25519_metadata) - True - - - key_type: - The 'rsa' or 'ed25519' strings. - - scheme: - The signature scheme used by the key. - - key_value: - A dictionary containing a private and public keys. - 'key_value' is of the form: - - {'public': '...', - 'private': '...'}}, - - conformant to 'securesystemslib.formats.KEYVAL_SCHEMA'. - - private: - Indicates if the private key should be included in the dictionary - returned. - - - securesystemslib.exceptions.FormatError, if 'key_value' does not conform to - 'securesystemslib.formats.KEYVAL_SCHEMA', or if the private key is not - present in 'key_value' if requested by the caller via 'private'. - - - None. - - - A 'securesystemslib.formats.KEY_SCHEMA' dictionary. - """ - - # Does 'keytype' have the correct format? - # This check will ensure 'keytype' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.KEYTYPE_SCHEMA.check_match(keytype) - - # Does 'scheme' have the correct format? - formats.SCHEME_SCHEMA.check_match(scheme) - - # Does 'key_value' have the correct format? - formats.KEYVAL_SCHEMA.check_match(key_value) - - if private is True: - # If the caller requests (via the 'private' argument) to include a private - # key in the returned dictionary, ensure the private key is actually - # present in 'key_val' (a private key is optional for 'KEYVAL_SCHEMA' - # dicts). - if "private" not in key_value: - raise exceptions.FormatError( - "The required private key is missing from: " + repr(key_value) - ) - - return {"keytype": keytype, "scheme": scheme, "keyval": key_value} - - public_key_value = {"public": key_value["public"]} - - return { - "keytype": keytype, - "scheme": scheme, - "keyid_hash_algorithms": settings.HASH_ALGORITHMS, - "keyval": public_key_value, - } - - -def format_metadata_to_key( - key_metadata, default_keyid=None, keyid_hash_algorithms=None -): - """ - - Construct a key dictionary (e.g., securesystemslib.formats.RSAKEY_SCHEMA) - according to the keytype of 'key_metadata'. The dict returned by this - function has the exact format as the dict returned by one of the key - generations functions, like generate_ed25519_key(). The dict returned - has the form: - - {'keytype': keytype, - 'scheme': scheme, - 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', - 'keyval': {'public': '...', - 'private': '...'}} - - For example, RSA key dictionaries in RSAKEY_SCHEMA format should be used by - modules storing a collection of keys, such as with keydb.py. RSA keys as - stored in metadata files use a different format, so this function should be - called if an RSA key is extracted from one of these metadata files and need - converting. The key generation functions create an entirely new key and - return it in the format appropriate for 'keydb.py'. - - >>> ed25519_key = generate_ed25519_key() - >>> key_val = ed25519_key['keyval'] - >>> keytype = ed25519_key['keytype'] - >>> scheme = ed25519_key['scheme'] - >>> ed25519_metadata = \ - format_keyval_to_metadata(keytype, scheme, key_val, private=True) - >>> ed25519_key_2, junk = format_metadata_to_key(ed25519_metadata) - >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(ed25519_key_2) - True - >>> ed25519_key == ed25519_key_2 - True - - - key_metadata: - The key dictionary as stored in Metadata files, conforming to - 'securesystemslib.formats.KEY_SCHEMA'. It has the form: - - {'keytype': '...', - 'scheme': scheme, - 'keyval': {'public': '...', - 'private': '...'}} - default_keyid: - A default keyid associated with the key metadata. If this is not - provided, the keyid will be calculated by _get_keyid using the default - hash algorithm. If provided, the default keyid can be any string. - - keyid_hash_algorithms: - An optional list of hash algorithms to use when generating keyids. - Defaults to securesystemslib.settings.HASH_ALGORITHMS. - - - securesystemslib.exceptions.FormatError, if 'key_metadata' does not conform - to 'securesystemslib.formats.KEY_SCHEMA'. - - - None. - - - In the case of an RSA key, a dictionary conformant to - 'securesystemslib.formats.RSAKEY_SCHEMA'. - """ - - # Does 'key_metadata' have the correct format? - # This check will ensure 'key_metadata' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.KEY_SCHEMA.check_match(key_metadata) - - # Construct the dictionary to be returned. - key_dict = {} - keytype = key_metadata["keytype"] - scheme = key_metadata["scheme"] - key_value = key_metadata["keyval"] - - # Convert 'key_value' to 'securesystemslib.formats.KEY_SCHEMA' and generate - # its hash The hash is in hexdigest form. - if default_keyid is None: - default_keyid = _get_keyid(keytype, scheme, key_value) - keyids = set() - keyids.add(default_keyid) - - if keyid_hash_algorithms is None: - keyid_hash_algorithms = settings.HASH_ALGORITHMS - - for hash_algorithm in keyid_hash_algorithms: - keyid = _get_keyid(keytype, scheme, key_value, hash_algorithm) - keyids.add(keyid) - - # All the required key values gathered. Build 'key_dict'. - # 'keyid_hash_algorithms' - key_dict["keytype"] = keytype - key_dict["scheme"] = scheme - key_dict["keyid"] = default_keyid - key_dict["keyid_hash_algorithms"] = keyid_hash_algorithms - key_dict["keyval"] = key_value - - return key_dict, keyids - - -def _get_keyid(keytype, scheme, key_value, hash_algorithm="sha256"): - """Return the keyid of 'key_value'.""" - - # 'keyid' will be generated from an object conformant to KEY_SCHEMA, - # which is the format Metadata files (e.g., root.json) store keys. - # 'format_keyval_to_metadata()' returns the object needed by _get_keyid(). - key_meta = format_keyval_to_metadata( - keytype, scheme, key_value, private=False - ) - - # Convert the key to JSON Canonical format, suitable for adding - # to digest objects. - key_update_data = formats.encode_canonical(key_meta) - - # Create a digest object and call update(), using the JSON - # canonical format of 'rskey_meta' as the update data. - digest_object = digest(hash_algorithm) - digest_object.update(key_update_data.encode("utf-8")) - - # 'keyid' becomes the hexadecimal representation of the hash. - keyid = digest_object.hexdigest() - - return keyid - - -def create_signature(key_dict, data): - """ - - Return a signature dictionary of the form: - {'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', - 'sig': '...'}. - - The signing process will use the private key in - key_dict['keyval']['private'] and 'data' to generate the signature. - - The following signature schemes are supported: - - 'RSASSA-PSS' - RFC3447 - RSASSA-PSS - http://www.ietf.org/rfc/rfc3447. - - 'ed25519' - ed25519 - high-speed high security signatures - http://ed25519.cr.yp.to/ - - Which signature to generate is determined by the key type of 'key_dict' - and the available cryptography library specified in 'settings'. - - >>> ed25519_key = generate_ed25519_key() - >>> data = 'The quick brown fox jumps over the lazy dog' - >>> signature = create_signature(ed25519_key, data) - >>> securesystemslib.formats.SIGNATURE_SCHEMA.matches(signature) - True - >>> len(signature['sig']) - 128 - >>> rsa_key = generate_rsa_key(2048) - >>> signature = create_signature(rsa_key, data) - >>> securesystemslib.formats.SIGNATURE_SCHEMA.matches(signature) - True - >>> ecdsa_key = generate_ecdsa_key() - >>> signature = create_signature(ecdsa_key, data) - >>> securesystemslib.formats.SIGNATURE_SCHEMA.matches(signature) - True - - - key_dict: - A dictionary containing the keys. An example RSA key dict has the - form: - - {'keytype': 'rsa', - 'scheme': 'rsassa-pss-sha256', - 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', - 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', - 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} - - The public and private keys are strings in PEM format. - - data: - Data to be signed. This should be a bytes object; data should be - encoded/serialized before it is passed here. The same value can be be - passed into securesystemslib.verify_signature() (along with the public - key) to later verify the signature. - - - securesystemslib.exceptions.FormatError, if 'key_dict' is improperly - formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'key_dict' - specifies an unsupported key type or signing scheme. - - securesystemslib.exceptions.CryptoError, if the signature cannot be - generated. - - TypeError, if 'key_dict' contains an invalid keytype. - - - The cryptography library specified in 'settings' is called to perform the - actual signing routine. - - - A signature dictionary conformant to - 'securesystemslib_format.SIGNATURE_SCHEMA'. - """ - - # Does 'key_dict' have the correct format? - # This check will ensure 'key_dict' has the appropriate number of objects - # and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - # The key type of 'key_dict' must be either 'rsa' or 'ed25519'. - formats.ANYKEY_SCHEMA.check_match(key_dict) - - # Signing the 'data' object requires a private key. Signing schemes that are - # currently supported are: 'ed25519', 'ecdsa-sha2-nistp256', - # 'ecdsa-sha2-nistp384' and rsa schemes defined in - # `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. - # RSASSA-PSS and RSA-PKCS1v15 keys and signatures can be generated and - # verified by rsa_keys.py, and Ed25519 keys by PyNaCl and PyCA's - # optimized, pure python implementation of Ed25519. - signature = {} - keytype = key_dict["keytype"] - scheme = key_dict["scheme"] - public = key_dict["keyval"]["public"] - private = key_dict["keyval"]["private"] - keyid = key_dict["keyid"] - sig = None - - if keytype == "rsa": - if scheme in RSA_SIGNATURE_SCHEMES: - private = private.replace("\r\n", "\n") - sig, scheme = rsa_keys.create_rsa_signature(private, data, scheme) - - else: - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " RSA signature scheme specified: " + repr(scheme) - ) - - elif keytype == "ed25519": - public = binascii.unhexlify(public.encode("utf-8")) - private = binascii.unhexlify(private.encode("utf-8")) - sig, scheme = ed25519_keys.create_signature( - public, private, data, scheme - ) - - # Continue to support keytypes of ecdsa-sha2-nistp256 and ecdsa-sha2-nistp384 - # for backwards compatibility with older securesystemslib releases - elif keytype in ["ecdsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]: - sig, scheme = ecdsa_keys.create_signature(public, private, data, scheme) - - # 'securesystemslib.formats.ANYKEY_SCHEMA' should have detected invalid key - # types. This is a defensive check against an invalid key type. - else: # pragma: no cover - raise TypeError("Invalid key type.") - - # Build the signature dictionary to be returned. - # The hexadecimal representation of 'sig' is stored in the signature. - signature["keyid"] = keyid - signature["sig"] = binascii.hexlify(sig).decode() - - return signature - - -def verify_signature( - key_dict, signature, data -): # pylint: disable=too-many-branches - """ - - Determine whether the private key belonging to 'key_dict' produced - 'signature'. verify_signature() will use the public key found in - 'key_dict', the 'sig' objects contained in 'signature', and 'data' to - complete the verification. - - >>> ed25519_key = generate_ed25519_key() - >>> data = 'The quick brown fox jumps over the lazy dog' - >>> signature = create_signature(ed25519_key, data) - >>> verify_signature(ed25519_key, signature, data) - True - >>> verify_signature(ed25519_key, signature, 'bad_data') - False - >>> rsa_key = generate_rsa_key() - >>> signature = create_signature(rsa_key, data) - >>> verify_signature(rsa_key, signature, data) - True - >>> verify_signature(rsa_key, signature, 'bad_data') - False - >>> ecdsa_key = generate_ecdsa_key() - >>> signature = create_signature(ecdsa_key, data) - >>> verify_signature(ecdsa_key, signature, data) - True - >>> verify_signature(ecdsa_key, signature, 'bad_data') - False - - - key_dict: - A dictionary containing the keys and other identifying information. - If 'key_dict' is an RSA key, it has the form: - - {'keytype': 'rsa', - 'scheme': 'rsassa-pss-sha256', - 'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', - 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', - 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} - - The public and private keys are strings in PEM format. - - signature: - The signature dictionary produced by one of the key generation functions. - 'signature' has the form: - - {'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...', - 'sig': sig}. - - Conformant to 'securesystemslib.formats.SIGNATURE_SCHEMA'. - - data: - Data that the signature is expected to be over. This should be a bytes - object; data should be encoded/serialized before it is passed here.) - This is the same value that can be passed into - securesystemslib.create_signature() in order to create the signature. - - - securesystemslib.exceptions.FormatError, raised if either 'key_dict' or - 'signature' are improperly formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'key_dict' or - 'signature' specifies an unsupported algorithm. - - securesystemslib.exceptions.CryptoError, if the KEYID in the given - 'key_dict' does not match the KEYID in 'signature'. - - - The cryptography library specified in 'settings' called to do the actual - verification. - - - Boolean. True if the signature is valid, False otherwise. - """ - - # Does 'key_dict' have the correct format? - # This check will ensure 'key_dict' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ANYKEY_SCHEMA.check_match(key_dict) - - # Does 'signature' have the correct format? - formats.SIGNATURE_SCHEMA.check_match(signature) - - # Verify that the KEYID in 'key_dict' matches the KEYID listed in the - # 'signature'. - if key_dict["keyid"] != signature["keyid"]: - raise exceptions.CryptoError( - "The KEYID (" - " " + repr(key_dict["keyid"]) + " ) in the given key does not match" - " the KEYID ( " + repr(signature["keyid"]) + " ) in the signature." - ) - - logger.debug("The KEYIDs of key_dict and the signature match.") - - # Using the public key belonging to 'key_dict' - # (i.e., rsakey_dict['keyval']['public']), verify whether 'signature' - # was produced by key_dict's corresponding private key - # key_dict['keyval']['private']. - sig = signature["sig"] - sig = binascii.unhexlify(sig.encode("utf-8")) - public = key_dict["keyval"]["public"] - keytype = key_dict["keytype"] - scheme = key_dict["scheme"] - valid_signature = False - - if keytype == "rsa": - if scheme in RSA_SIGNATURE_SCHEMES: - valid_signature = rsa_keys.verify_rsa_signature( - sig, scheme, public, data - ) - - else: - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " signature scheme is specified: " + repr(scheme) - ) - - elif keytype == "ed25519": - if scheme == "ed25519": - try: - public = binascii.unhexlify(public.encode("utf-8")) - except binascii.Error as e: - raise exceptions.FormatError( - f"Failed to parse key {public} as hex" - ) from e - valid_signature = ed25519_keys.verify_signature( - public, scheme, sig, data - ) - - else: - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " signature scheme is specified: " + repr(scheme) - ) - - elif keytype in ["ecdsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]: - if scheme in ["ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]: - valid_signature = ecdsa_keys.verify_signature( - public, scheme, sig, data - ) - - else: - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " signature scheme is specified: " + repr(scheme) - ) - - # 'securesystemslib.formats.ANYKEY_SCHEMA' should have detected invalid key - # types. This is a defensive check against an invalid key type. - else: # pragma: no cover - raise TypeError("Unsupported key type.") - - return valid_signature - - -def import_rsakey_from_private_pem( - pem, scheme="rsassa-pss-sha256", password=None -): - """ - - Import the private RSA key stored in 'pem', and generate its public key - (which will also be included in the returned rsakey object). In addition, - a keyid identifier for the RSA key is generated. The object returned - conforms to 'securesystemslib.formats.RSAKEY_SCHEMA' and has the form: - - {'keytype': 'rsa', - 'scheme': 'rsassa-pss-sha256', - 'keyid': keyid, - 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', - 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} - - The private key is a string in PEM format. - - >>> rsa_key = generate_rsa_key() - >>> scheme = rsa_key['scheme'] - >>> private = rsa_key['keyval']['private'] - >>> passphrase = 'secret' - >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) - >>> rsa_key2 = import_rsakey_from_private_pem(encrypted_pem, scheme, passphrase) - >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) - True - >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key2) - True - - - pem: - A string in PEM format. The private key is extracted and returned in - an rsakey object. - - scheme: - The signature scheme used by the imported key. - - password: (optional) - The password, or passphrase, to decrypt the private part of the RSA key - if it is encrypted. 'password' is not used directly as the encryption - key, a stronger encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'pem' specifies - an unsupported key type. - - - None. - - - A dictionary containing the RSA keys and other identifying information. - Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. - """ - - # Does 'pem' have the correct format? - # This check will ensure 'pem' conforms to - # 'securesystemslib.formats.PEMRSA_SCHEMA'. - formats.PEMRSA_SCHEMA.check_match(pem) - - # Is 'scheme' properly formatted? - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - if password is not None: - formats.PASSWORD_SCHEMA.check_match(password) - - else: - logger.debug( - "The password/passphrase is unset. The PEM is expected" - " to be unencrypted." - ) - - # Begin building the RSA key dictionary. - rsakey_dict = {} - keytype = "rsa" - public = None - private = None - - # Generate the public and private RSA keys. The pyca/cryptography library - # performs the actual crypto operations. - public, private = rsa_keys.create_rsa_public_and_private_from_pem( - pem, password - ) - - public = extract_pem(public, private_pem=False) - private = extract_pem(private, private_pem=True) - - # Generate the keyid of the RSA key. 'key_value' corresponds to the - # 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a - # consistent keyid is generated. - key_value = {"public": public.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - # Build the 'rsakey_dict' dictionary. Update 'key_value' with the RSA - # private key prior to adding 'key_value' to 'rsakey_dict'. - key_value["private"] = private - - rsakey_dict["keytype"] = keytype - rsakey_dict["scheme"] = scheme - rsakey_dict["keyid"] = keyid - rsakey_dict["keyval"] = key_value - - return rsakey_dict - - -def import_rsakey_from_public_pem(pem, scheme="rsassa-pss-sha256"): - """ - - Generate an RSA key object from 'pem'. In addition, a keyid identifier for - the RSA key is generated. The object returned conforms to - 'securesystemslib.formats.RSAKEY_SCHEMA' and has the form: - - {'keytype': 'rsa', - 'keyid': keyid, - 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ...', - 'private': ''}} - - The public portion of the RSA key is a string in PEM format. - - >>> rsa_key = generate_rsa_key() - >>> public = rsa_key['keyval']['public'] - >>> rsa_key['keyval']['private'] = '' - >>> rsa_key2 = import_rsakey_from_public_pem(public) - >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key) - True - >>> securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key2) - True - - - pem: - A string in PEM format (it should contain a public RSA key). - - - securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. - - - Only the public portion of the PEM is extracted. Leading or trailing - whitespace is not included in the PEM string stored in the rsakey object - returned. - - - A dictionary containing the RSA keys and other identifying information. - Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. - """ - - # Does 'pem' have the correct format? - # This check will ensure arguments has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(pem) - - # Does 'scheme' have the correct format? - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - # Ensure the PEM string has a public header and footer. Although a simple - # validation of 'pem' is performed here, a fully valid PEM string is needed - # later to successfully verify signatures. Performing stricter validation of - # PEMs are left to the external libraries that use 'pem'. - - if is_pem_public(pem): - public_pem = extract_pem(pem, private_pem=False) - - else: - raise exceptions.FormatError("Invalid public pem: " + repr(pem)) - - # Begin building the RSA key dictionary. - rsakey_dict = {} - keytype = "rsa" - - # Generate the keyid of the RSA key. 'key_value' corresponds to the - # 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a - # consistent keyid is generated. - key_value = {"public": public_pem.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - rsakey_dict["keytype"] = keytype - rsakey_dict["scheme"] = scheme - rsakey_dict["keyid"] = keyid - rsakey_dict["keyval"] = key_value - - # Add "keyid_hash_algorithms" so that equal RSA keys with different keyids - # can be associated using supported keyid_hash_algorithms. - rsakey_dict["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return rsakey_dict - - -def import_rsakey_from_pem(pem, scheme="rsassa-pss-sha256"): - """ - - Import either a public or private PEM. In contrast to the other explicit - import functions (import_rsakey_from_public_pem and - import_rsakey_from_private_pem), this function is useful for when it is not - known whether 'pem' is private or public. - - - pem: - A string in PEM format. - - scheme: - The signature scheme used by the imported key. - - - securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. - - - None. - - - A dictionary containing the RSA keys and other identifying information. - Conforms to 'securesystemslib.formats.RSAKEY_SCHEMA'. - """ - - # Does 'pem' have the correct format? - # This check will ensure arguments has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(pem) - - # Is 'scheme' properly formatted? - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - public_pem = "" - - # Ensure the PEM string has a public or private header and footer. Although - # a simple validation of 'pem' is performed here, a fully valid PEM string is - # needed later to successfully verify signatures. Performing stricter - # validation of PEMs are left to the external libraries that use 'pem'. - if is_pem_public(pem): - public_pem = extract_pem(pem, private_pem=False) - - elif is_pem_private(pem): - # Return an rsakey object (RSAKEY_SCHEMA) with the private key included. - return import_rsakey_from_private_pem(pem, scheme, password=None) - - else: - raise exceptions.FormatError( - "PEM contains neither a" " public nor private key: " + repr(pem) - ) - - # Begin building the RSA key dictionary. - rsakey_dict = {} - keytype = "rsa" - - # Generate the keyid of the RSA key. 'key_value' corresponds to the 'keyval' - # entry of the 'RSAKEY_SCHEMA' dictionary. The private key information is - # not included in the generation of the 'keyid' identifier. If a PEM is - # found to contain a private key, the generated rsakey object should be - # returned above. The following key object is for the case of a PEM with - # only a public key. Convert any '\r\n' (e.g., Windows) newline characters - # to '\n' so that a consistent keyid is generated. - key_value = {"public": public_pem.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - rsakey_dict["keytype"] = keytype - rsakey_dict["scheme"] = scheme - rsakey_dict["keyid"] = keyid - rsakey_dict["keyval"] = key_value - - # Add "keyid_hash_algorithms" so that equal RSA keys with - # different keyids can be associated using supported keyid_hash_algorithms. - rsakey_dict["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return rsakey_dict - - -def extract_pem(pem, private_pem=False): - """ - - Extract only the portion of the pem that includes the header and footer, - with any leading and trailing characters removed. The string returned has - the following form: - - '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----' - - or - - '-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----' - - Note: This function assumes "pem" is a valid pem in the following format: - pem header + key material + key footer. Crypto libraries (e.g., pyca's - cryptography) that parse the pem returned by this function are expected to - fully validate the pem. - - - pem: - A string in PEM format. - - private_pem: - Boolean that indicates whether 'pem' is a private PEM. Private PEMs - are not shown in exception messages. - - - securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. - - - Only the public and private portion of the PEM is extracted. Leading or - trailing whitespace is not included in the returned PEM string. - - - A PEM string (excluding leading and trailing newline characters). - That is: pem header + key material + pem footer. - - """ - - if private_pem: - pem_header = "-----BEGIN RSA PRIVATE KEY-----" - pem_footer = "-----END RSA PRIVATE KEY-----" - - else: - pem_header = "-----BEGIN PUBLIC KEY-----" - pem_footer = "-----END PUBLIC KEY-----" - - header_start = 0 - footer_start = 0 - - # Raise error message if the expected header or footer is not found in 'pem'. - try: - header_start = pem.index(pem_header) - - except ValueError: - # Be careful not to print private key material in exception message. - if not private_pem: - raise exceptions.FormatError( # pylint: disable=raise-missing-from - "Required PEM header " - + repr(pem_header) - + "\n not found in PEM string: " - + repr(pem) - ) - - raise exceptions.FormatError( # pylint: disable=raise-missing-from - "Required PEM header " - + repr(pem_header) - + "\n not found in private PEM string." - ) - - try: - # Search for 'pem_footer' after the PEM header. - footer_start = pem.index(pem_footer, header_start + len(pem_header)) - - except ValueError: - # Be careful not to print private key material in exception message. - if not private_pem: - raise exceptions.FormatError( # pylint: disable=raise-missing-from - "Required PEM footer " - + repr(pem_footer) - + "\n not found in PEM string " - + repr(pem) - ) - - raise exceptions.FormatError( # pylint: disable=raise-missing-from - "Required PEM footer " - + repr(pem_footer) - + "\n not found in private PEM string." - ) - - # Extract only the public portion of 'pem'. Leading or trailing whitespace - # is excluded. - pem = pem[header_start : footer_start + len(pem_footer)] - - return pem - - -def encrypt_key(key_object, password): - """ - - Return a string containing 'key_object' in encrypted form. Encrypted - strings may be safely saved to a file. The corresponding decrypt_key() - function can be applied to the encrypted string to restore the original key - object. 'key_object' is a key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). - This function relies on the rsa_keys.py module to perform the - actual encryption. - - Encrypted keys use AES-256-CTR-Mode, and passwords are strengthened with - PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in - 'securesystemslib.settings.PBKDF2_ITERATIONS' by the user). - - http://en.wikipedia.org/wiki/Advanced_Encryption_Standard - http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 - https://en.wikipedia.org/wiki/PBKDF2 - - >>> ed25519_key = generate_ed25519_key() - >>> password = 'secret' - >>> encrypted_key = encrypt_key(ed25519_key, password).encode('utf-8') - >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key) - True - - - key_object: - A key (containing also the private key portion) of the form - 'securesystemslib.formats.ANYKEY_SCHEMA' - - password: - The password, or passphrase, to encrypt the private part of the RSA - key. 'password' is not used directly as the encryption key, a stronger - encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if 'key_object' cannot be - encrypted. - - - None. - - - An encrypted string of the form: - 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. - """ - - # Does 'key_object' have the correct format? - # This check will ensure 'key_object' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ANYKEY_SCHEMA.check_match(key_object) - - # Does 'password' have the correct format? - formats.PASSWORD_SCHEMA.check_match(password) - - # Encrypted string of 'key_object'. The encrypted string may be safely saved - # to a file and stored offline. - encrypted_key = None - - # Generate an encrypted string of 'key_object' using AES-256-CTR-Mode, where - # 'password' is strengthened with PBKDF2-HMAC-SHA256. - encrypted_key = rsa_keys.encrypt_key(key_object, password) - - return encrypted_key - - -def decrypt_key(encrypted_key, passphrase): - """ - - Return a string containing 'encrypted_key' in non-encrypted form. The - decrypt_key() function can be applied to the encrypted string to restore - the original key object, a key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). - This function calls rsa_keys.py to perform the actual decryption. - - Encrypted keys use AES-256-CTR-Mode and passwords are strengthened with - PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in - 'settings.py' by the user). - - http://en.wikipedia.org/wiki/Advanced_Encryption_Standard - http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 - https://en.wikipedia.org/wiki/PBKDF2 - - >>> ed25519_key = generate_ed25519_key() - >>> password = 'secret' - >>> encrypted_key = encrypt_key(ed25519_key, password) - >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), password) - >>> securesystemslib.formats.ANYKEY_SCHEMA.matches(decrypted_key) - True - >>> decrypted_key == ed25519_key - True - - - encrypted_key: - An encrypted key (additional data is also included, such as salt, number - of password iterations used for the derived encryption key, etc) of the - form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. 'encrypted_key' - should have been generated with encrypt_key(). - - password: - The password, or passphrase, to decrypt 'encrypted_key'. 'password' is - not used directly as the encryption key, a stronger encryption key is - derived from it. The supported general-purpose module takes care of - re-deriving the encryption key. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if 'encrypted_key' cannot be - decrypted. - - - None. - - - A key object of the form: 'securesystemslib.formats.ANYKEY_SCHEMA' (e.g., - RSAKEY_SCHEMA, ED25519KEY_SCHEMA). - """ - - # Does 'encrypted_key' have the correct format? - # This check ensures 'encrypted_key' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) - - # Does 'passphrase' have the correct format? - formats.PASSWORD_SCHEMA.check_match(passphrase) - - # Store and return the decrypted key object. - key_object = None - - # Decrypt 'encrypted_key' so that the original key object is restored. - # encrypt_key() generates an encrypted string of the key object using - # AES-256-CTR-Mode, where 'password' is strengthened with PBKDF2-HMAC-SHA256. - key_object = rsa_keys.decrypt_key(encrypted_key, passphrase) - - # The corresponding encrypt_key() encrypts and stores key objects in - # non-metadata format (i.e., original format of key object argument to - # encrypt_key()) prior to returning. - - return key_object - - -def create_rsa_encrypted_pem(private_key, passphrase): - """ - - Return a string in PEM format (TraditionalOpenSSL), where the private part - of the RSA key is encrypted using the best available encryption for a given - key's backend. This is a curated (by cryptography.io) encryption choice and - the algorithm may change over time. - - c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ - #cryptography.hazmat.primitives.serialization.BestAvailableEncryption - - >>> rsa_key = generate_rsa_key() - >>> private = rsa_key['keyval']['private'] - >>> passphrase = 'secret' - >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) - True - - - private_key: - The private key string in PEM format. - - passphrase: - The passphrase, or password, to encrypt the private part of the RSA key. - 'passphrase' is not used directly as the encryption key, a stronger - encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if an RSA key in encrypted PEM - format cannot be created. - - TypeError, 'private_key' is unset. - - - None. - - - A string in PEM format, where the private RSA key is encrypted. - Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'. - """ - - # Does 'private_key' have the correct format? - # This check will ensure 'private_key' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(private_key) - - # Does 'passphrase' have the correct format? - formats.PASSWORD_SCHEMA.check_match(passphrase) - - encrypted_pem = None - - # Generate the public and private RSA keys. A 2048-bit minimum is enforced by - # create_rsa_encrypted_pem() via a - # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). - encrypted_pem = rsa_keys.create_rsa_encrypted_pem(private_key, passphrase) - - return encrypted_pem - - -def is_pem_public(pem): - """ - - Checks if a passed PEM formatted string is a PUBLIC key, by looking for the - following pattern: - - '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----' - - >>> rsa_key = generate_rsa_key() - >>> public = rsa_key['keyval']['public'] - >>> private = rsa_key['keyval']['private'] - >>> is_pem_public(public) - True - >>> is_pem_public(private) - False - - - pem: - A string in PEM format. - - - securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. - - - None - - - True if 'pem' is public and false otherwise. - """ - - # Do the arguments have the correct format? - # This check will ensure arguments have the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(pem) - - pem_header = "-----BEGIN PUBLIC KEY-----" - pem_footer = "-----END PUBLIC KEY-----" - - try: - header_start = pem.index(pem_header) - pem.index(pem_footer, header_start + len(pem_header)) - - except ValueError: - return False - - return True - - -def is_pem_private(pem, keytype="rsa"): - """ - - Checks if a passed PEM formatted string is a PRIVATE key, by looking for - the following patterns: - - '-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----' - '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----' - - >>> rsa_key = generate_rsa_key() - >>> private = rsa_key['keyval']['private'] - >>> public = rsa_key['keyval']['public'] - >>> is_pem_private(private) - True - >>> is_pem_private(public) - False - - - pem: - A string in PEM format. - - - securesystemslib.exceptions.FormatError, if any of the arguments are - improperly formatted. - - - None - - - True if 'pem' is private and false otherwise. - """ - - # Do the arguments have the correct format? - # This check will ensure arguments have the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(pem) - formats.NAME_SCHEMA.check_match(keytype) - - if keytype == "rsa": - pem_header = "-----BEGIN RSA PRIVATE KEY-----" - pem_footer = "-----END RSA PRIVATE KEY-----" - - elif keytype == "ec": - pem_header = "-----BEGIN EC PRIVATE KEY-----" - pem_footer = "-----END EC PRIVATE KEY-----" - - else: - raise exceptions.FormatError( - "Unsupported key" - " type: " + repr(keytype) + '. Supported keytypes: ["rsa", "ec"]' - ) - - try: - header_start = pem.index(pem_header) - pem.index(pem_footer, header_start + len(pem_header)) - - except ValueError: - return False - - return True - - -def import_ed25519key_from_private_json( - json_str, password=None -): # pylint: disable=missing-function-docstring - if password is not None: - # This check will not fail, because a mal-formatted passed password fails - # above and an entered password will always be a string (see get_password) - # However, we include it in case PASSWORD_SCHEMA or get_password changes. - formats.PASSWORD_SCHEMA.check_match(password) - - # Decrypt the loaded key file, calling the 'cryptography' library to - # generate the derived encryption key from 'password'. Raise - # 'securesystemslib.exceptions.CryptoError' if the decryption fails. - key_object = decrypt_key(json_str.decode("utf-8"), password) - - else: - logger.debug( - "No password was given. Attempting to import an" - " unencrypted file." - ) - try: - key_object = util.load_json_string(json_str.decode("utf-8")) - # If the JSON could not be decoded, it is very likely, but not necessarily, - # due to a non-empty password. - except exceptions.Error: - raise exceptions.CryptoError( # pylint: disable=raise-missing-from - "Malformed Ed25519 key JSON, " - "possibly due to encryption, " - "but no password provided?" - ) - - # Raise an exception if an unexpected key type is imported. - if key_object["keytype"] != "ed25519": - message = "Invalid key type loaded: " + repr(key_object["keytype"]) - raise exceptions.FormatError(message) - - # Add "keyid_hash_algorithms" so that equal ed25519 keys with - # different keyids can be associated using supported keyid_hash_algorithms. - key_object["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return key_object - - -def import_ecdsakey_from_private_pem( - pem, scheme="ecdsa-sha2-nistp256", password=None -): - """ - - Import the private ECDSA key stored in 'pem', and generate its public key - (which will also be included in the returned ECDSA key object). In addition, - a keyid identifier for the ECDSA key is generated. The object returned - conforms to: - - {'keytype': 'ecdsa', - 'scheme': 'ecdsa-sha2-nistp256', - 'keyid': keyid, - 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----', - 'private': '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----'}} - - The private key is a string in PEM format. - - >>> ecdsa_key = generate_ecdsa_key() - >>> private_pem = ecdsa_key['keyval']['private'] - >>> ecdsa_key = import_ecdsakey_from_private_pem(private_pem) - >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) - True - - - pem: - A string in PEM format. The private key is extracted and returned in - an ecdsakey object. - - scheme: - The signature scheme used by the imported key. - - password: (optional) - The password, or passphrase, to decrypt the private part of the ECDSA - key if it is encrypted. 'password' is not used directly as the encryption - key, a stronger encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if 'pem' specifies - an unsupported key type. - - - None. - - - A dictionary containing the ECDSA keys and other identifying information. - Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. - """ - - # Does 'pem' have the correct format? - # This check will ensure 'pem' conforms to - # 'securesystemslib.formats.ECDSARSA_SCHEMA'. - formats.PEMECDSA_SCHEMA.check_match(pem) - - # Is 'scheme' properly formatted? - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - - if password is not None: - formats.PASSWORD_SCHEMA.check_match(password) - - else: - logger.debug( - "The password/passphrase is unset. The PEM is expected" - " to be unencrypted." - ) - - # Begin building the ECDSA key dictionary. - ecdsakey_dict = {} - keytype = "ecdsa" - public = None - private = None - - public, private = ecdsa_keys.create_ecdsa_public_and_private_from_pem( - pem, password - ) - - # Generate the keyid of the ECDSA key. 'key_value' corresponds to the - # 'keyval' entry of the 'ECDSAKEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a - # consistent keyid is generated. - key_value = {"public": public.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - # Build the 'ecdsakey_dict' dictionary. Update 'key_value' with the ECDSA - # private key prior to adding 'key_value' to 'ecdsakey_dict'. - key_value["private"] = private - - ecdsakey_dict["keytype"] = keytype - ecdsakey_dict["scheme"] = scheme - ecdsakey_dict["keyid"] = keyid - ecdsakey_dict["keyval"] = key_value - - # Add "keyid_hash_algorithms" so equal ECDSA keys with - # different keyids can be associated using supported keyid_hash_algorithms - ecdsakey_dict["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return ecdsakey_dict - - -def import_ecdsakey_from_public_pem(pem, scheme="ecdsa-sha2-nistp256"): - """ - - Generate an ECDSA key object from 'pem'. In addition, a keyid identifier - for the ECDSA key is generated. The object returned conforms to - 'securesystemslib.formats.ECDSAKEY_SCHEMA' and has the form: - - {'keytype': 'ecdsa', - 'scheme': 'ecdsa-sha2-nistp256', - 'keyid': keyid, - 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ...', - 'private': ''}} - - The public portion of the ECDSA key is a string in PEM format. - - >>> ecdsa_key = generate_ecdsa_key() - >>> public = ecdsa_key['keyval']['public'] - >>> ecdsa_key['keyval']['private'] = '' - >>> scheme = ecdsa_key['scheme'] - >>> ecdsa_key2 = import_ecdsakey_from_public_pem(public, scheme) - >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) - True - >>> securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key2) - True - - - pem: - A string in PEM format (it should contain a public ECDSA key). - - scheme: - The signature scheme used by the imported key. - - - securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. - - - Only the public portion of the PEM is extracted. Leading or trailing - whitespace is not included in the PEM string stored in the rsakey object - returned. - - - A dictionary containing the ECDSA keys and other identifying information. - Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. - """ - - # Does 'pem' have the correct format? - # This check will ensure arguments has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMECDSA_SCHEMA.check_match(pem) - - # Is 'scheme' properly formatted? - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - - # Ensure the PEM string has a public header and footer. Although a simple - # validation of 'pem' is performed here, a fully valid PEM string is needed - # later to successfully verify signatures. Performing stricter validation of - # PEMs are left to the external libraries that use 'pem'. - - if is_pem_public(pem): - public_pem = extract_pem(pem, private_pem=False) - - else: - raise exceptions.FormatError("Invalid public" " pem: " + repr(pem)) - - # Begin building the ECDSA key dictionary. - ecdsakey_dict = {} - keytype = "ecdsa" - - # Generate the keyid of the ECDSA key. 'key_value' corresponds to the - # 'keyval' entry of the 'ECDSAKEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - # Convert any '\r\n' (e.g., Windows) newline characters to '\n' so that a - # consistent keyid is generated. - key_value = {"public": public_pem.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - ecdsakey_dict["keytype"] = keytype - ecdsakey_dict["scheme"] = scheme - ecdsakey_dict["keyid"] = keyid - ecdsakey_dict["keyval"] = key_value - - # Add "keyid_hash_algorithms" so that equal ECDSA keys with different keyids - # can be associated using supported keyid_hash_algorithms. - ecdsakey_dict["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS - - return ecdsakey_dict - - -def import_ecdsakey_from_pem(pem, scheme="ecdsa-sha2-nistp256"): - """ - - Import either a public or private ECDSA PEM. In contrast to the other - explicit import functions (import_ecdsakey_from_public_pem and - import_ecdsakey_from_private_pem), this function is useful for when it is - not known whether 'pem' is private or public. - - - pem: - A string in PEM format. - - scheme: - The signature scheme used by the imported key. - - securesystemslib.exceptions.FormatError, if 'pem' is improperly formatted. - - - None. - - - A dictionary containing the ECDSA keys and other identifying information. - Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'. - """ - - # Does 'pem' have the correct format? - # This check will ensure arguments has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMECDSA_SCHEMA.check_match(pem) - - # Is 'scheme' properly formatted? - formats.ECDSA_SCHEME_SCHEMA.check_match(scheme) - - public_pem = "" - - # Ensure the PEM string has a public or private header and footer. Although - # a simple validation of 'pem' is performed here, a fully valid PEM string is - # needed later to successfully verify signatures. Performing stricter - # validation of PEMs are left to the external libraries that use 'pem'. - if is_pem_public(pem): - public_pem = extract_pem(pem, private_pem=False) - - elif is_pem_private(pem, "ec"): - # Return an ecdsakey object (ECDSAKEY_SCHEMA) with the private key included. - return import_ecdsakey_from_private_pem(pem, password=None) - - else: - raise exceptions.FormatError( - "PEM contains neither a public" " nor private key: " + repr(pem) - ) - - # Begin building the ECDSA key dictionary. - ecdsakey_dict = {} - keytype = "ecdsa" - - # Generate the keyid of the ECDSA key. 'key_value' corresponds to the - # 'keyval' entry of the 'ECDSAKEY_SCHEMA' dictionary. The private key - # information is not included in the generation of the 'keyid' identifier. - # If a PEM is found to contain a private key, the generated rsakey object - # should be returned above. The following key object is for the case of a - # PEM with only a public key. Convert any '\r\n' (e.g., Windows) newline - # characters to '\n' so that a consistent keyid is generated. - key_value = {"public": public_pem.replace("\r\n", "\n"), "private": ""} - keyid = _get_keyid(keytype, scheme, key_value) - - ecdsakey_dict["keytype"] = keytype - ecdsakey_dict["scheme"] = scheme - ecdsakey_dict["keyid"] = keyid - ecdsakey_dict["keyval"] = key_value - - return ecdsakey_dict - - -if __name__ == "__main__": - # The interactive sessions of the documentation strings can - # be tested by running 'keys.py' as a standalone module: - # $ python keys.py - import doctest - - doctest.testmod() diff --git a/securesystemslib/rsa_keys.py b/securesystemslib/rsa_keys.py deleted file mode 100755 index 94c1a678..00000000 --- a/securesystemslib/rsa_keys.py +++ /dev/null @@ -1,1102 +0,0 @@ -""" - - rsa_keys.py - - - Vladimir Diaz - - - June 3, 2015. - - - See LICENSE for licensing information. - - - The goal of this module is to support public-key and general-purpose - cryptography through the pyca/cryptography (available as 'cryptography' on - pypi) library. - - The RSA-related functions provided include: - generate_rsa_public_and_private() - create_rsa_signature() - verify_rsa_signature() - create_rsa_encrypted_pem() - create_rsa_public_and_private_from_pem() - - The general-purpose functions include: - encrypt_key() - decrypt_key() - - pyca/cryptography performs the actual cryptographic operations and the - functions listed above can be viewed as the easy-to-use public interface. - - https://pypi.python.org/pypi/cryptography/ - https://github.com/pyca/cryptography - - https://en.wikipedia.org/wiki/RSA_(algorithm) - https://en.wikipedia.org/wiki/Advanced_Encryption_Standard - https://en.wikipedia.org/wiki/PBKDF - http://en.wikipedia.org/wiki/Scrypt - - securesystemslib key files are encrypted with the AES-256-CTR-Mode symmetric - key algorithm. User passwords are strengthened with PBKDF2, currently set to - 100,000 passphrase iterations. The previous evpy implementation used 1,000 - iterations. - - PEM-encrypted RSA key files use the Triple Data Encryption Algorithm (3DES), - and Cipher-block chaining (CBC) for the mode of operation. Password-Based - Key Derivation Function 1 (PBKF1) + MD5. - """ - -import binascii -import json -import os - -CRYPTO = True -NO_CRYPTO_MSG = "RSA key support requires the cryptography library" -try: - # Import pyca/cryptography routines needed to generate and load cryptographic - # keys in PEM format. - # Import Exception classes need to catch pyca/cryptography exceptions. - from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm - from cryptography.hazmat.backends import default_backend - - # pyca/cryptography requires hash objects to generate PKCS#1 PSS - # signatures (i.e., padding.PSS). The 'hmac' module is needed to verify - # ciphertexts in encrypted key files. - from cryptography.hazmat.primitives import hashes, hmac, serialization - - # RSA's probabilistic signature scheme with appendix (RSASSA-PSS). - # PKCS#1 v1.5 is available for compatibility with existing applications, but - # RSASSA-PSS is encouraged for newer applications. RSASSA-PSS generates - # a random salt to ensure the signature generated is probabilistic rather than - # deterministic (e.g., PKCS#1 v1.5). - # http://en.wikipedia.org/wiki/RSA-PSS#Schemes - # https://tools.ietf.org/html/rfc3447#section-8.1 - # The 'padding' module is needed for PSS signatures. - # 'cryptography.hazmat.primitives.asymmetric' (i.e., pyca/cryptography's - # public-key cryptography modules) supports algorithms like the Digital - # Signature Algorithm (DSA) and the ECDSA (Elliptic Curve Digital Signature - # Algorithm) encryption system. The 'rsa' module module is needed here to - # generate RSA keys and PS - from cryptography.hazmat.primitives.asymmetric import padding, rsa - - # The mode of operation is presently set to CTR (CounTeR Mode) for symmetric - # block encryption (AES-256, where the symmetric key is 256 bits). 'modes' can - # be used as an argument to 'ciphers.Cipher' to specify the mode of operation - # for the block cipher. The initial random block, or initialization vector - # (IV), can be set to begin the process of incrementing the 128-bit blocks and - # allowing the AES algorithm to perform cipher block operations on them. - # pyca/cryptography's AES implementation available in 'ciphers.Cipher. and - # 'ciphers.algorithms'. AES is a symmetric key algorithm that operates on - # fixed block sizes of 128-bits. - # https://en.wikipedia.org/wiki/Advanced_Encryption_Standard - from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - - # Import pyca/cryptography's Key Derivation Function (KDF) module. - # 'securesystemslib.keys.py' needs this module to derive a secret key according - # to the Password-Based Key Derivation Function 2 specification. The derived - # key is used as the symmetric key to encrypt securesystemslib key information. - # PKCS#5 v2.0 PBKDF2 specification: http://tools.ietf.org/html/rfc2898#section-5.2 - from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - from cryptography.hazmat.primitives.serialization import ( - load_pem_private_key, - ) -except ImportError: - CRYPTO = False - -from securesystemslib import ( # pylint: disable=wrong-import-position - exceptions, - formats, - settings, - util, -) -from securesystemslib.hash import ( # pylint: disable=wrong-import-position - digest_from_rsa_scheme, -) - -# Recommended RSA key sizes: -# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 -# According to the document above, revised May 6, 2003, RSA keys of size 3072 -# provide security through 2031 and beyond. -_DEFAULT_RSA_KEY_BITS = 3072 - -# The delimiter symbol used to separate the different sections of encrypted -# files (i.e., salt, iterations, hmac, IV, ciphertext). This delimiter is -# arbitrarily chosen and should not occur in the hexadecimal representations of -# the fields it is separating. -_ENCRYPTION_DELIMITER = "@@@@" - -# AES key size. Default key size = 32 bytes = AES-256. -_AES_KEY_SIZE = 32 - -# Default salt size, in bytes. A 128-bit salt (i.e., a random sequence of data -# to protect against attacks that use precomputed rainbow tables to crack -# password hashes) is generated for PBKDF2. -_SALT_SIZE = 16 - -# Default PBKDF2 passphrase iterations. The current "good enough" number -# of passphrase iterations. We recommend that important keys, such as root, -# be kept offline. 'settings.PBKDF2_ITERATIONS' should increase as CPU -# speeds increase, set here at 100,000 iterations by default (in 2013). -# Repository maintainers may opt to modify the default setting according to -# their security needs and computational restrictions. A strong user password -# is still important. Modifying the number of iterations will result in a new -# derived key+PBDKF2 combination if the key is loaded and re-saved, overriding -# any previous iteration setting used by the old '.key'. -# https://en.wikipedia.org/wiki/PBKDF2 -_PBKDF2_ITERATIONS = settings.PBKDF2_ITERATIONS - - -def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS): - """ - - Generate public and private RSA keys with modulus length 'bits'. The - public and private keys returned conform to - 'securesystemslib.formats.PEMRSA_SCHEMA' and have the form: - - '-----BEGIN RSA PUBLIC KEY----- ...' - - or - - '-----BEGIN RSA PRIVATE KEY----- ...' - - The public and private keys are returned as strings in PEM format. - - 'generate_rsa_public_and_private()' enforces a minimum key size of 2048 - bits. If 'bits' is unspecified, a 3072-bit RSA key is generated, which is - the key size recommended by TUF. - - >>> public, private = generate_rsa_public_and_private(2048) - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public) - True - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private) - True - - - bits: - The key size, or key length, of the RSA key. 'bits' must be 2048, or - greater. 'bits' defaults to 3072 if not specified. - - - securesystemslib.exceptions.FormatError, if 'bits' does not contain the - correct format. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - The RSA keys are generated from pyca/cryptography's - rsa.generate_private_key() function. - - - A (public, private) tuple containing the RSA keys in PEM format. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does 'bits' have the correct format? - # This check will ensure 'bits' conforms to - # 'securesystemslib.formats.RSAKEYBITS_SCHEMA'. 'bits' must be an integer - # object, with a minimum value of 2048. Raise - # 'securesystemslib.exceptions.FormatError' if the check fails. - formats.RSAKEYBITS_SCHEMA.check_match(bits) - - # Generate the public and private RSA keys. The pyca/cryptography 'rsa' - # module performs the actual key generation. The 'bits' argument is used, - # and a 2048-bit minimum is enforced by - # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). - private_key = rsa.generate_private_key( - public_exponent=65537, key_size=bits, backend=default_backend() - ) - - # Extract the public & private halves of the RSA key and generate their - # PEM-formatted representations. Return the key pair as a (public, private) - # tuple, where each RSA is a string in PEM format. - private_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ).strip() - - # Need to generate the public pem from the private key before serialization - # to PEM. - public_key = private_key.public_key() - public_pem = public_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ).strip() - - return public_pem.decode("utf-8"), private_pem.decode("utf-8") - - -def create_rsa_signature(private_key, data, scheme="rsassa-pss-sha256"): - """ - - Generate a 'scheme' signature. The signature, and the signature scheme - used, is returned as a (signature, scheme) tuple. - - The signing process will use 'private_key' to generate the signature of - 'data'. - - RFC3447 - RSASSA-PSS - http://www.ietf.org/rfc/rfc3447.txt - - >>> public, private = generate_rsa_public_and_private(2048) - >>> data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8') - >>> scheme = 'rsassa-pss-sha256' - >>> signature, scheme = create_rsa_signature(private, data, scheme) - >>> securesystemslib.formats.NAME_SCHEMA.matches(scheme) - True - >>> scheme == 'rsassa-pss-sha256' - True - >>> securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.matches(signature) - True - - - private_key: - The private RSA key, a string in PEM format. - - data: - Data (string) used by create_rsa_signature() to generate the signature. - - scheme: - The signature scheme used to generate the signature. - - - securesystemslib.exceptions.FormatError, if 'private_key' is improperly - formatted. - - ValueError, if 'private_key' is unset. - - securesystemslib.exceptions.CryptoError, if the signature cannot be - generated. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the - signature. - - - A (signature, scheme) tuple, where the signature is a string and the scheme - is one of the supported RSA signature schemes. For example: - 'rsassa-pss-sha256'. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does the arguments have the correct format? - # If not, raise 'securesystemslib.exceptions.FormatError' if any of the - # checks fail. - formats.PEMRSA_SCHEMA.check_match(private_key) - formats.DATA_SCHEMA.check_match(data) - formats.RSA_SCHEME_SCHEMA.check_match(scheme) - - # Signing 'data' requires a private key. Currently supported RSA signature - # schemes are defined in `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. - signature = None - - # Verify the signature, but only if the private key has been set. The - # private key is a NULL string if unset. Although it may be clearer to - # explicitly check that 'private_key' is not '', we can/should check for a - # value and not compare identities with the 'is' keyword. Up to this point - # 'private_key' has variable size and can be an empty string. - if not len(private_key): # pylint: disable=use-implicit-booleaness-not-len - raise ValueError("The required private key is unset.") - - try: - # 'private_key' (in PEM format) must first be converted to a - # pyca/cryptography private key object before a signature can be - # generated. - private_key_object = load_pem_private_key( - private_key.encode("utf-8"), - password=None, - backend=default_backend(), - ) - - digest_obj = digest_from_rsa_scheme(scheme, "pyca_crypto") - - if scheme.startswith("rsassa-pss"): - # Generate an RSSA-PSS signature. Raise - # 'securesystemslib.exceptions.CryptoError' for any of the expected - # exceptions raised by pyca/cryptography. - signature = private_key_object.sign( - data, - padding.PSS( - mgf=padding.MGF1(digest_obj.algorithm), - salt_length=padding.PSS.DIGEST_LENGTH, - ), - digest_obj.algorithm, - ) - - elif scheme.startswith("rsa-pkcs1v15"): - # Generate an RSA-PKCS1v15 signature. Raise - # 'securesystemslib.exceptions.CryptoError' for any of the expected - # exceptions raised by pyca/cryptography. - signature = private_key_object.sign( - data, padding.PKCS1v15(), digest_obj.algorithm - ) - - # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'. - # This is a defensive check check.. - else: # pragma: no cover - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" " signature scheme is specified: " + repr(scheme) - ) - - # If the PEM data could not be decrypted, or if its structure could not - # be decoded successfully. - except ValueError: - raise exceptions.CryptoError( # pylint: disable=raise-missing-from - "The private key" - " (in PEM format) could not be deserialized." # pylint: disable=implicit-str-concat - ) - - # 'TypeError' is raised if a password was given and the private key was - # not encrypted, or if the key was encrypted but no password was - # supplied. Note: A passphrase or password is not used when generating - # 'private_key', since it should not be encrypted. - except TypeError: - raise exceptions.CryptoError( # pylint: disable=raise-missing-from - "The private key was" - " unexpectedly encrypted." # pylint: disable=implicit-str-concat - ) - - # 'cryptography.exceptions.UnsupportedAlgorithm' is raised if the - # serialized key is of a type that is not supported by the backend, or if - # the key is encrypted with a symmetric cipher that is not supported by - # the backend. - except UnsupportedAlgorithm: # pragma: no cover - raise exceptions.CryptoError( # pylint: disable=raise-missing-from - "The private key is" - " encrypted with an unsupported algorithm." # pylint: disable=implicit-str-concat - ) - - return signature, scheme - - -def verify_rsa_signature(signature, signature_scheme, public_key, data): - """ - - Determine whether the corresponding private key of 'public_key' produced - 'signature'. verify_signature() will use the public key, signature scheme, - and 'data' to complete the verification. - - >>> public, private = generate_rsa_public_and_private(2048) - >>> data = b'The quick brown fox jumps over the lazy dog' - >>> scheme = 'rsassa-pss-sha256' - >>> signature, scheme = create_rsa_signature(private, data, scheme) - >>> verify_rsa_signature(signature, scheme, public, data) - True - >>> verify_rsa_signature(signature, scheme, public, b'bad_data') - False - - - signature: - A signature, as a string. This is the signature returned - by create_rsa_signature(). - - signature_scheme: - A string that indicates the signature scheme used to generate - 'signature'. Currently supported RSA signature schemes are defined in - `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`. - - public_key: - The RSA public key, a string in PEM format. - - data: - Data used by securesystemslib.keys.create_signature() to generate - 'signature'. 'data' (a string) is needed here to verify 'signature'. - - - securesystemslib.exceptions.FormatError, if 'signature', - 'signature_scheme', 'public_key', or 'data' are improperly formatted. - - securesystemslib.exceptions.UnsupportedAlgorithmError, if the signature - scheme used by 'signature' is not one supported by - securesystemslib.keys.create_signature(). - - securesystemslib.exceptions.CryptoError, if the private key cannot be - decoded or its key type is unsupported. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - pyca/cryptography's RSAPublicKey.verifier() called to do the actual - verification. - - - Boolean. True if the signature is valid, False otherwise. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does 'public_key' have the correct format? - # This check will ensure 'public_key' conforms to - # 'securesystemslib.formats.PEMRSA_SCHEMA'. Raise - # 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(public_key) - - # Does 'signature_scheme' have the correct format? - formats.RSA_SCHEME_SCHEMA.check_match(signature_scheme) - - # Does 'signature' have the correct format? - formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature) - - # What about 'data'? - formats.DATA_SCHEMA.check_match(data) - - # Verify the RSASSA-PSS signature with pyca/cryptography. - try: - public_key_object = serialization.load_pem_public_key( - public_key.encode("utf-8"), backend=default_backend() - ) - - digest_obj = digest_from_rsa_scheme(signature_scheme, "pyca_crypto") - - # verify() raises 'cryptography.exceptions.InvalidSignature' if the - # signature is invalid. 'salt_length' is automatically - # determined when verifying the signature. - try: - if signature_scheme.startswith("rsassa-pss"): - public_key_object.verify( - signature, - data, - padding.PSS( - mgf=padding.MGF1(digest_obj.algorithm), - salt_length=padding.PSS.AUTO, - ), - digest_obj.algorithm, - ) - - elif signature_scheme.startswith("rsa-pkcs1v15"): - public_key_object.verify( - signature, data, padding.PKCS1v15(), digest_obj.algorithm - ) - - # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'. - # This is a defensive check check.. - else: # pragma: no cover - raise exceptions.UnsupportedAlgorithmError( - "Unsupported" - " signature scheme is specified: " + repr(signature_scheme) - ) - - return True - - except InvalidSignature: - return False - - # Raised by load_pem_public_key(). - except (ValueError, UnsupportedAlgorithm) as e: - raise exceptions.CryptoError( - "The PEM could not be" - " decoded successfully, or contained an unsupported key type: " - + str(e) - ) - - -def create_rsa_encrypted_pem(private_key, passphrase): - """ - - Return a string in PEM format (TraditionalOpenSSL), where the private part - of the RSA key is encrypted using the best available encryption for a given - key's backend. This is a curated (by cryptography.io) encryption choice and - the algorithm may change over time. - - c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ - #cryptography.hazmat.primitives.serialization.BestAvailableEncryption - - >>> public, private = generate_rsa_public_and_private(2048) - >>> passphrase = 'secret' - >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) - True - - - private_key: - The private key string in PEM format. - - passphrase: - The passphrase, or password, to encrypt the private part of the RSA - key. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if the passed RSA key cannot be - deserialized by pyca cryptography. - - ValueError, if 'private_key' is unset. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - - A string in PEM format (TraditionalOpenSSL), where the private RSA key is - encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # This check will ensure 'private_key' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(private_key) - - # Does 'passphrase' have the correct format? - formats.PASSWORD_SCHEMA.check_match(passphrase) - - # 'private_key' may still be a NULL string after the - # 'securesystemslib.formats.PEMRSA_SCHEMA' so we need an additional check - if len(private_key): - try: - private_key = load_pem_private_key( - private_key.encode("utf-8"), - password=None, - backend=default_backend(), - ) - except ValueError: - raise exceptions.CryptoError( # pylint: disable=raise-missing-from - "The private key" - " (in PEM format) could not be deserialized." # pylint: disable=implicit-str-concat - ) - - else: - raise ValueError("The required private key is unset.") - - encrypted_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.BestAvailableEncryption( - passphrase.encode("utf-8") - ), - ).strip() - - return encrypted_pem.decode() - - -def create_rsa_public_and_private_from_pem(pem, passphrase=None): - """ - - Generate public and private RSA keys from an optionally encrypted PEM. The - public and private keys returned conform to - 'securesystemslib.formats.PEMRSA_SCHEMA' and have the form: - - '-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----' - - and - - '-----BEGIN RSA PRIVATE KEY----- ...-----END RSA PRIVATE KEY-----' - - The public and private keys are returned as strings in PEM format. - - In case the private key part of 'pem' is encrypted pyca/cryptography's - load_pem_private_key() method is passed passphrase. In the default case - here, pyca/cryptography will decrypt with a PBKDF1+MD5 - strengthened'passphrase', and 3DES with CBC mode for encryption/decryption. - Alternatively, key data may be encrypted with AES-CTR-Mode and the - passphrase strengthened with PBKDF2+SHA256, although this method is used - only with securesystemslib encrypted key files. - - >>> public, private = generate_rsa_public_and_private(2048) - >>> passphrase = 'secret' - >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) - >>> returned_public, returned_private = \ - create_rsa_public_and_private_from_pem(encrypted_pem, passphrase) - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_public) - True - >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_private) - True - >>> public == returned_public - True - >>> private == returned_private - True - - - pem: - A byte string in PEM format, where the private key can be encrypted. - It has the form: - - '-----BEGIN RSA PRIVATE KEY-----\n - Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...' - - passphrase: (optional) - The passphrase, or password, to decrypt the private part of the RSA - key. 'passphrase' is not directly used as the encryption key, instead - it is used to derive a stronger symmetric key. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if the public and private RSA keys - cannot be generated from 'pem', or exported in PEM format. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - pyca/cryptography's 'serialization.load_pem_private_key()' called to - perform the actual conversion from an encrypted RSA private key to - PEM format. - - - A (public, private) tuple containing the RSA keys in PEM format. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Does 'encryped_pem' have the correct format? - # This check will ensure 'pem' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.PEMRSA_SCHEMA.check_match(pem) - - # If passed, does 'passphrase' have the correct format? - if passphrase is not None: - formats.PASSWORD_SCHEMA.check_match(passphrase) - passphrase = passphrase.encode("utf-8") - - # Generate a pyca/cryptography key object from 'pem'. The generated - # pyca/cryptography key contains the required export methods needed to - # generate the PEM-formatted representations of the public and private RSA - # key. - try: - private_key = load_pem_private_key( - pem.encode("utf-8"), passphrase, backend=default_backend() - ) - - # pyca/cryptography's expected exceptions for 'load_pem_private_key()': - # ValueError: If the PEM data could not be decrypted. - # (possibly because the passphrase is wrong)." - # TypeError: If a password was given and the private key was not encrypted. - # Or if the key was encrypted but no password was supplied. - # UnsupportedAlgorithm: If the private key (or if the key is encrypted with - # an unsupported symmetric cipher) is not supported by the backend. - except (ValueError, TypeError, UnsupportedAlgorithm) as e: - # Raise 'securesystemslib.exceptions.CryptoError' and pyca/cryptography's - # exception message. Avoid propogating pyca/cryptography's exception trace - # to avoid revealing sensitive error. - raise exceptions.CryptoError( - "RSA (public, private) tuple" - " cannot be generated from the encrypted PEM string: " + str(e) - ) - - # Export the public and private halves of the pyca/cryptography RSA key - # object. The (public, private) tuple returned contains the public and - # private RSA keys in PEM format, as strings. - # Extract the public & private halves of the RSA key and generate their - # PEM-formatted representations. Return the key pair as a (public, private) - # tuple, where each RSA is a string in PEM format. - private_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ).strip() - - # Need to generate the public key from the private one before serializing - # to PEM format. - public_key = private_key.public_key() - public_pem = public_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ).strip() - - return public_pem.decode(), private_pem.decode() - - -def encrypt_key(key_object, password): - """ - - Return a string containing 'key_object' in encrypted form. Encrypted - strings may be safely saved to a file. The corresponding decrypt_key() - function can be applied to the encrypted string to restore the original key - object. 'key_object' is a securesystemslib key (e.g., RSAKEY_SCHEMA, - ED25519KEY_SCHEMA). This function calls the pyca/cryptography library to - perform the encryption and derive a suitable encryption key. - - Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm - (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password - Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password', - encrypted securesystemslib keys use AES-256-CTR-Mode and passwords - strengthened with PBKDF2-HMAC-SHA256 (100K iterations by default, but may - be overriden in 'settings.PBKDF2_ITERATIONS' by the user). - - http://en.wikipedia.org/wiki/Advanced_Encryption_Standard - http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 - https://en.wikipedia.org/wiki/PBKDF2 - - >>> ed25519_key = {'keytype': 'ed25519', \ - 'scheme': 'ed25519', \ - 'keyid': \ - 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ - 'keyval': {'public': \ - '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ - 'private': \ - '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} - >>> passphrase = 'secret' - >>> encrypted_key = encrypt_key(ed25519_key, passphrase) - >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8')) - True - - - key_object: - The securesystemslib key object that should contain the private portion - of the ED25519 key. - - password: - The password, or passphrase, to encrypt the private part of the RSA - key. 'password' is not used directly as the encryption key, a stronger - encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if any of the arguments are - improperly formatted or 'key_object' does not contain the private portion - of the key. - - securesystemslib.exceptions.CryptoError, if an Ed25519 key in encrypted - securesystemslib format cannot be created. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - - pyca/Cryptography cryptographic operations called to perform the actual - encryption of 'key_object'. 'password' used to derive a suitable - encryption key. - - - An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Do the arguments have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ANYKEY_SCHEMA.check_match(key_object) - - # Does 'password' have the correct format? - formats.PASSWORD_SCHEMA.check_match(password) - - # Ensure the private portion of the key is included in 'key_object'. - if ( - "private" not in key_object["keyval"] - or not key_object["keyval"]["private"] - ): - raise exceptions.FormatError( - "Key object does not contain a private part." - ) - - # Derive a key (i.e., an appropriate encryption key and not the - # user's password) from the given 'password'. Strengthen 'password' with - # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in - # 'settings.PBKDF2_ITERATIONS' by the user). - salt, iterations, derived_key = _generate_derived_key(password) - - # Store the derived key info in a dictionary, the object expected - # by the non-public _encrypt() routine. - derived_key_information = { - "salt": salt, - "iterations": iterations, - "derived_key": derived_key, - } - - # Convert the key object to json string format and encrypt it with the - # derived key. - encrypted_key = _encrypt(json.dumps(key_object), derived_key_information) - - return encrypted_key - - -def decrypt_key(encrypted_key, password): - """ - - Return a string containing 'encrypted_key' in non-encrypted form. - The decrypt_key() function can be applied to the encrypted string to restore - the original key object, a securesystemslib key (e.g., RSAKEY_SCHEMA, - ED25519KEY_SCHEMA). This function calls the appropriate cryptography module - (i.e., rsa_keys.py) to perform the decryption. - - Encrypted securesystemslib keys use AES-256-CTR-Mode and passwords - strengthened with PBKDF2-HMAC-SHA256 (100K iterations be default, but may - be overriden in 'settings.py' by the user). - - http://en.wikipedia.org/wiki/Advanced_Encryption_Standard - http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 - https://en.wikipedia.org/wiki/PBKDF2 - - >>> ed25519_key = {'keytype': 'ed25519', \ - 'scheme': 'ed25519', \ - 'keyid': \ - 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ - 'keyval': {'public': \ - '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ - 'private': \ - '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} - >>> passphrase = 'secret' - >>> encrypted_key = encrypt_key(ed25519_key, passphrase) - >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase) - >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(decrypted_key) - True - >>> decrypted_key == ed25519_key - True - - - encrypted_key: - An encrypted securesystemslib key (additional data is also included, such - as salt, number of password iterations used for the derived encryption - key, etc) of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. - 'encrypted_key' should have been generated with encrypted_key(). - - password: - The password, or passphrase, to encrypt the private part of the RSA - key. 'password' is not used directly as the encryption key, a stronger - encryption key is derived from it. - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.CryptoError, if a securesystemslib key cannot - be decrypted from 'encrypted_key'. - - securesystemslib.exceptions.Error, if a valid securesystemslib key object - is not found in 'encrypted_key'. - - securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography - module is not available. - - - The pyca/cryptography is library called to perform the actual decryption - of 'encrypted_key'. The key derivation data stored in 'encrypted_key' is - used to re-derive the encryption/decryption key. - - - The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format. - """ - - if not CRYPTO: # pragma: no cover - raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) - - # Do the arguments have the correct format? - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) - - # Does 'password' have the correct format? - formats.PASSWORD_SCHEMA.check_match(password) - - # Decrypt 'encrypted_key', using 'password' (and additional key derivation - # data like salts and password iterations) to re-derive the decryption key. - json_data = _decrypt(encrypted_key, password) - - # Raise 'securesystemslib.exceptions.Error' if 'json_data' cannot be - # deserialized to a valid 'securesystemslib.formats.ANYKEY_SCHEMA' key - # object. - key_object = util.load_json_string(json_data.decode()) - - return key_object - - -def _generate_derived_key(password, salt=None, iterations=None): - """ - Generate a derived key by feeding 'password' to the Password-Based Key - Derivation Function (PBKDF2). pyca/cryptography's PBKDF2 implementation is - used in this module. 'salt' may be specified so that a previous derived key - may be regenerated, otherwise '_SALT_SIZE' is used by default. 'iterations' - is the number of SHA-256 iterations to perform, otherwise - '_PBKDF2_ITERATIONS' is used by default. - """ - - # Use pyca/cryptography's default backend (e.g., openSSL, CommonCrypto, etc.) - # The default backend is not fixed and can be changed by pyca/cryptography - # over time. - backend = default_backend() - - # If 'salt' and 'iterations' are unspecified, a new derived key is generated. - # If specified, a deterministic key is derived according to the given - # 'salt' and 'iterrations' values. - if salt is None: - salt = os.urandom(_SALT_SIZE) - - if iterations is None: - iterations = _PBKDF2_ITERATIONS - - # Derive an AES key with PBKDF2. The 'length' is the desired key length of - # the derived key. - pbkdf_object = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=iterations, - backend=backend, - ) - - derived_key = pbkdf_object.derive(password.encode("utf-8")) - - return salt, iterations, derived_key - - -def _encrypt(key_data, derived_key_information): - """ - Encrypt 'key_data' using the Advanced Encryption Standard (AES-256) algorithm. - 'derived_key_information' should contain a key strengthened by PBKDF2. The - key size is 256 bits and AES's mode of operation is set to CTR (CounTeR Mode). - The HMAC of the ciphertext is generated to ensure the ciphertext has not been - modified. - - 'key_data' is the JSON string representation of the key. In the case - of RSA keys, this format would be 'securesystemslib.formats.RSAKEY_SCHEMA': - - {'keytype': 'rsa', - 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', - 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} - - 'derived_key_information' is a dictionary of the form: - {'salt': '...', - 'derived_key': '...', - 'iterations': '...'} - - 'securesystemslib.exceptions.CryptoError' raised if the encryption fails. - """ - - # Generate a random Initialization Vector (IV). Follow the provably secure - # encrypt-then-MAC approach, which affords the ability to verify ciphertext - # without needing to decrypt it and preventing an attacker from feeding the - # block cipher malicious data. Modes like GCM provide both encryption and - # authentication, whereas CTR only provides encryption. - - # Generate a random 128-bit IV. Random bits of data is needed for salts and - # initialization vectors suitable for the encryption algorithms used in - # 'rsa_keys.py'. - iv = os.urandom(16) - - # Construct an AES-CTR Cipher object with the given key and a randomly - # generated IV. - symmetric_key = derived_key_information["derived_key"] - encryptor = Cipher( - algorithms.AES(symmetric_key), modes.CTR(iv), backend=default_backend() - ).encryptor() - - # Encrypt the plaintext and get the associated ciphertext. - # Do we need to check for any exceptions? - ciphertext = ( - encryptor.update(key_data.encode("utf-8")) + encryptor.finalize() - ) - - # Generate the hmac of the ciphertext to ensure it has not been modified. - # The decryption routine may verify a ciphertext without having to perform - # a decryption operation. - symmetric_key = derived_key_information["derived_key"] - salt = derived_key_information["salt"] - hmac_object = hmac.HMAC( - symmetric_key, hashes.SHA256(), backend=default_backend() - ) - hmac_object.update(ciphertext) - hmac_value = binascii.hexlify(hmac_object.finalize()) - - # Store the number of PBKDF2 iterations used to derive the symmetric key so - # that the decryption routine can regenerate the symmetric key successfully. - # The PBKDF2 iterations are allowed to vary for the keys loaded and saved. - iterations = derived_key_information["iterations"] - - # Return the salt, iterations, hmac, initialization vector, and ciphertext - # as a single string. These five values are delimited by - # '_ENCRYPTION_DELIMITER' to make extraction easier. This delimiter is - # arbitrarily chosen and should not occur in the hexadecimal representations - # of the fields it is separating. - return ( - binascii.hexlify(salt).decode() - + _ENCRYPTION_DELIMITER - + str(iterations) - + _ENCRYPTION_DELIMITER - + hmac_value.decode() - + _ENCRYPTION_DELIMITER - + binascii.hexlify(iv).decode() - + _ENCRYPTION_DELIMITER - + binascii.hexlify(ciphertext).decode() - ) - - -def _decrypt(file_contents, password): - """ - The corresponding decryption routine for _encrypt(). - - 'securesystemslib.exceptions.CryptoError' raised if the decryption fails. - """ - - # Extract the salt, iterations, hmac, initialization vector, and ciphertext - # from 'file_contents'. These five values are delimited by - # '_ENCRYPTION_DELIMITER'. This delimiter is arbitrarily chosen and should - # not occur in the hexadecimal representations of the fields it is - # separating. Raise 'securesystemslib.exceptions.CryptoError', if - # 'file_contents' does not contains the expected data layout. - try: - salt, iterations, read_hmac, iv, ciphertext = file_contents.split( - _ENCRYPTION_DELIMITER - ) - - except ValueError: - raise exceptions.CryptoError( # pylint: disable=raise-missing-from - "Invalid encrypted file." - ) - - # Ensure we have the expected raw data for the delimited cryptographic data. - salt = binascii.unhexlify(salt.encode("utf-8")) - iterations = int(iterations) - iv = binascii.unhexlify(iv.encode("utf-8")) - ciphertext = binascii.unhexlify(ciphertext.encode("utf-8")) - - # Generate derived key from 'password'. The salt and iterations are - # specified so that the expected derived key is regenerated correctly. - # Discard the old "salt" and "iterations" values, as we only need the old - # derived key. - _, _, symmetric_key = _generate_derived_key(password, salt, iterations) - - # Verify the hmac to ensure the ciphertext is valid and has not been altered. - # See the encryption routine for why we use the encrypt-then-MAC approach. - # The decryption routine may verify a ciphertext without having to perform - # a decryption operation. - generated_hmac_object = hmac.HMAC( - symmetric_key, hashes.SHA256(), backend=default_backend() - ) - generated_hmac_object.update(ciphertext) - generated_hmac = binascii.hexlify(generated_hmac_object.finalize()) - - if not util.digests_are_equal(generated_hmac.decode(), read_hmac): - raise exceptions.CryptoError("Decryption failed.") - - # Construct a Cipher object, with the key and iv. - decryptor = Cipher( - algorithms.AES(symmetric_key), modes.CTR(iv), backend=default_backend() - ).decryptor() - - # Decryption gets us the authenticated plaintext. - plaintext = decryptor.update(ciphertext) + decryptor.finalize() - - return plaintext - - -if __name__ == "__main__": - # The interactive sessions of the documentation strings can be tested by - # running 'rsa_keys.py' as a standalone module: - # $ python rsa_keys.py - import doctest - - doctest.testmod() diff --git a/securesystemslib/settings.py b/securesystemslib/settings.py deleted file mode 100755 index 0556e539..00000000 --- a/securesystemslib/settings.py +++ /dev/null @@ -1,37 +0,0 @@ -""" - - settings.py - - - Vladimir Diaz - - - December 7, 2016 - - - See LICENSE for licensing information. - - - Store all crypto-related settings used by securesystemslib. -""" - -# Set a directory that should be used for all temporary files. If this -# is None, then the system default will be used. The system default -# will also be used if a directory path set here is invalid or -# unusable. -temporary_directory = None - -# The current "good enough" number of PBKDF2 passphrase iterations. We -# recommend that important keys, such as root, be kept offline. -# 'toto.settings.PBKDF2_ITERATIONS' should increase as CPU speeds increase, set -# here at 100,000 iterations by default (in 2013). The repository maintainer -# may opt to modify the default setting according to their security needs and -# computational restrictions. A strong user password is still important. -# Modifying the number of iterations will result in a new derived key+PBDKF2 -# combination if the key is loaded and re-saved, overriding any previous -# iteration setting used in the old '' key file. -# https://en.wikipedia.org/wiki/PBKDF2 -PBKDF2_ITERATIONS = 100000 - -# The algorithm(s) in HASH_ALGORITHMS are used to generate key IDs. -HASH_ALGORITHMS = ["sha256", "sha512"] diff --git a/tests/check_public_interfaces.py b/tests/check_public_interfaces.py index 5e0e85df..c3f2ee3c 100644 --- a/tests/check_public_interfaces.py +++ b/tests/check_public_interfaces.py @@ -12,8 +12,8 @@ See LICENSE for licensing information. - Public facing modules (e.g. interface.py and keys.py) must be - importable, even if the optional dependencies are not installed. + Public facing modules must be importable, even if the optional dependencies + are not installed. Each public facing function should always be callable and present meaningful user-feedback if an optional dependency that is required for @@ -31,14 +31,11 @@ import shutil import tempfile import unittest -from unittest import mock import securesystemslib.exceptions # pylint: disable=wrong-import-position import securesystemslib.gpg.constants # pylint: disable=wrong-import-position import securesystemslib.gpg.functions # pylint: disable=wrong-import-position import securesystemslib.gpg.util # pylint: disable=wrong-import-position -import securesystemslib.interface # pylint: disable=wrong-import-position -import securesystemslib.keys # pylint: disable=wrong-import-position from securesystemslib.exceptions import ( UnsupportedLibraryError, VerificationError, @@ -66,231 +63,6 @@ def setUpClass(cls): def tearDownClass(cls): shutil.rmtree(cls.temp_dir) - def test_interface(self): - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface._generate_and_write_rsa_keypair( # pylint: disable=protected-access - password="pw" - ) - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_rsa_keypair("pw") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_rsa_keypair("pw") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - # Mock entry on prompt which is presented before lower-level functions - # raise UnsupportedLibraryError - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - securesystemslib.interface.generate_and_write_rsa_keypair_with_prompt() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_unencrypted_rsa_keypair() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - path = os.path.join(self.temp_dir, "rsa_key") - with open(path, "a"): # pylint: disable=unspecified-encoding - securesystemslib.interface.import_rsa_privatekey_from_file(path) - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface._generate_and_write_ed25519_keypair( # pylint: disable=protected-access - password="pw" - ) - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_ed25519_keypair("pw") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - # Mock entry on prompt which is presented before lower-level functions - # raise UnsupportedLibraryError - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - securesystemslib.interface.generate_and_write_ed25519_keypair_with_prompt() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_unencrypted_ed25519_keypair() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - path = os.path.join(self.temp_dir, "ed25519_priv.json") - with open(path, "a") as f: # pylint: disable=unspecified-encoding - f.write("{}") - securesystemslib.interface.import_ed25519_privatekey_from_file( - path, "pw" - ) - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface._generate_and_write_ecdsa_keypair( # pylint: disable=protected-access - password="pw" - ) - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_ecdsa_keypair("pw") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - # Mock entry on prompt which is presented before lower-level functions - # raise UnsupportedLibraryError - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - securesystemslib.interface.generate_and_write_ecdsa_keypair_with_prompt() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.interface.generate_and_write_unencrypted_ecdsa_keypair() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - path = os.path.join(self.temp_dir, "ecddsa.priv") - with open(path, "a") as f: # pylint: disable=unspecified-encoding - f.write("{}") - securesystemslib.interface.import_ecdsa_privatekey_from_file( - path, password="pw" - ) - - def test_keys(self): - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.generate_rsa_key() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.generate_ecdsa_key() - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.generate_ed25519_key() - - data = "foo" - keydict = { - "keytype": "ed25519", - "scheme": "ed25519", - "keyid": "f00", - "keyval": {"private": "f001", "public": "b00f"}, - } - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.create_signature(keydict, data) - - keydict["keytype"] = "ecdsa" - keydict["scheme"] = "ecdsa-sha2-nistp256" - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.create_signature(keydict, data) - - keydict["keytype"] = "rsa" - keydict["scheme"] = "rsassa-pss-sha256" - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.create_signature(keydict, data) - - keydict["keytype"] = "ecdsa" - keydict["scheme"] = "ecdsa-sha2-nistp256" - sig = { - "keyid": "f00", - "sig": "cfbce8e23eef478975a4339036de2335002d57c7b1632dd01e526a3bc52a5b261508ad50b9e25f1b819d61017e7347e912db1af019bf47ee298cc58bbdef9703", - } - # NOTE: we don't test ed25519 keys as they can be verified in pure python - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.verify_signature(keydict, sig, data) - - keydict["keytype"] = "rsa" - keydict["scheme"] = "rsassa-pss-sha256" - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.verify_signature(keydict, sig, data) - - priv = "-----BEGIN RSA PRIVATE KEY-----\nMIIG5AIBAAKCAYEA2WC/pM+6/NbOE/b+N9L+5BOa5sLHCF88okpiCJAZhtIEMw8O\n/EX4CjSy5Qilrmj7ZXmwRyPf7ksd6dbgxAJYk555lE2dywdvzsd31B+nKuAky8/K\nNjpfH4bn2sBKxbA9FFrBenpBkBrq0qDyK85VGJO7ieUdjQepiBQbqctU/PxmPJcE\neO0f1X4IjA+MQv6j/Wt+dnCQSFpCHgOEA0CBWByfRR+DIX74y8RYyKHgj+LpNv1A\nUD1K2vbNc/LrZWEIojCz+2QcXtz/g0kXX5DmRP3feGMC/S/r9bIjEdP55XP70LQU\ndaly64Y/nOlwWHhDNRjtu0lfdqxrK30/O8S8NC6A+nXrav1DzOufffd6wuRKiEqc\nEXZGitSyt/Bg5z70jIHgP6sZ69F0uORr3CaX/YAcQdjPzvSkJEvSj1/sSa+iKOPe\nixQx3VoEpdI3wWu7TQBmTOA3gi2XEZFYdThMGUA5Yv/qNHQVHBkEvOdtTRbWFX0m\npBHLTwBoMO+VJI6hAgMBAAECggGATAC5wOQomrJ4Bx76r4YEPLZmGHzNni2+Q3gC\nYsAPTMYtVbTUJnxIRzk5uz6UvzBRhZ9QdO8kImr9IH9SwvWXBrYICERDAXOuMfwn\n93DBwAnyk5gpOWCbVaiTdDZ7bjc6g91ffHU2ay4eIFrJkWto8Vjl30bOWDrvmXZ+\nXZWMN5AAJvseQzGVSc3xKxdckSf7KmXlJ4Af0kxMhbXw+DobfzUysrZb4OBGGOij\nqjJ/E4/gvqs5S1TC0WAtYXbzutR7zVGuZUFVK7Lk1fq8XcJP5wXCrIjxGnP6V97y\nWn1h64eD+7Gt4wQ+IGr0zKxhSYWI4ou+6QIV3kGlFv9ZRI22yym9MalG1Z1g2GP4\nrgcBZ6j87siSG2L5WoA62pxPPm+vfgEW3GYty1sYqVVQEQhy7GGHWT1kYcc0H7Sr\nALspSr3VbDJtylMQ+wl2IHs8qQ2GAW/utHwPyPzgY2wswi/6L8oYKBrEKK66gSlF\nPHek3uSbho2cPVW7RpG3NA5AHJBhAoHBAO48GEnmacBvMwHfhHex6XUX+VW0QxMl\n/8uNbAp4MEgdyqLw1TLUUAvEbV6qOwL3IWxAvJjXl/9zPtiBUiniZfUI7Rm0LMlv\n1jUlXfzuLwZtL8dHUDFBaZNWlY+eG5dniWkhzMnKqYYGbs9DDO741AKWUtM9UtBA\nm6g0AP6maa3RRAFQ+JtoVFuMYg6R4oE621pKI5ZJ1Zmz/L6H1xoj1QH0JPND1Mxa\nqYEj5SAKE+tj4dbsHjKeaPjk30qnlulQPQKBwQDpln8mJ3z7EXGCYMQrbtg94YuR\n/AVM5pZL9V1YNB8jiydg3j5tMjXWSxd+Hc3Kg1Ey0SjWGtPGD1RQQM+ZQgubRFHP\n7RwQwhxwxji5Azl5LoupsNueMGLQ0bBxSQWTx8zxc4z5oVBcZgD4Pm+5wi17L/77\nqM9Md2nw4ONbsxMiNol65dc/XUPuxaUpPAe2XlV4EGsyWDee6OhH288WhOAzpixS\nB1Ywc6f7LNLc065w2rjzogzyONAFkTP4kKe/2jUCgcEAxznuPe64RTs49roLN2XL\nDCcOVgO3jA3dCkasMV0tU0HGsdihEi7G+fA8XkwRqXstsi+5CEBTVkb0KW6MXYZ9\nKRtb3ID2a0ZhZnRnUxuEq+UnbYlPoMFJHvPrgvz/qe/l08t2TNJ0TiaXCDDUYgwo\nkDlR7mF8HbfJ9DH5GvvjqH42Vrt2C9CFq0GMxw5s0xF7WthhRk9cl3sTQ+qpkayh\nd07Kj70L+hFfayWveMm0usb+mBNBdadPtcUAjpfz9g0pAoHBALWdULDOpQrkThfr\nurp2TWUXlxfjFg/rfNIELRZmOAu/ptdXFLx7/IXoDpT9AUNChIB5RUHqy9tDke9v\n5LkpM7L+FIoQtfCFq+03AWVAD5Cb0vUV0DuXLU1kq8X424BCKaNVjzeL59pfaMOa\nb+3C/u+3qo3qe3rdoZ4qjDuA6RCBzLSkPY5DqozcWQTNasWtZNCcG2yiUGSae/da\n/RFqMJOX0P/aOnYjhmjxOeV+JDQUqxaqWVx/NaYOdpT9i5/MPQKBwGaMbFVt0+CR\nRT5Ts/ZS1qCmyoIepFMOI0SyU8h5+qk4dGutXCm1zjyyxwdJAjG1PYny5imsc795\nR7g7PLSUA+pkXWU8aoiCuCkY6IYz8JFLAw74mxZdLaFQUfBBtSqMz4B9YvUOysr1\nj7Og3AYXob4Me1+ueq59YLM9fEd4Tbw+aBg5T27jwZEmmNripamNFFb6RuPq6u6H\nMZW81M7ahgizqGQsRcOskA/uBC1w3N7o/lUYa3I+OY6EqA4KigIuGw==\n-----END RSA PRIVATE KEY-----\n" - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.import_rsakey_from_private_pem("") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.encrypt_key(keydict, "foo") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.decrypt_key("enc", "pw") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.create_rsa_encrypted_pem(priv, "pw") - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.import_ed25519key_from_private_json( - "".encode("utf-8"), "" - ) - - with self.assertRaises( - securesystemslib.exceptions.UnsupportedLibraryError - ): - securesystemslib.keys.import_ecdsakey_from_private_pem(priv) - - def test_purepy_ed25519(self): - data = b"The quick brown fox jumps over the lazy dog" - pub = b"\xbe\xb7\\&\x82\x06UN\x96 - test_ecdsa_keys.py - - - Vladimir Diaz - - - November 23, 2016. - - - See LICENSE for licensing information. - - - Test cases for test_ecdsa_keys.py. -""" - -import unittest - -import securesystemslib.ecdsa_keys -import securesystemslib.exceptions -import securesystemslib.formats -import securesystemslib.rsa_keys - -public, private = securesystemslib.ecdsa_keys.generate_public_and_private() -FORMAT_ERROR_MSG = ( - "securesystemslib.exceptions.FormatError raised. Check object's format." -) - - -class TestECDSA_keys( - unittest.TestCase -): # pylint: disable=missing-class-docstring,invalid-name - def setUp(self): - pass - - def test_generate_public_and_private(self): - ( - public, # pylint: disable=redefined-outer-name - private, # pylint: disable=redefined-outer-name - ) = securesystemslib.ecdsa_keys.generate_public_and_private() - - # Check format of 'public' and 'private'. - self.assertEqual( - True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) - ) - self.assertEqual( - True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) - ) - - # Test for invalid argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.generate_public_and_private, - "bad_algo", - ) - - def test_create_ecdsa_public_and_private_from_pem(self): - global public # pylint: disable=global-statement - global private # pylint: disable=global-statement - - # Check format of 'public' and 'private'. - self.assertEqual( - True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(public) - ) - self.assertEqual( - True, securesystemslib.formats.PEMECDSA_SCHEMA.matches(private) - ) - - # Check for a valid private pem. - ( - public, - private, - ) = securesystemslib.ecdsa_keys.create_ecdsa_public_and_private_from_pem( - private - ) - - # Check for an invalid pem (non-private). - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.ecdsa_keys.create_ecdsa_public_and_private_from_pem, - public, - ) - - # Test for invalid argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.create_ecdsa_public_and_private_from_pem, - 123, - ) - - def test_create_signature(self): - global public # pylint: disable=global-variable-not-assigned - global private # pylint: disable=global-variable-not-assigned - data = b"The quick brown fox jumps over the lazy dog" - signature, method = securesystemslib.ecdsa_keys.create_signature( - public, private, data - ) - - # Verify format of returned values. - self.assertEqual( - True, - securesystemslib.formats.ECDSASIGNATURE_SCHEMA.matches(signature), - ) - - self.assertEqual( - True, securesystemslib.formats.NAME_SCHEMA.matches(method) - ) - self.assertEqual("ecdsa-sha2-nistp256", method) - - # Check for improperly formatted argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.create_signature, - 123, - private, - data, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.create_signature, - public, - 123, - data, - ) - - # Check for invalid 'data'. - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.ecdsa_keys.create_signature, - public, - private, - 123, - ) - - def test_verify_signature(self): - global public # pylint: disable=global-variable-not-assigned - global private # pylint: disable=global-variable-not-assigned - data = b"The quick brown fox jumps over the lazy dog" - scheme = "ecdsa-sha2-nistp256" - signature, scheme = securesystemslib.ecdsa_keys.create_signature( - public, private, data, scheme - ) - - valid_signature = securesystemslib.ecdsa_keys.verify_signature( - public, scheme, signature, data - ) - self.assertEqual(True, valid_signature) - - # Generate an RSA key so that we can verify that non-ECDSA keys are - # rejected. - rsa_pem, _ = securesystemslib.rsa_keys.generate_rsa_public_and_private() - - # Verify that a non-ECDSA key (via the PEM argument) is rejected. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.verify_signature, - rsa_pem, - scheme, - signature, - data, - ) - - # Check for improperly formatted arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.verify_signature, - 123, - scheme, - signature, - data, - ) - - # Signature method improperly formatted. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.verify_signature, - public, - 123, - signature, - data, - ) - - # Invalid signature method. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.verify_signature, - public, - "unsupported_scheme", - signature, - data, - ) - - # Signature not a string. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.verify_signature, - public, - scheme, - 123, - data, - ) - - # Invalid signature.. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ecdsa_keys.verify_signature, - public, - scheme, - "bad_signature", - data, - ) - - # Check for invalid signature and data. - self.assertEqual( - False, - securesystemslib.ecdsa_keys.verify_signature( - public, scheme, signature, b"123" - ), - ) - - # Mismatched signature. - bad_signature = b"a" * 64 - self.assertEqual( - False, - securesystemslib.ecdsa_keys.verify_signature( - public, scheme, bad_signature, data - ), - ) - - # Generated signature created with different data. - new_signature, scheme = securesystemslib.ecdsa_keys.create_signature( - public, private, b"mismatched data" - ) - - self.assertEqual( - False, - securesystemslib.ecdsa_keys.verify_signature( - public, scheme, new_signature, data - ), - ) - - -# Run the unit tests. -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_ed25519_keys.py b/tests/test_ed25519_keys.py deleted file mode 100755 index f60ac1a7..00000000 --- a/tests/test_ed25519_keys.py +++ /dev/null @@ -1,200 +0,0 @@ -""" - - test_ed25519_keys.py - - - Vladimir Diaz - - - October 11, 2013. - - - See LICENSE for licensing information. - - - Test cases for test_ed25519_keys.py. -""" - -import os -import unittest - -import securesystemslib.ed25519_keys -import securesystemslib.exceptions -import securesystemslib.formats - -public, private = securesystemslib.ed25519_keys.generate_public_and_private() -FORMAT_ERROR_MSG = ( - "securesystemslib.exceptions.FormatError raised. Check object's format." -) - - -class TestEd25519_keys( - unittest.TestCase -): # pylint: disable=missing-class-docstring,invalid-name - def setUp(self): - pass - - def test_generate_public_and_private(self): - pub, priv = securesystemslib.ed25519_keys.generate_public_and_private() - - # Check format of 'pub' and 'priv'. - self.assertEqual( - True, securesystemslib.formats.ED25519PUBLIC_SCHEMA.matches(pub) - ) - self.assertEqual( - True, securesystemslib.formats.ED25519SEED_SCHEMA.matches(priv) - ) - - def test_create_signature(self): - global public # pylint: disable=global-variable-not-assigned - global private # pylint: disable=global-variable-not-assigned - data = b"The quick brown fox jumps over the lazy dog" - scheme = "ed25519" - signature, scheme = securesystemslib.ed25519_keys.create_signature( - public, private, data, scheme - ) - - # Verify format of returned values. - self.assertEqual( - True, - securesystemslib.formats.ED25519SIGNATURE_SCHEMA.matches(signature), - ) - - self.assertEqual( - True, securesystemslib.formats.ED25519_SIG_SCHEMA.matches(scheme) - ) - self.assertEqual("ed25519", scheme) - - # Check for improperly formatted argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.create_signature, - 123, - private, - data, - scheme, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.create_signature, - public, - 123, - data, - scheme, - ) - - # Check for invalid 'data'. - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.ed25519_keys.create_signature, - public, - private, - 123, - scheme, - ) - - def test_verify_signature(self): - global public # pylint: disable=global-variable-not-assigned - global private # pylint: disable=global-variable-not-assigned - data = b"The quick brown fox jumps over the lazy dog" - scheme = "ed25519" - signature, scheme = securesystemslib.ed25519_keys.create_signature( - public, private, data, scheme - ) - - valid_signature = securesystemslib.ed25519_keys.verify_signature( - public, scheme, signature, data - ) - self.assertEqual(True, valid_signature) - - bad_signature = os.urandom(64) - valid_signature = securesystemslib.ed25519_keys.verify_signature( - public, scheme, bad_signature, data - ) - self.assertEqual(False, valid_signature) - - # Check for improperly formatted arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.verify_signature, - 123, - scheme, - signature, - data, - ) - - # Signature method improperly formatted. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.verify_signature, - public, - 123, - signature, - data, - ) - - # Invalid signature method. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.verify_signature, - public, - "unsupported_scheme", - signature, - data, - ) - - # Signature not a string. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.verify_signature, - public, - scheme, - 123, - data, - ) - - # Invalid signature length, which must be exactly 64 bytes.. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.ed25519_keys.verify_signature, - public, - scheme, - "bad_signature", - data, - ) - - # Check for invalid signature and data. - # Mismatched data. - self.assertEqual( - False, - securesystemslib.ed25519_keys.verify_signature( - public, scheme, signature, b"123" - ), - ) - - # Mismatched signature. - bad_signature = b"a" * 64 - self.assertEqual( - False, - securesystemslib.ed25519_keys.verify_signature( - public, scheme, bad_signature, data - ), - ) - - # Generated signature created with different data. - new_signature, scheme = securesystemslib.ed25519_keys.create_signature( - public, private, b"mismatched data", scheme - ) - - self.assertEqual( - False, - securesystemslib.ed25519_keys.verify_signature( - public, scheme, new_signature, data - ), - ) - - -# Run the unit tests. -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py deleted file mode 100755 index 3f3af413..00000000 --- a/tests/test_exceptions.py +++ /dev/null @@ -1,59 +0,0 @@ -""" - - test_exceptions.py - - - Vladimir Diaz - - - December 20, 2016. - - - See LICENSE for licensing information. - - - Test cases for exceptions.py (mainly the exceptions defined there). -""" - -import logging -import unittest - -import securesystemslib.exceptions - -logger = logging.getLogger(__name__) - - -class TestExceptions( - unittest.TestCase -): # pylint: disable=missing-class-docstring - def setUp(self): - pass - - def tearDown(self): - pass - - def test_bad_signature_error(self): - bad_signature_error = securesystemslib.exceptions.BadSignatureError( - "bad sig" - ) - logger.error(bad_signature_error) - - def test_bad_hash_error(self): - bad_hash_error = securesystemslib.exceptions.BadHashError( - "01234", "56789" - ) - logger.error(bad_hash_error) - - def test_invalid_metadata_json_error(self): - format_error = securesystemslib.exceptions.FormatError( - "Improperly formatted JSON" - ) - invalid_metadata_json_error = ( - securesystemslib.exceptions.InvalidMetadataJSONError(format_error) - ) - logger.error(invalid_metadata_json_error) - - -# Run the unit tests. -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_interface.py b/tests/test_interface.py deleted file mode 100755 index 32093184..00000000 --- a/tests/test_interface.py +++ /dev/null @@ -1,1039 +0,0 @@ -""" - - test_interface.py - - - Vladimir Diaz - - - January 5, 2017. - - - See LICENSE for licensing information. - - - Unit test for 'interface.py'. -""" - -import os -import shutil -import stat -import tempfile -import unittest -from unittest import mock - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.serialization import load_pem_private_key - -from securesystemslib import ( # pylint: disable=wrong-import-position - KEY_TYPE_ECDSA, - KEY_TYPE_ED25519, - KEY_TYPE_RSA, -) -from securesystemslib.exceptions import ( # pylint: disable=wrong-import-position - CryptoError, - Error, - FormatError, -) -from securesystemslib.formats import ( # pylint: disable=wrong-import-position - ANY_PUBKEY_DICT_SCHEMA, - ECDSAKEY_SCHEMA, - ED25519KEY_SCHEMA, - PUBLIC_KEY_SCHEMA, - RSAKEY_SCHEMA, -) -from securesystemslib.interface import ( # pylint: disable=wrong-import-position - _generate_and_write_ecdsa_keypair, - _generate_and_write_ed25519_keypair, - _generate_and_write_rsa_keypair, - generate_and_write_ecdsa_keypair, - generate_and_write_ecdsa_keypair_with_prompt, - generate_and_write_ed25519_keypair, - generate_and_write_ed25519_keypair_with_prompt, - generate_and_write_rsa_keypair, - generate_and_write_rsa_keypair_with_prompt, - generate_and_write_unencrypted_ecdsa_keypair, - generate_and_write_unencrypted_ed25519_keypair, - generate_and_write_unencrypted_rsa_keypair, - import_ecdsa_privatekey_from_file, - import_ecdsa_publickey_from_file, - import_ed25519_privatekey_from_file, - import_ed25519_publickey_from_file, - import_privatekey_from_file, - import_publickeys_from_file, - import_rsa_privatekey_from_file, - import_rsa_publickey_from_file, -) - - -class TestInterfaceFunctions( - unittest.TestCase -): # pylint: disable=missing-class-docstring - @classmethod - def setUpClass(cls): - cls.test_data_dir = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "data" - ) - - cls.path_rsa = os.path.join(cls.test_data_dir, "keystore", "rsa_key") - cls.path_ed25519 = os.path.join( - cls.test_data_dir, "keystore", "ed25519_key" - ) - cls.path_ecdsa = os.path.join( - cls.test_data_dir, "keystore", "ecdsa_key" - ) - cls.path_no_key = os.path.join(cls.test_data_dir, "keystore", "no_key") - - cls.orig_cwd = os.getcwd() - - def setUp(self): - self.tmp_dir = tempfile.mkdtemp(dir=self.orig_cwd) - os.chdir(self.tmp_dir) - - def tearDown(self): - os.chdir(self.orig_cwd) - shutil.rmtree(self.tmp_dir) - - def test_rsa(self): # pylint: disable=too-many-locals,too-many-statements - """Test RSA key _generation and import interface functions.""" - - # TEST: Generate default keys and import - # Assert location and format - fn_default = "default" - fn_default_ret = _generate_and_write_rsa_keypair(filepath=fn_default) - - pub = import_rsa_publickey_from_file(fn_default + ".pub") - priv = import_rsa_privatekey_from_file(fn_default) - - self.assertEqual(fn_default, fn_default_ret) - self.assertTrue(RSAKEY_SCHEMA.matches(pub)) - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - self.assertTrue(RSAKEY_SCHEMA.matches(priv)) - # NOTE: There is no private key schema, at least check it has a value - self.assertTrue(priv["keyval"]["private"]) - - # TEST: Generate unencrypted keys with empty prompt - # Assert importable without password - fn_empty_prompt = "empty_prompt" - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - _generate_and_write_rsa_keypair( - filepath=fn_empty_prompt, prompt=True - ) - import_rsa_privatekey_from_file(fn_empty_prompt) - - # TEST: Generate keys with auto-filename, i.e. keyid - # Assert filename is keyid - fn_keyid = _generate_and_write_rsa_keypair() - pub = import_rsa_publickey_from_file(fn_keyid + ".pub") - priv = import_rsa_privatekey_from_file(fn_keyid) - self.assertTrue( - os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"] - ) - - # TEST: Generate keys with custom bits - # Assert length - bits = 4096 - fn_bits = "bits" - _generate_and_write_rsa_keypair(filepath=fn_bits, bits=bits) - - priv = import_rsa_privatekey_from_file(fn_bits) - # NOTE: Parse PEM with pyca/cryptography to get the key size property - obj_bits = load_pem_private_key( - priv["keyval"]["private"].encode("utf-8"), - password=None, - backend=default_backend(), - ) - - self.assertEqual(obj_bits.key_size, bits) - - # TEST: Generate two keypairs with encrypted private keys using ... - pw = "pw" - fn_encrypted = "encrypted" - fn_prompt = "prompt" - - # ... a passed pw ... - _generate_and_write_rsa_keypair(filepath=fn_encrypted, password=pw) - with mock.patch( - "securesystemslib.interface.get_password", return_value=pw - ): - # ... and a prompted pw. - _generate_and_write_rsa_keypair(filepath=fn_prompt, prompt=True) - - # Assert that both private keys are importable using the prompted pw ... - import_rsa_privatekey_from_file(fn_prompt, prompt=True) - import_rsa_privatekey_from_file(fn_encrypted, prompt=True) - - # ... and the passed pw. - import_rsa_privatekey_from_file(fn_prompt, password=pw) - import_rsa_privatekey_from_file(fn_encrypted, password=pw) - - # TEST: Import existing keys with encrypted private key (test regression) - # Assert format - pub = import_rsa_publickey_from_file(self.path_rsa + ".pub") - priv = import_rsa_privatekey_from_file(self.path_rsa, "password") - - self.assertTrue(RSAKEY_SCHEMA.matches(pub)) - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - self.assertTrue(RSAKEY_SCHEMA.matches(priv)) - # NOTE: There is no private key schema, at least check it has a value - self.assertTrue(priv["keyval"]["private"]) - - # TEST: Generation errors - for idx, (kwargs, err_msg) in enumerate( - [ - # Error on empty password - ( - {"password": ""}, - "encryption password must be 1 or more characters long", - ), - # Error on 'password' and 'prompt=True' - ( - {"password": pw, "prompt": True}, - "passing 'password' and 'prompt=True' is not allowed", - ), - ] - ): - with self.assertRaises( - ValueError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - _generate_and_write_rsa_keypair(**kwargs) - - self.assertEqual( - err_msg, - str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on bad argument format - for idx, kwargs in enumerate( - [ - {"bits": 1024}, # Too low - {"bits": "not-an-int"}, - {"filepath": 123456}, # Not a string - {"password": 123456}, # Not a string - {"prompt": "not-a-bool"}, - ] - ): - with self.assertRaises( - FormatError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ): - _generate_and_write_rsa_keypair(**kwargs) - - # TEST: Import errors - - # Error public key import - err_msg = "Invalid public pem" - with self.assertRaises(Error) as ctx: - import_rsa_publickey_from_file(fn_default) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}'".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception - ), - ) - - # Error on private key import... - for idx, (args, kwargs, err, err_msg) in enumerate( - [ - # Error on not a private key - ( - [fn_default + ".pub"], - {}, - CryptoError, - "Could not deserialize key data", - ), - # Error on not encrypted - ( - [fn_default], - {"password": pw}, - CryptoError, - "Password was given but private key is not encrypted", - ), - # Error on encrypted but no pw - ( - [fn_encrypted], - {}, - CryptoError, - "Password was not given but private key is encrypted", - ), - # Error on encrypted but empty pw passed - ( - [fn_encrypted], - {"password": ""}, - CryptoError, - "Password was not given but private key is encrypted", - ), - # Error on pw and prompt - ( - [fn_default], - {"password": pw, "prompt": True}, - ValueError, - "passing 'password' and 'prompt=True' is not allowed", - ), - ] - ): - with self.assertRaises( - err, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - import_rsa_privatekey_from_file(*args, **kwargs) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on encrypted but bad pw passed - # NOTE: for some key+pw combos the error differs, see pyca/cryptography#8563 - with self.assertRaises(CryptoError) as ctx: - import_rsa_privatekey_from_file(fn_encrypted, password="bad pw") - - error = str(ctx.exception) - self.assertTrue( - "Bad decrypt. Incorrect password?" in error - or "Could not deserialize key data" in error, - f"unexpected: {error}", - ) - - # Error on encrypted but bad pw prompted - err_msg = "Password was not given but private key is encrypted" - with self.assertRaises(CryptoError) as ctx, mock.patch( - "securesystemslib.interface.get_password", return_value="bad_pw" - ): - import_rsa_privatekey_from_file(fn_encrypted) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}'".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception - ), - ) - - # Error on bad argument format - for idx, (args, kwargs) in enumerate( - [ - ([123456], {}), # bad path - ([fn_default], {"scheme": 123456}), # bad scheme - ([fn_default], {"scheme": "bad scheme"}), # bad scheme - ] - ): - with self.assertRaises( - FormatError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ): - import_rsa_publickey_from_file(*args, **kwargs) - with self.assertRaises( - FormatError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ): - import_rsa_privatekey_from_file(*args, **kwargs) - - # bad password - with self.assertRaises(FormatError): - import_rsa_privatekey_from_file(fn_default, password=123456) - - # bad prompt - with self.assertRaises(FormatError): - import_rsa_privatekey_from_file(fn_default, prompt="not-a-bool") - - def test_ed25519( - self, - ): # pylint: disable=too-many-locals,too-many-statements - """Test ed25519 key _generation and import interface functions.""" - - # TEST: Generate default keys and import - # Assert location and format - fn_default = "default" - fn_default_ret = _generate_and_write_ed25519_keypair( - filepath=fn_default - ) - - pub = import_ed25519_publickey_from_file(fn_default + ".pub") - priv = import_ed25519_privatekey_from_file(fn_default) - - self.assertEqual(fn_default, fn_default_ret) - self.assertTrue(ED25519KEY_SCHEMA.matches(pub)) - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) - # NOTE: There is no private key schema, at least check it has a value - self.assertTrue(priv["keyval"]["private"]) - - # TEST: Generate unencrypted keys with empty prompt - # Assert importable with empty prompt password and without password - fn_empty_prompt = "empty_prompt" - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - _generate_and_write_ed25519_keypair(filepath=fn_empty_prompt) - import_ed25519_privatekey_from_file(fn_empty_prompt, prompt=True) - import_ed25519_privatekey_from_file(fn_empty_prompt) - - # TEST: Generate keys with auto-filename, i.e. keyid - # Assert filename is keyid - fn_keyid = _generate_and_write_ed25519_keypair() - pub = import_ed25519_publickey_from_file(fn_keyid + ".pub") - priv = import_ed25519_privatekey_from_file(fn_keyid) - self.assertTrue( - os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"] - ) - - # TEST: Generate two keypairs with encrypted private keys using ... - pw = "pw" - fn_encrypted = "encrypted" - fn_prompt = "prompt" - # ... a passed pw ... - _generate_and_write_ed25519_keypair(filepath=fn_encrypted, password=pw) - with mock.patch( - "securesystemslib.interface.get_password", return_value=pw - ): - # ... and a prompted pw. - _generate_and_write_ed25519_keypair(filepath=fn_prompt, prompt=True) - - # Assert that both private keys are importable using the prompted pw ... - import_ed25519_privatekey_from_file(fn_prompt, prompt=True) - import_ed25519_privatekey_from_file(fn_encrypted, prompt=True) - - # ... and the passed pw. - import_ed25519_privatekey_from_file(fn_prompt, password=pw) - import_ed25519_privatekey_from_file(fn_encrypted, password=pw) - - # TEST: Import existing keys with encrypted private key (test regression) - # Assert format - pub = import_ed25519_publickey_from_file(self.path_ed25519 + ".pub") - priv = import_ed25519_privatekey_from_file( - self.path_ed25519, "password" - ) - - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - self.assertTrue(ED25519KEY_SCHEMA.matches(pub)) - self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) - # NOTE: There is no private key schema, at least check it has a value - self.assertTrue(priv["keyval"]["private"]) - - # TEST: Unexpected behavior - # FIXME: Should 'import_ed25519_publickey_from_file' be able to import a - # a non-encrypted ed25519 private key? I think it should not, but it is: - priv = import_ed25519_publickey_from_file(fn_default) - self.assertTrue(ED25519KEY_SCHEMA.matches(priv)) - self.assertTrue(priv["keyval"]["private"]) - - # FIXME: Should 'import_ed25519_privatekey_from_file' be able to import a - # an ed25519 public key? I think it should not, but it is: - pub = import_ed25519_privatekey_from_file(fn_default + ".pub") - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - - # TEST: Generation errors - for idx, (kwargs, err_msg) in enumerate( - [ - # Error on empty password - ( - {"password": ""}, - "encryption password must be 1 or more characters long", - ), - # Error on 'password' and 'prompt=True' - ( - {"password": pw, "prompt": True}, - "passing 'password' and 'prompt=True' is not allowed", - ), - ] - ): - with self.assertRaises( - ValueError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - _generate_and_write_ed25519_keypair(**kwargs) - - self.assertEqual( - err_msg, - str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on bad argument format - for idx, kwargs in enumerate( - [ - {"filepath": 123456}, # Not a string - {"password": 123456}, # Not a string - {"prompt": "not-a-bool"}, - ] - ): - with self.assertRaises( - FormatError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ): - _generate_and_write_ed25519_keypair(**kwargs) - - # TEST: Import errors - # Error on public key import... - for idx, (fn, err_msg) in enumerate( - [ - # Error on invalid json (custom key format) - (fn_encrypted, "Cannot deserialize to a Python object"), - # Error on invalid custom key format - (self.path_no_key, "Missing key"), - # Error on invalid key type - (self.path_ecdsa + ".pub", "Invalid key type loaded"), - ] - ): - with self.assertRaises( - Error, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - import_ed25519_publickey_from_file(fn) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on private key import... - for idx, (args, kwargs, err, err_msg) in enumerate( - [ - # Error on not an ed25519 private key - ( - [self.path_ecdsa], - {}, - CryptoError, - "Malformed Ed25519 key JSON, possibly due to encryption, " - "but no password provided?", - ), - # Error on not encrypted - ( - [fn_default], - {"password": pw}, - CryptoError, - "Invalid encrypted file.", - ), - # Error on encrypted but no pw - ( - [fn_encrypted], - {}, - CryptoError, - "Malformed Ed25519 key JSON, possibly due to encryption, " - "but no password provided?", - ), - # Error on encrypted but empty pw - ( - [fn_encrypted], - {"password": ""}, - CryptoError, - "Decryption failed.", - ), - # Error on encrypted but bad pw passed - ( - [fn_encrypted], - {"password": "bad pw"}, - CryptoError, - "Decryption failed.", - ), - # Error on pw and prompt - ( - [fn_default], - {"password": pw, "prompt": True}, - ValueError, - "passing 'password' and 'prompt=True' is not allowed", - ), - ] - ): - with self.assertRaises( - err, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - import_ed25519_privatekey_from_file(*args, **kwargs) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on encrypted but bad pw prompted - err_msg = ( - "Malformed Ed25519 key JSON, possibly due to encryption, " - "but no password provided?" - ) - with self.assertRaises(CryptoError) as ctx, mock.patch( - "securesystemslib.interface.get_password", return_value="bad_pw" - ): - import_ed25519_privatekey_from_file(fn_encrypted) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}'".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception - ), - ) - - # Error on bad path format - with self.assertRaises(FormatError): - import_ed25519_publickey_from_file(123456) - with self.assertRaises(FormatError): - import_ed25519_privatekey_from_file(123456) - - # Error on bad password format - with self.assertRaises(FormatError): - import_ed25519_privatekey_from_file(fn_default, password=123456) - - # Error on bad prompt format - with self.assertRaises(FormatError): - import_ed25519_privatekey_from_file(fn_default, prompt="not-a-bool") - - def test_ecdsa(self): # pylint: disable=too-many-locals,too-many-statements - """Test ecdsa key _generation and import interface functions.""" - # TEST: Generate default keys and import - # Assert location and format - fn_default = "default" - fn_default_ret = _generate_and_write_ecdsa_keypair(filepath=fn_default) - - pub = import_ecdsa_publickey_from_file(fn_default + ".pub") - priv = import_ecdsa_privatekey_from_file(fn_default) - - self.assertEqual(fn_default, fn_default_ret) - self.assertTrue(ECDSAKEY_SCHEMA.matches(pub)) - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - self.assertTrue(ECDSAKEY_SCHEMA.matches(priv)) - # NOTE: There is no private key schema, at least check it has a value - self.assertTrue(priv["keyval"]["private"]) - - # TEST: Generate unencrypted keys with empty prompt - # Assert importable with empty prompt password and without password - fn_empty_prompt = "empty_prompt" - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - _generate_and_write_ecdsa_keypair(filepath=fn_empty_prompt) - import_ecdsa_privatekey_from_file(fn_empty_prompt, prompt=True) - import_ecdsa_privatekey_from_file(fn_empty_prompt) - - # TEST: Generate keys with auto-filename, i.e. keyid - # Assert filename is keyid - fn_keyid = _generate_and_write_ecdsa_keypair() - pub = import_ecdsa_publickey_from_file(fn_keyid + ".pub") - priv = import_ecdsa_privatekey_from_file(fn_keyid) - self.assertTrue( - os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"] - ) - - # TEST: Generate two key pairs with encrypted private keys using ... - pw = "pw" - fn_encrypted = "encrypted" - fn_prompt = "prompt" - # ... a passed pw ... - _generate_and_write_ecdsa_keypair(filepath=fn_encrypted, password=pw) - with mock.patch( - "securesystemslib.interface.get_password", return_value=pw - ): - # ... and a prompted pw. - _generate_and_write_ecdsa_keypair(filepath=fn_prompt, prompt=True) - - # Assert that both private keys are importable using the prompted pw ... - import_ecdsa_privatekey_from_file(fn_prompt, prompt=True) - import_ecdsa_privatekey_from_file(fn_encrypted, prompt=True) - - # ... and the passed pw. - import_ecdsa_privatekey_from_file(fn_prompt, password=pw) - import_ecdsa_privatekey_from_file(fn_encrypted, password=pw) - - # TEST: Import existing keys with encrypted private key (test regression) - # Assert format - pub = import_ecdsa_publickey_from_file(self.path_ecdsa + ".pub") - priv = import_ecdsa_privatekey_from_file(self.path_ecdsa, "password") - - self.assertTrue(ECDSAKEY_SCHEMA.matches(pub)) - self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) - self.assertTrue(ECDSAKEY_SCHEMA.matches(priv)) - # NOTE: There is no private key schema, at least check it has a value - self.assertTrue(priv["keyval"]["private"]) - - # FIXME: Should 'import_ecdsa_publickey_from_file' be able to import a - # an ed25519 public key? I think it should not, but it is: - import_ecdsa_publickey_from_file(self.path_ed25519 + ".pub") - self.assertTrue(ECDSAKEY_SCHEMA.matches(pub)) - - # TEST: Generation errors - for idx, (kwargs, err_msg) in enumerate( - [ - # Error on empty password - ( - {"password": ""}, - "encryption password must be 1 or more characters long", - ), - # Error on 'password' and 'prompt=True' - ( - {"password": pw, "prompt": True}, - "passing 'password' and 'prompt=True' is not allowed", - ), - ] - ): - with self.assertRaises( - ValueError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - _generate_and_write_ecdsa_keypair(**kwargs) - - self.assertEqual( - err_msg, - str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on bad argument format - for idx, kwargs in enumerate( - [ - {"filepath": 123456}, # Not a string - {"password": 123456}, # Not a string - {"prompt": "not-a-bool"}, - ] - ): - with self.assertRaises( - FormatError, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ): - _generate_and_write_ecdsa_keypair(**kwargs) - - # TEST: Import errors - - # Error on public key import... - for idx, (fn, err_msg) in enumerate( - [ - # Error on invalid json (custom key format) - (fn_encrypted, "Cannot deserialize to a Python object"), - # Error on invalid custom key format - (self.path_no_key, "Missing key"), - ] - ): - with self.assertRaises( - Error, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - import_ecdsa_publickey_from_file(fn) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on private key import... - for idx, (args, kwargs, err, err_msg) in enumerate( - [ - # Error on not an ecdsa private key - ( - [self.path_ed25519], - {}, - Error, - "Cannot deserialize to a Python object", - ), - # Error on not encrypted - ( - [fn_default], - {"password": pw}, - CryptoError, - "Invalid encrypted file.", - ), - # Error on encrypted but no pw - ( - [fn_encrypted], - {}, - Error, - "Cannot deserialize to a Python object", - ), - # Error on encrypted but empty pw - ( - [fn_encrypted], - {"password": ""}, - CryptoError, - "Decryption failed.", - ), - # Error on encrypted but bad pw passed - ( - [fn_encrypted], - {"password": "bad pw"}, - CryptoError, - "Decryption failed.", - ), - # Error on pw and prompt - ( - [fn_default], - {"password": pw, "prompt": True}, - ValueError, - "passing 'password' and 'prompt=True' is not allowed", - ), - ] - ): - with self.assertRaises( - err, - msg="(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) as ctx: - import_ecdsa_privatekey_from_file(*args, **kwargs) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}' (row {})".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception, idx - ), - ) - - # Error on encrypted but bad pw prompted - err_msg = "Decryption failed" - with self.assertRaises(CryptoError) as ctx, mock.patch( - "securesystemslib.interface.get_password", return_value="bad_pw" - ): - import_ecdsa_privatekey_from_file(fn_encrypted, prompt=True) - - self.assertTrue( - err_msg in str(ctx.exception), - "expected: '{}' got: '{}'".format( # pylint: disable=consider-using-f-string - err_msg, ctx.exception - ), - ) - - # Error on bad path format - with self.assertRaises(FormatError): - import_ecdsa_publickey_from_file(123456) - with self.assertRaises(FormatError): - import_ecdsa_privatekey_from_file(123456) - - # Error on bad password format - with self.assertRaises(FormatError): # bad password - import_ecdsa_privatekey_from_file(fn_default, password=123456) - - # Error on bad prompt format - with self.assertRaises(FormatError): - import_ecdsa_privatekey_from_file(fn_default, prompt="not-a-bool") - - def test_generate_keypair_wrappers(self): - """Basic tests for thin wrappers around _generate_and_write_*_keypair. - See 'test_rsa', 'test_ed25519' and 'test_ecdsa' for more thorough key - generation tests for each key type. - - """ - key_pw = "pw" - expected_priv_mode = stat.S_IFREG | stat.S_IRUSR | stat.S_IWUSR - if os.name == "nt": - expected_priv_mode = ( - stat.S_IFREG - | stat.S_IWUSR - | stat.S_IRUSR - | stat.S_IWGRP - | stat.S_IRGRP - | stat.S_IWOTH - | stat.S_IROTH - ) - - for idx, (gen, gen_prompt, gen_plain, import_priv, schema) in enumerate( - [ - ( - generate_and_write_rsa_keypair, - generate_and_write_rsa_keypair_with_prompt, - generate_and_write_unencrypted_rsa_keypair, - import_rsa_privatekey_from_file, - RSAKEY_SCHEMA, - ), - ( - generate_and_write_ed25519_keypair, - generate_and_write_ed25519_keypair_with_prompt, - generate_and_write_unencrypted_ed25519_keypair, - import_ed25519_privatekey_from_file, - ED25519KEY_SCHEMA, - ), - ( - generate_and_write_ecdsa_keypair, - generate_and_write_ecdsa_keypair_with_prompt, - generate_and_write_unencrypted_ecdsa_keypair, - import_ecdsa_privatekey_from_file, - ECDSAKEY_SCHEMA, - ), - ] - ): - assert_msg = ( - "(row {})".format( # pylint: disable=consider-using-f-string - idx - ) - ) - # Test generate_and_write_*_keypair creates an encrypted private key - fn_encrypted = gen(key_pw) - priv = import_priv(fn_encrypted, key_pw) - self.assertTrue(schema.matches(priv), assert_msg) - - # Test that encrypted private key is generated with read and write - # permissions for user only - self.assertEqual(os.stat(fn_encrypted).st_mode, expected_priv_mode) - - # Test generate_and_write_*_keypair errors if password is None or empty - with self.assertRaises(FormatError, msg=assert_msg): - fn_encrypted = gen(None) - with self.assertRaises(ValueError, msg=assert_msg): - fn_encrypted = gen("") - - # Test generate_and_write_*_keypair_with_prompt creates encrypted private - # key - with mock.patch( - "securesystemslib.interface.get_password", return_value=key_pw - ): - fn_prompt = gen_prompt() - priv = import_priv(fn_prompt, key_pw) - self.assertTrue(schema.matches(priv), assert_msg) - - # Test generate_and_write_*_keypair_with_prompt creates unencrypted - # private key if no password is entered - with mock.patch( - "securesystemslib.interface.get_password", return_value="" - ): - fn_empty_prompt = gen_prompt() - priv = import_priv(fn_empty_prompt) - self.assertTrue(schema.matches(priv), assert_msg) - - # Test generate_and_write_unencrypted_*_keypair doesn't encrypt - fn_unencrypted = gen_plain() - priv = import_priv(fn_unencrypted) - self.assertTrue(schema.matches(priv), assert_msg) - - # Test that unencrypted private key is generated with read and write - # permissions for user only - self.assertEqual( - os.stat(fn_unencrypted).st_mode, expected_priv_mode - ) - - def test_import_publickeys_from_file(self): - """Test import multiple public keys with different types.""" - - # Successfully import key dict with one key per supported key type - key_dict = import_publickeys_from_file( - [ - self.path_rsa + ".pub", - self.path_ed25519 + ".pub", - self.path_ecdsa + ".pub", - ], - [KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA], - ) - - ANY_PUBKEY_DICT_SCHEMA.check_match(key_dict) - self.assertListEqual( - sorted([key["keytype"] for key in key_dict.values()]), - sorted([KEY_TYPE_RSA, KEY_TYPE_ED25519, KEY_TYPE_ECDSA]), - ) - - # Successfully import default rsa key - key_dict = import_publickeys_from_file([self.path_rsa + ".pub"]) - ANY_PUBKEY_DICT_SCHEMA.check_match(key_dict) - RSAKEY_SCHEMA.check_match(list(key_dict.values()).pop()) - - # Bad default rsa key type for ed25519 - with self.assertRaises(Error): - import_publickeys_from_file([self.path_ed25519 + ".pub"]) - - # Bad ed25519 key type for rsa key - with self.assertRaises(Error): - import_publickeys_from_file( - [self.path_rsa + ".pub"], [KEY_TYPE_ED25519] - ) - - # Unsupported key type - with self.assertRaises(FormatError): - import_publickeys_from_file( - [self.path_ed25519 + ".pub"], ["KEY_TYPE_UNSUPPORTED"] - ) - - # Mismatching arguments lists lenghts - with self.assertRaises(FormatError): - import_publickeys_from_file( - [self.path_rsa + ".pub", self.path_ed25519 + ".pub"], - [KEY_TYPE_ED25519], - ) - - def test_import_privatekey_from_file(self): - """Test generic private key import function.""" - - pw = "password" - for idx, (path, key_type, key_schema) in enumerate( - [ - (self.path_rsa, None, RSAKEY_SCHEMA), # default key type - (self.path_rsa, KEY_TYPE_RSA, RSAKEY_SCHEMA), - (self.path_ed25519, KEY_TYPE_ED25519, ED25519KEY_SCHEMA), - (self.path_ecdsa, KEY_TYPE_ECDSA, ECDSAKEY_SCHEMA), - ] - ): - # Successfully import key per supported type, with ... - # ... passed password - key = import_privatekey_from_file( - path, key_type=key_type, password=pw - ) - self.assertTrue( - key_schema.matches(key), - "(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) - - # ... entered password on mock-prompt - with mock.patch( - "securesystemslib.interface.get_password", return_value=pw - ): - key = import_privatekey_from_file( - path, key_type=key_type, prompt=True - ) - self.assertTrue( - key_schema.matches(key), - "(row {})".format( # pylint: disable=consider-using-f-string - idx - ), - ) - - # Error on wrong key for default key type - with self.assertRaises(Error): - import_privatekey_from_file(self.path_ed25519, password=pw) - - # Error on unsupported key type - with self.assertRaises(FormatError): - import_privatekey_from_file( - self.path_rsa, key_type="KEY_TYPE_UNSUPPORTED", password=pw - ) - - -# Run the test cases. -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_keys.py b/tests/test_keys.py deleted file mode 100755 index 731c73b4..00000000 --- a/tests/test_keys.py +++ /dev/null @@ -1,933 +0,0 @@ -""" - - test_keys.py - - - Vladimir Diaz - - - October 10, 2013. - - - See LICENSE for licensing information. - - - Test cases for test_keys.py. -""" - -import copy -import unittest - -import securesystemslib.ecdsa_keys -import securesystemslib.exceptions -import securesystemslib.formats -import securesystemslib.keys - -KEYS = securesystemslib.keys -FORMAT_ERROR_MSG = ( - "securesystemslib.exceptions.FormatError was raised!" - + " Check object's format." -) -DATA_STR = "SOME DATA REQUIRING AUTHENTICITY." -DATA = securesystemslib.formats.encode_canonical(DATA_STR).encode("utf-8") - - -class TestKeys(unittest.TestCase): # pylint: disable=missing-class-docstring - @classmethod - def setUpClass(cls): - cls.rsakey_dict = KEYS.generate_rsa_key() - cls.ed25519key_dict = KEYS.generate_ed25519_key() - cls.ecdsakey_dict = KEYS.generate_ecdsa_key() - - def test_generate_rsa_key(self): - _rsakey_dict = KEYS.generate_rsa_key() # pylint: disable=invalid-name - - # Check if the format of the object returned by generate() corresponds - # to RSAKEY_SCHEMA format. - self.assertEqual( - None, - securesystemslib.formats.RSAKEY_SCHEMA.check_match(_rsakey_dict), - FORMAT_ERROR_MSG, - ) - - # Passing a bit value that is <2048 to generate() - should raise - # 'securesystemslib.exceptions.FormatError'. - self.assertRaises( - securesystemslib.exceptions.FormatError, KEYS.generate_rsa_key, 555 - ) - - # Passing a string instead of integer for a bit value. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.generate_rsa_key, - "bits", - ) - - # NOTE if random bit value >=2048 (not 4096) is passed generate(bits) - # does not raise any errors and returns a valid key. - self.assertTrue( - securesystemslib.formats.RSAKEY_SCHEMA.matches( - KEYS.generate_rsa_key(2048) - ) - ) - self.assertTrue( - securesystemslib.formats.RSAKEY_SCHEMA.matches( - KEYS.generate_rsa_key(4096) - ) - ) - - def test_generate_ecdsa_key(self): - _ecdsakey_dict = ( # pylint: disable=invalid-name - KEYS.generate_ecdsa_key() - ) - - # Check if the format of the object returned by generate_ecdsa_key() - # corresponds to ECDSAKEY_SCHEMA format. - self.assertEqual( - None, - securesystemslib.formats.ECDSAKEY_SCHEMA.check_match( - _ecdsakey_dict - ), - FORMAT_ERROR_MSG, - ) - - # Passing an invalid algorithm to generate() should raise - # 'securesystemslib.exceptions.FormatError'. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.generate_rsa_key, - "bad_algorithm", - ) - - # Passing a string instead of integer for a bit value. - self.assertRaises( - securesystemslib.exceptions.FormatError, KEYS.generate_rsa_key, 123 - ) - - def test_format_keyval_to_metadata(self): - keyvalue = self.rsakey_dict["keyval"] - keytype = self.rsakey_dict["keytype"] - scheme = self.rsakey_dict["scheme"] - - key_meta = KEYS.format_keyval_to_metadata(keytype, scheme, keyvalue) - - # Check if the format of the object returned by this function corresponds - # to KEY_SCHEMA format. - self.assertEqual( - None, - securesystemslib.formats.KEY_SCHEMA.check_match(key_meta), - FORMAT_ERROR_MSG, - ) - key_meta = KEYS.format_keyval_to_metadata( - keytype, scheme, keyvalue, private=True - ) - - # Check if the format of the object returned by this function corresponds - # to KEY_SCHEMA format. - self.assertEqual( - None, - securesystemslib.formats.KEY_SCHEMA.check_match(key_meta), - FORMAT_ERROR_MSG, - ) - - # Supplying a 'bad' keyvalue. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.format_keyval_to_metadata, - "bad_keytype", - scheme, - keyvalue, - private=True, - ) - - # Test for missing 'public' entry. - public = keyvalue["public"] - del keyvalue["public"] - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.format_keyval_to_metadata, - keytype, - scheme, - keyvalue, - ) - keyvalue["public"] = public - - # Test for missing 'private' entry. - private = keyvalue["private"] - del keyvalue["private"] - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.format_keyval_to_metadata, - keytype, - scheme, - keyvalue, - private=True, - ) - keyvalue["private"] = private - - def test_format_metadata_to_key(self): - # Copying self.rsakey_dict so that rsakey_dict remains - # unchanged during and after this test execution. - test_rsakey_dict = copy.copy(self.rsakey_dict) - del test_rsakey_dict["keyid"] - - # Call format_metadata_to_key by using the default value for keyid_hash_algorithms - rsakey_formatted, _ = KEYS.format_metadata_to_key(test_rsakey_dict) - - # Check if the format of the object returned by calling this function with - # default hash algorithms e.g. securesystemslib.settings.HASH_ALGORITHMS corresponds - # to RSAKEY_SCHEMA format. - self.assertTrue( - securesystemslib.formats.RSAKEY_SCHEMA.matches(rsakey_formatted), - FORMAT_ERROR_MSG, - ) - - self.assertTrue( - securesystemslib.formats.KEY_SCHEMA.matches(rsakey_formatted), - FORMAT_ERROR_MSG, - ) - - # Call format_metadata_to_key by using custom value for keyid_hash_algorithms - rsakey_dict_from_meta_custom, _ = KEYS.format_metadata_to_key( - test_rsakey_dict, keyid_hash_algorithms=["sha384"] - ) - - # Check if the format of the object returned by calling this function with - # custom hash algorithms corresponds to RSAKEY_SCHEMA format. - self.assertTrue( - securesystemslib.formats.RSAKEY_SCHEMA.matches( - rsakey_dict_from_meta_custom - ), - FORMAT_ERROR_MSG, - ) - - self.assertTrue( - securesystemslib.formats.KEY_SCHEMA.matches( - rsakey_dict_from_meta_custom - ), - FORMAT_ERROR_MSG, - ) - - test_rsakey_dict["keyid"] = self.rsakey_dict["keyid"] - - # Supplying a wrong number of arguments. - self.assertRaises(TypeError, KEYS.format_metadata_to_key) - args = (test_rsakey_dict, test_rsakey_dict) - self.assertRaises(TypeError, KEYS.format_metadata_to_key, *args) - - # Supplying a malformed argument to the function - should get FormatError - del test_rsakey_dict["keyval"] - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.format_metadata_to_key, - test_rsakey_dict, - ) - - def test_helper_get_keyid(self): - keytype = self.rsakey_dict["keytype"] - keyvalue = self.rsakey_dict["keyval"] - scheme = self.rsakey_dict["scheme"] - - # Check format of 'keytype'. - self.assertEqual( - None, - securesystemslib.formats.KEYTYPE_SCHEMA.check_match(keytype), - FORMAT_ERROR_MSG, - ) - - # Check format of 'keyvalue'. - self.assertEqual( - None, - securesystemslib.formats.KEYVAL_SCHEMA.check_match(keyvalue), - FORMAT_ERROR_MSG, - ) - - # Check format of 'scheme'. - self.assertEqual( - None, - securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme), - FORMAT_ERROR_MSG, - ) - - keyid = KEYS._get_keyid( # pylint: disable=protected-access - keytype, scheme, keyvalue - ) - - # Check format of 'keyid' - the output of '_get_keyid()' function. - self.assertEqual( - None, - securesystemslib.formats.KEYID_SCHEMA.check_match(keyid), - FORMAT_ERROR_MSG, - ) - - def test_create_signature(self): - # Creating a signature for 'DATA'. - rsa_signature = KEYS.create_signature(self.rsakey_dict, DATA) - ed25519_signature = KEYS.create_signature(self.ed25519key_dict, DATA) - - # Check format of output. - self.assertEqual( - None, - securesystemslib.formats.SIGNATURE_SCHEMA.check_match( - rsa_signature - ), - FORMAT_ERROR_MSG, - ) - self.assertEqual( - None, - securesystemslib.formats.SIGNATURE_SCHEMA.check_match( - ed25519_signature - ), - FORMAT_ERROR_MSG, - ) - - # Test for invalid signature scheme. - args = (self.rsakey_dict, DATA) - - valid_scheme = self.rsakey_dict["scheme"] - self.rsakey_dict["scheme"] = "invalid_scheme" - self.assertRaises( - securesystemslib.exceptions.UnsupportedAlgorithmError, - KEYS.create_signature, - *args, - ) - self.rsakey_dict["scheme"] = valid_scheme - - # Removing private key from 'rsakey_dict' - should raise a TypeError. - private = self.rsakey_dict["keyval"]["private"] - self.rsakey_dict["keyval"]["private"] = "" - - self.assertRaises(ValueError, KEYS.create_signature, *args) - - # Supplying an incorrect number of arguments. - self.assertRaises(TypeError, KEYS.create_signature) - self.rsakey_dict["keyval"]["private"] = private - - # Test generation of ECDSA signatures. - - # Creating a signature for 'DATA'. - ecdsa_signature = KEYS.create_signature(self.ecdsakey_dict, DATA) - - # Check format of output. - self.assertEqual( - None, - securesystemslib.formats.SIGNATURE_SCHEMA.check_match( - ecdsa_signature - ), - FORMAT_ERROR_MSG, - ) - - # Removing private key from 'ecdsakey_dict' - should raise a TypeError. - private = self.ecdsakey_dict["keyval"]["private"] - self.ecdsakey_dict["keyval"]["private"] = "" - - args = (self.ecdsakey_dict, DATA) - self.assertRaises(ValueError, KEYS.create_signature, *args) - - # Supplying an incorrect number of arguments. - self.assertRaises(TypeError, KEYS.create_signature) - self.ecdsakey_dict["keyval"]["private"] = private - - def test_verify_signature(self): # pylint: disable=too-many-statements - # Creating a signature of 'DATA' to be verified. - rsa_signature = KEYS.create_signature(self.rsakey_dict, DATA) - ed25519_signature = KEYS.create_signature(self.ed25519key_dict, DATA) - ecdsa_signature = KEYS.create_signature(self.ecdsakey_dict, DATA) - - # Verifying the 'signature' of 'DATA'. - verified = KEYS.verify_signature(self.rsakey_dict, rsa_signature, DATA) - self.assertTrue(verified, "Incorrect signature.") - - # Verifying the 'ed25519_signature' of 'DATA'. - verified = KEYS.verify_signature( - self.ed25519key_dict, ed25519_signature, DATA - ) - self.assertTrue(verified, "Incorrect signature.") - - # Verify that an invalid ed25519 signature scheme is rejected. - valid_scheme = self.ed25519key_dict["scheme"] - self.ed25519key_dict["scheme"] = "invalid_scheme" - self.assertRaises( - securesystemslib.exceptions.UnsupportedAlgorithmError, - KEYS.verify_signature, - self.ed25519key_dict, - ed25519_signature, - DATA, - ) - self.ed25519key_dict["scheme"] = valid_scheme - - # Verifying the 'ecdsa_signature' of 'DATA'. - verified = KEYS.verify_signature( - self.ecdsakey_dict, ecdsa_signature, DATA - ) - self.assertTrue(verified, "Incorrect signature.") - - # Verifying the 'ecdsa_signature' of 'DATA' with an old-style key dict - old_key_dict = self.ecdsakey_dict.copy() - old_key_dict["keytype"] = "ecdsa-sha2-nistp256" - verified = KEYS.verify_signature(old_key_dict, ecdsa_signature, DATA) - self.assertTrue(verified, "Incorrect signature.") - - # Test for an invalid ecdsa signature scheme. - valid_scheme = self.ecdsakey_dict["scheme"] - self.ecdsakey_dict["scheme"] = "invalid_scheme" - self.assertRaises( - securesystemslib.exceptions.UnsupportedAlgorithmError, - KEYS.verify_signature, - self.ecdsakey_dict, - ecdsa_signature, - DATA, - ) - self.ecdsakey_dict["scheme"] = valid_scheme - - # Testing invalid signatures. Same signature is passed, with 'DATA' being - # different than the original 'DATA' that was used in creating the - # 'rsa_signature'. Function should return 'False'. - - # Modifying 'DATA'. - _DATA_STR = "1111" + DATA_STR + "1111" # pylint: disable=invalid-name - _DATA = securesystemslib.formats.encode_canonical( # pylint: disable=invalid-name - _DATA_STR - ).encode( - "utf-8" - ) - - # Verifying the 'signature' of modified '_DATA'. - verified = KEYS.verify_signature(self.rsakey_dict, rsa_signature, _DATA) - self.assertFalse(verified, "Returned 'True' on an incorrect signature.") - - verified = KEYS.verify_signature( - self.ed25519key_dict, ed25519_signature, _DATA - ) - self.assertFalse(verified, "Returned 'True' on an incorrect signature.") - - verified = KEYS.verify_signature( - self.ecdsakey_dict, ecdsa_signature, _DATA - ) - self.assertFalse(verified, "Returned 'True' on an incorrect signature.") - - # Modifying 'rsakey_dict' to pass an incorrect scheme. - valid_scheme = self.rsakey_dict["scheme"] - self.rsakey_dict["scheme"] = "Biff" - - args = (self.rsakey_dict, rsa_signature, DATA) - self.assertRaises( - securesystemslib.exceptions.UnsupportedAlgorithmError, - KEYS.verify_signature, - *args, - ) - - # Restore - self.rsakey_dict["scheme"] = valid_scheme - - # Verify that the KEYIDS of 'key_dict' and 'signature' match. - valid_keyid = self.rsakey_dict["keyid"] = "12345" - self.rsakey_dict["keyid"] = "bad123" - - self.assertRaises( - securesystemslib.exceptions.CryptoError, - KEYS.verify_signature, - self.rsakey_dict, - rsa_signature, - DATA, - ) - self.rsakey_dict["keyid"] = valid_keyid - - # Passing incorrect number of arguments. - self.assertRaises(TypeError, KEYS.verify_signature) - - # Verify that the pure python 'ed25519' base case (triggered if 'pynacl' - # is unavailable) is executed in securesystemslib.keys.verify_signature(). - KEYS._ED25519_CRYPTO_LIBRARY = ( # pylint: disable=protected-access - "invalid" - ) - KEYS._available_crypto_libraries = [ # pylint: disable=protected-access - "invalid" - ] - verified = KEYS.verify_signature( - self.ed25519key_dict, ed25519_signature, DATA - ) - self.assertTrue(verified, "Incorrect signature.") - - # Verify ecdsa key with HEX encoded keyval instead of PEM encoded keyval - ecdsa_key = KEYS.generate_ecdsa_key() - ecdsa_key["keyval"]["public"] = "abcd" - # sig is not important as long as keyid is the same as the one in ecdsa_key - sig = {"keyid": ecdsa_key["keyid"], "sig": "bb"} - with self.assertRaises(securesystemslib.exceptions.FormatError): - KEYS.verify_signature(ecdsa_key, sig, b"data") - - # Verify ed25519 key with PEM encoded keyval instead of HEX encoded keyval - ed25519 = KEYS.generate_ed25519_key() - ed25519["keyval"][ - "public" - ] = "-----BEGIN PUBLIC KEY-----\nfoo\n-----END PUBLIC KEY-----\n" - # sig is not important as long as keyid is the same as the one in ed25519 - sig = {"keyid": ed25519["keyid"], "sig": "bb"} - with self.assertRaises(securesystemslib.exceptions.FormatError): - KEYS.verify_signature(ed25519, sig, b"data") - - def test_create_rsa_encrypted_pem(self): - # Test valid arguments. - private = self.rsakey_dict["keyval"]["private"] - passphrase = "secret" - scheme = "rsassa-pss-sha256" - encrypted_pem = KEYS.create_rsa_encrypted_pem(private, passphrase) - self.assertTrue( - securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) - ) - self.assertTrue(KEYS.is_pem_private(encrypted_pem)) - - # Try to import the encrypted PEM file. - rsakey = KEYS.import_rsakey_from_private_pem( - encrypted_pem, scheme, passphrase - ) - self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(rsakey)) - - # Test improperly formatted arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.create_rsa_encrypted_pem, - 8, - passphrase, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.create_rsa_encrypted_pem, - private, - 8, - ) - - def test_import_rsakey_from_private_pem(self): - # Try to import an rsakey from a valid PEM. - private_pem = self.rsakey_dict["keyval"]["private"] - _ = KEYS.import_rsakey_from_private_pem(private_pem) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_private_pem, - 123, - ) - - def test_import_rsakey_from_public_pem(self): - # Try to import an rsakey from a public PEM. - pem = self.rsakey_dict["keyval"]["public"] - rsa_key = KEYS.import_rsakey_from_public_pem(pem) - - # Check if the format of the object returned by this function corresponds - # to 'securesystemslib.formats.RSAKEY_SCHEMA' format. - self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(rsa_key)) - - # Verify whitespace is stripped. - self.assertEqual( - rsa_key, KEYS.import_rsakey_from_public_pem(pem + "\n") - ) - - # Supplying a 'bad_pem' argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_public_pem, - "bad_pem", - ) - - # Supplying an improperly formatted PEM. - # Strip the PEM header and footer. - pem_header = "-----BEGIN PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_public_pem, - pem[len(pem_header) :], - ) - - pem_footer = "-----END PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_public_pem, - pem[: -len(pem_footer)], - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_public_pem, - 123, - ) - - def test_import_rsakey_from_pem(self): - # Try to import an rsakey from a public PEM. - public_pem = self.rsakey_dict["keyval"]["public"] - private_pem = self.rsakey_dict["keyval"]["private"] - public_rsakey = KEYS.import_rsakey_from_pem(public_pem) - private_rsakey = KEYS.import_rsakey_from_pem(private_pem) - - # Check if the format of the object returned by this function corresponds - # to 'securesystemslib.formats.RSAKEY_SCHEMA' format. - self.assertTrue( - securesystemslib.formats.RSAKEY_SCHEMA.matches(public_rsakey) - ) - self.assertTrue( - securesystemslib.formats.RSAKEY_SCHEMA.matches(private_rsakey) - ) - - # Verify whitespace is stripped. - self.assertEqual( - public_rsakey, KEYS.import_rsakey_from_pem(public_pem + "\n") - ) - self.assertEqual( - private_rsakey, KEYS.import_rsakey_from_pem(private_pem + "\n") - ) - - # Supplying a 'bad_pem' argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_pem, - "bad_pem", - ) - - # Supplying an improperly formatted public PEM. - # Strip the PEM header and footer. - pem_header = "-----BEGIN PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_pem, - public_pem[len(pem_header) :], - ) - - pem_footer = "-----END PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_pem, - public_pem[: -len(pem_footer)], - ) - - # Supplying an improperly formatted private PEM. - # Strip the PEM header and footer. - pem_header = "-----BEGIN PRIVATE KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_pem, - private_pem[len(pem_header) :], - ) - - pem_footer = "-----END PRIVATE KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_pem, - private_pem[: -len(pem_footer)], - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_rsakey_from_pem, - 123, - ) - - def test_import_ecdsakey_from_private_pem(self): - # Try to import an ecdsakey from a valid PEM. - private_pem = self.ecdsakey_dict["keyval"]["private"] - _ = KEYS.import_ecdsakey_from_private_pem(private_pem) - - # Test for an encrypted PEM. - scheme = "ecdsa-sha2-nistp256" - encrypted_pem = securesystemslib.ecdsa_keys.create_ecdsa_encrypted_pem( - private_pem, "password" - ) - _ = KEYS.import_ecdsakey_from_private_pem( - encrypted_pem.decode("utf-8"), scheme, "password" - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_private_pem, - 123, - ) - - def test_import_ecdsakey_from_public_pem(self): - # Try to import an ecdsakey from a public PEM. - pem = self.ecdsakey_dict["keyval"]["public"] - ecdsa_key = KEYS.import_ecdsakey_from_public_pem(pem) - - # Check if the format of the object returned by this function corresponds - # to 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. - self.assertTrue( - securesystemslib.formats.ECDSAKEY_SCHEMA.matches(ecdsa_key) - ) - - # Verify whitespace is stripped. - self.assertEqual( - ecdsa_key, KEYS.import_ecdsakey_from_public_pem(pem + "\n") - ) - - # Supplying a 'bad_pem' argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_public_pem, - "bad_pem", - ) - - # Supplying an improperly formatted PEM. Strip the PEM header and footer. - pem_header = "-----BEGIN PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_public_pem, - pem[len(pem_header) :], - ) - - pem_footer = "-----END PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_public_pem, - pem[: -len(pem_footer)], - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_public_pem, - 123, - ) - - def test_import_ecdsakey_from_pem(self): - # Try to import an ecdsakey from a public PEM. - public_pem = self.ecdsakey_dict["keyval"]["public"] - private_pem = self.ecdsakey_dict["keyval"]["private"] - public_ecdsakey = KEYS.import_ecdsakey_from_pem(public_pem) - private_ecdsakey = KEYS.import_ecdsakey_from_pem(private_pem) - - # Check if the format of the object returned by this function corresponds - # to 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. - self.assertTrue( - securesystemslib.formats.ECDSAKEY_SCHEMA.matches(public_ecdsakey) - ) - self.assertTrue( - securesystemslib.formats.ECDSAKEY_SCHEMA.matches(private_ecdsakey) - ) - - # Verify whitespace is stripped. - self.assertEqual( - public_ecdsakey, KEYS.import_ecdsakey_from_pem(public_pem + "\n") - ) - self.assertEqual( - private_ecdsakey, KEYS.import_ecdsakey_from_pem(private_pem + "\n") - ) - - # Supplying a 'bad_pem' argument. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_pem, - "bad_pem", - ) - - # Supplying an improperly formatted public PEM. Strip the PEM header and - # footer. - pem_header = "-----BEGIN PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_pem, - public_pem[len(pem_header) :], - ) - - pem_footer = "-----END PUBLIC KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_pem, - public_pem[: -len(pem_footer)], - ) - - # Supplying an improperly formatted private PEM. Strip the PEM header and - # footer. - pem_header = "-----BEGIN EC PRIVATE KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_pem, - private_pem[len(pem_header) :], - ) - - pem_footer = "-----END EC PRIVATE KEY-----" - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_pem, - private_pem[: -len(pem_footer)], - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.import_ecdsakey_from_pem, - 123, - ) - - def test_decrypt_key(self): - # Test valid arguments. - passphrase = "secret" - encrypted_key = KEYS.encrypt_key(self.rsakey_dict, passphrase) - decrypted_key = KEYS.decrypt_key(encrypted_key, passphrase) - - self.assertTrue( - securesystemslib.formats.ANYKEY_SCHEMA.matches(decrypted_key) - ) - - # Test improperly formatted arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.decrypt_key, - 8, - passphrase, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.decrypt_key, - encrypted_key, - 8, - ) - - def test_extract_pem(self): - # Normal case. - private_pem = KEYS.extract_pem( - self.rsakey_dict["keyval"]["private"], private_pem=True - ) - self.assertTrue( - securesystemslib.formats.PEMRSA_SCHEMA.matches(private_pem) - ) - - public_pem = KEYS.extract_pem( - self.rsakey_dict["keyval"]["public"], private_pem=False - ) - self.assertTrue( - securesystemslib.formats.PEMRSA_SCHEMA.matches(public_pem) - ) - - # Test encrypted private pem - encrypted_private_pem = KEYS.create_rsa_encrypted_pem(private_pem, "pw") - encrypted_private_pem_stripped = KEYS.extract_pem( - encrypted_private_pem, private_pem=True - ) - self.assertTrue( - securesystemslib.formats.PEMRSA_SCHEMA.matches( - encrypted_private_pem_stripped - ) - ) - - # Test for an invalid PEM. - pem_header = "-----BEGIN RSA PRIVATE KEY-----" - pem_footer = "-----END RSA PRIVATE KEY-----" - - private_header_start = private_pem.index(pem_header) - private_footer_start = private_pem.index( - pem_footer, private_header_start + len(pem_header) - ) - - private_missing_header = private_pem[ - private_header_start - + len(pem_header) : private_footer_start - + len(pem_footer) - ] - private_missing_footer = private_pem[ - private_header_start:private_footer_start - ] - - pem_header = "-----BEGIN PUBLIC KEY-----" - pem_footer = "-----END PUBLIC KEY-----" - - public_header_start = public_pem.index(pem_header) - public_footer_start = public_pem.index( - pem_footer, public_header_start + len(pem_header) - ) - - public_missing_header = public_pem[ - public_header_start - + len(pem_header) : public_footer_start - + len(pem_footer) - ] - public_missing_footer = public_pem[ - public_header_start:public_footer_start - ] - - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.extract_pem, - "invalid_pem", - private_pem=False, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.extract_pem, - public_missing_header, - private_pem=False, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.extract_pem, - private_missing_header, - private_pem=True, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.extract_pem, - public_missing_footer, - private_pem=False, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.extract_pem, - private_missing_footer, - private_pem=True, - ) - - def test_is_pem_public(self): - # Test for a valid PEM string. - public_pem = self.rsakey_dict["keyval"]["public"] - self.assertTrue(KEYS.is_pem_public(public_pem)) - - # Test for a valid non-public PEM string. - private_pem = self.rsakey_dict["keyval"]["private"] - self.assertFalse(KEYS.is_pem_public(private_pem)) - - # Test for an invalid PEM string. - self.assertRaises( - securesystemslib.exceptions.FormatError, KEYS.is_pem_public, 123 - ) - - def test_is_pem_private(self): - # Test for a valid PEM string. - private_pem_rsa = self.rsakey_dict["keyval"]["private"] - private_pem_ec = self.ecdsakey_dict["keyval"]["private"] - encrypted_private_pem_rsa = KEYS.create_rsa_encrypted_pem( - private_pem_rsa, "pw" - ) - - self.assertTrue(KEYS.is_pem_private(private_pem_rsa)) - self.assertTrue(KEYS.is_pem_private(private_pem_ec, "ec")) - self.assertTrue(KEYS.is_pem_private(encrypted_private_pem_rsa)) - - # Test for a valid non-private PEM string. - public_pem = self.rsakey_dict["keyval"]["public"] - public_pem_ec = self.ecdsakey_dict["keyval"]["public"] - self.assertFalse(KEYS.is_pem_private(public_pem)) - self.assertFalse(KEYS.is_pem_private(public_pem_ec, "ec")) - - # Test for unsupported keytype. - self.assertRaises( - securesystemslib.exceptions.FormatError, - KEYS.is_pem_private, - private_pem_rsa, - "bad_keytype", - ) - - # Test for an invalid PEM string. - self.assertRaises( - securesystemslib.exceptions.FormatError, KEYS.is_pem_private, 123 - ) - - -# Run the unit tests. -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_rsa_keys.py b/tests/test_rsa_keys.py deleted file mode 100755 index 03750dd2..00000000 --- a/tests/test_rsa_keys.py +++ /dev/null @@ -1,458 +0,0 @@ -""" - - test_rsa_keys.py - - - Vladimir Diaz - - - June 3, 2015. - - - See LICENSE for licensing information. - - - Test cases for 'rsa_keys.py'. -""" - -import unittest - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.serialization import load_pem_private_key - -import securesystemslib.exceptions -import securesystemslib.formats -import securesystemslib.hash -import securesystemslib.keys -import securesystemslib.rsa_keys - -( - public_rsa, - private_rsa, -) = securesystemslib.rsa_keys.generate_rsa_public_and_private() -FORMAT_ERROR_MSG = ( - "securesystemslib.exceptions.FormatError raised. Check object's format." -) - - -class TestRSA_keys( - unittest.TestCase -): # pylint: disable=missing-class-docstring,invalid-name - def setUp(self): - pass - - def test_generate_rsa_public_and_private(self): - pub, priv = securesystemslib.rsa_keys.generate_rsa_public_and_private() - - # Check format of 'pub' and 'priv'. - self.assertEqual( - None, - securesystemslib.formats.PEMRSA_SCHEMA.check_match(pub), - FORMAT_ERROR_MSG, - ) - self.assertEqual( - None, - securesystemslib.formats.PEMRSA_SCHEMA.check_match(priv), - FORMAT_ERROR_MSG, - ) - - # Check for an invalid "bits" argument. bits >= 2048. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.generate_rsa_public_and_private, - 1024, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.generate_rsa_public_and_private, - "2048", - ) - - def test_create_rsa_signature(self): - global private_rsa # pylint: disable=global-variable-not-assigned - global public_rsa # pylint: disable=global-variable-not-assigned - data = "The quick brown fox jumps over the lazy dog".encode("utf-8") - - for rsa_scheme in securesystemslib.keys.RSA_SIGNATURE_SCHEMES: - signature, scheme = securesystemslib.rsa_keys.create_rsa_signature( - private_rsa, data, rsa_scheme - ) - - # Verify format of returned values. - self.assertNotEqual(None, signature) - self.assertEqual( - None, - securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme), - FORMAT_ERROR_MSG, - ) - self.assertEqual(rsa_scheme, scheme) - - # Check for improperly formatted arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.create_rsa_signature, - 123, - data, - ) - - # Check for an unset private key. - self.assertRaises( - ValueError, - securesystemslib.rsa_keys.create_rsa_signature, - "", - data, - ) - - # Check for an invalid PEM. - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.create_rsa_signature, - "123", - data, - ) - - # Check for invalid 'data'. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.create_rsa_signature, - private_rsa, - "", - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.create_rsa_signature, - private_rsa, - 123, - ) - - # Check for a missing private key. - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.create_rsa_signature, - public_rsa, - data, - ) - - # Check for a TypeError by attempting to create a signature with an - # encrypted key. - encrypted_pem = securesystemslib.rsa_keys.create_rsa_encrypted_pem( - private_rsa, "pw" - ) - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.create_rsa_signature, - encrypted_pem, - data, - ) - - def test_verify_rsa_signature(self): - global public_rsa # pylint: disable=global-variable-not-assigned - global private_rsa # pylint: disable=global-variable-not-assigned - data = "The quick brown fox jumps over the lazy dog".encode("utf-8") - - for rsa_scheme in securesystemslib.keys.RSA_SIGNATURE_SCHEMES: - signature, scheme = securesystemslib.rsa_keys.create_rsa_signature( - private_rsa, data, rsa_scheme - ) - - valid_signature = securesystemslib.rsa_keys.verify_rsa_signature( - signature, scheme, public_rsa, data - ) - self.assertEqual(True, valid_signature) - - # Check for an invalid public key. - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.verify_rsa_signature, - signature, - scheme, - private_rsa, - data, - ) - - # Check for improperly formatted arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.verify_rsa_signature, - signature, - 123, - public_rsa, - data, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.verify_rsa_signature, - signature, - scheme, - 123, - data, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.verify_rsa_signature, - 123, - scheme, - public_rsa, - data, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.verify_rsa_signature, - signature, - "invalid_scheme", - public_rsa, - data, - ) - - # Check for invalid 'signature' and 'data' arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.verify_rsa_signature, - signature, - scheme, - public_rsa, - 123, - ) - - self.assertEqual( - False, - securesystemslib.rsa_keys.verify_rsa_signature( - signature, scheme, public_rsa, b"mismatched data" - ), - ) - - ( - mismatched_signature, - scheme, - ) = securesystemslib.rsa_keys.create_rsa_signature( - private_rsa, b"mismatched data" - ) - - self.assertEqual( - False, - securesystemslib.rsa_keys.verify_rsa_signature( - mismatched_signature, scheme, public_rsa, data - ), - ) - - def test_verify_rsa_pss_different_salt_lengths(self): - rsa_scheme = "rsassa-pss-sha256" - data = "The ancients say, salt length does not matter that much".encode( - "utf-8" - ) - - private_key = load_pem_private_key( - private_rsa.encode("utf-8"), - password=None, - backend=default_backend(), - ) - digest = securesystemslib.hash.digest_from_rsa_scheme( - rsa_scheme, "pyca_crypto" - ) - - # Make sure digest size and max salt length are not accidentally the same - self.assertNotEqual( - digest.algorithm.digest_size, - padding.calculate_max_pss_salt_length( - private_key, digest.algorithm - ), - ) - - # Sign with max salt length (briefly available in sslib v0.24.0): - max_salt_sig = private_key.sign( - data, - padding.PSS( - mgf=padding.MGF1(digest.algorithm), - salt_length=padding.PSS.MAX_LENGTH, - ), - digest.algorithm, - ) - - # Sign with salt length == digest length - fix_salt_sig, _ = securesystemslib.rsa_keys.create_rsa_signature( - private_rsa, data - ) - - # Verification infers salt length automatically and so works for both - for signature in (max_salt_sig, fix_salt_sig): - verified = securesystemslib.rsa_keys.verify_rsa_signature( - signature, rsa_scheme, public_rsa, data - ) - self.assertTrue(verified) - - def test_create_rsa_encrypted_pem(self): - global public_rsa # pylint: disable=global-variable-not-assigned - global private_rsa # pylint: disable=global-variable-not-assigned - - encrypted_pem = securesystemslib.rsa_keys.create_rsa_encrypted_pem( - private_rsa, "password" - ) - self.assertTrue( - securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) - ) - - # Test for invalid private key (via PEM). - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.create_rsa_encrypted_pem, - public_rsa, - "password", - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.create_rsa_encrypted_pem, - public_rsa, - 123, - ) - - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.create_rsa_encrypted_pem, - 123, - "password", - ) - - self.assertRaises( - ValueError, - securesystemslib.rsa_keys.create_rsa_encrypted_pem, - "", - "password", - ) - - def test_create_rsa_public_and_private_from_pem(self): - global public_rsa # pylint: disable=global-variable-not-assigned - global private_rsa # pylint: disable=global-variable-not-assigned - - ( - public, - private, - ) = securesystemslib.rsa_keys.create_rsa_public_and_private_from_pem( - private_rsa - ) - - self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(public)) - self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(private)) - - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.create_rsa_public_and_private_from_pem, - public_rsa, - ) - - def test_encrypt_key(self): - global public_rsa # pylint: disable=global-variable-not-assigned - global private_rsa # pylint: disable=global-variable-not-assigned - - key_object = { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyid": "1223", - "keyval": {"public": public_rsa, "private": private_rsa}, - } - - encrypted_key = securesystemslib.rsa_keys.encrypt_key( - key_object, "password" - ) - self.assertTrue( - securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key) - ) - - key_object["keyval"]["private"] = "" - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.rsa_keys.encrypt_key, - key_object, - "password", - ) - - def test_decrypt_key(self): - # Test for valid arguments. - global public_rsa # pylint: disable=global-variable-not-assigned - global private_rsa # pylint: disable=global-variable-not-assigned - passphrase = "pw" - - rsa_key = { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyid": "d62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d", - "keyval": {"public": public_rsa, "private": private_rsa}, - } - - encrypted_rsa_key = securesystemslib.rsa_keys.encrypt_key( - rsa_key, passphrase - ) - - _ = securesystemslib.rsa_keys.decrypt_key(encrypted_rsa_key, passphrase) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.decrypt_key, - "bad", - passphrase, - ) - - # Test for invalid encrypted content (i.e., invalid hmac and ciphertext.) - encryption_delimiter = ( - securesystemslib.rsa_keys._ENCRYPTION_DELIMITER # pylint: disable=protected-access - ) - salt, iterations, hmac, iv, ciphertext = encrypted_rsa_key.split( - encryption_delimiter - ) - - # Set an invalid hmac. The decryption routine sould raise a - # securesystemslib.exceptions.CryptoError exception because 'hmac' does not - # match the hmac calculated by the decryption routine. - bad_hmac = "12345abcd" - invalid_encrypted_rsa_key = ( - salt - + encryption_delimiter - + iterations - + encryption_delimiter - + bad_hmac - + encryption_delimiter - + iv - + encryption_delimiter - + ciphertext - ) - - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.decrypt_key, - invalid_encrypted_rsa_key, - passphrase, - ) - - # Test for invalid 'ciphertext' - bad_ciphertext = "12345abcde" - invalid_encrypted_rsa_key = ( - salt - + encryption_delimiter - + iterations - + encryption_delimiter - + hmac - + encryption_delimiter - + iv - + encryption_delimiter - + bad_ciphertext - ) - - self.assertRaises( - securesystemslib.exceptions.CryptoError, - securesystemslib.rsa_keys.decrypt_key, - invalid_encrypted_rsa_key, - passphrase, - ) - - -# Run the unit tests. -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_util.py b/tests/test_util.py index d94fb9bb..e108b348 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -23,7 +23,6 @@ import unittest import securesystemslib.hash -import securesystemslib.settings import securesystemslib.util from securesystemslib import exceptions, unittest_toolbox diff --git a/tox.ini b/tox.ini index e6b2996b..6ce07715 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 83 + coverage report -m --fail-under 75 [testenv:purepy311] deps =