test_common.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. # -*- coding: utf-8 -*-
  2. """ tests.test_common
  3. (Mostly) unit tests for conda-content-trust/conda_content_trust/common.py.
  4. Run the tests this way:
  5. pytest tests/test_common.py
  6. """
  7. # Python2 Compatibility
  8. from __future__ import absolute_import, division, print_function, unicode_literals
  9. import os
  10. import pytest
  11. from conda_content_trust.common import *
  12. # A 40-hex-character GPG public key fingerprint
  13. SAMPLE_FINGERPRINT = 'f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589'
  14. SAMPLE_UNKNOWN_FINGERPRINT = '0123456789abcdef0123456789abcdef01234567'
  15. # The real key value of the public key (q, 32-byte ed25519 public key val),
  16. # as a length-64 hex string.
  17. SAMPLE_KEYVAL = 'bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07'
  18. SAMPLE_GPG_KEY_OBJ = {
  19. 'creation_time': 1571411344,
  20. 'hashes': ['pgp+SHA2'],
  21. 'keyid': SAMPLE_FINGERPRINT,
  22. 'keyval': {
  23. 'private': '',
  24. 'public': {'q': SAMPLE_KEYVAL}
  25. },
  26. 'method': 'pgp+eddsa-ed25519',
  27. 'type': 'eddsa'
  28. }
  29. SAMPLE_ROOT_MD_CONTENT = {
  30. 'delegations': {
  31. 'key_mgr.json': {'pubkeys': [], 'threshold': 1},
  32. 'root.json': {
  33. 'pubkeys': ['bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07'],
  34. 'threshold': 1}
  35. },
  36. 'expiration': '2020-12-09T17:20:19Z',
  37. '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
  38. 'type': 'root',
  39. 'version': 1
  40. }
  41. SAMPLE_GPG_SIG = {
  42. 'see_also': 'f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589',
  43. 'other_headers': '04001608001d162104f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd58905025defd3d3',
  44. 'signature': 'd6a3754dbd604a703434058c70db6a510b84a571236155df0b1f7f42605eb9e0faabca111d6ee808a7fcba663eafb5d66ecdfd33bd632df016fde3aed0f75201'
  45. }
  46. SAMPLE_SIGNED_ROOT_MD = {
  47. 'signatures': {
  48. 'bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07': SAMPLE_GPG_SIG
  49. },
  50. 'signed': SAMPLE_ROOT_MD_CONTENT
  51. }
  52. EXPECTED_SERIALIZED_SAMPLE_SIGNED_ROOT_MD = (b'{\n '
  53. b'"signatures": {\n '
  54. b'"bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07": {\n '
  55. b'"other_headers": "04001608001d162104f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd58905025defd3d3",\n '
  56. b'"see_also": "f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589",\n '
  57. b'"signature": "d6a3754dbd604a703434058c70db6a510b84a571236155df0b1f7f42605eb9e0faabca111d6ee808a7fcba663eafb5d66ecdfd33bd632df016fde3aed0f75201"\n }\n },\n '
  58. b'"signed": {\n '
  59. b'"delegations": {\n '
  60. b'"key_mgr.json": {\n '
  61. b'"pubkeys": [],\n '
  62. b'"threshold": 1\n },\n '
  63. b'"root.json": {\n '
  64. b'"pubkeys": [\n "bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07"\n ],\n '
  65. b'"threshold": 1\n }\n },\n '
  66. b'"expiration": "2020-12-09T17:20:19Z",\n '
  67. 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
  68. b'"type": "root",\n '
  69. b'"version": 1\n }\n}')
  70. # # Some REGRESSION test data.
  71. # REG__KEYPAIR_NAME = 'keytest_old'
  72. 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'
  73. 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"
  74. REG__PUBLIC_HEX = '013ddd714962866d12ba5bae273f14d48c89cf0773dee2dbf6d4561e521c83f7'
  75. REG__PRIVATE_HEX = 'c9c2060d7e0d93616c2654840b4983d00221d8b6b69c850107da74b42168f937'
  76. # REG__MESSAGE_THAT_WAS_SIGNED = b'123456\x067890'
  77. # # Signature is over REG__MESSAGE_THAT_WAS_SIGNED using key REG__PRIVATE_BYTES.
  78. # 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'
  79. REG__HASHED_VAL = b'string to hash\n'
  80. REG__HASH_HEX = '73aec9a93f4beb41a9bad14b9d1398f60e78ccefd97e4eb7d3cf26ba71dbe0ce'
  81. # #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'
  82. # def test_sha512256():
  83. # # Test the SHA-512-truncate-256 hashing function w/ an expected result.
  84. # assert sha512256(REG__HASHED_VAL) == REG__HASH_HEX
  85. # # TODO: Test more? Unusual input
  86. def test_canonserialize():
  87. # Simple primitives
  88. assert canonserialize('') == b'""'
  89. assert canonserialize('a') == b'"a"'
  90. assert canonserialize(12) == b'12'
  91. assert canonserialize(SAMPLE_KEYVAL) == (
  92. b'"bfbeb6554fca9558da7aa05c5e9952b7a1aa3995dede93f3bb89f0abecc7dc07"')
  93. # Tuples
  94. assert canonserialize((1, 2, 3)) == b'[\n 1,\n 2,\n 3\n]'
  95. assert canonserialize(('ABC', 1, 16)) == b'[\n "ABC",\n 1,\n 16\n]'
  96. # Dictionaries indexed by ints, strings, or both
  97. assert canonserialize({}) == b'{}'
  98. assert canonserialize({1: 'v1', 2: 'v2'}) == (
  99. b'{\n "1": "v1",\n "2": "v2"\n}')
  100. assert canonserialize({'a': 'v1', 'b': 'v2'}) == (
  101. b'{\n "a": "v1",\n "b": "v2"\n}')
  102. with pytest.raises(TypeError):
  103. # Currently, json.dumps(...sort_keys=True) raises a TypeError while
  104. # sorting if it has to sort string keys and integer keys together.
  105. canonserialize({5: 'value', 'key': 9, 'key2': 'value2'})
  106. assert canonserialize(SAMPLE_SIGNED_ROOT_MD) == (
  107. EXPECTED_SERIALIZED_SAMPLE_SIGNED_ROOT_MD)
  108. # TODO: Tricksy tests that mess with encoding.
  109. def test_keyfile_operations():
  110. """
  111. Unit tests for functions:
  112. keyfiles_to_keys
  113. keyfiles_to_bytes
  114. """
  115. # Test keyfiles_to_keys and keyfiles_to_bytes
  116. # Regression: load old key pair, two ways.
  117. # First, dump them to temp files (to test keyfiles_to_keys).
  118. with open('keytest_old.pri', 'wb') as fobj:
  119. fobj.write(REG__PRIVATE_BYTES)
  120. with open('keytest_old.pub', 'wb') as fobj:
  121. fobj.write(REG__PUBLIC_BYTES)
  122. loaded_old_private_bytes, loaded_old_public_bytes = keyfiles_to_bytes(
  123. 'keytest_old')
  124. loaded_old_private, loaded_old_public = keyfiles_to_keys('keytest_old')
  125. # Clean up a bit.
  126. for fname in ['keytest_old.pri', 'keytest_old.pub']:
  127. if os.path.exists(fname):
  128. os.remove(fname)
  129. # Check the keys we wrote and then loaded.
  130. assert loaded_old_private_bytes == REG__PRIVATE_BYTES
  131. assert loaded_old_public_bytes == REG__PUBLIC_BYTES
  132. def test_key_functions():
  133. # """
  134. # Tests for functions:
  135. # from_bytes
  136. # from_hex
  137. # to_bytes
  138. # to_hex
  139. # is_equivalent_to
  140. # """
  141. # First key, generated in two ways:
  142. private_1_byt = PrivateKey.from_bytes(b'1'*32)
  143. private_1_hex = PrivateKey.from_hex('31'*32) # hex representation of b'1'
  144. # Second key, generated in two ways:
  145. private_2_byt = PrivateKey.from_bytes(b'10' + b'1'*30)
  146. private_2_hex = PrivateKey.from_hex('3130' + '31'*30)
  147. # Regression key, generated in two ways:
  148. private_reg_byt = PrivateKey.from_bytes(REG__PRIVATE_BYTES)
  149. private_reg_hex = PrivateKey.from_hex(REG__PRIVATE_HEX)
  150. # Check these against each other and also against the expected output:
  151. # - to_bytes
  152. # - to_hex
  153. # - is_equivalent_to
  154. # - from_bytes
  155. # - from_hex
  156. # key 1 from bytes vs key 1 from hex, also vs raw key 1 value
  157. assert private_1_byt.is_equivalent_to(private_1_hex)
  158. assert private_1_hex.is_equivalent_to(private_1_byt)
  159. assert b'1'*32 == private_1_byt.to_bytes() == private_1_hex.to_bytes()
  160. assert '31'*32 == private_1_byt.to_hex() == private_1_hex.to_hex()
  161. # key 1 vs key 2 vs regression key
  162. assert not private_1_byt.is_equivalent_to(private_2_byt)
  163. assert not private_2_byt.is_equivalent_to(private_1_byt)
  164. assert not private_1_byt.is_equivalent_to(private_2_hex)
  165. assert not private_2_hex.is_equivalent_to(private_1_byt)
  166. assert not private_reg_byt.is_equivalent_to(private_1_byt)
  167. assert not private_1_byt.is_equivalent_to(private_reg_byt)
  168. # key 2 from bytes vs key 2 from hex, also vs raw key 2 value
  169. assert private_2_byt.is_equivalent_to(private_2_hex)
  170. assert private_2_hex.is_equivalent_to(private_2_byt)
  171. assert b'10' + b'1'*30 == private_2_byt.to_bytes() == private_2_hex.to_bytes()
  172. assert '3130' + '31'*30 == private_2_byt.to_hex() == private_2_hex.to_hex()
  173. # regression key from bytes vs from hex, and vs raw key value
  174. assert private_reg_byt.is_equivalent_to(private_reg_hex)
  175. assert private_reg_hex.is_equivalent_to(private_reg_byt)
  176. assert REG__PRIVATE_BYTES == private_reg_byt.to_bytes()
  177. assert REG__PRIVATE_BYTES == private_reg_hex.to_bytes()
  178. assert REG__PRIVATE_HEX == private_reg_byt.to_hex()
  179. assert REG__PRIVATE_HEX == private_reg_hex.to_hex()
  180. # Test the behavior when is_equivalent_to is provided a bad argument.
  181. for bad_argument in ['1', 1, '1'*32, REG__PRIVATE_BYTES, b'1'*31, b'1'*33]:
  182. with pytest.raises(TypeError):
  183. private_reg_byt.is_equivalent_to(bad_argument)
  184. # This is the version of the tests before PrivateKey and PublicKey classes were
  185. # created to cut down on the utility function noise and make things easier to
  186. # work with.
  187. # def test_key_functions():
  188. # """
  189. # Unit tests for functions:
  190. # keyfiles_to_keys
  191. # keyfiles_to_bytes
  192. # key_to_bytes
  193. # public_key_from_bytes
  194. # private_key_from_bytes
  195. # keys_are_equivalent
  196. # """
  197. #
  198. # # Test keyfiles_to_keys and keyfiles_to_bytes
  199. # # Regression: load old key pair, two ways.
  200. # # First, dump them to temp files (to test keyfiles_to_keys).
  201. # with open('keytest_old.pri', 'wb') as fobj:
  202. # fobj.write(REG__PRIVATE_BYTES)
  203. # with open('keytest_old.pub', 'wb') as fobj:
  204. # fobj.write(REG__PUBLIC_BYTES)
  205. # loaded_old_private_bytes, loaded_old_public_bytes = keyfiles_to_bytes(
  206. # 'keytest_old')
  207. # loaded_old_private, loaded_old_public = keyfiles_to_keys('keytest_old')
  208. #
  209. # # Clean up a bit.
  210. # for fname in ['keytest_old.pri', 'keytest_old.pub']:
  211. # if os.path.exists(fname):
  212. # os.remove(fname)
  213. #
  214. # # Check the keys we wrote and then loaded.
  215. # assert loaded_old_private_bytes == REG__PRIVATE_BYTES
  216. # assert loaded_old_public_bytes == REG__PUBLIC_BYTES
  217. #
  218. # # Test key object construction (could also call it "key loading")
  219. # other_private = private_key_from_bytes(b'1'*32)
  220. # other_private_dupe = private_key_from_bytes(b'1'*32)
  221. # other_public = public_key_from_bytes(b'2'*32)
  222. # other_public_dupe = public_key_from_bytes(b'2'*32)
  223. # for bad_argument in ['1', 1, '1'*32, loaded_old_private, b'1'*31, b'1'*33]:
  224. # with pytest.raises((TypeError, ValueError)):
  225. # public_key_from_bytes(bad_argument)
  226. # with pytest.raises((TypeError, ValueError)):
  227. # private_key_from_bytes(bad_argument)
  228. #
  229. # # Test key equivalence checker.
  230. # assert keys_are_equivalent(other_private, other_private_dupe)
  231. # assert keys_are_equivalent(other_public, other_public_dupe)
  232. # assert not keys_are_equivalent(other_private, other_public)
  233. # assert not keys_are_equivalent(loaded_old_private, loaded_old_public)
  234. # assert not keys_are_equivalent(loaded_old_private, other_private)
  235. #
  236. # for bad_argument in ['1', 1, '1'*32, REG__PRIVATE_BYTES, b'1'*31, b'1'*33]:
  237. # with pytest.raises(TypeError):
  238. # keys_are_equivalent(bad_argument, loaded_old_private)
  239. # with pytest.raises(TypeError):
  240. # keys_are_equivalent(loaded_old_private, bad_argument)
  241. # with pytest.raises(TypeError):
  242. # keys_are_equivalent(bad_argument, bad_argument)
  243. #
  244. #
  245. # # Test key_to_bytes
  246. # assert REG__PUBLIC_BYTES == key_to_bytes(loaded_old_public)
  247. # assert REG__PRIVATE_BYTES == key_to_bytes(loaded_old_private)
  248. # for bad_argument in ['1', 1, '1'*32, REG__PRIVATE_BYTES, b'1'*32]:
  249. # with pytest.raises(TypeError):
  250. # key_to_bytes(bad_argument)
  251. #
  252. #
  253. # # # Make a new keypair. Returns keys and writes keys to disk.
  254. # # # Then load it from disk and compare that to the return value. Exercise
  255. # # # some of the functions redundantly.
  256. # # assert keys_are_equivalent(generated_public, loaded_new_public)
  257. # # assert keys_are_equivalent(
  258. # # loaded_new_private,
  259. # # private_key_from_bytes(loaded_new_private_bytes))
  260. # # assert keys_are_equivalent(
  261. # # loaded_new_public, public_key_from_bytes(loaded_new_public_bytes))
  262. # Pull these from the integration tests in test_authentication.py
  263. def test_is_gpg_signature():
  264. """
  265. Tests:
  266. is_gpg_signature
  267. checkformat_gpg_signature
  268. is_a_signature (only for cases relevant to gpg signatures)
  269. checkformat_signature (only for cases relevant to gpg signatures)
  270. """
  271. def expect_success(sig):
  272. checkformat_gpg_signature(sig)
  273. checkformat_signature(sig)
  274. assert is_gpg_signature(sig)
  275. assert is_a_signature(sig)
  276. def expect_failure(sig, exception_class):
  277. with pytest.raises(exception_class):
  278. checkformat_gpg_signature(sig)
  279. with pytest.raises(exception_class):
  280. checkformat_signature(sig)
  281. assert not is_gpg_signature(sig)
  282. assert not is_a_signature(sig)
  283. gpg_sig = {
  284. 'other_headers': '04001608001d162104f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd58905025defd3d3',
  285. 'signature': 'd6a3754dbd604a703434058c70db6a510b84a571236155df0b1f7f42605eb9e0faabca111d6ee808a7fcba663eafb5d66ecdfd33bd632df016fde3aed0f75201'
  286. }
  287. expect_success(gpg_sig)
  288. # Add optional fingerprint entry.
  289. gpg_sig['see_also'] = 'f075dd2f6f4cb3bd76134bbb81b6ca16ef9cd589'
  290. expect_success(gpg_sig)
  291. # Too short
  292. gpg_sig['see_also'] = gpg_sig['see_also'][:-1]
  293. expect_failure(gpg_sig, ValueError)
  294. # Nonsense
  295. expect_failure(42, TypeError)
  296. del gpg_sig['see_also']
  297. # Also too short
  298. gpg_sig['signature'] = gpg_sig['signature'][:-1]
  299. expect_failure(gpg_sig, ValueError)
  300. # def test_wrap_as_signable():
  301. # raise(NotImplementedError())
  302. # def test_is_a_signable():
  303. # raise(NotImplementedError())
  304. # def test_is_hex_signature():
  305. # raise(NotImplementedError())
  306. def test_is_hex_key():
  307. assert is_hex_key('00' * 32)
  308. assert is_hex_key(SAMPLE_KEYVAL)
  309. assert not is_hex_key('00' * 31)
  310. assert not is_hex_key('00' * 33)
  311. assert not is_hex_key('00' * 64)
  312. assert not is_hex_key('1g' * 32)
  313. assert not is_hex_key(b'1g' * 32)
  314. pubkey_bytes = binascii.unhexlify(SAMPLE_KEYVAL)
  315. assert not is_hex_key(pubkey_bytes)
  316. public = PublicKey.from_bytes(pubkey_bytes)
  317. assert not is_hex_key(public)
  318. assert is_hex_key(public.to_hex())
  319. def test_checkformat_hex_string():
  320. # TODO ✅: Add other tests.
  321. with pytest.raises(ValueError):
  322. checkformat_hex_string('A') # single case is important
  323. checkformat_hex_string('a')
  324. checkformat_hex_string(SAMPLE_KEYVAL)
  325. # def test_checkformat_hex_key():
  326. # raise NotImplementedError()
  327. # def test_checkformat_list_of_hex_keys():
  328. # raise NotImplementedError()
  329. # def test_checkformat_byteslike():
  330. # raise NotImplementedError()
  331. # def test_checkformat_natural_int():
  332. # raise NotImplementedError()
  333. # def test_checkformat_expiration_distance():
  334. # raise NotImplementedError()
  335. # def test_checkformat_utc_isoformat():
  336. # raise NotImplementedError()
  337. # def test_checkformat_gpg_fingerprint():
  338. # raise NotImplementedError()
  339. # def test_checkformat_gpg_signature():
  340. # raise NotImplementedError()
  341. def test_checkformat_delegation():
  342. # TODO ✅: Add other tests.
  343. with pytest.raises(TypeError):
  344. checkformat_delegation(1)
  345. with pytest.raises(ValueError):
  346. checkformat_delegation({})
  347. with pytest.raises(ValueError):
  348. checkformat_delegation({
  349. 'threshold': 0, 'pubkeys': ['01'*32]})
  350. with pytest.raises(ValueError):
  351. checkformat_delegation({
  352. 'threshold': 1.5, 'pubkeys': ['01'*32]})
  353. checkformat_delegation({
  354. 'threshold': 1, 'pubkeys': ['01'*32]})
  355. with pytest.raises(ValueError):
  356. checkformat_delegation({
  357. 'threshold': 1, 'pubkeys': ['01'*31]})
  358. with pytest.raises(ValueError):
  359. checkformat_delegation({
  360. 'threshold': 1, 'pubkeys': ['01'*31]})
  361. def test_checkformat_delegating_metadata():
  362. checkformat_delegating_metadata(SAMPLE_SIGNED_ROOT_MD)
  363. # TODO ✅: Add a few other kinds of valid metadata to this test:
  364. # - key_mgr metadata:
  365. # - one signed using raw ed25519, one signed using OpenPGP
  366. # - one with and one without version provided
  367. # - root metadata:
  368. # - one signed using raw ed25519 instead of OpenPGP
  369. for badval in [
  370. SAMPLE_ROOT_MD_CONTENT,
  371. # TODO ✅: Add more bad values (bad sig formats, etc.)
  372. ]:
  373. with pytest.raises( (TypeError, ValueError) ):
  374. checkformat_delegating_metadata(badval)
  375. # def test_iso8601_time_plus_delta():
  376. # raise NotImplementedError()
  377. # def test_is_hex_string():
  378. # raise(NotImplementedError())
  379. # def test_set_expiry():
  380. # raise NotImplementedError()