# -*- 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()