123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- # -*- coding: utf-8 -*-
- """ tests.test_common
- (Mostly) unit tests for conda-content-trust/conda_content_trust/common.py.
- Run the tests this way:
- pytest tests/test_common.py
- """
- # Python2 Compatibility
- from __future__ import absolute_import, division, print_function, unicode_literals
- import os
- import pytest
- from conda_content_trust.common import *
- # A 40-hex-character GPG public key fingerprint
- SAMPLE_FINGERPRINT = 'f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589'
- SAMPLE_UNKNOWN_FINGERPRINT = '0123456789abcdef0123456789abcdef01234567'
- # The real key value of the public key (q, 32-byte ed25519 public key val),
- # as a length-64 hex string.
- SAMPLE_KEYVAL = 'bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07'
- SAMPLE_GPG_KEY_OBJ = {
- 'creation_time': 1571411344,
- 'hashes': ['pgp+SHA2'],
- 'keyid': SAMPLE_FINGERPRINT,
- 'keyval': {
- 'private': '',
- 'public': {'q': SAMPLE_KEYVAL}
- },
- 'method': 'pgp+eddsa-ed25519',
- 'type': 'eddsa'
- }
- SAMPLE_ROOT_MD_CONTENT = {
- 'delegations': {
- 'key_mgr.json': {'pubkeys': [], 'threshold': 1},
- 'root.json': {
- 'pubkeys': ['bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07'],
- 'threshold': 1}
- },
- 'expiration': '2020-12-09T17:20:19Z',
- 'metadata_spec_version': '0.1.0', # TODO ✅⚠️❌💣: Update to 0.6.0 and remove the ".json" in the delegation names above, update the pubkey, and then re-sign this test metadata with the updated pubkey and adjust SAMPLE_GPG_SIG
- 'type': 'root',
- 'version': 1
- }
- SAMPLE_GPG_SIG = {
- 'see_also': 'f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589',
- 'other_headers': '04001608001d162104f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd58905025defd3d3',
- 'signature': 'd6a3754dbd604a703434058c70db6a510b84a571236155df0b1f7f42605eb9e0faabca111d6ee808a7fcba663eafb5d66ecdfd33bd632df016fde3aed0f75201'
- }
- SAMPLE_SIGNED_ROOT_MD = {
- 'signatures': {
- 'bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07': SAMPLE_GPG_SIG
- },
- 'signed': SAMPLE_ROOT_MD_CONTENT
- }
- EXPECTED_SERIALIZED_SAMPLE_SIGNED_ROOT_MD = (b'{\n '
- b'"signatures": {\n '
- b'"bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07": {\n '
- b'"other_headers": "04001608001d162104f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd58905025defd3d3",\n '
- b'"see_also": "f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589",\n '
- b'"signature": "d6a3754dbd604a703434058c70db6a510b84a571236155df0b1f7f42605eb9e0faabca111d6ee808a7fcba663eafb5d66ecdfd33bd632df016fde3aed0f75201"\n }\n },\n '
- b'"signed": {\n '
- b'"delegations": {\n '
- b'"key_mgr.json": {\n '
- b'"pubkeys": [],\n '
- b'"threshold": 1\n },\n '
- b'"root.json": {\n '
- b'"pubkeys": [\n "bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07"\n ],\n '
- b'"threshold": 1\n }\n },\n '
- b'"expiration": "2020-12-09T17:20:19Z",\n '
- b'"metadata_spec_version": "0.1.0",\n ' # TODO ✅⚠️❌💣: Update to 0.6.0 and remove the ".json" in the delegation names above, update the pubkey, and then re-sign this test metadata with the updated pubkey and adjust SAMPLE_GPG_SIG
- b'"type": "root",\n '
- b'"version": 1\n }\n}')
- # # Some REGRESSION test data.
- # REG__KEYPAIR_NAME = 'keytest_old'
- REG__PRIVATE_BYTES = b'\xc9\xc2\x06\r~\r\x93al&T\x84\x0bI\x83\xd0\x02!\xd8\xb6\xb6\x9c\x85\x01\x07\xdat\xb4!h\xf97'
- REG__PUBLIC_BYTES = b"\x01=\xddqIb\x86m\x12\xba[\xae'?\x14\xd4\x8c\x89\xcf\x07s\xde\xe2\xdb\xf6\xd4V\x1eR\x1c\x83\xf7"
- REG__PUBLIC_HEX = '013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7'
- REG__PRIVATE_HEX = 'c9c2060d7e0d93616c2654840b4983d00221d8b6b69c850107da74b42168f937'
- # REG__MESSAGE_THAT_WAS_SIGNED = b'123456\x067890'
- # # Signature is over REG__MESSAGE_THAT_WAS_SIGNED using key REG__PRIVATE_BYTES.
- # REG__SIGNATURE = b'\xb6\xda\x14\xa1\xedU\x9e\xbf\x01\xb3\xa9\x18\xc9\xb8\xbd\xccFM@\x87\x99\xe8\x98\x84C\xe4}9;\xa4\xe5\xfd\xcf\xdaau\x04\xf5\xcc\xc0\xe7O\x0f\xf0F\x91\xd3\xb8"\x7fD\x1dO)*\x1f?\xd7&\xd6\xd3\x1f\r\x0e'
- REG__HASHED_VAL = b'string to hash\n'
- REG__HASH_HEX = '73aec9a93f4beb41a9bad14b9d1398f60e78ccefd97e4eb7d3cf26ba71dbe0ce'
- # #REG__HASH_BYTES = b's\xae\xc9\xa9?K\xebA\xa9\xba\xd1K\x9d\x13\x98\xf6\x0ex\xcc\xef\xd9~N\xb7\xd3\xcf&\xbaq\xdb\xe0\xce'
- # def test_sha512256():
- # # Test the SHA-512-truncate-256 hashing function w/ an expected result.
- # assert sha512256(REG__HASHED_VAL) == REG__HASH_HEX
- # # TODO: Test more? Unusual input
- def test_canonserialize():
- # Simple primitives
- assert canonserialize('') == b'""'
- assert canonserialize('a') == b'"a"'
- assert canonserialize(12) == b'12'
- assert canonserialize(SAMPLE_KEYVAL) == (
- b'"bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07"')
- # Tuples
- assert canonserialize((1, 2, 3)) == b'[\n 1,\n 2,\n 3\n]'
- assert canonserialize(('ABC', 1, 16)) == b'[\n "ABC",\n 1,\n 16\n]'
- # Dictionaries indexed by ints, strings, or both
- assert canonserialize({}) == b'{}'
- assert canonserialize({1: 'v1', 2: 'v2'}) == (
- b'{\n "1": "v1",\n "2": "v2"\n}')
- assert canonserialize({'a': 'v1', 'b': 'v2'}) == (
- b'{\n "a": "v1",\n "b": "v2"\n}')
- with pytest.raises(TypeError):
- # Currently, json.dumps(...sort_keys=True) raises a TypeError while
- # sorting if it has to sort string keys and integer keys together.
- canonserialize({5: 'value', 'key': 9, 'key2': 'value2'})
- assert canonserialize(SAMPLE_SIGNED_ROOT_MD) == (
- EXPECTED_SERIALIZED_SAMPLE_SIGNED_ROOT_MD)
- # TODO: Tricksy tests that mess with encoding.
- def test_keyfile_operations():
- """
- Unit tests for functions:
- keyfiles_to_keys
- keyfiles_to_bytes
- """
- # Test keyfiles_to_keys and keyfiles_to_bytes
- # Regression: load old key pair, two ways.
- # First, dump them to temp files (to test keyfiles_to_keys).
- with open('keytest_old.pri', 'wb') as fobj:
- fobj.write(REG__PRIVATE_BYTES)
- with open('keytest_old.pub', 'wb') as fobj:
- fobj.write(REG__PUBLIC_BYTES)
- loaded_old_private_bytes, loaded_old_public_bytes = keyfiles_to_bytes(
- 'keytest_old')
- loaded_old_private, loaded_old_public = keyfiles_to_keys('keytest_old')
- # Clean up a bit.
- for fname in ['keytest_old.pri', 'keytest_old.pub']:
- if os.path.exists(fname):
- os.remove(fname)
- # Check the keys we wrote and then loaded.
- assert loaded_old_private_bytes == REG__PRIVATE_BYTES
- assert loaded_old_public_bytes == REG__PUBLIC_BYTES
- def test_key_functions():
- # """
- # Tests for functions:
- # from_bytes
- # from_hex
- # to_bytes
- # to_hex
- # is_equivalent_to
- # """
- # First key, generated in two ways:
- private_1_byt = PrivateKey.from_bytes(b'1'*32)
- private_1_hex = PrivateKey.from_hex('31'*32) # hex representation of b'1'
- # Second key, generated in two ways:
- private_2_byt = PrivateKey.from_bytes(b'10' + b'1'*30)
- private_2_hex = PrivateKey.from_hex('3130' + '31'*30)
- # Regression key, generated in two ways:
- private_reg_byt = PrivateKey.from_bytes(REG__PRIVATE_BYTES)
- private_reg_hex = PrivateKey.from_hex(REG__PRIVATE_HEX)
- # Check these against each other and also against the expected output:
- # - to_bytes
- # - to_hex
- # - is_equivalent_to
- # - from_bytes
- # - from_hex
- # key 1 from bytes vs key 1 from hex, also vs raw key 1 value
- assert private_1_byt.is_equivalent_to(private_1_hex)
- assert private_1_hex.is_equivalent_to(private_1_byt)
- assert b'1'*32 == private_1_byt.to_bytes() == private_1_hex.to_bytes()
- assert '31'*32 == private_1_byt.to_hex() == private_1_hex.to_hex()
- # key 1 vs key 2 vs regression key
- assert not private_1_byt.is_equivalent_to(private_2_byt)
- assert not private_2_byt.is_equivalent_to(private_1_byt)
- assert not private_1_byt.is_equivalent_to(private_2_hex)
- assert not private_2_hex.is_equivalent_to(private_1_byt)
- assert not private_reg_byt.is_equivalent_to(private_1_byt)
- assert not private_1_byt.is_equivalent_to(private_reg_byt)
- # key 2 from bytes vs key 2 from hex, also vs raw key 2 value
- assert private_2_byt.is_equivalent_to(private_2_hex)
- assert private_2_hex.is_equivalent_to(private_2_byt)
- assert b'10' + b'1'*30 == private_2_byt.to_bytes() == private_2_hex.to_bytes()
- assert '3130' + '31'*30 == private_2_byt.to_hex() == private_2_hex.to_hex()
- # regression key from bytes vs from hex, and vs raw key value
- assert private_reg_byt.is_equivalent_to(private_reg_hex)
- assert private_reg_hex.is_equivalent_to(private_reg_byt)
- assert REG__PRIVATE_BYTES == private_reg_byt.to_bytes()
- assert REG__PRIVATE_BYTES == private_reg_hex.to_bytes()
- assert REG__PRIVATE_HEX == private_reg_byt.to_hex()
- assert REG__PRIVATE_HEX == private_reg_hex.to_hex()
- # Test the behavior when is_equivalent_to is provided a bad argument.
- for bad_argument in ['1', 1, '1'*32, REG__PRIVATE_BYTES, b'1'*31, b'1'*33]:
- with pytest.raises(TypeError):
- private_reg_byt.is_equivalent_to(bad_argument)
- # This is the version of the tests before PrivateKey and PublicKey classes were
- # created to cut down on the utility function noise and make things easier to
- # work with.
- # def test_key_functions():
- # """
- # Unit tests for functions:
- # keyfiles_to_keys
- # keyfiles_to_bytes
- # key_to_bytes
- # public_key_from_bytes
- # private_key_from_bytes
- # keys_are_equivalent
- # """
- #
- # # Test keyfiles_to_keys and keyfiles_to_bytes
- # # Regression: load old key pair, two ways.
- # # First, dump them to temp files (to test keyfiles_to_keys).
- # with open('keytest_old.pri', 'wb') as fobj:
- # fobj.write(REG__PRIVATE_BYTES)
- # with open('keytest_old.pub', 'wb') as fobj:
- # fobj.write(REG__PUBLIC_BYTES)
- # loaded_old_private_bytes, loaded_old_public_bytes = keyfiles_to_bytes(
- # 'keytest_old')
- # loaded_old_private, loaded_old_public = keyfiles_to_keys('keytest_old')
- #
- # # Clean up a bit.
- # for fname in ['keytest_old.pri', 'keytest_old.pub']:
- # if os.path.exists(fname):
- # os.remove(fname)
- #
- # # Check the keys we wrote and then loaded.
- # assert loaded_old_private_bytes == REG__PRIVATE_BYTES
- # assert loaded_old_public_bytes == REG__PUBLIC_BYTES
- #
- # # Test key object construction (could also call it "key loading")
- # other_private = private_key_from_bytes(b'1'*32)
- # other_private_dupe = private_key_from_bytes(b'1'*32)
- # other_public = public_key_from_bytes(b'2'*32)
- # other_public_dupe = public_key_from_bytes(b'2'*32)
- # for bad_argument in ['1', 1, '1'*32, loaded_old_private, b'1'*31, b'1'*33]:
- # with pytest.raises((TypeError, ValueError)):
- # public_key_from_bytes(bad_argument)
- # with pytest.raises((TypeError, ValueError)):
- # private_key_from_bytes(bad_argument)
- #
- # # Test key equivalence checker.
- # assert keys_are_equivalent(other_private, other_private_dupe)
- # assert keys_are_equivalent(other_public, other_public_dupe)
- # assert not keys_are_equivalent(other_private, other_public)
- # assert not keys_are_equivalent(loaded_old_private, loaded_old_public)
- # assert not keys_are_equivalent(loaded_old_private, other_private)
- #
- # for bad_argument in ['1', 1, '1'*32, REG__PRIVATE_BYTES, b'1'*31, b'1'*33]:
- # with pytest.raises(TypeError):
- # keys_are_equivalent(bad_argument, loaded_old_private)
- # with pytest.raises(TypeError):
- # keys_are_equivalent(loaded_old_private, bad_argument)
- # with pytest.raises(TypeError):
- # keys_are_equivalent(bad_argument, bad_argument)
- #
- #
- # # Test key_to_bytes
- # assert REG__PUBLIC_BYTES == key_to_bytes(loaded_old_public)
- # assert REG__PRIVATE_BYTES == key_to_bytes(loaded_old_private)
- # for bad_argument in ['1', 1, '1'*32, REG__PRIVATE_BYTES, b'1'*32]:
- # with pytest.raises(TypeError):
- # key_to_bytes(bad_argument)
- #
- #
- # # # Make a new keypair. Returns keys and writes keys to disk.
- # # # Then load it from disk and compare that to the return value. Exercise
- # # # some of the functions redundantly.
- # # assert keys_are_equivalent(generated_public, loaded_new_public)
- # # assert keys_are_equivalent(
- # # loaded_new_private,
- # # private_key_from_bytes(loaded_new_private_bytes))
- # # assert keys_are_equivalent(
- # # loaded_new_public, public_key_from_bytes(loaded_new_public_bytes))
- # Pull these from the integration tests in test_authentication.py
- def test_is_gpg_signature():
- """
- Tests:
- is_gpg_signature
- checkformat_gpg_signature
- is_a_signature (only for cases relevant to gpg signatures)
- checkformat_signature (only for cases relevant to gpg signatures)
- """
- def expect_success(sig):
- checkformat_gpg_signature(sig)
- checkformat_signature(sig)
- assert is_gpg_signature(sig)
- assert is_a_signature(sig)
- def expect_failure(sig, exception_class):
- with pytest.raises(exception_class):
- checkformat_gpg_signature(sig)
- with pytest.raises(exception_class):
- checkformat_signature(sig)
- assert not is_gpg_signature(sig)
- assert not is_a_signature(sig)
- gpg_sig = {
- 'other_headers': '04001608001d162104f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd58905025defd3d3',
- 'signature': 'd6a3754dbd604a703434058c70db6a510b84a571236155df0b1f7f42605eb9e0faabca111d6ee808a7fcba663eafb5d66ecdfd33bd632df016fde3aed0f75201'
- }
- expect_success(gpg_sig)
- # Add optional fingerprint entry.
- gpg_sig['see_also'] = 'f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589'
- expect_success(gpg_sig)
- # Too short
- gpg_sig['see_also'] = gpg_sig['see_also'][:-1]
- expect_failure(gpg_sig, ValueError)
- # Nonsense
- expect_failure(42, TypeError)
- del gpg_sig['see_also']
- # Also too short
- gpg_sig['signature'] = gpg_sig['signature'][:-1]
- expect_failure(gpg_sig, ValueError)
- # def test_wrap_as_signable():
- # raise(NotImplementedError())
- # def test_is_a_signable():
- # raise(NotImplementedError())
- # def test_is_hex_signature():
- # raise(NotImplementedError())
- def test_is_hex_key():
- assert is_hex_key('00' * 32)
- assert is_hex_key(SAMPLE_KEYVAL)
- assert not is_hex_key('00' * 31)
- assert not is_hex_key('00' * 33)
- assert not is_hex_key('00' * 64)
- assert not is_hex_key('1g' * 32)
- assert not is_hex_key(b'1g' * 32)
- pubkey_bytes = binascii.unhexlify(SAMPLE_KEYVAL)
- assert not is_hex_key(pubkey_bytes)
- public = PublicKey.from_bytes(pubkey_bytes)
- assert not is_hex_key(public)
- assert is_hex_key(public.to_hex())
- def test_checkformat_hex_string():
- # TODO ✅: Add other tests.
- with pytest.raises(ValueError):
- checkformat_hex_string('A') # single case is important
- checkformat_hex_string('a')
- checkformat_hex_string(SAMPLE_KEYVAL)
- # def test_checkformat_hex_key():
- # raise NotImplementedError()
- # def test_checkformat_list_of_hex_keys():
- # raise NotImplementedError()
- # def test_checkformat_byteslike():
- # raise NotImplementedError()
- # def test_checkformat_natural_int():
- # raise NotImplementedError()
- # def test_checkformat_expiration_distance():
- # raise NotImplementedError()
- # def test_checkformat_utc_isoformat():
- # raise NotImplementedError()
- # def test_checkformat_gpg_fingerprint():
- # raise NotImplementedError()
- # def test_checkformat_gpg_signature():
- # raise NotImplementedError()
- def test_checkformat_delegation():
- # TODO ✅: Add other tests.
- with pytest.raises(TypeError):
- checkformat_delegation(1)
- with pytest.raises(ValueError):
- checkformat_delegation({})
- with pytest.raises(ValueError):
- checkformat_delegation({
- 'threshold': 0, 'pubkeys': ['01'*32]})
- with pytest.raises(ValueError):
- checkformat_delegation({
- 'threshold': 1.5, 'pubkeys': ['01'*32]})
- checkformat_delegation({
- 'threshold': 1, 'pubkeys': ['01'*32]})
- with pytest.raises(ValueError):
- checkformat_delegation({
- 'threshold': 1, 'pubkeys': ['01'*31]})
- with pytest.raises(ValueError):
- checkformat_delegation({
- 'threshold': 1, 'pubkeys': ['01'*31]})
- def test_checkformat_delegating_metadata():
- checkformat_delegating_metadata(SAMPLE_SIGNED_ROOT_MD)
- # TODO ✅: Add a few other kinds of valid metadata to this test:
- # - key_mgr metadata:
- # - one signed using raw ed25519, one signed using OpenPGP
- # - one with and one without version provided
- # - root metadata:
- # - one signed using raw ed25519 instead of OpenPGP
- for badval in [
- SAMPLE_ROOT_MD_CONTENT,
- # TODO ✅: Add more bad values (bad sig formats, etc.)
- ]:
- with pytest.raises( (TypeError, ValueError) ):
- checkformat_delegating_metadata(badval)
- # def test_iso8601_time_plus_delta():
- # raise NotImplementedError()
- # def test_is_hex_string():
- # raise(NotImplementedError())
- # def test_set_expiry():
- # raise NotImplementedError()
|