Beispiel #1
0
def compute_keyid(pubkey_packet_data):
    """
  <Purpose>
    compute a keyid from an RFC4880 public-key buffer

  <Arguments>
    pubkey_packet_data: the public-key packet buffer

  <Exceptions>
    securesystemslib.exceptions.UnsupportedLibraryError if:
      the cryptography module is unavailable

  <Side Effects>
    None

  <Returns>
    The RFC4880-compliant hashed buffer
  """
    if not CRYPTO:  # pragma: no cover
        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

    hasher = hashing.Hash(hashing.SHA1(), backend=backends.default_backend())
    hasher.update(b'\x99')
    hasher.update(struct.pack(">H", len(pubkey_packet_data)))
    hasher.update(bytes(pubkey_packet_data))
    return binascii.hexlify(hasher.finalize()).decode("ascii")
Beispiel #2
0
def get_version():
    """
  <Purpose>
    Uses `gpg2 --version` to get the version info of the installed gpg2
    and extracts and returns the version number.

    The executed base command is defined in constants.GPG_VERSION_COMMAND.

  <Exceptions>
    securesystemslib.exceptions.UnsupportedLibraryError:
            If the gpg command is not available

  <Returns>
    Version number string, e.g. "2.1.22"

  """
    if not constants.HAVE_GPG:  # pragma: no cover
        raise exceptions.UnsupportedLibraryError(constants.NO_GPG_MSG)

    command = constants.GPG_VERSION_COMMAND
    gpg_process = process.run(command,
                              stdout=process.PIPE,
                              stderr=process.PIPE,
                              universal_newlines=True)

    full_version_info = gpg_process.stdout
    version_string = re.search(r'(\d\.\d\.\d+)', full_version_info).group(1)

    return version_string
Beispiel #3
0
def create_pubkey(pubkey_info):
    """
  <Purpose>
    Create and return an RSAPublicKey object from the passed pubkey_info
    using pyca/cryptography.

  <Arguments>
    pubkey_info:
            The RSA pubkey info dictionary as specified by
            securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA

  <Exceptions>
    securesystemslib.exceptions.FormatError if
      pubkey_info does not match securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA

    securesystemslib.exceptions.UnsupportedLibraryError if
      the cryptography module is unavailable

  <Returns>
    A cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey based on the
    passed pubkey_info.

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

    formats.GPG_RSA_PUBKEY_SCHEMA.check_match(pubkey_info)

    e = int(pubkey_info['keyval']['public']['e'], 16)
    n = int(pubkey_info['keyval']['public']['n'], 16)
    pubkey = rsa.RSAPublicNumbers(e, n).public_key(backends.default_backend())

    return pubkey
Beispiel #4
0
def create_pubkey(pubkey_info):
  """
  <Purpose>
    Create and return an Ed25519PublicKey object from the passed pubkey_info
    using pyca/cryptography.

  <Arguments>
    pubkey_info:
          The ED25519 public key dictionary as specified by
          securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA

  <Exceptions>
    securesystemslib.exceptions.FormatError if
      pubkey_info does not match securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA

    securesystemslib.exceptions.UnsupportedLibraryError if
      the cryptography module is unavailable

  <Returns>
    A cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey based
    on the passed pubkey_info.

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

  formats.GPG_ED25519_PUBKEY_SCHEMA.check_match(pubkey_info)

  public_bytes = binascii.unhexlify(pubkey_info["keyval"]["public"]["q"])
  public_key = pyca_ed25519.Ed25519PublicKey.from_public_bytes(public_bytes)

  return public_key
def generate_public_and_private():
  """
  <Purpose>
    Generate a pair of ed25519 public and private keys with PyNaCl.  The public
    and private keys returned conform to
    'securesystemslib.formats.ED25519PUBLIC_SCHEMA' and
    'securesystemslib.formats.ED25519SEED_SCHEMA', respectively.

    An ed25519 seed key is a random 32-byte string.  Public keys are also 32
    bytes.

    >>> public, private = generate_public_and_private()
    >>> securesystemslib.formats.ED25519PUBLIC_SCHEMA.matches(public)
    True
    >>> securesystemslib.formats.ED25519SEED_SCHEMA.matches(private)
    True

  <Arguments>
    None.

  <Exceptions>
    securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl')
    module is unavailable.

    NotImplementedError, if a randomness source is not found by 'os.urandom'.

  <Side Effects>
    The ed25519 keys are generated by first creating a random 32-byte seed
    with os.urandom() and then calling PyNaCl's nacl.signing.SigningKey().

  <Returns>
    A (public, private) tuple that conform to
    'securesystemslib.formats.ED25519PUBLIC_SCHEMA' and
    'securesystemslib.formats.ED25519SEED_SCHEMA', respectively.
  """

  if not NACL: # pragma: no cover
    raise exceptions.UnsupportedLibraryError(NO_NACL_MSG)

  # Generate ed25519's seed key by calling os.urandom().  The random bytes
  # returned should be suitable for cryptographic use and is OS-specific.
  # Raise 'NotImplementedError' if a randomness source is not found.
  # ed25519 seed keys are fixed at 32 bytes (256-bit keys).
  # http://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
  seed = os.urandom(32)
  public = None

  # Generate the public key.  PyNaCl (i.e., 'nacl' module) performs the actual
  # key generation.
  nacl_key = SigningKey(seed)
  public = nacl_key.verify_key.encode(encoder=RawEncoder())

  return public, seed
Beispiel #6
0
def get_signature_params(data):
    """
  <Purpose>
    Parse the signature parameters as multi-precision-integers.

  <Arguments>
    data:
           the RFC4880-encoded signature data buffer as described
           in the fourth paragraph of section 5.2.2

  <Exceptions>
    securesystemslib.gpg.exceptions.PacketParsingError:
           if the public key parameters are malformed

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

  <Side Effects>
    None.

  <Returns>
    The decoded signature buffer
  """
    if not CRYPTO:  # pragma: no cover
        return exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

    ptr = 0
    r_length = gpg_util.get_mpi_length(data[ptr:ptr + 2])
    ptr += 2
    r = data[ptr:ptr + r_length]
    if len(r) != r_length:  # pragma: no cover
        raise PacketParsingError("r-value truncated in signature")
    ptr += r_length

    s_length = gpg_util.get_mpi_length(data[ptr:ptr + 2])
    ptr += 2
    s = data[ptr:ptr + s_length]
    if len(s) != s_length:  # pragma: no cover
        raise PacketParsingError("s-value truncated in signature")

    s = int(binascii.hexlify(s), 16)
    r = int(binascii.hexlify(r), 16)

    signature = dsautils.encode_dss_signature(r, s)

    return signature
Beispiel #7
0
def hash_object(headers, algorithm, content):
    """
  <Purpose>
    Hash data prior to signature verification in conformance of the RFC4880
    openPGP standard.

  <Arguments>
    headers: the additional OpenPGP headers as populated from
    gpg_generate_signature

    algorithm: The hash algorithm object defined by the cryptography.io hashes
    module

    content: the signed content

  <Exceptions>
    securesystemslib.exceptions.UnsupportedLibraryError if:
      the cryptography module is unavailable

  <Side Effects>
    None

  <Returns>
    The RFC4880-compliant hashed buffer
  """
    if not CRYPTO:  # pragma: no cover
        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

    # As per RFC4880 Section 5.2.4., we need to hash the content,
    # signature headers and add a very opinionated trailing header
    hasher = hashing.Hash(algorithm, backend=backends.default_backend())
    hasher.update(content)
    hasher.update(headers)
    hasher.update(b'\x04\xff')
    hasher.update(struct.pack(">I", len(headers)))

    return hasher.finalize()
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
Beispiel #9
0
def generate_public_and_private(scheme='ecdsa-sha2-nistp256'):
    """
  <Purpose>
    Generate a pair of ECDSA public and private keys with one of the supported,
    external cryptography libraries.  The public and private keys returned
    conform to 'securesystemslib.formats.PEMECDSA_SCHEMA' and
    'securesystemslib.formats.PEMECDSA_SCHEMA', respectively.

    The public ECDSA public key has the PEM format:
    TODO: should we encrypt the private keys returned here?  Should the
    create_signature() accept encrypted keys?

    '-----BEGIN PUBLIC KEY-----

    ...

    '-----END PUBLIC KEY-----'



    The private ECDSA private key has the PEM format:

    '-----BEGIN EC PRIVATE KEY-----

    ...

    -----END EC PRIVATE KEY-----'

    >>> public, private = generate_public_and_private()
    >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(public)
    True
    >>> securesystemslib.formats.PEMECDSA_SCHEMA.matches(private)
    True

  <Arguments>
    scheme:
      A string indicating which algorithm to use for the generation of the
      public and private ECDSA keys.  'ecdsa-sha2-nistp256' is the only
      currently supported ECDSA algorithm, which is supported by OpenSSH and
      specified in RFC 5656 (https://tools.ietf.org/html/rfc5656).

  <Exceptions>
    securesystemslib.exceptions.FormatError, if 'algorithm' is improperly
    formatted.

    securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is an
    unsupported algorithm.

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

  <Side Effects>
    None.

  <Returns>
    A (public, private) tuple that conform to
    'securesystemslib.formats.PEMECDSA_SCHEMA' and
    'securesystemslib.formats.PEMECDSA_SCHEMA', respectively.
  """

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

    # Does 'scheme' have the correct format?
    # Verify that 'scheme' is of the correct type, and that it's one of the
    # supported ECDSA .  It must conform to
    # 'securesystemslib.formats.ECDSA_SCHEME_SCHEMA'.  Raise
    # 'securesystemslib.exceptions.FormatError' if the check fails.
    formats.ECDSA_SCHEME_SCHEMA.check_match(scheme)

    public_key = None
    private_key = None

    # An if-clause is strictly not needed, since 'ecdsa_sha2-nistp256' is the
    # only currently supported ECDSA signature scheme.  Nevertheness, include the
    # conditional statement to accomodate any schemes that might be added.
    if scheme == 'ecdsa-sha2-nistp256':
        private_key = ec.generate_private_key(ec.SECP256R1, default_backend())
        public_key = private_key.public_key()

    # The ECDSA_SCHEME_SCHEMA.check_match() above should have detected any
    # invalid 'scheme'.  This is a defensive check.
    else:  #pragma: no cover
        raise exceptions.UnsupportedAlgorithmError(
            'An unsupported'
            ' scheme specified: ' + repr(scheme) + '.\n  Supported'
            ' algorithms: ' + repr(_SUPPORTED_ECDSA_SCHEMES))

    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption())

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

    return public_pem.decode('utf-8'), private_pem.decode('utf-8')
Beispiel #10
0
def create_ecdsa_encrypted_pem(private_pem, passphrase):
    """
  <Purpose>
    Return a string in PEM format, where the private part of the ECDSA key is
    encrypted. The private part of the ECDSA key is encrypted as done by
    pyca/cryptography: "Encrypt using the best available encryption for a given
    key's backend. This is a curated encryption choice and the algorithm may
    change over time."

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

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

    passphrase:
    The passphrase, or password, to encrypt the private part of the ECDSA
    key. 'passphrase' is not used directly as the encryption key, a stronger
    encryption key is derived from it.

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

      securesystemslib.exceptions.CryptoError, if an ECDSA key in encrypted PEM
      format cannot be created.

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

  <Side Effects>
    None.

  <Returns>
    A string in PEM format, where the private RSA portion is encrypted.
    Conforms to 'securesystemslib.formats.PEMECDSA_SCHEMA'.
  """

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

    # Does 'private_key' have the correct format?
    # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
    formats.PEMRSA_SCHEMA.check_match(private_pem)

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

    private = load_pem_private_key(private_pem.encode('utf-8'),
                                   password=None,
                                   backend=default_backend())

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

    return encrypted_private_pem
Beispiel #11
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')
Beispiel #12
0
def verify_signature(public_key, scheme, signature, data):
    """
  <Purpose>
    Verify that 'signature' was produced by the private key associated with
    'public_key'.

    >>> scheme = 'ecdsa-sha2-nistp256'
    >>> public, private = generate_public_and_private(scheme)
    >>> data = b'The quick brown fox jumps over the lazy dog'
    >>> signature, scheme = create_signature(public, private, data, scheme)
    >>> verify_signature(public, scheme, signature, data)
    True
    >>> verify_signature(public, scheme, signature, b'bad data')
    False

  <Arguments>
    public_key:
      The ECDSA public key in PEM format.  The public key is needed to verify
      'signature'.

    scheme:
      The signature scheme used to generate 'signature'.  For example:
      'ecdsa-sha2-nistp256'.

    signature:
      The signature to be verified, which should have been generated by
      the private key associated with 'public_key'.  'data'.

    data:
      Byte data that was used by create_signature() to generate 'signature'.

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

    securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is
    not one of the supported signature schemes.

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

  <Side Effects>
    None.

  <Returns>
    Boolean, indicating whether the 'signature' of data was generated by
    the private key associated with 'public_key'.
  """

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

    # Are the arguments properly formatted?
    # If not, raise 'securesystemslib.exceptions.FormatError'.
    formats.PEMECDSA_SCHEMA.check_match(public_key)
    formats.ECDSA_SCHEME_SCHEMA.check_match(scheme)
    formats.ECDSASIGNATURE_SCHEMA.check_match(signature)

    ecdsa_key = load_pem_public_key(public_key.encode('utf-8'),
                                    backend=default_backend())

    if not isinstance(ecdsa_key, ec.EllipticCurvePublicKey):
        raise exceptions.FormatError('Invalid ECDSA public'
                                     ' key: ' + repr(public_key))

    else:
        logger.debug('Loaded a valid ECDSA public key.')

    # verify() raises an 'InvalidSignature' exception if 'signature'
    # is invalid.
    try:
        ecdsa_key.verify(signature, data, _SCHEME_HASHER[scheme])
        return True

    except (TypeError, InvalidSignature):
        return False
Beispiel #13
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
Beispiel #14
0
def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS):
    """
  <Purpose>
    Generate public and private RSA keys with modulus length 'bits'.  The
    public and private keys returned conform to
    'securesystemslib.formats.PEMRSA_SCHEMA' and have the form:

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

    or

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

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

    'generate_rsa_public_and_private()' enforces a minimum key size of 2048
    bits.  If 'bits' is unspecified, a 3072-bit RSA key is generated, which is
    the key size recommended by TUF.

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public)
    True
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private)
    True

  <Arguments>
    bits:
      The key size, or key length, of the RSA key.  'bits' must be 2048, or
      greater.  'bits' defaults to 3072 if not specified.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if 'bits' does not contain the
    correct format.

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

  <Side Effects>
    The RSA keys are generated from pyca/cryptography's
    rsa.generate_private_key() function.

  <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 'bits' have the correct format?
    # This check will ensure 'bits' conforms to
    # 'securesystemslib.formats.RSAKEYBITS_SCHEMA'.  'bits' must be an integer
    # object, with a minimum value of 2048.  Raise
    # 'securesystemslib.exceptions.FormatError' if the check fails.
    formats.RSAKEYBITS_SCHEMA.check_match(bits)

    # Generate the public and private RSA keys.  The pyca/cryptography 'rsa'
    # module performs the actual key generation.  The 'bits' argument is used,
    # and a 2048-bit minimum is enforced by
    # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match().
    private_key = rsa.generate_private_key(public_exponent=65537,
                                           key_size=bits,
                                           backend=default_backend())

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

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

    return public_pem.decode('utf-8'), private_pem.decode('utf-8')
Beispiel #15
0
def verify_signature(signature_object, pubkey_info, content,
                     hash_algorithm_id):
    """
  <Purpose>
    Verify the passed signature against the passed content with the passed
    RSA public key using pyca/cryptography.

  <Arguments>
    signature_object:
            A signature dictionary as specified by
            securesystemslib.formats.GPG_SIGNATURE_SCHEMA

    pubkey_info:
            The RSA public key info dictionary as specified by
            securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA

    content:
            The signed bytes against which the signature is verified

    hash_algorithm_id:
            one of SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants)
            used to verify the signature
            NOTE: Overrides any hash algorithm specification in "pubkey_info"'s
            "hashes" or "method" fields.

  <Exceptions>
    securesystemslib.exceptions.FormatError if:
      signature_object does not match
      securesystemslib.formats.GPG_SIGNATURE_SCHEMA,
      pubkey_info does not match securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA

    securesystemslib.exceptions.UnsupportedLibraryError if:
      the cryptography module is unavailable

    ValueError:
      if the passed hash_algorithm_id is not supported (see
      securesystemslib.gpg.util.get_hashing_class)

  <Returns>
    True if signature verification passes and False otherwise

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

    formats.GPG_SIGNATURE_SCHEMA.check_match(signature_object)
    formats.GPG_RSA_PUBKEY_SCHEMA.check_match(pubkey_info)

    hasher = gpg_util.get_hashing_class(hash_algorithm_id)

    pubkey_object = create_pubkey(pubkey_info)

    # zero-pad the signature due to a discrepancy between the openssl backend
    # and the gnupg interpretation of PKCSv1.5. Read more at:
    # https://github.com/in-toto/in-toto/issues/171#issuecomment-440039256
    # we are skipping this if on the tests because well, how would one test this
    # deterministically.
    pubkey_length = len(pubkey_info['keyval']['public']['n'])
    signature_length = len(signature_object['signature'])
    if pubkey_length != signature_length:  # pragma: no cover
        zero_pad = "0" * (pubkey_length - signature_length)
        signature_object['signature'] = "{}{}".format(
            zero_pad, signature_object['signature'])

    digest = gpg_util.hash_object(
        binascii.unhexlify(signature_object['other_headers']), hasher(),
        content)

    try:
        pubkey_object.verify(binascii.unhexlify(signature_object['signature']),
                             digest, padding.PKCS1v15(),
                             utils.Prehashed(hasher()))
        return True
    except InvalidSignature:
        return False
Beispiel #16
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
Beispiel #17
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))
Beispiel #18
0
def decrypt_key(encrypted_key, password):
    """
  <Purpose>
    Return a string containing 'encrypted_key' in non-encrypted form.
    The decrypt_key() function can be applied to the encrypted string to restore
    the original key object, a securesystemslib key (e.g., RSAKEY_SCHEMA,
    ED25519KEY_SCHEMA). This function calls the appropriate cryptography module
    (i.e., rsa_keys.py) to perform the decryption.

    Encrypted securesystemslib keys use AES-256-CTR-Mode and passwords
    strengthened with PBKDF2-HMAC-SHA256 (100K iterations be default, but may
    be overriden in 'settings.py' by the user).

    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
    https://en.wikipedia.org/wiki/PBKDF2

    >>> ed25519_key = {'keytype': 'ed25519', \
                       'scheme': 'ed25519', \
                       'keyid': \
          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
                       'keyval': {'public': \
          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
                                  'private': \
          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
    >>> passphrase = 'secret'
    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
    >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase)
    >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(decrypted_key)
    True
    >>> decrypted_key == ed25519_key
    True

  <Arguments>
    encrypted_key:
      An encrypted securesystemslib key (additional data is also included, such
      as salt, number of password iterations used for the derived encryption
      key, etc) of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'.
      'encrypted_key' should have been generated with encrypted_key().

    password:
      The password, or passphrase, to encrypt the private part of the RSA
      key.  'password' is not used directly as the encryption key, a stronger
      encryption key is derived from it.

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

    securesystemslib.exceptions.CryptoError, if a securesystemslib key cannot
    be decrypted from 'encrypted_key'.

    securesystemslib.exceptions.Error, if a valid securesystemslib key object
    is not found in 'encrypted_key'.

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

  <Side Effects>
    The pyca/cryptography is library called to perform the actual decryption
    of 'encrypted_key'.  The key derivation data stored in 'encrypted_key' is
    used to re-derive the encryption/decryption key.

  <Returns>
    The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format.
  """

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

    # Do the arguments have the correct format?
    # Ensure the arguments have the appropriate number of objects and object
    # types, and that all dict keys are properly named.
    # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
    formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key)

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

    # Decrypt 'encrypted_key', using 'password' (and additional key derivation
    # data like salts and password iterations) to re-derive the decryption key.
    json_data = _decrypt(encrypted_key, password)

    # Raise 'securesystemslib.exceptions.Error' if 'json_data' cannot be
    # deserialized to a valid 'securesystemslib.formats.ANYKEY_SCHEMA' key
    # object.
    key_object = util.load_json_string(json_data.decode())

    return key_object
Beispiel #19
0
def encrypt_key(key_object, password):
    """
  <Purpose>
    Return a string containing 'key_object' in encrypted form. Encrypted
    strings may be safely saved to a file.  The corresponding decrypt_key()
    function can be applied to the encrypted string to restore the original key
    object.  'key_object' is a securesystemslib key (e.g., RSAKEY_SCHEMA,
    ED25519KEY_SCHEMA).  This function calls the pyca/cryptography library to
    perform the encryption and derive a suitable encryption key.

    Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm
    (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password
    Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password',
    encrypted securesystemslib keys use AES-256-CTR-Mode and passwords
    strengthened with PBKDF2-HMAC-SHA256 (100K iterations by default, but may
    be overriden in 'settings.PBKDF2_ITERATIONS' by the user).

    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
    https://en.wikipedia.org/wiki/PBKDF2

    >>> ed25519_key = {'keytype': 'ed25519', \
                       'scheme': 'ed25519', \
                       'keyid': \
          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
                       'keyval': {'public': \
          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
                                  'private': \
          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
    >>> passphrase = 'secret'
    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
    >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8'))
    True

  <Arguments>
    key_object:
      The securesystemslib key object that should contain the private portion
      of the ED25519 key.

    password:
      The password, or passphrase, to encrypt the private part of the RSA
      key.  'password' is not used directly as the encryption key, a stronger
      encryption key is derived from it.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if any of the arguments are
    improperly formatted or 'key_object' does not contain the private portion
    of the key.

    securesystemslib.exceptions.CryptoError, if an Ed25519 key in encrypted
    securesystemslib format cannot be created.

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


  <Side Effects>
    pyca/Cryptography cryptographic operations called to perform the actual
    encryption of 'key_object'.  'password' used to derive a suitable
    encryption key.

  <Returns>
    An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format.
  """

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

    # Do the arguments have the correct format?
    # Ensure the arguments have the appropriate number of objects and object
    # types, and that all dict keys are properly named.
    # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
    formats.ANYKEY_SCHEMA.check_match(key_object)

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

    # Ensure the private portion of the key is included in 'key_object'.
    if 'private' not in key_object[
            'keyval'] or not key_object['keyval']['private']:
        raise exceptions.FormatError(
            'Key object does not contain a private part.')

    # Derive a key (i.e., an appropriate encryption key and not the
    # user's password) from the given 'password'.  Strengthen 'password' with
    # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
    # 'settings.PBKDF2_ITERATIONS' by the user).
    salt, iterations, derived_key = _generate_derived_key(password)

    # Store the derived key info in a dictionary, the object expected
    # by the non-public _encrypt() routine.
    derived_key_information = {
        'salt': salt,
        'iterations': iterations,
        'derived_key': derived_key
    }

    # Convert the key object to json string format and encrypt it with the
    # derived key.
    encrypted_key = _encrypt(json.dumps(key_object), derived_key_information)

    return encrypted_key
Beispiel #20
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()
Beispiel #21
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()
Beispiel #22
0
def digest(algorithm=DEFAULT_HASH_ALGORITHM,
           hash_library=DEFAULT_HASH_LIBRARY):
    """
  <Purpose>
    Provide the caller with the ability to create digest objects without having
    to worry about crypto library availability or which library to use.  The
    caller also has the option of specifying which hash algorithm and/or
    library to use.

    # Creation of a digest object using defaults or by specifying hash
    # algorithm and library.
    digest_object = securesystemslib.hash.digest()
    digest_object = securesystemslib.hash.digest('sha384')
    digest_object = securesystemslib.hash.digest('sha256', 'hashlib')

    # The expected interface for digest objects.
    digest_object.digest_size
    digest_object.hexdigest()
    digest_object.update('data')
    digest_object.digest()

    # Added hash routines by this module.
    digest_object = securesystemslib.hash.digest_fileobject(file_object)
    digest_object = securesystemslib.hash.digest_filename(filename)

  <Arguments>
    algorithm:
      The hash algorithm (e.g., 'md5', 'sha1', 'sha256').

    hash_library:
      The crypto library to use for the given hash algorithm (e.g., 'hashlib').

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

    securesystemslib.exceptions.UnsupportedAlgorithmError, if an unsupported
    hashing algorithm is specified, or digest could not be generated with given
    the algorithm.

    securesystemslib.exceptions.UnsupportedLibraryError, if an unsupported
    library was requested via 'hash_library'.

  <Side Effects>
    None.

  <Returns>
    Digest object

    e.g.
      hashlib.new(algorithm) or
      PycaDiggestWrapper object
  """

    # Are the arguments properly formatted?  If not, raise
    # 'securesystemslib.exceptions.FormatError'.
    formats.NAME_SCHEMA.check_match(algorithm)
    formats.NAME_SCHEMA.check_match(hash_library)

    # Was a hashlib digest object requested and is it supported?
    # If so, return the digest object.
    if hash_library == 'hashlib' and hash_library in SUPPORTED_LIBRARIES:
        try:
            if algorithm == 'blake2b-256':
                return hashlib.new('blake2b', digest_size=32)
            else:
                return hashlib.new(algorithm)

        except (ValueError, TypeError):
            # ValueError: the algorithm value was unknown
            # TypeError: unexpected argument digest_size (on old python)
            raise exceptions.UnsupportedAlgorithmError(algorithm)

    # Was a pyca_crypto digest object requested and is it supported?
    elif hash_library == 'pyca_crypto' and hash_library in SUPPORTED_LIBRARIES:
        try:
            hash_algorithm = PYCA_DIGEST_OBJECTS_CACHE[algorithm]()
            return PycaDiggestWrapper(
                _pyca_hashes.Hash(hash_algorithm, default_backend()))

        except KeyError:
            raise exceptions.UnsupportedAlgorithmError(algorithm)

    # The requested hash library is not supported.
    else:
        raise exceptions.UnsupportedLibraryError(
            'Unsupported'
            ' library requested.  Supported hash'
            ' libraries: ' + repr(SUPPORTED_LIBRARIES))
Beispiel #23
0
def verify_signature(signature_object, pubkey_info, content,
    hash_algorithm_id):
  """
  <Purpose>
    Verify the passed signature against the passed content with the passed
    ED25519 public key using pyca/cryptography.

  <Arguments>
    signature_object:
            A signature dictionary as specified by
            securesystemslib.formats.GPG_SIGNATURE_SCHEMA

    pubkey_info:
            The DSA public key info dictionary as specified by
            securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA

    hash_algorithm_id:
            one of SHA1, SHA256, SHA512 (see securesystemslib.gpg.constants)
            used to verify the signature
            NOTE: Overrides any hash algorithm specification in "pubkey_info"'s
            "hashes" or "method" fields.

    content:
            The signed bytes against which the signature is verified

  <Exceptions>
    securesystemslib.exceptions.FormatError if:
      signature_object does not match securesystemslib.formats.GPG_SIGNATURE_SCHEMA
      pubkey_info does not match securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA

    securesystemslib.exceptions.UnsupportedLibraryError if:
      the cryptography module is unavailable

    ValueError:
      if the passed hash_algorithm_id is not supported (see
      securesystemslib.gpg.util.get_hashing_class)

  <Returns>
    True if signature verification passes and False otherwise.

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

  formats.GPG_SIGNATURE_SCHEMA.check_match(signature_object)
  formats.GPG_ED25519_PUBKEY_SCHEMA.check_match(pubkey_info)

  hasher = gpg_util.get_hashing_class(hash_algorithm_id)

  pubkey_object = create_pubkey(pubkey_info)

  # See RFC4880-bis8 14.8. EdDSA and 5.2.4 "Computing Signatures"
  digest = gpg_util.hash_object(
      binascii.unhexlify(signature_object["other_headers"]),
      hasher(), content)

  try:
    pubkey_object.verify(
      binascii.unhexlify(signature_object["signature"]),
      digest
    )
    return True

  except InvalidSignature:
    return False