Ejemplo n.º 1
0
def _check_crypto_libraries():
    """ Ensure all the crypto libraries specified in tuf.conf are available. """

    # The checks below all raise 'tuf.CryptoError' if the RSA and ED25519
    # crypto libraries specified in 'tuf.conf.py' are not supported or
    # unavailable.  The appropriate error message is added to the exception.
    # The funcions of this module that depend on user-installed crypto libraries
    # should call this private function to ensure the called routine does not fail
    # with unpredictable exceptions in the event of a missing library.
    # The supported and available lists checked are populated when 'tuf.keys.py'
    # is imported.
    if _RSA_CRYPTO_LIBRARY not in _SUPPORTED_RSA_CRYPTO_LIBRARIES:
        message = 'The '+repr(_RSA_CRYPTO_LIBRARY)+' crypto library specified'+ \
          ' in "tuf.conf.RSA_CRYPTO_LIBRARY" is not supported.\n'+ \
          'Supported crypto libraries: '+repr(_SUPPORTED_RSA_CRYPTO_LIBRARIES)+'.'
        raise tuf.CryptoError(message)

    if _ED25519_CRYPTO_LIBRARY not in _SUPPORTED_ED25519_CRYPTO_LIBRARIES:
        message = 'The '+repr(_ED25519_CRYPTO_LIBRARY)+' crypto library specified'+\
          ' in "tuf.conf.ED25519_CRYPTO_LIBRARY" is not supported.\n'+ \
          'Supported crypto libraries: '+repr(_SUPPORTED_ED25519_CRYPTO_LIBRARIES)+'.'
        raise tuf.CryptoError(message)

    if _RSA_CRYPTO_LIBRARY not in _available_crypto_libraries:
        message = 'The '+repr(_RSA_CRYPTO_LIBRARY)+' crypto library specified'+ \
          ' in "tuf.conf.RSA_CRYPTO_LIBRARY" could not be imported.'
        raise tuf.CryptoError(message)

    if _ED25519_CRYPTO_LIBRARY not in _available_crypto_libraries:
        message = 'The '+repr(_ED25519_CRYPTO_LIBRARY)+' crypto library specified'+\
          ' in "tuf.conf.ED25519_CRYPTO_LIBRARY" could not be imported.'
        raise tuf.CryptoError(message)
Ejemplo n.º 2
0
def _decrypt(file_contents, password):
    """
  The corresponding decryption routine for _encrypt().

  'tuf.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.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.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.util.digests_are_equal(generated_hmac.decode(), hmac):
        raise tuf.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
Ejemplo n.º 3
0
def generate(bits=_DEFAULT_RSA_KEY_BITS):
    """
  <Purpose> 
    Generate public and private RSA keys, with modulus length 'bits'.
    In addition, a keyid used as an identifier for RSA keys is generated.
    The object returned conforms to tuf.formats.RSAKEY_SCHEMA and as the form:
    {'keytype': 'rsa',
     'keyid': keyid,
     'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
    
    The public and private keys are in PEM format and stored as strings.
  
  <Arguments>
    bits:
      The key size, or key length, of the RSA key.

  <Exceptions>
    tuf.CryptoError, if an exception occurs after calling evpy.envelope.keygen().

    tuf.FormatError, if 'bits' does not contain the correct format.

  <Side Effects>
    The RSA keys are generated by calling evpy.envelope.keygen().

  <Returns>
    A dictionary containing the RSA keys and other identifying information.
  
  """

    # Does 'bits' have the correct format?
    # This check will ensure 'bits' conforms to 'tuf.formats.RSAKEYBITS_SCHEMA'.
    # 'bits' must be an integer object, with a minimum value of 2048.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits)

    # Begin building the RSA key dictionary.
    rsakey_dict = {}
    keytype = 'rsa'

    # Generate the public and private keys.  'public_key' and 'private_key'
    # will both be strings containing RSA keys in PEM format.
    # The evpy.envelope module performs the actual key generation.  The
    # evpy.envelope.keygen() function returns a (public, private) tuple.

    try:
        public_key, private_key = evpy.envelope.keygen(bits, pem=True)
    except (evpy.envelope.EnvelopeError, evpy.envelope.KeygenError,
            MemoryError), e:
        raise tuf.CryptoError(e)
Ejemplo n.º 4
0
def verify_rsa_signature(signature, signature_method, public_key, data):
    """
  <Purpose>
    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

  <Arguments>
    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 object used by tuf.keys.create_signature() to generate
      'signature'.  'data' is needed here to verify the signature.

  <Exceptions>
    tuf.UnknownMethodError.  Raised if the signing method used by
    'signature' is not one supported by tuf.keys.create_signature().
    
    tuf.FormatError. Raised if 'signature', 'signature_method', or 'public_key'
    is improperly formatted.

  <Side Effects>
    Crypto.Signature.PKCS1_PSS.verify() called to do the actual 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 'tuf.formats.PEMRSA_SCHEMA'.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(public_key)

    # Does 'signature_method' have the correct format?
    tuf.formats.NAME_SCHEMA.check_match(signature_method)

    # Does 'signature' have the correct format?
    tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.check_match(signature)

    # 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 signature with PyCrypto if the signature method is valid,
    # otherwise raise 'tuf.UnknownMethodError'.
    if signature_method == 'RSASSA-PSS':
        try:
            rsa_key_object = Crypto.PublicKey.RSA.importKey(public_key)
            pkcs1_pss_verifier = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
            sha256_object = Crypto.Hash.SHA256.new(data)
            valid_signature = pkcs1_pss_verifier.verify(
                sha256_object, signature)

        except (ValueError, IndexError, TypeError) as e:
            message = 'The RSA signature could not be verified.'
            raise tuf.CryptoError(message)

    else:
        raise tuf.UnknownMethodError(signature_method)

    return valid_signature
Ejemplo n.º 5
0
def create_rsa_signature(private_key, data):
    """
  <Purpose>
    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' and 'data' to generate the
    signature.

    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.formats.NAME_SCHEMA.matches(method)
    True
    >>> method == 'RSASSA-PSS'
    True
    >>> tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.matches(signature)
    True

  <Arguments>
    private_key: 
      The private RSA key, a string in PEM format.

    data:
      Data object used by create_rsa_signature() to generate the signature.

  <Exceptions>
    tuf.FormatError, if 'private_key' is improperly formatted.
    
    TypeError, if 'private_key' is unset.

    tuf.CryptoError, if the signature cannot be generated. 

  <Side Effects>
    PyCrypto's 'Crypto.Signature.PKCS1_PSS' called to generate the signature.

  <Returns>
    A (signature, method) tuple, where the signature is a string and the method
    is 'RSASSA-PSS'.
  """

    # Does 'private_key' have the correct format?
    # This check will ensure 'private_key' conforms to 'tuf.formats.PEMRSA_SCHEMA'.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(private_key)

    # Signing the 'data' object requires a private key.
    # The 'RSASSA-PSS' (i.e., PyCrypto module) 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):
        # Calculate the SHA256 hash of 'data' and generate the hash's PKCS1-PSS
        # signature.

        # PyCrypto's expected exceptions when generating RSA key object:
        # "ValueError/IndexError/TypeError:  When the given key cannot be parsed
        # (possibly because the passphrase is wrong)."
        # If the passphrase is incorrect, PyCrypto returns: "RSA key format is not
        # supported".
        try:
            sha256_object = Crypto.Hash.SHA256.new(data)
            rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)

        except (ValueError, IndexError, TypeError) as e:
            message = 'Invalid private key or hash data: ' + str(e)
            raise tuf.CryptoError(message)

        # Generate RSSA-PSS signature.  Raise 'tuf.CryptoError' for the expected
        # PyCrypto exceptions.
        try:
            pkcs1_pss_signer = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
            signature = pkcs1_pss_signer.sign(sha256_object)

        except ValueError:  #pragma: no cover
            raise tuf.CryptoError(
                'The RSA key too small for given hash algorithm.')

        except TypeError:
            raise tuf.CryptoError('Missing required RSA private key.')

        except IndexError:  # pragma: no cover
            message = 'An RSA signature cannot be generated: ' + str(e)
            raise tuf.CryptoError(message)

    else:
        raise TypeError('The required private key is unset.')

    return signature, method
Ejemplo n.º 6
0
def create_from_encrypted_pem(encrypted_pem, passphrase):
    """
  <Purpose>
    Return an RSA key in 'tuf.formats.RSAKEY_SCHEMA' format, which has the
    form:
      {'keytype': 'rsa',
       'keyid': keyid,
       'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                  'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
    
    The RSAKEY_SCHEMA object is generated from a byte string in PEM format,
    where the private part of the RSA key is encrypted.  PyCrypto's importKey
    method is used, where a passphrase is specified.  PyCrypto uses PBKDF1+MD5
    to strengthen '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.  See 'keystore.py'.

  <Arguments>
    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.

  <Exceptions>
    TypeError, if a private key is not defined for 'rsakey_dict'.

    tuf.FormatError, if an incorrect format is found for the
    'rsakey_dict' object.

  <Side Effects>
    PyCrypto's 'Crypto.PublicKey.RSA.importKey()' called to perform the actual
    conversion from an encrypted RSA private key.

  <Returns>
    A dictionary in 'tuf.formats.RSAKEY_SCHEMA' 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.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(encrypted_pem)

    # Does 'passphrase' have the correct format?
    tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)

    keytype = 'rsa'
    rsakey_dict = {}

    try:
        rsa_key_object = Crypto.PublicKey.RSA.importKey(
            encrypted_pem, passphrase)
    except (ValueError, IndexError, TypeError), e:
        message = 'An RSA key object could not be generated from the encrypted '+\
          'PEM string.'
        # Raise 'tuf.CryptoError' instead of PyCrypto's exception to avoid
        # revealing sensitive error, such as a decryption error due to an
        # invalid passphrase.
        raise tuf.CryptoError(message)
Ejemplo n.º 7
0
def create_rsa_signature(private_key, data):
    """
  <Purpose>
    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' and 'data' to generate the
    signature.

    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'
    >>> signature, method = create_rsa_signature(private, data)
    >>> tuf.formats.NAME_SCHEMA.matches(method)
    True
    >>> method == 'PyCrypto-PKCS#1 PSS'
    True
    >>> tuf.formats.PYCRYPTOSIGNATURE_SCHEMA.matches(method)
    True

  <Arguments>
    private_key: 
      The private RSA key, a string in PEM format.

    data:
      Data object used by create_rsa_signature() to generate the signature.

  <Exceptions>
    tuf.FormatError, if 'private_key' is improperly formatted.
    
    TypeError, if 'private_key' is unset.

    tuf.CryptoError, if the signature cannot be generated. 

  <Side Effects>
    PyCrypto's 'Crypto.Signature.PKCS1_PSS' called to generate the signature.

  <Returns>
    A (signature, method) tuple, where the signature is a string and the method
    is 'PyCrypto-PKCS#1 PSS'.
  """

    # Does 'private_key' have the correct format?
    # This check will ensure 'private_key' conforms to 'tuf.formats.PEMRSA_SCHEMA'.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(private_key)

    # Signing the 'data' object requires a private key.
    # The 'PyCrypto-PKCS#1 PSS' (i.e., PyCrypto module) signing method is the
    # only method currently supported.
    method = 'PyCrypto-PKCS#1 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 explicit check
    # that 'private_key' is not '', we can/should check for a value and not
    # compare identities with the 'is' keyword.
    if len(private_key):
        # Calculate the SHA256 hash of 'data' and generate the hash's PKCS1-PSS
        # signature.
        try:
            rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
            sha256_object = Crypto.Hash.SHA256.new(data)
            pkcs1_pss_signer = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
            signature = pkcs1_pss_signer.sign(sha256_object)
        except (ValueError, IndexError, TypeError), e:
            message = 'An RSA signature could not be generated.'
            raise tuf.CryptoError(message)
Ejemplo n.º 8
0
def create_rsa_encrypted_pem(private_key, passphrase):
    """
  <Purpose>
    Return a string in PEM format, where the private part of the RSA key is
    encrypted.  The private part of the RSA key is encrypted by the Triple
    Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the
    mode of operation.  Password-Based Key Derivation Function 1 (PBKF1) + MD5
    is used to strengthen 'passphrase'.

    TODO: Generate encrypted PEM (that matches PyCrypto's) once support is
    added to pyca/cryptography.

    https://en.wikipedia.org/wiki/Triple_DES
    https://en.wikipedia.org/wiki/PBKDF2

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> passphrase = 'secret'
    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
    >>> tuf.formats.PEMRSA_SCHEMA.matches(encrypted_pem)
    True

  <Arguments>
    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.

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

    tuf.CryptoError, if an RSA key in encrypted PEM format cannot be created.

    TypeError, if 'private_key' is unset.

  <Side Effects>
    PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
    generation of the PEM-formatted output.

  <Returns>
    A string in PEM format, where the private RSA key is encrypted.
    Conforms to 'tuf.formats.PEMRSA_SCHEMA'.
  """

    I_TO_PRINT = TO_PRINT + uptane.YELLOW + '[create_rsa_encrypted_pem(private_key, passphrase)]: ' + uptane.ENDCOLORS
    #TODO: Print to be deleted
    print(
        str('%s %s' % (
            I_TO_PRINT,
            'Return a string in PEM format, where the private part of the RSA key is encrypted. The private part of the RSA key is encrypted by the Triple Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the mode of operation. Password-Based Key Derivation Function 1 (PBKF1) + MD5 is used to strengthen \'passphrase\'.'
        )))
    #TODO: Until here

    # 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.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(private_key)

    # Does 'passphrase' have the correct format?
    tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)

    # 'private_key' is in PEM format and unencrypted.  The extracted key will be
    # imported and converted to PyCrypto's RSA key object
    # (i.e., Crypto.PublicKey.RSA).  Use PyCrypto's exportKey method, with a
    # passphrase specified, to create the string.  PyCrypto uses PBKDF1+MD5 to
    # strengthen 'passphrase', and 3DES with CBC mode for encryption.
    # 'private_key' may still be a NULL string after the
    # 'tuf.formats.PEMRSA_SCHEMA' (i.e., 'private_key' has variable size and can
    # be an empty string.
    # TODO: Use PyCrypto to generate the encrypted PEM string.  Generating
    # encrypted PEMs appears currently unsupported by pyca/cryptography.
    if len(private_key):
        try:
            rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
            encrypted_pem = rsa_key_object.exportKey(format='PEM',
                                                     passphrase=passphrase)

        except (ValueError, IndexError, TypeError) as e:
            raise tuf.CryptoError(
                'An encrypted RSA key in PEM format cannot be'
                ' generated: ' + str(e))

    else:
        raise TypeError('The required private key is unset.')

    #TODO: Print to be deleted
    print(str('%s %s ' % (I_TO_PRINT, 'Returning encrypted_pem.decode()')))
    #TODO: Until here

    return encrypted_pem.decode()
Ejemplo n.º 9
0
def create_rsa_signature(private_key, data):
    """
  <Purpose>
    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.formats.NAME_SCHEMA.matches(method)
    True
    >>> method == 'RSASSA-PSS'
    True
    >>> tuf.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.

  <Exceptions>
    tuf.FormatError, if 'private_key' is improperly formatted.

    ValueError, if 'private_key' is unset.

    tuf.CryptoError, if the signature cannot be generated.

  <Side Effects>
    pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the
    signature.

  <Returns>
    A (signature, method) tuple, where the signature is a string and the method
    is 'RSASSA-PSS'.
  """

    I_TO_PRINT = TO_PRINT + uptane.YELLOW + '[create_rsa_signature(private_key, data)]: ' + uptane.ENDCOLORS
    #TODO: Print to be deleted
    print(
        str('%s %s %s %s %s' %
            (I_TO_PRINT,
             'Generating an RSASSA-PSS signature with private_key:',
             private_key, 'data:', data)))
    #TODO: Until here

    # Does the arguments have the correct format?
    # This check will ensure the arguments conform to 'tuf.formats.PEMRSA_SCHEMA'.
    # and 'tuf.formats.DATA_SCHEMA'
    # Raise 'tuf.FormatError' if the checks fail.
    tuf.formats.PEMRSA_SCHEMA.check_match(private_key)
    tuf.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.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.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.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.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.')

    #TODO: Print to be deleted
    print(str('%s %s ' % (I_TO_PRINT, 'Returning signature and method')))
    #TODO: Until here

    return signature, method
Ejemplo n.º 10
0
def _decrypt(file_contents, password):
    """
  The corresponding decryption routine for _encrypt().

  'tuf.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.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.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, derived_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.
    generated_hmac_object = Crypto.Hash.HMAC.new(derived_key, ciphertext,
                                                 Crypto.Hash.SHA256)
    generated_hmac = generated_hmac_object.hexdigest()

    if generated_hmac != hmac:
        raise tuf.CryptoError('Decryption failed.')

    # The following decryption routine assumes 'ciphertext' was encrypted with
    # AES-256.
    stateful_counter_128bit_blocks = Crypto.Util.Counter.new(
        128, initial_value=int(binascii.hexlify(iv), 16))
    aes_cipher = Crypto.Cipher.AES.new(derived_key,
                                       Crypto.Cipher.AES.MODE_CTR,
                                       counter=stateful_counter_128bit_blocks)
    try:
        key_plaintext = aes_cipher.decrypt(ciphertext)

    # PyCrypto does not document the exceptions that may be raised or under
    # what circumstances.  PyCrypto example given is to call decrypt() without
    # checking for exceptions.  Avoid propogating the exception trace and only
    # raise 'tuf.CryptoError', along with the cause of decryption failure.
    # Note: decryption failure, due to malicious ciphertext, should not occur here
    # if the hmac check above passed.
    except (ValueError, IndexError, TypeError) as e:  # pragma: no cover
        raise tuf.CryptoError('Decryption failed: ' + str(e))

    return key_plaintext
Ejemplo n.º 11
0
def create_signature(ed25519_key_dict, data, use_pynacl=False):
    """
  <Purpose>
    Return a signature dictionary of the form:
    {'keyid': 'a0469d9491e3c0b42dd41fe3455359dbacb3306b6e8fb59...',
     'method': 'ed25519-python',
     'sig': '4b3829671b2c6b90034518a918d2447c722474c878c2431dd...'}

     Note: 'method' may also be 'ed25519-pynacl', if the signature was created
     by the 'nacl' module.

    The signing process will use the public and seed key
    ed25519_key_dict['keyval']['private'],
    ed25519_key_dict['keyval']['public']
    
    and 'data' to generate the signature.
    
    >>> ed25519_key_dict = generate()
    >>> data = 'The quick brown fox jumps over the lazy dog.'
    >>> signature = create_signature(ed25519_key_dict, data)
    >>> tuf.formats.SIGNATURE_SCHEMA.matches(signature)
    True
    >>> len(signature['sig'])
    128
    >>> signature_pynacl = create_signature(ed25519_key_dict, data, True)
    >>> tuf.formats.SIGNATURE_SCHEMA.matches(signature_pynacl)
    True
    >>> len(signature_pynacl['sig'])
    128

  <Arguments>
    ed25519_key_dict:
      A dictionary containing the ed25519 keys and other identifying information.
      'ed25519_key_dict' has the form:
    
      {'keytype': 'ed25519',
       'keyid': keyid,
       'keyval': {'public': '876f5584a9db99b8546c0d8608d6...',
                  'private': 'bf7336055c7638276efe9afe039...'}}

      The public and private keys are 32-byte strings, although hexlified to 64
      bytes.

    data:
      Data object used by create_signature() to generate the signature.
    
    use_pynacl:
      True, if the ed25519 signature should be generated with PyNaCl.  False,
      if the signature should be generated with the pure Python implementation
      of ed25519 (much slower).

  <Exceptions>
    TypeError, if a private key is not defined for 'ed25519_key_dict'.

    tuf.FormatError, if an incorrect format is found for 'ed25519_key_dict'.

    tuf.CryptoError, if a signature cannot be created.

  <Side Effects>
    ed25519.ed25519.signature() or nacl.signing.SigningKey.sign() called to
    generate the actual signature.

  <Returns>
    A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'.
    ed25519 signatures are 64 bytes, however, the hexlified signature
    (128 bytes) is stored in the dictionary returned.
  """

    # Does 'ed25519_key_dict' have the correct format?
    # This check will ensure 'ed25519_key_dict' has the appropriate number
    # of objects and object types, and that all dict keys are properly named.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.ED25519KEY_SCHEMA.check_match(ed25519_key_dict)

    # Signing the 'data' object requires a seed and public key.
    # 'ed25519.ed25519.py' generates the actual 64-byte signature in pure Python.
    # nacl.signing.SigningKey.sign() generates the signature if 'use_pynacl'
    # is True.
    signature = {}
    private_key = ed25519_key_dict['keyval']['private']
    public_key = ed25519_key_dict['keyval']['public']
    private_key = binascii.unhexlify(private_key)
    public_key = binascii.unhexlify(public_key)

    keyid = ed25519_key_dict['keyid']
    method = None
    sig = 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 explicit check
    # that 'private_key' is not '', we can/should check for a value and not
    # compare identities with the 'is' keyword.
    if len(private_key):
        if use_pynacl:
            method = 'ed25519-pynacl'
            try:
                nacl_key = nacl.signing.SigningKey(private_key)
                nacl_sig = nacl_key.sign(data)
                sig = nacl_sig.signature
            except (ValueError, nacl.signing.CryptoError):
                message = 'An "ed25519-pynacl" signature could not be created.'
                raise tuf.CryptoError(message)

        # Generate an "ed25519-python" (i.e., pure python implementation) signature.
        else:
            # ed25519.ed25519.signature() requires both the seed and public keys.
            # It calculates the SHA512 of the seed key, which is 32 bytes.
            method = 'ed25519-python'
            try:
                sig = ed25519.ed25519.signature(data, private_key, public_key)
            except Exception, e:
                message = 'An "ed25519-python" signature could not be generated.'
                raise tuf.CryptoError(message)
Ejemplo n.º 12
0
def create_rsa_signature(private_key, data):
    """
  <Purpose>
    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.formats.NAME_SCHEMA.matches(method)
    True
    >>> method == 'RSASSA-PSS'
    True
    >>> tuf.formats.PYCRYPTOSIGNATURE_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.

  <Exceptions>
    tuf.FormatError, if 'private_key' is improperly formatted.
    
    TypeError, if 'private_key' is unset.

    tuf.CryptoError, if the signature cannot be generated. 

  <Side Effects>
    pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the
    signature.

  <Returns>
    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.formats.PEMRSA_SCHEMA'.
    # and 'tuf.formats.DATA_SCHEMA'
    # Raise 'tuf.FormatError' if the checks fail.
    tuf.formats.PEMRSA_SCHEMA.check_match(private_key)
    tuf.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):

        # pyca/cryptography's expected exceptions when generating RSA key object:
        # "ValueError/IndexError/TypeError:  When the given key cannot be parsed
        # (possibly because the passphrase is wrong)."
        # A passphrase or password is not used to 'private_key', since it should
        # not be encrypted.
        try:
            # 'private_key' (in PEM format) must 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())

        except ValueError:  #pragma: no cover
            raise tuf.CryptoError('The private key could not be deserialized.')

        except cryptography.exceptions.UnsupportedAlgorithm:  #pragma: no cover
            message = 'The private key is encrypted with an unsupported algorithm.'
            raise tuf.CryptoError(message)

        # Generate an RSSA-PSS signature.  Raise 'tuf.CryptoError' for the expected
        # pyca/cryptography exceptions.
        rsa_signer.update(data)
        signature = rsa_signer.finalize()

    else:
        raise TypeError('The required private key is unset.')

    return signature, method
Ejemplo n.º 13
0
def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem,
                                                     passphrase):
    """
  <Purpose>
    Generate public and private RSA keys from an encrypted PEM.
    The public and private keys returned conform to 'tuf.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.

    The private key part of 'encrypted_pem' is encrypted.  PyCrypto's importKey
    method is used, where a passphrase is specified.  PyCrypto uses PBKDF1+MD5
    to strengthen '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.  See 'keystore.py'.

    >>> 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.formats.PEMRSA_SCHEMA.matches(returned_public)
    True
    >>> tuf.formats.PEMRSA_SCHEMA.matches(returned_private)
    True
    >>> public == returned_public
    True
    >>> private == returned_private
    True
  
  <Arguments>
    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.

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

  <Side Effects>
    PyCrypto's 'Crypto.PublicKey.RSA.importKey()' called to perform the actual
    conversion from an encrypted RSA private key.

  <Returns>
    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.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(encrypted_pem)

    # Does 'passphrase' have the correct format?
    tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)

    try:
        rsa_key_object = Crypto.PublicKey.RSA.importKey(
            encrypted_pem, passphrase)
    except (ValueError, IndexError, TypeError), e:
        message = 'An RSA key object could not be generated from the encrypted '+\
          'PEM string.'
        # Raise 'tuf.CryptoError' instead of PyCrypto's exception to avoid
        # revealing sensitive error, such as a decryption error due to an
        # invalid passphrase.
        raise tuf.CryptoError(message)
Ejemplo n.º 14
0
def create_rsa_encrypted_pem(private_key, passphrase):
    """
  <Purpose>
    Return a string in PEM format, where the private part of the RSA key is
    encrypted.  The private part of the RSA key is encrypted by the Triple
    Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the 
    mode of operation.  Password-Based Key Derivation Function 1 (PBKF1) + MD5
    is used to strengthen 'passphrase'.

    https://en.wikipedia.org/wiki/Triple_DES
    https://en.wikipedia.org/wiki/PBKDF2

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> passphrase = 'secret'
    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
    >>> tuf.formats.PEMRSA_SCHEMA.matches(encrypted_pem)
    True

  <Arguments>
    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. 

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

    tuf.CryptoError, if an RSA key in encrypted PEM format cannot be created.

    TypeError, 'private_key' is unset. 

  <Side Effects>
    PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
    generation of the PEM-formatted output.

  <Returns>
    A string in PEM format, where the private RSA key is encrypted.
    Conforms to 'tuf.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.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(private_key)

    # Does 'passphrase' have the correct format?
    tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)

    # 'private_key' is in PEM format and unencrypted.  The extracted key will be
    # imported and converted to PyCrypto's RSA key object
    # (i.e., Crypto.PublicKey.RSA).  Use PyCrypto's exportKey method, with a
    # passphrase specified, to create the string.  PyCrypto uses PBKDF1+MD5 to
    # strengthen 'passphrase', and 3DES with CBC mode for encryption.
    # 'private_key' may still be a NULL string after the tuf.formats check.
    if len(private_key):
        try:
            rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
            encrypted_pem = rsa_key_object.exportKey(format='PEM',
                                                     passphrase=passphrase)
        except (ValueError, IndexError, TypeError), e:
            message = 'An encrypted RSA key in PEM format could not be generated.'
            raise tuf.CryptoError(message)
Ejemplo n.º 15
0
def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem,
                                                     passphrase):
    """
  <Purpose>
    Generate public and private RSA keys from an encrypted PEM.
    The public and private keys returned conform to 'tuf.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.

    The private key part of 'encrypted_pem' is encrypted.  PyCrypto's importKey
    method is used, where a passphrase is specified.  PyCrypto uses PBKDF1+MD5
    to strengthen '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.  See 'keystore.py'.

    >>> 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.formats.PEMRSA_SCHEMA.matches(returned_public)
    True
    >>> tuf.formats.PEMRSA_SCHEMA.matches(returned_private)
    True
    >>> public == returned_public
    True
    >>> private == returned_private
    True
  
  <Arguments>
    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.

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

    tuf.CryptoError, if the public and private RSA keys cannot be generated
    from 'encrypted_pem', or exported in PEM format.

  <Side Effects>
    PyCrypto's 'Crypto.PublicKey.RSA.importKey()' called to perform the actual
    conversion from an encrypted RSA private key.

  <Returns>
    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.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(encrypted_pem)

    # Does 'passphrase' have the correct format?
    tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)

    # Generate a PyCrypto 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:
        rsa_key_object = Crypto.PublicKey.RSA.importKey(
            encrypted_pem, passphrase)

    # PyCrypto's expected exceptions:
    # "ValueError/IndexError/TypeError:  When the given key cannot be parsed
    # (possibly because the passphrase is wrong)."
    # If the passphrase is incorrect, PyCrypto returns: "RSA key format is not
    # supported".
    except (ValueError, IndexError, TypeError) as e:
        message = 'RSA (public, private) tuple cannot be generated from the' +\
          ' encrypted PEM string: ' + str(e)
        # Raise 'tuf.CryptoError' and PyCrypto's exception message.  Avoid
        # propogating PyCrypto's exception trace to avoid revealing sensitive error.
        raise tuf.CryptoError(message)

    # Export the public and private halves of the PyCrypto RSA key object.  The
    # (public, private) tuple returned contains the public and private RSA keys
    # in PEM format, as strings.
    try:
        private = rsa_key_object.exportKey(format='PEM')
        rsa_pubkey = rsa_key_object.publickey()
        public = rsa_pubkey.exportKey(format='PEM')

    # PyCrypto raises 'ValueError' if the public or private keys cannot be
    # exported.  See 'Crypto.PublicKey.RSA'.  'ValueError' should not be raised
    # if the 'Crypto.PublicKey.RSA.importKey() call above passed.
    except (ValueError):  #pragma: no cover
        message = 'The public and private keys cannot be exported in PEM format.'
        raise tuf.CryptoError(message)

    return public.decode(), private.decode()
Ejemplo n.º 16
0
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.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.CryptoError' raised if the encryption fails.
  """

    # Generate a random initialization vector (IV).  The 'iv' is treated as the
    # initial counter block to a stateful counter block function (i.e.,
    # PyCrypto's 'Crypto.Util.Counter').  The AES block cipher operates on 128-bit
    # blocks, so generate a random 16-byte initialization block.  PyCrypto expects
    # the initial value of the stateful counter to be an integer.
    # 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.
    iv = Crypto.Random.new().read(16)
    stateful_counter_128bit_blocks = Crypto.Util.Counter.new(
        128, initial_value=int(binascii.hexlify(iv), 16))
    symmetric_key = derived_key_information['derived_key']
    aes_cipher = Crypto.Cipher.AES.new(symmetric_key,
                                       Crypto.Cipher.AES.MODE_CTR,
                                       counter=stateful_counter_128bit_blocks)

    # Use AES-256 to encrypt 'key_data'.  The key size determines how many cycle
    # repetitions are performed by AES, 14 cycles for 256-bit keys.
    try:
        ciphertext = aes_cipher.encrypt(key_data)

    # PyCrypto does not document the exceptions that may be raised or under
    # what circumstances.  PyCrypto example given is to call encrypt() without
    # checking for exceptions.  Avoid propogating the exception trace and only
    # raise 'tuf.CryptoError', along with the cause of encryption failure.
    except (ValueError, IndexError, TypeError) as e:
        message = 'The key data cannot be encrypted: ' + str(e)
        raise tuf.CryptoError(message)

    # 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.
    salt = derived_key_information['salt']
    hmac_object = Crypto.Hash.HMAC.new(symmetric_key, ciphertext,
                                       Crypto.Hash.SHA256)
    hmac = hmac_object.hexdigest()

    # 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 + _ENCRYPTION_DELIMITER + \
           binascii.hexlify(iv).decode() + _ENCRYPTION_DELIMITER + \
           binascii.hexlify(ciphertext).decode()
Ejemplo n.º 17
0
def create_signature(rsakey_dict, data):
    """
  <Purpose>
    Return a signature dictionary of the form:
    {'keyid': keyid,
     'method': 'PyCrypto-PKCS#1 PPS',
     'sig': sig}.

    The signing process will use the private key
    rsakey_dict['keyval']['private'] and 'data' to generate the signature.

    RFC3447 - RSASSA-PSS 
    http://www.ietf.org/rfc/rfc3447.txt

  <Arguments>
    rsakey_dict:
      A dictionary containing the RSA keys and other identifying information.
      'rsakey_dict' has the form:
    
      {'keytype': 'rsa',
       'keyid': keyid,
       'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                  'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}

      The public and private keys are in PEM format and stored as strings.

    data:
      Data object used by create_signature() to generate the signature.

  <Exceptions>
    TypeError, if a private key is not defined for 'rsakey_dict'.

    tuf.FormatError, if an incorrect format is found for the
    'rsakey_dict' object.

  <Side Effects>
    PyCrypto's 'Crypto.Signature.PKCS1_PSS' called to perform the actual
    signing.

  <Returns>
    A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'.

  """

    # Does 'rsakey_dict' have the correct format?
    # This check will ensure 'rsakey_dict' has the appropriate number
    # of objects and object types, and that all dict keys are properly named.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)

    # Signing the 'data' object requires a private key.
    # The 'PyCrypto-PKCS#1 PSS' (i.e., PyCrypto module) signing method is the
    # only method currently supported.
    signature = {}
    private_key = rsakey_dict['keyval']['private']
    keyid = rsakey_dict['keyid']
    method = 'PyCrypto-PKCS#1 PSS'
    sig = 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 explicit check
    # that 'private_key' is not '', we can/should check for a value and not
    # compare identities with the 'is' keyword.
    if len(private_key):
        # Calculate the SHA256 hash of 'data' and generate the hash's PKCS1-PSS
        # signature.
        try:
            rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
            sha256_object = Crypto.Hash.SHA256.new(data)
            pkcs1_pss_signer = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
            sig = pkcs1_pss_signer.sign(sha256_object)
        except (ValueError, IndexError, TypeError), e:
            message = 'An RSA signature could not be generated.'
            raise tuf.CryptoError(message)
Ejemplo n.º 18
0
def create_signature(public_key, private_key, data):
    """
  <Purpose>
    Return a (signature, method) tuple, where the method is 'ed25519' and is
    always generated by PyNaCl (i.e., 'nacl').  The signature returned conforms
    to 'tuf.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'
    >>> signature, method = \
        create_signature(public, private, data)
    >>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
    True
    >>> method == 'ed25519'
    True
    >>> signature, method = \
        create_signature(public, private, data)
    >>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
    True
    >>> method == '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.

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

    tuf.CryptoError, if a signature cannot be created.

  <Side Effects>
    nacl.signing.SigningKey.sign() called to generate the actual signature.

  <Returns>
    A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'.
    ed25519 signatures are 64 bytes, however, the hexlified signature is
    stored in the dictionary returned.
  """

    # Does 'public_key' have the correct format?
    # This check will ensure 'public_key' conforms to
    # 'tuf.formats.ED25519PUBLIC_SCHEMA', which must have length 32 bytes.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.ED25519PUBLIC_SCHEMA.check_match(public_key)

    # Is 'private_key' properly formatted?
    tuf.formats.ED25519SEED_SCHEMA.check_match(private_key)

    # Signing the 'data' object requires a seed and public key.
    # nacl.signing.SigningKey.sign() generates the signature.
    public = public_key
    private = private_key

    method = None
    signature = None

    # The private and public keys have been validated above by 'tuf.formats' and
    # should be 32-byte strings.
    method = 'ed25519'
    try:
        nacl_key = nacl.signing.SigningKey(private)
        nacl_sig = nacl_key.sign(data)
        signature = nacl_sig.signature

    except NameError:  # pragma: no cover
        message = 'The PyNaCl library and/or its dependencies unavailable.'
        raise tuf.UnsupportedLibraryError(message)

    except (ValueError, TypeError, nacl.exceptions.CryptoError) as e:
        message = 'An "ed25519" signature could not be created with PyNaCl.'
        raise tuf.CryptoError(message + str(e))

    return signature, method
Ejemplo n.º 19
0
def verify_signature(rsakey_dict, signature, data):
    """
  <Purpose>
    Determine whether the private key belonging to 'rsakey_dict' produced
    'signature'.  verify_signature() will use the public key found in
    'rsakey_dict', the 'method' and 'sig' objects contained in 'signature',
    and 'data' to complete the verification.  Type-checking performed on both
    'rsakey_dict' and 'signature'.

  <Arguments>
    rsakey_dict:
      A dictionary containing the RSA keys and other identifying information.
      'rsakey_dict' has the form:
     
      {'keytype': 'rsa',
       'keyid': keyid,
       'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                  'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}

      The public and private keys are in PEM format and stored as strings.
      
    signature:
      The signature dictionary produced by tuf.rsa_key.create_signature().
      'signature' has the form:
      {'keyid': keyid, 'method': 'method', 'sig': sig}.  Conformant to
      'tuf.formats.SIGNATURE_SCHEMA'.
      
    data:
      Data object used by tuf.rsa_key.create_signature() to generate
      'signature'.  'data' is needed here to verify the signature.

  <Exceptions>
    tuf.UnknownMethodError.  Raised if the signing method used by
    'signature' is not one supported by tuf.rsa_key.create_signature().
    
    tuf.FormatError. Raised if either 'rsakey_dict'
    or 'signature' do not match their respective tuf.formats schema.
    'rsakey_dict' must conform to 'tuf.formats.RSAKEY_SCHEMA'.
    'signature' must conform to 'tuf.formats.SIGNATURE_SCHEMA'.

  <Side Effects>
    Crypto.Signature.PKCS1_PSS.verify() called to do the actual verification.

  <Returns>
    Boolean.  True if the signature is valid, False otherwise.

  """

    # Does 'rsakey_dict' have the correct format?
    # This check will ensure 'rsakey_dict' has the appropriate number
    # of objects and object types, and that all dict keys are properly named.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)

    # Does 'signature' have the correct format?
    tuf.formats.SIGNATURE_SCHEMA.check_match(signature)

    # Using the public key belonging to 'rsakey_dict'
    # (i.e., rsakey_dict['keyval']['public']), verify whether 'signature'
    # was produced by rsakey_dict's corresponding private key
    # rsakey_dict['keyval']['private'].  Before returning the Boolean result,
    # ensure 'PyCrypto-PKCS#1 PSS' was used as the signing method.
    method = signature['method']
    sig = signature['sig']
    public_key = rsakey_dict['keyval']['public']
    valid_signature = False

    if method == 'PyCrypto-PKCS#1 PSS':
        try:
            rsa_key_object = Crypto.PublicKey.RSA.importKey(public_key)
            pkcs1_pss_verifier = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
            sha256_object = Crypto.Hash.SHA256.new(data)

            # The metadata stores signatures in hex.  Unhexlify and verify the
            # signature.
            signature = binascii.unhexlify(sig)
            valid_signature = pkcs1_pss_verifier.verify(
                sha256_object, signature)
        except (ValueError, IndexError, TypeError), e:
            message = 'The RSA signature could not be verified.'
            raise tuf.CryptoError(message)
Ejemplo n.º 20
0
def verify_rsa_signature(signature, signature_method, public_key, data):
    """
  <Purpose>
    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

  <Arguments>
    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'.

  <Exceptions>
    tuf.FormatError, if 'signature', 'signature_method', 'public_key', or
    'data' are improperly formatted.

    tuf.UnknownMethodError, if the signing method used by
    'signature' is not one supported by tuf.keys.create_signature().

    tuf.CryptoError, if the private key cannot be decoded or its key type
    is unsupported.

  <Side Effects>
    pyca/cryptography's RSAPublicKey.verifier() called to do the actual
    verification.

   <Returns>
    Boolean.  True if the signature is valid, False otherwise.
  """

    I_TO_PRINT = TO_PRINT + uptane.YELLOW + '[verify_rsa_signature(signature, signature_method, public_key, data)]: ' + uptane.ENDCOLORS
    #TODO: Print to be deleted
    print(
        str('%s %s %s %s %s %s %s %s %s' %
            (I_TO_PRINT, 'Verifying RSA public_key:', public_key,
             'has generate signature:', signature, 'signature_method:',
             signature_method, 'data:', data)))
    #TODO: Until here

    # Does 'public_key' have the correct format?
    # This check will ensure 'public_key' conforms to 'tuf.formats.PEMRSA_SCHEMA'.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(public_key)

    # Does 'signature_method' have the correct format?
    tuf.formats.NAME_SCHEMA.check_match(signature_method)

    # Does 'signature' have the correct format?
    tuf.formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature)

    # What about 'data'?
    tuf.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.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:
        raise tuf.CryptoError('The PEM could not be decoded successfully.')

    # Raised by load_pem_public_key().
    except cryptography.exceptions.UnsupportedAlgorithm:
        raise tuf.CryptoError('The private key type is not supported.')
Ejemplo n.º 21
0
def create_encrypted_pem(rsakey_dict, passphrase):
    """
  <Purpose>
    Return a string in PEM format, where the private part of the RSA key is
    encrypted.  The private part of the RSA key is encrypted by the Triple
    Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the 
    mode of operation.  Password-Based Key Derivation Function 1 (PBKF1) + MD5
    is used to strengthen 'passphrase'.

    https://en.wikipedia.org/wiki/Triple_DES
    https://en.wikipedia.org/wiki/PBKDF2

  <Arguments>
    rsakey_dict:
      A dictionary containing the RSA keys and other identifying information.
      'rsakey_dict' has the form:
    
      {'keytype': 'rsa',
       'keyid': keyid,
       'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                  'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}

      The public and private keys are in PEM format and stored as strings.

    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. 

  <Exceptions>
    TypeError, if a private key is not defined for 'rsakey_dict'.

    tuf.FormatError, if an incorrect format is found for 'rsakey_dict'.

  <Side Effects>
    PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
    generation of the PEM-formatted output.

  <Returns>
    A string in PEM format, where the private RSA key is encrypted.

  """

    # Does 'rsakey_dict' have the correct format?
    # This check will ensure 'rsakey_dict' has the appropriate number
    # of objects and object types, and that all dict keys are properly named.
    # Raise 'tuf.FormatError' if the check fails.
    tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)

    # Does 'signature' have the correct format?
    tuf.formats.PASSWORD_SCHEMA.check_match(passphrase)

    # Extract the private key from 'rsakey_dict', which is stored in PEM format
    # and unencrypted.  The extracted key will be imported and converted to
    # PyCrypto's RSA key object (i.e., Crypto.PublicKey.RSA).Use PyCrypto's
    # exportKey method, with a passphrase specified, to create the string.
    # PyCrypto uses PBKDF1+MD5 to strengthen 'passphrase', and 3DES with CBC mode
    # for encryption.
    private_key = rsakey_dict['keyval']['private']
    try:
        rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
        rsakey_pem_encrypted = rsa_key_object.exportKey(format='PEM',
                                                        passphrase=passphrase)
    except (ValueError, IndexError, TypeError), e:
        message = 'An encrypted RSA key in PEM format could not be generated.'
        raise tuf.CryptoError(message)
Ejemplo n.º 22
0
def create_rsa_public_and_private_from_encrypted_pem(encrypted_pem,
                                                     passphrase):
    """
  <Purpose>
    Generate public and private RSA keys from an encrypted PEM.
    The public and private keys returned conform to 'tuf.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.formats.PEMRSA_SCHEMA.matches(returned_public)
    True
    >>> tuf.formats.PEMRSA_SCHEMA.matches(returned_private)
    True
    >>> public == returned_public
    True
    >>> private == returned_private
    True

  <Arguments>
    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.

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

    tuf.CryptoError, if the public and private RSA keys cannot be generated
    from 'encrypted_pem', or exported in PEM format.

  <Side Effects>
    pyca/cryptography's 'serialization.load_pem_private_key()' called to
    perform the actual conversion from an encrypted RSA private key to
    PEM format.

  <Returns>
    A (public, private) tuple containing the RSA keys in PEM format.
  """

    I_TO_PRINT = TO_PRINT + uptane.YELLOW + '[create_rsa_public_and_private_from_encrypted_pem(encrypted_pem, passphrase)]: ' + uptane.ENDCOLORS
    #TODO: Print to be deleted
    print(
        str('%s %s %s %s %s' %
            (I_TO_PRINT,
             'Creating RSA public and private keys from encrypted_pem:',
             encrypted_pem, 'passphrase:', passphrase)))
    #TODO: Until here

    # 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.FormatError' if the check fails.
    tuf.formats.PEMRSA_SCHEMA.check_match(encrypted_pem)

    # Does 'passphrase' have the correct format?
    tuf.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.CryptoError' and pyca/cryptography's exception message.  Avoid
        # propogating pyca/cryptography's exception trace to avoid revealing
        # sensitive error.
        raise tuf.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)

    #TODO: Print to be deleted
    print(
        str('%s %s ' %
            (I_TO_PRINT,
             'Returning public_pem.decode(), private_pem.decode()')))
    #TODO: Until here

    return public_pem.decode(), private_pem.decode()
Ejemplo n.º 23
0
def create_signature(public_key, private_key, data, use_pynacl=False):
  """
  <Purpose>
    Return a (signature, method) tuple, where the method is either:
    'ed25519-python' if the signature is generated by the pure python
    implemenation, or 'ed25519-pynacl' if generated by 'nacl'.
    signature conforms to 'tuf.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(use_pynacl=False)
    >>> data = 'The quick brown fox jumps over the lazy dog'
    >>> signature, method = \
        create_signature(public, private, data, use_pynacl=False)
    >>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
    True
    >>> method == 'ed25519-python'
    True
    >>> signature, method = \
        create_signature(public, private, data, use_pynacl=True)
    >>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature)
    True
    >>> method == 'ed25519-pynacl'
    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.
    
    use_pynacl:
      True, if the ed25519 signature should be generated with PyNaCl.  False,
      if the signature should be generated with the pure Python implementation
      of ed25519 (much slower).

  <Exceptions>
    tuf.FormatError, if the arguments are improperly formatted.

    tuf.CryptoError, if a signature cannot be created.

  <Side Effects>
    ed25519.ed25519.signature() or nacl.signing.SigningKey.sign() called to
    generate the actual signature.

  <Returns>
    A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'.
    ed25519 signatures are 64 bytes, however, the hexlified signature is
    stored in the dictionary returned.
  """
  
  # Does 'public_key' have the correct format?
  # This check will ensure 'public_key' conforms to
  # 'tuf.formats.ED25519PUBLIC_SCHEMA', which must have length 32 bytes.
  # Raise 'tuf.FormatError' if the check fails.
  tuf.formats.ED25519PUBLIC_SCHEMA.check_match(public_key)

  # Is 'private_key' properly formatted?
  tuf.formats.ED25519SEED_SCHEMA.check_match(private_key)
  
  # Is 'use_pynacl' properly formatted?
  tuf.formats.TOGGLE_SCHEMA.check_match(use_pynacl)
  
  # Signing the 'data' object requires a seed and public key.
  # 'ed25519.ed25519.py' generates the actual 64-byte signature in pure Python.
  # nacl.signing.SigningKey.sign() generates the signature if 'use_pynacl'
  # is True.
  public = public_key
  private = private_key

  method = None 
  signature = None
 
  # The private and public keys have been validated above by 'tuf.formats' and
  # should be 32-byte strings.
  if use_pynacl:
    method = 'ed25519-pynacl'
    try:
      nacl_key = nacl.signing.SigningKey(private)
      nacl_sig = nacl_key.sign(data)
      signature = nacl_sig.signature
    
    except NameError:
      message = 'The PyNaCl library and/or its dependencies unavailable.'
      raise tuf.UnsupportedLibraryError(message)
    
    except (ValueError, nacl.signing.CryptoError):
      message = 'An "ed25519-pynacl" signature could not be created.'
      raise tuf.CryptoError(message)
   
  # Generate an "ed25519-python" (i.e., pure python implementation) signature.
  else:
    # ed25519.ed25519.signature() requires both the seed and public keys.
    # It calculates the SHA512 of the seed key, which is 32 bytes.
    method = 'ed25519-python'
    try:
      signature = ed25519.ed25519.signature(data, private, public)
   
    # 'Exception' raised by ed25519.py for any exception that may occur.
    except Exception, e:
      message = 'An "ed25519-python" signature could not be generated.'
      raise tuf.CryptoError(message)