From a4dae8e1ffa3087276fcb3f775ce8140513e8f4e Mon Sep 17 00:00:00 2001 From: Artiom Baloian Date: Wed, 2 Nov 2016 11:50:21 -0400 Subject: [PATCH] Moved pyca_crypto_keys, now it is common --- tests/test_pyca_crypto_keys.py | 2 +- tuf/keys.py | 14 +- tuf/pyca_crypto_keys.py | 1015 -------------------------------- 3 files changed, 8 insertions(+), 1023 deletions(-) delete mode 100755 tuf/pyca_crypto_keys.py diff --git a/tests/test_pyca_crypto_keys.py b/tests/test_pyca_crypto_keys.py index d46d7b28..c30a2c73 100755 --- a/tests/test_pyca_crypto_keys.py +++ b/tests/test_pyca_crypto_keys.py @@ -31,7 +31,7 @@ import tuf import tuf.log import tuf.tufformats -import tuf.pyca_crypto_keys as crypto_keys +import tuf.ssl_crypto.pyca_crypto_keys as crypto_keys logger = logging.getLogger('tuf.test_pyca_crypto_keys') diff --git a/tuf/keys.py b/tuf/keys.py index bde08bc0..829021f8 100755 --- a/tuf/keys.py +++ b/tuf/keys.py @@ -99,7 +99,7 @@ # Try to import TUF's pyca/Cryptography module (pyca_crypto_keys.py), which is # used for general-purpose cryptography and RSA. try: - import tuf.pyca_crypto_keys + import tuf.ssl_crypto.pyca_crypto_keys _available_crypto_libraries.append('pyca-cryptography') except ImportError: # pragma: no cover @@ -247,7 +247,7 @@ def generate_rsa_key(bits=_DEFAULT_RSA_KEY_BITS): # Unlike PyCrypto, PyCA Cryptography does not require 'bits' to be a multiple # 256. elif _RSA_CRYPTO_LIBRARY == 'pyca-cryptography': - public, private = tuf.pyca_crypto_keys.generate_rsa_public_and_private(bits) + public, private = tuf.ssl_crypto.pyca_crypto_keys.generate_rsa_public_and_private(bits) else: # pragma: no cover raise tuf.ssl_commons.exceptions.UnsupportedLibraryError('Invalid crypto' @@ -747,7 +747,7 @@ def create_signature(key_dict, data): sig, method = tuf.pycrypto_keys.create_rsa_signature(private, data.encode('utf-8')) elif _RSA_CRYPTO_LIBRARY == 'pyca-cryptography': - sig, method = tuf.pyca_crypto_keys.create_rsa_signature(private, data.encode('utf-8')) + sig, method = tuf.ssl_crypto.pyca_crypto_keys.create_rsa_signature(private, data.encode('utf-8')) else: # pragma: no cover raise tuf.ssl_commons.exceptions.UnsupportedLibraryError('Unsupported' @@ -896,7 +896,7 @@ def verify_signature(key_dict, signature, data): ' (tuf/settings.py) to use PyCrypto if that is available instead.') else: - valid_signature = tuf.pyca_crypto_keys.verify_rsa_signature(sig, method, + valid_signature = tuf.ssl_crypto.pyca_crypto_keys.verify_rsa_signature(sig, method, public, data) else: # pragma: no cover @@ -1005,7 +1005,7 @@ def import_rsakey_from_encrypted_pem(encrypted_pem, password): elif _RSA_CRYPTO_LIBRARY == 'pyca-cryptography': public, private = \ - tuf.pyca_crypto_keys.create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, + tuf.ssl_crypto.pyca_crypto_keys.create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, password) public = format_rsakey_from_pem(public)['keyval']['public'] private = extract_pem(private, private_pem=True) @@ -1278,7 +1278,7 @@ def encrypt_key(key_object, password): elif _GENERAL_CRYPTO_LIBRARY == 'pyca-cryptography': encrypted_key = \ - tuf.pyca_crypto_keys.encrypt_key(key_object, password) + tuf.ssl_crypto.pyca_crypto_keys.encrypt_key(key_object, password) # check_crypto_libraries() should have fully verified _GENERAL_CRYPTO_LIBRARY. else: # pragma: no cover @@ -1380,7 +1380,7 @@ def decrypt_key(encrypted_key, passphrase): elif _GENERAL_CRYPTO_LIBRARY == 'pyca-cryptography': key_object = \ - tuf.pyca_crypto_keys.decrypt_key(encrypted_key, passphrase) + tuf.ssl_crypto.pyca_crypto_keys.decrypt_key(encrypted_key, passphrase) # check_crypto_libraries() should have fully verified _GENERAL_CRYPTO_LIBRARY. else: # pragma: no cover diff --git a/tuf/pyca_crypto_keys.py b/tuf/pyca_crypto_keys.py deleted file mode 100755 index 1e0a01f9..00000000 --- a/tuf/pyca_crypto_keys.py +++ /dev/null @@ -1,1015 +0,0 @@ -#!/usr/bin/env python - -""" - - pyca_crypto_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_encrypted_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 - - TUF 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 (PBKDF1) + MD5. - """ - -# Help with Python 3 compatibility, where the print statement is a function, an -# implicit relative import is invalid, and the '/' operator performs true -# division. Example: print 'hello world' raises a 'SyntaxError' exception. -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division -from __future__ import unicode_literals - -import os -import binascii -import json - - -# Import pyca/cryptography routines needed to generate and load cryptographic -# keys in PEM format. -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.backends.interfaces import PEMSerializationBackend -from cryptography.hazmat.primitives.serialization import load_pem_private_key -from cryptography.hazmat.backends import default_backend - -# Import Exception classes need to catch pyca/cryptography exceptions. -import cryptography.exceptions - -# '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 rsa - -# PyCrypo's RSA module is needed to generate and import encrypted RSA keys. -# Generating and loading encrypted key files with pyca/cryptography will be -# added once these routines are supported. -import Crypto.PublicKey.RSA - -# 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 -from cryptography.hazmat.primitives import hmac - -# 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. -from cryptography.hazmat.primitives.asymmetric import padding - -# Import pyca/cryptography's Key Derivation Function (KDF) module. -# 'tuf.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 TUF 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 - -# 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 - -# 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. -from cryptography.hazmat.primitives.ciphers import modes - -# Import the TUF package and TUF-defined exceptions in __init__.py. -import tuf - -# Digest objects are needed to generate hashes. -import tuf.ssl_crypto.hash - -# Perform object format-checking. -import tuf.tufformats - -# Extract/reference the cryptography library settings. For example: -# 'settings.RSA_CRYPTO_LIBRARY' -from simple_settings import settings - -# Import routine to process key files containing JSON data. -import tuf.ssl_crypto.util - -# 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 'tuf.ssl_crypto.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) - >>> tuf.ssl_crypto.formats.PEMRSA_SCHEMA.matches(public) - True - >>> tuf.ssl_crypto.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. - - - tuf.ssl_commons.exceptions.FormatError, if 'bits' does not contain the correct format. - - - 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. - """ - - # Does 'bits' have the correct format? - # This check will ensure 'bits' conforms to 'tuf.ssl_crypto.formats.RSAKEYBITS_SCHEMA'. - # 'bits' must be an integer object, with a minimum value of 2048. - # Raise 'tuf.ssl_commons.exceptions.FormatError' if the check fails. - tuf.ssl_crypto.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 - # tuf.ssl_crypto.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()) - - # 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) - - return public_pem.decode(), private_pem.decode() - - - - - -def create_rsa_signature(private_key, data): - """ - - Generate an RSASSA-PSS signature. The signature, and the method (signature - algorithm) used, is returned as a (signature, method) 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') - >>> signature, method = create_rsa_signature(private, data) - >>> tuf.ssl_crypto.formats.NAME_SCHEMA.matches(method) - True - >>> method == 'RSASSA-PSS' - True - >>> tuf.ssl_crypto.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. - - - tuf.ssl_commons.exceptions.FormatError, if 'private_key' is improperly formatted. - - ValueError, if 'private_key' is unset. - - tuf.ssl_commons.exceptions.CryptoError, if the signature cannot be generated. - - - pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the - signature. - - - A (signature, method) tuple, where the signature is a string and the method - is 'RSASSA-PSS'. - """ - - # Does the arguments have the correct format? - # This check will ensure the arguments conform to 'tuf.ssl_crypto.formats.PEMRSA_SCHEMA'. - # and 'tuf.ssl_crypto.formats.DATA_SCHEMA' - # Raise 'tuf.ssl_commons.exceptions.FormatError' if the checks fail. - tuf.ssl_crypto.formats.PEMRSA_SCHEMA.check_match(private_key) - tuf.ssl_crypto.formats.DATA_SCHEMA.check_match(data) - - # Signing 'data' requires a private key. The 'RSASSA-PSS' signing method is - # the only method currently supported. - method = 'RSASSA-PSS' - 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 len(private_key): - - # Generate an RSSA-PSS signature. Raise 'tuf.ssl_commons.exceptions.CryptoError' for any of the - # expected exceptions raised by pyca/cryptography. - 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()) - - # Calculate the SHA256 hash of 'data' and generate the hash's PKCS1-PSS - # signature. - rsa_signer = \ - private_key_object.signer(padding.PSS(mgf=padding.MGF1(hashes.SHA256()), - salt_length=hashes.SHA256().digest_size), hashes.SHA256()) - - # If the PEM data could not be decrypted, or if its structure could not be - # decoded successfully. - except ValueError: #pragma: no cover - raise tuf.ssl_commons.exceptions.CryptoError('The private key (in PEM format) could not be' - ' deserialized.') - - # 'TypeError' 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: #pragma: no cover - raise tuf.ssl_commons.exceptions.CryptoError('The private key was unexpectedly encrypted.') - - # 'cryptography.exceptions.UnsupportedAlgorithm' 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 cryptography.exceptions.UnsupportedAlgorithm: #pragma: no cover - raise tuf.ssl_commons.exceptions.CryptoError('The private key is encrypted with an' - ' unsupported algorithm.') - - # Generate an RSSA-PSS signature. - rsa_signer.update(data) - signature = rsa_signer.finalize() - - else: - raise ValueError('The required private key is unset.') - - return signature, method - - - - - -def verify_rsa_signature(signature, signature_method, public_key, data): - """ - - Determine whether the corresponding private key of 'public_key' produced - 'signature'. verify_signature() will use the public key, signature method, - 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' - >>> signature, method = create_rsa_signature(private, data) - >>> verify_rsa_signature(signature, method, public, data) - True - >>> verify_rsa_signature(signature, method, public, b'bad_data') - False - - - signature: - An RSASSA PSS signature, as a string. This is the signature returned - by create_rsa_signature(). - - signature_method: - A string that indicates the signature algorithm used to generate - 'signature'. 'RSASSA-PSS' is currently supported. - - public_key: - The RSA public key, a string in PEM format. - - data: - Data used by tuf.keys.create_signature() to generate - 'signature'. 'data' (a string) is needed here to verify 'signature'. - - - tuf.ssl_commons.exceptions.FormatError, if 'signature', 'signature_method', 'public_key', or - 'data' are improperly formatted. - - tuf.ssl_commons.exceptions.UnknownMethodError, if the signing method used by - 'signature' is not one supported by tuf.keys.create_signature(). - - tuf.ssl_commons.exceptions.CryptoError, if the private key cannot be decoded or its key type - is unsupported. - - - pyca/cryptography's RSAPublicKey.verifier() called to do the actual - 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 'tuf.ssl_crypto.formats.PEMRSA_SCHEMA'. - # Raise 'tuf.ssl_commons.exceptions.FormatError' if the check fails. - tuf.ssl_crypto.formats.PEMRSA_SCHEMA.check_match(public_key) - - # Does 'signature_method' have the correct format? - tuf.ssl_crypto.formats.NAME_SCHEMA.check_match(signature_method) - - # Does 'signature' have the correct format? - tuf.ssl_crypto.formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature) - - # What about 'data'? - tuf.ssl_crypto.formats.DATA_SCHEMA.check_match(data) - - # Verify whether the private key of 'public_key' produced 'signature'. - # Before returning the 'valid_signature' Boolean result, ensure 'RSASSA-PSS' - # was used as the signing method. - valid_signature = False - - # Verify the expected 'signature_method' value. - if signature_method != 'RSASSA-PSS': - raise tuf.ssl_commons.exceptions.UnknownMethodError(signature_method) - - # 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()) - - # 'salt_length' is set to the digest size of the hashing algorithm (to - # match the default size used by 'tuf.pycrypto_keys.py'). - verifier = public_key_object.verifier(signature, - padding.PSS(mgf=padding.MGF1(hashes.SHA256()), - salt_length=hashes.SHA256().digest_size), - hashes.SHA256()) - - verifier.update(data) - - # verify() raises 'cryptograpahy.exceptions.InvalidSignature' if the - # signature is invalid. - try: - verifier.verify() - return True - - except cryptography.exceptions.InvalidSignature: - return False - - # Raised by load_pem_public_key(). - except (ValueError, cryptography.exceptions.UnsupportedAlgorithm) as e: - raise tuf.ssl_commons.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, where the private portion of the RSA key is - encrypted. The format of the encrypted PEM is PKCS8, while the encryption - algorithm used varies. pyca/cryptography will try to use the best - available encryption algorithm in this case. - - >>> public, private = generate_rsa_public_and_private(2048) - >>> passphrase = 'secret' - >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) - >>> tuf.ssl_crypto.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. - - - tuf.ssl_commons.exceptions.FormatError, if the arguments are improperly formatted. - - tuf.ssl_commons.exceptions.CryptoError, if 'private_key' (private PEM format) cannot be - deserialized. - - TypeError, if 'private_key' is unset. - - - pyca/cryptography's key serialization functions are called to the - PEM-formatted output. - - - A string in PEM format, where the private RSA key is encrypted. - Conforms to 'tuf.ssl_crypto.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 'tuf.ssl_commons.exceptions.FormatError' if the check fails. - tuf.ssl_crypto.formats.PEMRSA_SCHEMA.check_match(private_key) - - # Does 'passphrase' have the correct format? - tuf.ssl_crypto.formats.PASSWORD_SCHEMA.check_match(passphrase) - - # 'private_key' is in PEM format and unencrypted. The extracted key will be - # imported and converted to PyCA's RSA key object (. Use PyCA's - # private_bytes() method, with a passphrase specified, to create the expected - # format of the private key. In contrast, pycrypto_keys.py uses PBKDF1+MD5 - # to strengthen 'passphrase', and 3DES with CBC mode for encryption. - # 'private_key' may still be a NULL string after the - # 'tuf.ssl_crypto.formats.PEMRSA_SCHEMA' (i.e., 'private_key' has variable size and can - # be an empty string. - - if len(private_key): - try: - private_key = load_pem_private_key(private_key.encode('utf-8'), - password=None, - backend=default_backend()) - except ValueError: - raise tuf.ssl_commons.exceptions.CryptoError('The private key (in PEM format) could not be' - ' deserialized.') - - else: - raise TypeError('The required private key is unset.') - - encrypted_pem = \ - private_key.private_bytes(encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.BestAvailableEncryption(passphrase.encode('utf-8'))) - - return encrypted_pem.decode() - - - -def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, passphrase): - """ - - Generate public and private RSA keys from an encrypted PEM. - The public and private keys returned conform to 'tuf.ssl_crypto.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. - - The private key part of 'encrypted_pem' is encrypted. pyca/cryptography's - load_pem_private_key() method is used, where a passphrase is specified. 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 TUF 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_encrypted_pem(encrypted_pem, passphrase) - >>> tuf.ssl_crypto.formats.PEMRSA_SCHEMA.matches(returned_public) - True - >>> tuf.ssl_crypto.formats.PEMRSA_SCHEMA.matches(returned_private) - True - >>> public == returned_public - True - >>> private == returned_private - True - - - encrypted_pem: - A byte string in PEM format, where the private key is encrypted. It has - the form: - - '-----BEGIN RSA PRIVATE KEY-----\n - Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...' - - passphrase: - 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. - - - tuf.ssl_commons.exceptions.FormatError, if the arguments are improperly formatted. - - tuf.ssl_commons.exceptions.CryptoError, if the public and private RSA keys cannot be generated - from 'encrypted_pem', or exported in PEM format. - - - 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. - """ - - # Does 'encryped_pem' have the correct format? - # This check will ensure 'encrypted_pem' has the appropriate number - # of objects and object types, and that all dict keys are properly named. - # Raise 'tuf.ssl_commons.exceptions.FormatError' if the check fails. - tuf.ssl_crypto.formats.PEMRSA_SCHEMA.check_match(encrypted_pem) - - # Does 'passphrase' have the correct format? - tuf.ssl_crypto.formats.PASSWORD_SCHEMA.check_match(passphrase) - - # Generate a pyca/cryptography key object from 'encrypted_pem'. The - # generated PyCrypto 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(encrypted_pem.encode('utf-8'), - passphrase.encode('utf-8'), - 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, cryptography.exceptions.UnsupportedAlgorithm) as e: - # Raise 'tuf.ssl_commons.exceptions.CryptoError' and pyca/cryptography's exception message. Avoid - # propogating pyca/cryptography's exception trace to avoid revealing - # sensitive error. - raise tuf.ssl_commons.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()) - - # 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) - - 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 TUF 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 (PBKDF1) + MD5 to strengthen 'password', - encrypted TUF 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', \ - 'keyid': \ - 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ - 'keyval': {'public': \ - '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ - 'private': \ - '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} - >>> passphrase = 'secret' - >>> encrypted_key = encrypt_key(ed25519_key, passphrase) - >>> tuf.ssl_crypto.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8')) - True - - - key_object: - The TUF 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. - - - tuf.ssl_commons.exceptions.FormatError, if any of the arguments are improperly formatted or - 'key_object' does not contain the private portion of the key. - - tuf.ssl_commons.exceptions.CryptoError, if an ED25519 key in encrypted TUF format cannot be - created. - - - 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 'tuf.ssl_crypto.formats.ENCRYPTEDKEY_SCHEMA' format. - """ - - # 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 'tuf.ssl_commons.exceptions.FormatError' if the check fails. - tuf.ssl_crypto.formats.ANYKEY_SCHEMA.check_match(key_object) - - # Does 'password' have the correct format? - tuf.ssl_crypto.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 tuf.ssl_commons.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 TUF key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). - This function calls the appropriate cryptography module (i.e., - pyca_crypto_keys.py) to perform the decryption. - - Encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with - PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in - 'tuf.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', \ - '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) - >>> tuf.ssl_crypto.formats.ED25519KEY_SCHEMA.matches(decrypted_key) - True - >>> decrypted_key == ed25519_key - True - - - encrypted_key: - An encrypted TUF key (additional data is also included, such as salt, - number of password iterations used for the derived encryption key, etc) - of the form 'tuf.ssl_crypto.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. - - - tuf.ssl_commons.exceptions.FormatError, if the arguments are improperly formatted. - - tuf.ssl_commons.exceptions.CryptoError, if a TUF key cannot be decrypted from 'encrypted_key'. - - tuf.ssl_commons.exceptions.Error, if a valid TUF key object is not found in 'encrypted_key'. - - - 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 'tuf.ssl_crypto.formats.ANYKEY_SCHEMA' format. - """ - - # 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 'tuf.ssl_commons.exceptions.FormatError' if the check fails. - tuf.ssl_crypto.formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) - - # Does 'password' have the correct format? - tuf.ssl_crypto.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.decode('utf-8'), password) - - # Raise 'tuf.ssl_commons.exceptions.Error' if 'json_data' cannot be deserialized to a valid - # 'tuf.ssl_crypto.formats.ANYKEY_SCHEMA' key object. - key_object = tuf.ssl_crypto.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 'tuf.ssl_crypto.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': '...'} - - 'tuf.ssl_commons.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 - # 'pyca_crypto_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 = \ - cryptography.hazmat.primitives.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(). - - 'tuf.ssl_commons.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 'tuf.ssl_commons.exceptions.CryptoError', if 'file_contents' does not contains the expected - # data layout. - try: - salt, iterations, hmac, iv, ciphertext = \ - file_contents.split(_ENCRYPTION_DELIMITER) - - except ValueError: - raise tuf.ssl_commons.exceptions.CryptoError('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. - junk_old_salt, junk_old_iterations, 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 = \ - cryptography.hazmat.primitives.hmac.HMAC(symmetric_key, hashes.SHA256(), - backend=default_backend()) - generated_hmac_object.update(ciphertext) - generated_hmac = binascii.hexlify(generated_hmac_object.finalize()) - - - if not tuf.ssl_crypto.util.digests_are_equal(generated_hmac.decode(), hmac): - raise tuf.ssl_commons.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 'pyca_crypto_keys.py' as a standalone module: - # $ python pyca_crypto_keys.py - import doctest - doctest.testmod()