123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- # -*- coding: utf-8 -*-
- """ tests.test_authentication
- Unit tests for conda-content-trust/car/authentication.py
- as well as integration tests for the signing.py + authentication.py.
- Run the tests this way:
- pytest tests/test_authentication.py
- """
- # Python2 Compatibility
- from __future__ import absolute_import, division, print_function, unicode_literals
- # std libs
- import copy
- import os
- # dependencies
- import pytest
- import cryptography.exceptions
- # this codebase
- from conda_content_trust.authentication import *
- from conda_content_trust.metadata_construction import (
- gen_keys, gen_and_write_keys, # for new-key tests
- # build_repodata_verification_metadata
- )
- from conda_content_trust.common import (
- PrivateKey, PublicKey, keyfiles_to_bytes, keyfiles_to_keys,
- SignatureError, MetadataVerificationError)
- from conda_content_trust.signing import wrap_as_signable, sign_signable
- # 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_ROOT = 'c8bd83b3bfc991face417d97b9c0db011b5d256476b602b92fec92849fc2b36c'
- 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__SIGNATURE_HEX = 'b6da14a1ed559ebf01b3a918c9b8bdcc464d408799e8988443e47d393ba4e5fdcfda617504f5ccc0e74f0ff04691d3b8227f441d4f292a1f3fd726d6d31f0d0e'
- # 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'
- # REG__REPODATA_HASHMAP = {
- # "noarch/current_repodata.json": "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "noarch/repodata.json": "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "noarch/repodata_from_packages.json": "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "osx-64/current_repodata.json": "8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2",
- # "osx-64/repodata.json": "8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2",
- # "osx-64/repodata_from_packages.json": "8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2"
- # }
- REG__TEST_TIMESTAMP = '2019-10-01T00:00:00Z'
- REG__TEST_EXPIRY_DATE = '2025-01-01T10:30:00Z'
- # REG__EXPECTED_UNSIGNED_REPODATA_VERIFY = {
- # 'type': 'repodata_verify', 'timestamp': REG__TEST_TIMESTAMP,
- # 'metadata_spec_version': '0.0.5', 'expiration': REG__TEST_EXPIRY_DATE,
- # 'secured_files': {
- # 'noarch/current_repodata.json': '908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe',
- # 'noarch/repodata.json': '908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe',
- # 'noarch/repodata_from_packages.json': '908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe',
- # 'osx-64/current_repodata.json': '8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2',
- # 'osx-64/repodata.json': '8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2',
- # 'osx-64/repodata_from_packages.json': '8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2'}
- # }
- # REG__EXPECTED_REGSIGNED_REPODATA_VERIFY = {
- # # Re-sign this if its data changes: it's signed!
- # 'type': 'repodata_verify', 'timestamp': '2019-10-01T00:00:00Z',
- # 'metadata_spec_version': '0.0.5', 'expiration': '2025-01-01T10:30:00Z',
- # 'secured_files': {
- # 'noarch/current_repodata.json': '908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe',
- # 'noarch/repodata.json': '908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe',
- # 'noarch/repodata_from_packages.json': '908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe',
- # 'osx-64/current_repodata.json': '8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2',
- # 'osx-64/repodata.json': '8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2',
- # 'osx-64/repodata_from_packages.json': '8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2'}
- # }
- TEST_ROOT_MD_V1 = {
- "signatures": {
- "c8bd83b3bfc991face417d97b9c0db011b5d256476b602b92fec92849fc2b36c": {
- "other_headers": "04001608001d162104917adb684e2e9fb5ed4e59909ddd19a1268b62d005025f96ff1a",
- "signature": "9327c2c4907c964741924420c4c35eb01805c15ec2fe5cad17bc98c0c3daf03006fcafb332eaa543a1ed212fac05f227662d8617970afc6c919ee4b78bacb004"
- }
- },
- "signed": {
- "delegations": {
- "key_mgr": {
- "pubkeys": [
- "013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7"
- ],
- "threshold": 1
- },
- "root": {
- "pubkeys": [
- "c8bd83b3bfc991face417d97b9c0db011b5d256476b602b92fec92849fc2b36c"
- ],
- "threshold": 1
- }
- },
- "expiration": "2021-10-26T16:53:46Z",
- "metadata_spec_version": "0.6.0",
- "timestamp": "2020-10-26T16:53:46Z",
- "type": "root",
- "version": 1
- }
- }
- TEST_ROOT_MD_V2 = {
- "signatures": {
- "a59cea0987ee9046d68d2d011e919eb9278e3f478cca77f5204d65191ff8d7a5": {
- "other_headers": "04001608001d1621040a14b126c986f276831c7b04134f35b47db4364305025f96ff1b",
- "signature": "d406839499630a75350ba6f6c009aae173f15dd8c9be069c9b535ff77b6d624d6092487fe18e2c4f5c13b252a3ebe3f89ab15f4c52c66db752f8cbbfc6d96609"
- },
- "c8bd83b3bfc991face417d97b9c0db011b5d256476b602b92fec92849fc2b36c": {
- "other_headers": "04001608001d162104917adb684e2e9fb5ed4e59909ddd19a1268b62d005025f96ff1b",
- "signature": "f4c13d4456028778026639fcdc63ec7d6005e1e88f2dcfaf87afa3b89ce6a1ec8938af83fdc9d3d7045d0ebd648654c6af027daaf7164e014a8a53f373e9b906"
- }
- },
- "signed": {
- "delegations": {
- "key_mgr": {
- "pubkeys": [
- "013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7"
- ],
- "threshold": 1
- },
- "root": {
- "pubkeys": [
- "c8bd83b3bfc991face417d97b9c0db011b5d256476b602b92fec92849fc2b36c"
- ],
- "threshold": 1
- }
- },
- "expiration": "2021-10-26T16:53:46Z",
- "metadata_spec_version": "0.6.0",
- "timestamp": "2020-10-26T16:53:46Z",
- "type": "root",
- "version": 2
- }
- }
- # NOTE to dev:
- # test_authenticate was originally a long sequence of tests in a single
- # function. I pulled out most of it, and what remains is has to be compared
- # to the new tests to see if it's still useful.
- def test_wrap_sign_verify_signable():
- # 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.
- generated_private, generated_public = gen_and_write_keys('keytest_new')
- loaded_new_private_bytes, loaded_new_public_bytes = keyfiles_to_bytes(
- 'keytest_new')
- loaded_new_private, loaded_new_public = keyfiles_to_keys('keytest_new')
- old_private = PrivateKey.from_bytes(REG__PRIVATE_BYTES)
- old_public = PublicKey.from_bytes(REG__PUBLIC_BYTES)
- assert generated_private.is_equivalent_to(loaded_new_private)
- assert generated_public.is_equivalent_to(loaded_new_public)
- assert loaded_new_private.is_equivalent_to(
- PrivateKey.from_bytes(loaded_new_private_bytes))
- assert loaded_new_public.is_equivalent_to(
- PublicKey.from_bytes(loaded_new_public_bytes))
- # Clean up a bit for the next tests.
- new_private = loaded_new_private
- new_public = loaded_new_public
- del (
- loaded_new_public, loaded_new_private,
- generated_private, generated_public,
- loaded_new_private_bytes, loaded_new_public_bytes)
- # Test wrapping, signing signables, and verifying signables.
- d = {'foo': 'bar', '1': 2}
- d_modified = {'foo': 'DOOM', '1': 2}
- signable_d = wrap_as_signable(d)
- assert is_a_signable(signable_d)
- sign_signable(signable_d, old_private)
- assert is_a_signable(signable_d)
- verify_signable(
- signable=signable_d,
- authorized_pub_keys=[old_public.to_hex()],
- threshold=1)
- # Expect failure this time due to bad format.
- try:
- verify_signable(
- signable=signable_d['signed'],
- authorized_pub_keys=[old_public.to_hex()],
- threshold=1)
- except TypeError:
- pass
- else:
- assert False, 'Failed to raise expected exception.'
- # Expect failure this time due to non-matching signature.
- try:
- modified_signable_d = copy.deepcopy(signable_d)
- modified_signable_d['signed'] = d_modified
- verify_signable(
- signable=modified_signable_d,
- authorized_pub_keys=[old_public.to_hex()],
- threshold=1)
- except SignatureError:
- pass
- else:
- assert False, 'Failed to raise expected exception.'
- # Clean up a bit.
- for fname in [
- 'keytest_new.pub', 'keytest_new.pri',
- 'keytest_old.pri', 'keytest_old.pub']:
- if os.path.exists(fname):
- os.remove(fname)
- # def test_repodata_verify_funcs():
- # # Test construction and verification of signed repodata_verify, including
- # # wrapping, signing the signable, and verifying the signables with a real
- # # example.
- # repodata_hashmap = {
- # "noarch/current_repodata.json":
- # "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "noarch/repodata.json":
- # "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "noarch/repodata_from_packages.json":
- # "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "osx-64/current_repodata.json":
- # "8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2",
- # "osx-64/repodata.json":
- # "8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2",
- # "osx-64/repodata_from_packages.json":
- # "8120fb07a6a8a280ffa2b89fb2fbb89484823d0b0357ff0cfa7c333352b2faa2"}
- # rd_v_md = build_repodata_verification_metadata(repodata_hashmap)
- # signable_rd_v_md = wrap_as_signable(rd_v_md)
- # assert is_a_signable(signable_rd_v_md)
- # sign_signable(signable_rd_v_md, old_private)
- # assert is_a_signable(signable_rd_v_md)
- # verify_signable(
- # signable=signable_rd_v_md,
- # authorized_pub_keys=[old_public.to_hex()],
- # threshold=1)
- # # Expect failure this time due to non-matching signature.
- # try:
- # modified_signable_rd_v_md = copy.deepcopy(signable_rd_v_md)
- # modified_signable_rd_v_md[
- # 'signed']['secured_files']['noarch/current_repodata.json'
- # ] = modified_signable_rd_v_md['signed']['secured_files'][
- # 'noarch/current_repodata.json'][:-1] + 'f' # TODO: Generalize test condition. (Also, un-ugly.)
- # verify_signable(
- # signable=modified_signable_rd_v_md,
- # authorized_pub_keys=[old_public.to_hex()],
- # threshold=1)
- # except SignatureError:
- # pass
- # else:
- # assert False, 'Failed to raise expected exception.'
- # # DEBUG: 💥💥💥💥 Dump the various bits and pieces for debugging.
- # # Remove this.
- # with open('_test_output__repodata_hashmap.json', 'wb') as fobj:
- # fobj.write(canonserialize(repodata_hashmap))
- # with open('_test_output__repodata_verify.json', 'wb') as fobj:
- # fobj.write(canonserialize(signable_rd_v_md))
- # # Additional regression test for a file produced by the indexer.
- # # This should come up as good.
- # verify_signable(
- # signable={
- # "signatures": {
- # "013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7": "740a426113cb83a62e58eb41fcd0b5f36691b0b18bffbe7eb3da30b5baf83f6c703a0fdb584599702470c74f55572a27cf9de250fc3afb723c43fef4dc778401"
- # },
- # "signed": {
- # "expiration": "2019-10-28T15:36:32Z",
- # "metadata_spec_version": "0.0.4",
- # "secured_files": {
- # "noarch/current_repodata.json": "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "noarch/repodata.json": "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "noarch/repodata_from_packages.json": "908724926552827ab58dfc0bccba92426cec9f1f483883da3ff0d8664e18c0fe",
- # "osx-64/current_repodata.json": "fc9268ea2b4add37e090b7f2b2c88b95c513cab445fb099e8631d8815a384ae4",
- # "osx-64/repodata.json": "fc9268ea2b4add37e090b7f2b2c88b95c513cab445fb099e8631d8815a384ae4",
- # "osx-64/repodata_from_packages.json": "fc9268ea2b4add37e090b7f2b2c88b95c513cab445fb099e8631d8815a384ae4"
- # },
- # "timestamp": "2019-09-27T15:36:32Z",
- # "type": "repodata_verify"
- # }
- # },
- # authorized_pub_keys=[old_public.to_hex()],
- # threshold=1)
- def test_sign_and_verify():
- """
- Tests functions:
- - sign
- - verify
- """
- # Generate new keys and construct key objects for old keys.
- new_private, new_public = gen_keys()
- old_private = PrivateKey.from_bytes(REG__PRIVATE_BYTES)
- old_public = PublicKey.from_bytes(REG__PUBLIC_BYTES)
- old_sig = old_private.sign(REG__MESSAGE_THAT_WAS_SIGNED)
- new_sig = new_private.sign(REG__MESSAGE_THAT_WAS_SIGNED)
- new_sig2 = new_private.sign(REG__MESSAGE_THAT_WAS_SIGNED)
- assert new_sig == new_sig2 # deterministic (obv not a thorough test)
- assert old_sig == REG__SIGNATURE # regression
- # Test verify()
- # Good signatures first.
- old_public.verify(REG__SIGNATURE, REG__MESSAGE_THAT_WAS_SIGNED)
- old_public.verify(old_sig, REG__MESSAGE_THAT_WAS_SIGNED)
- new_public.verify(new_sig, REG__MESSAGE_THAT_WAS_SIGNED)
- # Use wrong public key.
- wrong_pubkey_obj = PublicKey.from_hex(
- '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
- with pytest.raises(cryptography.exceptions.InvalidSignature):
- wrong_pubkey_obj.verify(REG__SIGNATURE, REG__MESSAGE_THAT_WAS_SIGNED)
- # Use bad data.
- with pytest.raises(cryptography.exceptions.InvalidSignature):
- old_public.verify(new_sig, REG__MESSAGE_THAT_WAS_SIGNED + b'a')
- with pytest.raises(cryptography.exceptions.InvalidSignature):
- new_public.verify(new_sig, REG__MESSAGE_THAT_WAS_SIGNED[0:-1])
- def test_verify_signature():
- verify_signature(
- REG__SIGNATURE_HEX,
- PublicKey.from_bytes(REG__PUBLIC_BYTES),
- REG__MESSAGE_THAT_WAS_SIGNED)
- # invalid signatures
- with pytest.raises(cryptography.exceptions.InvalidSignature):
- verify_signature(
- REG__SIGNATURE_HEX[:-6] + 'ffffff', # wrong value
- PublicKey.from_bytes(REG__PUBLIC_BYTES),
- REG__MESSAGE_THAT_WAS_SIGNED)
- with pytest.raises(TypeError):
- verify_signature(
- REG__SIGNATURE_HEX[:-1], # wrong length
- PublicKey.from_bytes(REG__PUBLIC_BYTES),
- REG__MESSAGE_THAT_WAS_SIGNED)
- with pytest.raises(TypeError):
- verify_signature(
- REG__SIGNATURE, # wrong type
- PublicKey.from_bytes(REG__PUBLIC_BYTES),
- REG__MESSAGE_THAT_WAS_SIGNED)
- # other bad args
- with pytest.raises(cryptography.exceptions.InvalidSignature):
- verify_signature(
- REG__SIGNATURE_HEX, # wrong type
- PublicKey.from_bytes(REG__PUBLIC_BYTES),
- REG__MESSAGE_THAT_WAS_SIGNED + b'\xc9') # altered message
- with pytest.raises(cryptography.exceptions.InvalidSignature):
- verify_signature(
- REG__SIGNATURE_HEX,
- PublicKey.from_bytes(REG__PUBLIC_BYTES[:-4] + b'0000'), # wrong key
- REG__MESSAGE_THAT_WAS_SIGNED)
- with pytest.raises(TypeError):
- verify_signature(
- REG__SIGNATURE_HEX,
- REG__PUBLIC_BYTES, # wrong type
- REG__MESSAGE_THAT_WAS_SIGNED)
- with pytest.raises(TypeError):
- verify_signature(
- REG__SIGNATURE_HEX,
- PublicKey.from_bytes(REG__PUBLIC_BYTES),
- {'this is not bytes': 1}) # wrong type
- # verify_root is also tested in test_root.py (but test_root.py expects GPG)
- def test_verify_root():
- """
- Tests conda_content_trust.authentication.verify_root
- """
- # Root chaining: normal test
- verify_root(TEST_ROOT_MD_V1, TEST_ROOT_MD_V2)
- # Now we tinker a bit to break stuff.
- root_v2_edited = copy.deepcopy(TEST_ROOT_MD_V2)
- # Can't verify root v10 using root v1 (chaining)
- with pytest.raises(MetadataVerificationError):
- root_v2_edited['signed']['version'] = 10
- verify_root(TEST_ROOT_MD_V1, root_v2_edited)
- # Reset.
- root_v2_edited['signed']['version'] = TEST_ROOT_MD_V2['signed']['version']
- # Bad signature, same keys, same contents
- # with pytest.raises(cryptography.exceptions.InvalidSignature):
- with pytest.raises(SignatureError):
- sig = root_v2_edited['signatures'][REG__PUBLIC_HEX_ROOT]['signature']
- sig = sig[:-6] + 'ffffff'
- root_v2_edited['signatures'][REG__PUBLIC_HEX_ROOT]['signature'] = sig
- verify_root(TEST_ROOT_MD_V1, root_v2_edited)
- # Reset.
- root_v2_edited['signatures'] = copy.deepcopy(TEST_ROOT_MD_V2['signatures'])
- # Not enough signatures from authorized keys:
- # Have one of the signatures claim to be from the wrong key.
- with pytest.raises(SignatureError):
- root_v2_edited['signatures'][REG__PUBLIC_HEX_ROOT[:-6] + 'ffffff'] \
- = root_v2_edited['signatures'][REG__PUBLIC_HEX_ROOT]
- del root_v2_edited['signatures'][REG__PUBLIC_HEX_ROOT]
- verify_root(TEST_ROOT_MD_V1, root_v2_edited)
- # Reset.
- root_v2_edited['signatures'] = copy.deepcopy(TEST_ROOT_MD_V2['signatures'])
- # Not enough signatures from authorized keys:
- # Change the trusted metadata such that we expect sigs from 3 distinct
- # authorized keys (and still provide only 2).
- with pytest.raises(SignatureError):
- root_v1_edited = copy.deepcopy(TEST_ROOT_MD_V1)
- root_v1_edited['signed']['delegations']['root']['threshold'] += 1
- verify_root(root_v1_edited, TEST_ROOT_MD_V2)
- # Reset.
- root_v1_edited['signed']['delegations']['root']['threshold'] -= 1
- # def test_verify_delegation():
- # """
- # Tests conda_content_trust.authentication.verify_delegation
- # """
- # raise NotImplementedError('verify_delegation requires unit tests.')
|