示例#1
0
  def test_gpg_sign_and_verify_object_with_default_key(self):
    """Create a signature using the default key on the keyring """

    test_data = b'test_data'
    wrong_data = b'something malicious'

    signature = create_signature(test_data, homedir=self.gnupg_home)
    key_data = export_pubkey(self.default_keyid, homedir=self.gnupg_home)

    self.assertTrue(verify_signature(signature, key_data, test_data))
    self.assertFalse(verify_signature(signature, key_data, wrong_data))
def _verify_gpg_sig_using_ssl(signature, gpg_key_fingerprint, key_value, data):
    """
    THIS IS PROVIDED ONLY FOR TESTING PURPOSES.
    We will verify signatures using our own code in conda_content_trust.authentication, not
    by using the securesystemslib.gpg.functions.verify_signature call that
    sits here.

    Wraps securesystemslib.gpg.functions.verify_signature.  to format the
    arguments in a manner ssl will like (i.e. conforming to
    securesystemslib.formats.GPG_SIGNATURE_SCHEMA).
    """
    if not SSLIB_AVAILABLE:
        # TODO✅: Consider a missing-optional-dependency exception class.
        raise Exception(
            'verifygpg_sig_using_ssl requires the securesystemslib '
            'library, which appears to be unavailable.')

    checkformat_key(key_value)

    # This function validates these two args in the process of formatting them.
    ssl_format_key = gpg_pubkey_in_ssl_format(gpg_key_fingerprint, key_value)

    securesystemslib.formats.GPG_SIGNATURE_SCHEMA.check_match(signature)
    securesystemslib.formats._GPG_ED25519_PUBKEY_SCHEMA.check_match(
        ssl_format_key)

    # TODO: ✅ Validate sig (ssl-format gpg sig dict) and content (bytes).

    # Note: if we change the signature format to deviate from what ssl uses,
    #       then we need to correct it here if we're going to use ssl.

    validity = gpg_funcs.verify_signature(signature, ssl_format_key, data)

    return validity
示例#3
0
  def test_verify_signature_with_expired_key(self):
    """Test sig verification with expired key raises KeyExpirationError. """
    signature = {
      "keyid": self.expired_key_keyid,
      "other_headers": "deadbeef",
      "signature": "deadbeef",
    }
    content = b"livestock"
    key = export_pubkey(self.expired_key_keyid,
        homedir=self.gnupg_home)

    with self.assertRaises(KeyExpirationError) as ctx:
      verify_signature(signature, key, content)

    expected = ("GPG key 'e8ac80c924116dabb51d4b987cb07d6d2c199c7c' "
        "created on '2019-03-25 12:46 UTC' with validity period '1 day, "
        "0:25:01' expired on '2019-03-26 13:11 UTC'.")
    self.assertTrue(expected == str(ctx.exception),
        "\nexpected: {}"
        "\ngot:      {}".format(expected, ctx.exception))
示例#4
0
  def test_gpg_sign_and_verify_object_default_keyring(self):
    """Sign/verify using keyring from envvar. """

    test_data = b'test_data'

    gnupg_home_backup = os.environ.get("GNUPGHOME")
    os.environ["GNUPGHOME"] = self.gnupg_home

    signature = create_signature(test_data, keyid=self.default_keyid)
    key_data = export_pubkey(self.default_keyid)
    self.assertTrue(verify_signature(signature, key_data, test_data))

    # Reset GNUPGHOME
    if gnupg_home_backup:
      os.environ["GNUPGHOME"] = gnupg_home_backup
    else:
      del os.environ["GNUPGHOME"]
def verify_gpg_sig_using_ssl(signature, gpg_key_fingerprint, key_value, data):
    """
    # TODO ✅: full docstring
    # TODO 💣💥: It is critical to verify that the fingerprint and value match
    #             if we are going to use the fingerprint.

    Returns True if the given gpg signature is verified as being by the given
    gpg key and over the given data.

    Wraps securesystemslib.gpg.functions.verify_siganture to format the
    arguments in a manner ssl will like (i.e. conforming to
    securesystemslib.formats.GPG_SIGNATURE_SCHEMA).

    """
    if not SSLIB_AVAILABLE:
        # TODO✅: Consider a missing-optional-dependency exception class.
        raise Exception(
                'sign_root_metadata_via_gpg requires the securesystemslib library, which '
                'appears to be unavailable.')

    # This function validates these two args in the process of formatting them.
    ssl_format_key = gpg_pubkey_in_ssl_format(gpg_key_fingerprint, key_value)

    securesystemslib.formats.GPG_SIGNATURE_SCHEMA.check_match(signature)
    securesystemslib.formats._GPG_ED25519_PUBKEY_SCHEMA.check_match(
            ssl_format_key)



    # TODO: ✅ Validate sig (ssl-format gpg sig dict) and content (bytes).



    # Note: if we change the signature format to deviate from what ssl uses,
    #       then we need to correct it here if we're going to use ssl.








    validity = gpg_funcs.verify_signature(signature, ssl_format_key, data)

    return validity
示例#6
0
  def test_verify_short_signature(self):
    """Correctly verify a special-crafted short signature. """

    test_data = b"hello"
    signature_path = os.path.join(self.gnupg_home, "short.sig")

    # Read special-crafted raw gpg signature that is one byte too short
    with open(signature_path, "rb") as f:
      signature_data = f.read()

    # Check that the signature is padded upon parsing
    # NOTE: The returned signature is a hex string and thus twice as long
    signature = parse_signature_packet(signature_data)
    self.assertTrue(len(signature["signature"]) == (ED25519_SIG_LENGTH * 2))

    # Check that the signature can be successfully verified
    key = export_pubkey(self.default_keyid, homedir=self.gnupg_home)
    self.assertTrue(verify_signature(signature, key, test_data))
    def test_gpg_verify(self):
        """Signature verification does not require gpg to be installed on the host.
    To prove it, we run basic verification tests for rsa, dsa and eddsa with
    pre-generated/exported signatures and keys. More thorough testing is
    available in test_gpg.py

    """
        data = b"deadbeef"
        key_signature_pairs = [
            # RSA
            ({
                'method': 'pgp+rsa-pkcsv1.5',
                'type': 'rsa',
                'hashes': ['pgp+SHA2'],
                'creation_time': 1519661780,
                'keyid': 'c5a0abe6ec19d0d65f85e2c39be9df5131d924e9',
                'keyval': {
                    'private': '',
                    'public': {
                        'e':
                        '010001',
                        'n':
                        'c152fc1f1535a6d3c1e8c0dece7f0a1d09324466e10e4ea51d5d7223ab125c1743393eebca73ccb1022d44c379fae30ef63b263d0a793882a7332ef06f28a4b9ae777f5d2d8d289167e86c162df1b9a9e127acb26803688556ecb08492d071f06caf88cea95571354349d8ef131eff03b0d259fae30ebf8dac9ab5acd6f26f4770fe2f30fcd0a3c54f03463a3094aa6524e39027a625108f04e12475da248fb3b536df61b0f6e2954739b8828c61171f66f8e176823e1c887e65fa0aec081013b2a50ed60515f7e3b3291ca443e1222b9b625005dba045a7208188fb88d436d473f6340348953e891354c7a5734bf64e6274e196db3074a7ce3607960baacb1b'
                    }
                }
            }, {
                'keyid':
                'c5a0abe6ec19d0d65f85e2c39be9df5131d924e9',
                'other_headers':
                '04000108001d162104c5a0abe6ec19d0d65f85e2c39be9df5131d924e905025e56444b',
                'signature':
                'bc4490901bd6edfe0ec49e0358c0a7ef37fc229824ca75dd4f163205745c78baaa2ca5cda79be259a5ac8323b4c1a1ee18fab0a8cc90eeafeb3eb1221d4bafb55510f34cf99e7ac121874f3c01152d6d8953c661c3e5147a387fffaee672318ed39c49fa02c80fa806956695f2fdfe0429a61639e7fb544f1531100eb02b7a140ffa284746fa1620e8461e4af5f93594f8aed6d34a33d51b265bae90ea8bedccb7497594003eb46516bddb1778a4fadd02cbb227e1931eeb5ef445fb9745f85cfbebfa169c3ae7d15e2ca75b15dd020877c9a968ff853993a06420d3c3ff158800014f21e558103cd4e7e84cf5e320ebf7c525e0eab9ab22ad4af02c7ad48b5e'
            }),
            # DSA
            ({
                'method': 'pgp+dsa-fips-180-2',
                'type': 'dsa',
                'hashes': ['pgp+SHA2'],
                'creation_time': 1510870182,
                'keyid': 'c242a830daaf1c2bef604a9ef033a3a3e267b3b1',
                'keyval': {
                    'private': '',
                    'public': {
                        'y':
                        '2dd50b2292441444581f9a0b7d8d7f88b573fc451f5e7207c324694232c22e171b508f6842ae9babc56fe4e586a22086188b4827b7aba8c7bff4a4ac9aa80c835420b1afba4ab4f1b1c0ef894437903a9f4c56ebef037804a99925c9a153b8a16c1562f297755aeaa20fa02ab32aa5366e052b6baa9a934356d4f5fc218785018dd12b2c8e6d605d2afb36cb06a9cced9ea1f5f82798d635de264ef0eb59590c4a4b2fdf2369a36f95614804c7aa5966ba9597404ba2d2c6881959112de52de4b6d4f1e2c8a59ddaadb08a59ac8334118f15aa01593e851024905ea6d884c3a545af6fdd03c8d2b54da1d35e710ef75a2b4775bb78c50b28d1e2fb48416dc941',
                        'p':
                        'fca3276cd78c20e3c73ae2398674046039f5d90f41e3ede9bc99f94000d145693522671fba481d22e0a9b31e695d198da5e62f4ffb4db5dc64076d0f2d7d03ce953fc7846a6d4e17a10bf1dcd17167f7aff761b59fa2180e7fcd2ca527c03c50c78665b5539bf2b45648b6d23f31f37999e6a7b4e0876ddad7ec783b8eec7e1fb14733e74b6b0b105cbdc5a7de8e094657f2146ce43a3177581cb022a4e2ce6678a3364a56e02090559a6dfd81d91ca3b7c6afd4fcfc66fd88339d217062462f51c5c91d6eccfafb32065be68e6b91ec837c59a51baebeca1c70fd3891c9bbb67f7d920f9153fc4d2ca03f88a27b70df1684709f99ad18707189b015441b2bfb',
                        'g':
                        '7f7252ae1824baf2be5fc8f431a1978683a38d4a22cc2bcdc01ccd1f5eee47a964aa57639a618cfb1b10707b4d09ff11a448e83ba70123573f2d49a599f5313a74463e5bb3ca3d6172a00f02b01065ce312501e1797f7b57e606947c44bd839fde8d43269f1fb74af6cedf4db7fabf0b2357ed09d56381ac769ef5a8af1b4450e0c88b64ee1cab9fadeb31b7be6207b7e17008a33a7613831f70a123d59279dcbc2238f46eeaa8097795b7805f1b837ef3b8e807164e186fae9fa3ff510213096bf54040eac545a6a5b47c910e6cf7e306e1f46723f14b02cd9e0b0ff2a56c3b2604869431ab3263d61bf5068bee36c880c7bf2c746dcae5d0d7b2fff244ef43',
                        'q':
                        '84779eeae0238d7a9a030a639bf01a0f9ef517a5d950599c19a4e54fbbf23219'
                    }
                }
            }, {
                'keyid':
                'c242a830daaf1c2bef604a9ef033a3a3e267b3b1',
                'other_headers':
                '04001108001d162104c242a830daaf1c2bef604a9ef033a3a3e267b3b105025e5644d1',
                'signature':
                '3044022009e95f952f64f559852fb6b321173f3cb142a5dbe0c84d709d55026ab945582802203144ee0f4c2cb70fa00ca6942c847208b96811271445ed85c75ebebdb609b174'
            }),
            # EDDSA
            ({
                'method': 'pgp+eddsa-ed25519',
                'type': 'eddsa',
                'hashes': ['pgp+SHA2'],
                'creation_time': 1572269200,
                'keyid': '4e630f84838bf6f7447b830b22692f5fea9e2dd2',
                'keyval': {
                    'private': '',
                    'public': {
                        'q':
                        '716e57b8c5d4397a4194f80bd43af2e07691db7ee58d2473ceb56cef1eda7569'
                    }
                }
            }, {
                'keyid':
                '4e630f84838bf6f7447b830b22692f5fea9e2dd2',
                'other_headers':
                '04001608001d1621044e630f84838bf6f7447b830b22692f5fea9e2dd205025e564505',
                'signature':
                '70ba3fe785bccac105b837b6b27cc8d5ddd0159c3f640bbac026b744e0b10839bf4ea53e786074d32f9617389a4fe3356ec1c4a19045c5c02821563786e1d10d'
            })
        ]

        for key, sig in key_signature_pairs:
            self.assertTrue(verify_signature(sig, key, data))
def sign_via_gpg(data_to_sign, gpg_key_fingerprint):
    """
    <Purpose>

        This is an alternative to the car.authenticate.sign() function, for use
        with OpenPGP keys, allowing us to use protected keys in YubiKeys (which
        provide an OpenPGP interface) to sign data.

        The signature is not simply over data_to_sign, as is the case with the
        car.authenticate.sign() function, but over an expanded payload with
        metadata about the signature to be signed, as specified by the OpenPGP
        standard (RFC 4880).  See data_to_sign and Security Note below.

        This process is nominally deterministic, but varies with the precise
        time, since there is a timestamp added by GPG into the signed payload.
        Nonetheless, this process does not depend at any point on the ability
        to generate random data (unlike key generation).

        This function requires securesystemslib, which is otherwise an optional
        dependency.

    <Arguments>

        data_to_sign
            The raw bytes of interest that will be signed by GPG.  Note that
            pursuant to the OpenPGP standard, GPG will add to this data:
            specifically, it includes metadata about the signature that is
            about to be made into the data that will be signed.  We do not care
            about that metadata, and we do not want to burden signature
            verification with its processing, so we essentially ignore it.
            This should have negligible security impact, but for more
            information, see "A note on security" below.


        gpg_key_fingerprint
            This is a (fairly) unique identifier for an OpenPGP key pair.
            Also Known as a "long" GPG keyid, a GPG fingerprint is
            40-hex-character string representing 20 bytes of raw data, the
            SHA-1 hash of a collection of the GPG key's properties.
            Internally, GPG uses the key fingerprint to identify keys the
            client knows of.

            Note that an OpenPGP public key is a larger object identified by a
            fingerprint.  GPG keys include two things, from our perspective:

             - the raw bytes of the actual cryptographic key
               (in our case the 32-byte value "q" for an ed25519 public key)

             - lots of data that is totally extraneous to us, including a
               timestamp, some representations of relationships with other keys
               (subkeys, signed-by lists, etc.), potential revocations, etc...)
               We do not care about this extra data because we are using the
               OpenPGP standard not for its key-to-key semantics or any element
               of its Public Key Infrastructure features (revocation, vouching
               for other keys, key relationships, etc.), but simply as a means
               of asking YubiKeys to sign data for us, with ed25519 keys whose
               raw public key value ("q") we know to expect.


    <Returns>
        Returns two values:
          - a dictionary representing a GPG signature, conforming to
            securesystemslib.formats.GPG_SIGNATURE_SCHEMA, and
          - a gpg public key object, a dictionary conforming to
            securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA.

        This is unlike sign(), which returns 64 bytes of raw ed25519 signature.


    <Security Note>

        A note on the security implications of this treatment of OpenPGP
        signatures:

        TL;DR:
            It is NOT easier for an attacker to find a collision; however, it
            IS easier, IF an attacker CAN find a collision, to do so in a way
            that presents a specific, arbitrary payload.

        Note that pursuant to the OpenPGP standard, GPG will add to the data we
        ask it to sign (data_to_sign) before signing it. Specifically, GPG will
        add, to the payload-to-be-signed, OpenPGP metadata about the signature
        it is about to create.  We do not care about that metadata, and we do
        not want to burden signature verification with its processing (that is,
        we do not want to use GPG to verify these signatures; conda will do
        that with simpler code).  As a result, we will ignore this data when
        parsing the signed payload.  This will mean that there will be many
        different messages that have the same meaning to us:

            signed:
                <some raw data we send to GPG: 'ABCDEF...'>
                <some data GPG adds in: '123456...'>

            Since we will not be processing the '123456...' above, '654321...'
            would have the same effect: as long as the signature is verified,
            we don't care what's in that portion of the payload.

        Since there are many, many payloads that mean the same thing to us, an
        attacker has a vast space of options all with the same meaning to us in
        which to search for (effectively) a useful SHA256 hash collision to
        find different data that says something *specific* and still
        *succeeds* in signature verification using the same signature.
        While that is not ideal, it is difficult enough simply to find a SHA256
        collision that this is acceptable.
    """
    if not SSLIB_AVAILABLE:
        # TODO✅: Consider a missing-optional-dependency exception class.
        raise Exception(
                'sign_via_gpg requires the securesystemslib library, which '
                'appears to be unavailable.')

    sig = gpg_funcs.create_signature(data_to_sign, gpg_key_fingerprint)
    full_gpg_pubkey = gpg_funcs.export_pubkey(gpg_key_fingerprint)

    # 💣💥 Debug only.
    # 💣💥 Debug only.
    assert gpg_funcs.verify_signature(sig, full_gpg_pubkey, data_to_sign)

    return sig, full_gpg_pubkey