Exemplo n.º 1
0
def _decrypt(file_contents, password):
    """
  The corresponding decryption routine for _encrypt().

  'securesystemslib.exceptions.CryptoError' raised if the decryption fails.
  """

    # Extract the salt, iterations, hmac, initialization vector, and ciphertext
    # from 'file_contents'.  These five values are delimited by
    # '_ENCRYPTION_DELIMITER'.  This delimiter is arbitrarily chosen and should
    # not occur in the hexadecimal representations of the fields it is
    # separating.  Raise 'securesystemslib.exceptions.CryptoError', if
    # 'file_contents' does not contains the expected data layout.
    try:
        salt, iterations, read_hmac, iv, ciphertext = \
          file_contents.split(_ENCRYPTION_DELIMITER)

    except ValueError:
        raise exceptions.CryptoError('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 = hmac.HMAC(symmetric_key,
                                      hashes.SHA256(),
                                      backend=default_backend())
    generated_hmac_object.update(ciphertext)
    generated_hmac = binascii.hexlify(generated_hmac_object.finalize())

    if not util.digests_are_equal(generated_hmac.decode(), read_hmac):
        raise exceptions.CryptoError('Decryption failed.')

    # Construct a Cipher object, with the key and iv.
    decryptor = Cipher(algorithms.AES(symmetric_key),
                       modes.CTR(iv),
                       backend=default_backend()).decryptor()

    # Decryption gets us the authenticated plaintext.
    plaintext = decryptor.update(ciphertext) + decryptor.finalize()

    return plaintext
Exemplo n.º 2
0
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
Exemplo n.º 3
0
def create_ecdsa_public_and_private_from_pem(pem, password=None):
    """
  <Purpose>
    Create public and private ECDSA keys from a private 'pem'.  The public and
    private keys are strings in PEM format:

    public: '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----',
    private: '-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----'}}

    >>> junk, private = generate_public_and_private()
    >>> public, private = create_ecdsa_public_and_private_from_pem(private)
    >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public)
    True
    >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private)
    True
    >>> passphrase = 'secret'
    >>> encrypted_pem = create_ecdsa_encrypted_pem(private, passphrase)
    >>> public, private = create_ecdsa_public_and_private_from_pem(encrypted_pem, passphrase)
    >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public)
    True
    >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private)
    True

  <Arguments>
    pem:
      A string in PEM format.  The private key is extracted and returned in
      an ecdsakey object.

    password: (optional)
      The password, or passphrase, to decrypt the private part of the ECDSA key
      if it is encrypted.  'password' is not used directly as the encryption
      key, a stronger encryption key is derived from it.

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

    securesystemslib.exceptions.UnsupportedAlgorithmError, if the ECDSA key
    pair could not be extracted, possibly due to an unsupported algorithm.

    securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
    module is not available.

  <Side Effects>
    None.

  <Returns>
    A dictionary containing the ECDSA keys and other identifying information.
    Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'.
  """

    if not CRYPTO:  # pragma: no cover
        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

    # Does 'pem' have the correct format?
    # This check will ensure 'pem' conforms to
    # 'securesystemslib.formats.ECDSARSA_SCHEMA'.
    formats.PEMECDSA_SCHEMA.check_match(pem)

    if password is not None:
        formats.PASSWORD_SCHEMA.check_match(password)
        password = password.encode('utf-8')

    else:
        logger.debug('The password/passphrase is unset.  The PEM is expected'
                     ' to be unencrypted.')

    public = None
    private = None

    # Generate the public and private ECDSA keys.  The pyca/cryptography library
    # performs the actual import operation.
    try:
        private = load_pem_private_key(pem.encode('utf-8'),
                                       password=password,
                                       backend=default_backend())

    except (ValueError, UnsupportedAlgorithm) as e:
        raise exceptions.CryptoError('Could not import private'
                                     ' PEM.\n' + str(e))

    public = private.public_key()

    # Serialize public and private keys to PEM format.
    private = private.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption())

    public = public.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo)

    return public.decode('utf-8'), private.decode('utf-8')
Exemplo n.º 4
0
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
Exemplo n.º 5
0
def create_rsa_public_and_private_from_pem(pem, passphrase=None):
    """
  <Purpose>
    Generate public and private RSA keys from an optionally encrypted PEM.  The
    public and private keys returned conform to
    'securesystemslib.formats.PEMRSA_SCHEMA' and have the form:

    '-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----'

    and

    '-----BEGIN RSA PRIVATE KEY----- ...-----END RSA PRIVATE KEY-----'

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

    In case the private key part of 'pem' is encrypted  pyca/cryptography's
    load_pem_private_key() method is passed passphrase.  In the default case
    here, pyca/cryptography will decrypt with a PBKDF1+MD5
    strengthened'passphrase', and 3DES with CBC mode for encryption/decryption.
    Alternatively, key data may be encrypted with AES-CTR-Mode and the
    passphrase strengthened with PBKDF2+SHA256, although this method is used
    only with securesystemslib encrypted key files.

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> passphrase = 'secret'
    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
    >>> returned_public, returned_private = \
    create_rsa_public_and_private_from_pem(encrypted_pem, passphrase)
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_public)
    True
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_private)
    True
    >>> public == returned_public
    True
    >>> private == returned_private
    True

  <Arguments>
    pem:
      A byte string in PEM format, where the private key can be encrypted.
      It has the form:

      '-----BEGIN RSA PRIVATE KEY-----\n
      Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...'

    passphrase: (optional)
      The passphrase, or password, to decrypt the private part of the RSA
      key.  'passphrase' is not directly used as the encryption key, instead
      it is used to derive a stronger symmetric key.

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

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

    securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
    module is not available.

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

    if not CRYPTO:  # pragma: no cover
        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

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

    # If passed, does 'passphrase' have the correct format?
    if passphrase is not None:
        formats.PASSWORD_SCHEMA.check_match(passphrase)
        passphrase = passphrase.encode('utf-8')

    # Generate a pyca/cryptography key object from 'pem'.  The generated
    # pyca/cryptography key contains the required export methods needed to
    # generate the PEM-formatted representations of the public and private RSA
    # key.
    try:
        private_key = load_pem_private_key(pem.encode('utf-8'),
                                           passphrase,
                                           backend=default_backend())

    # pyca/cryptography's expected exceptions for 'load_pem_private_key()':
    # ValueError: If the PEM data could not be decrypted.
    # (possibly because the passphrase is wrong)."
    # TypeError: If a password was given and the private key was not encrypted.
    # Or if the key was encrypted but no password was supplied.
    # UnsupportedAlgorithm: If the private key (or if the key is encrypted with
    # an unsupported symmetric cipher) is not supported by the backend.
    except (ValueError, TypeError, UnsupportedAlgorithm) as e:
        # Raise 'securesystemslib.exceptions.CryptoError' and pyca/cryptography's
        # exception message.  Avoid propogating pyca/cryptography's exception trace
        # to avoid revealing sensitive error.
        raise exceptions.CryptoError(
            'RSA (public, private) tuple'
            ' cannot be generated from the encrypted PEM string: ' + str(e))

    # Export the public and private halves of the pyca/cryptography RSA key
    # object.  The (public, private) tuple returned contains the public and
    # private RSA keys in PEM format, as strings.
    # Extract the public & private halves of the RSA key and generate their
    # PEM-formatted representations.  Return the key pair as a (public, private)
    # tuple, where each RSA is a string in PEM format.
    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption())

    # Need to generate the public key from the private one before serializing
    # to PEM format.
    public_key = private_key.public_key()
    public_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo)

    return public_pem.decode(), private_pem.decode()
Exemplo n.º 6
0
def create_rsa_encrypted_pem(private_key, passphrase):
    """
  <Purpose>
    Return a string in PEM format (TraditionalOpenSSL), where the private part
    of the RSA key is encrypted using the best available encryption for a given
    key's backend. This is a curated (by cryptography.io) encryption choice and
    the algorithm may change over time.

    c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/
        #cryptography.hazmat.primitives.serialization.BestAvailableEncryption

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

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

    passphrase:
      The passphrase, or password, to encrypt the private part of the RSA
      key.

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

    securesystemslib.exceptions.CryptoError, if the passed RSA key cannot be
        deserialized by pyca cryptography.

    ValueError, if 'private_key' is unset.

    securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
    module is not available.


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

    if not CRYPTO:  # pragma: no cover
        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

    # This check will ensure 'private_key' has the appropriate number
    # of objects and object types, and that all dict keys are properly named.
    # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
    formats.PEMRSA_SCHEMA.check_match(private_key)

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

    # 'private_key' may still be a NULL string after the
    # 'securesystemslib.formats.PEMRSA_SCHEMA' so we need an additional check
    if len(private_key):
        try:
            private_key = load_pem_private_key(private_key.encode('utf-8'),
                                               password=None,
                                               backend=default_backend())
        except ValueError:
            raise exceptions.CryptoError(
                'The private key'
                ' (in PEM format) could not be deserialized.')

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

    encrypted_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.BestAvailableEncryption(
            passphrase.encode('utf-8')))

    return encrypted_pem.decode()
Exemplo n.º 7
0
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))
Exemplo n.º 8
0
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