def verify_signature(public_key, scheme, signature, data): """ <Purpose> 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 <Arguments> 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. <Exceptions> 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. <Side Effects> nacl.signing.VerifyKey.verify() called if available, otherwise securesystemslib._vendor.ed25519.ed25519.checkvalid() called to do the verification. <Returns> 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: 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
def digest(algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY): """ <Purpose> Provide the caller with the ability to create digest objects without having to worry about crypto library availability or which library to use. The caller also has the option of specifying which hash algorithm and/or library to use. # Creation of a digest object using defaults or by specifying hash # algorithm and library. digest_object = securesystemslib.hash.digest() digest_object = securesystemslib.hash.digest('sha384') digest_object = securesystemslib.hash.digest('sha256', 'hashlib') # The expected interface for digest objects. digest_object.digest_size digest_object.hexdigest() digest_object.update('data') digest_object.digest() # Added hash routines by this module. digest_object = securesystemslib.hash.digest_fileobject(file_object) digest_object = securesystemslib.hash.digest_filename(filename) <Arguments> algorithm: The hash algorithm (e.g., 'md5', 'sha1', 'sha256'). hash_library: The crypto library to use for the given hash algorithm (e.g., 'hashlib'). <Exceptions> securesystemslib.exceptions.FormatError, if the arguments are improperly formatted. securesystemslib.exceptions.UnsupportedAlgorithmError, if an unsupported hashing algorithm is specified, or digest could not be generated with given the algorithm. securesystemslib.exceptions.UnsupportedLibraryError, if an unsupported library was requested via 'hash_library'. <Side Effects> None. <Returns> Digest object e.g. hashlib.new(algorithm) or PycaDiggestWrapper object """ # Are the arguments properly formatted? If not, raise # 'securesystemslib.exceptions.FormatError'. formats.NAME_SCHEMA.check_match(algorithm) formats.NAME_SCHEMA.check_match(hash_library) # Was a hashlib digest object requested and is it supported? # If so, return the digest object. if hash_library == 'hashlib' and hash_library in SUPPORTED_LIBRARIES: try: if algorithm == 'blake2b-256': return hashlib.new('blake2b', digest_size=32) else: return hashlib.new(algorithm) except (ValueError, TypeError): # ValueError: the algorithm value was unknown # TypeError: unexpected argument digest_size (on old python) raise exceptions.UnsupportedAlgorithmError(algorithm) # Was a pyca_crypto digest object requested and is it supported? elif hash_library == 'pyca_crypto' and hash_library in SUPPORTED_LIBRARIES: try: hash_algorithm = PYCA_DIGEST_OBJECTS_CACHE[algorithm]() return PycaDiggestWrapper( _pyca_hashes.Hash(hash_algorithm, default_backend())) except KeyError: raise exceptions.UnsupportedAlgorithmError(algorithm) # The requested hash library is not supported. else: raise exceptions.UnsupportedLibraryError( 'Unsupported' ' library requested. Supported hash' ' libraries: ' + repr(SUPPORTED_LIBRARIES))
def create_signature(public_key, private_key, data, scheme): """ <Purpose> 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 <Arguments> 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. <Exceptions> 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. <Side Effects> nacl.signing.SigningKey.sign() called to generate the actual signature. <Returns> 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 generate_public_and_private(scheme='ecdsa-sha2-nistp256'): """ <Purpose> 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 <Arguments> 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). <Exceptions> 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. <Side Effects> None. <Returns> 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()) public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) return public_pem.decode('utf-8'), private_pem.decode('utf-8')
def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'): """ <Purpose> 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 <Arguments> 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'. <Exceptions> 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. <Side Effects> None. <Returns> 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_rsa_signature(signature, signature_scheme, public_key, data): """ <Purpose> 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 <Arguments> 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'. <Exceptions> 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. <Side Effects> pyca/cryptography's RSAPublicKey.verifier() called to do the actual verification. <Returns> 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 set to the digest size of the # hashing algorithm. try: if signature_scheme.startswith('rsassa-pss'): public_key_object.verify( signature, data, padding.PSS(mgf=padding.MGF1(digest_obj.algorithm), salt_length=digest_obj.algorithm.digest_size), 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_signature(private_key, data, scheme='rsassa-pss-sha256'): """ <Purpose> 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 <Arguments> 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. <Exceptions> 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. <Side Effects> pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the signature. <Returns> 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): 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=digest_obj.algorithm.digest_size), 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( 'The private key' ' (in PEM format) could not be deserialized.') # '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('The private key was' ' unexpectedly encrypted.') # '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( 'The private key is' ' encrypted with an unsupported algorithm.') return signature, scheme