key.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import os
  2. import re
  3. import shutil
  4. import tempfile
  5. from binascii import hexlify
  6. from attic.testsuite import AtticTestCase
  7. from attic.key import PlaintextKey, PassphraseKey, KeyfileKey, COMPR_DEFAULT, increment_iv
  8. from attic.helpers import Location, unhexlify
  9. class KeyTestCase(AtticTestCase):
  10. class MockArgs:
  11. repository = Location(tempfile.mkstemp()[1])
  12. compression = COMPR_DEFAULT
  13. mac = None
  14. cipher = None
  15. keyfile2_key_file = """
  16. ATTIC KEY 0000000000000000000000000000000000000000000000000000000000000000
  17. hqRzYWx02gAgA1l4jfyv22y6U/mxxDT8HodSWAcX0g3nOESrQcNnBsundmVyc2lvbgGqaX
  18. RlcmF0aW9uc84AAYagqWFsZ29yaXRobaRnbWFjpGhhc2iw7eaB54JssAOnM1S4S9CeTaRk
  19. YXRh2gDQzmuyg3iYjMeTLObY+ybI+QfngB+5mmHeEAfBa42fuEZgqM3rYyMj2XfgvamF+O
  20. 0asvhEyy9om190FaOxQ4RiiTMNqSP0FKLmd1i5ZyDMfRyp7JbscRFs9Ryk28yXWkv0MgQy
  21. EAYlaycY+6lWdRSgEPxidyPl9t9dr2AI/UuiQytwqmcmXgWD6Px6wgpOS/4AcRmEvDqIIl
  22. Rc2xsu+RevGAxk5rnrIIRPr7WB5R2cinzEn9ylDgBDt9LZbq706ELgtwVTnjWB8FBTPwVI
  23. vLTTXQ==
  24. """.strip()
  25. keyfile2_cdata = unhexlify(re.sub('\W', '', """
  26. 0393c4102e5ce8f5e9477c9e4ce2de453121aa139600001402c41000000000000000000000000000000000
  27. c2c407b0147a64a379d1
  28. """))
  29. keyfile2_id = unhexlify('dd9451069663931c8abd85452d016733')
  30. def setUp(self):
  31. self.tmppath = tempfile.mkdtemp()
  32. os.environ['ATTIC_KEYS_DIR'] = self.tmppath
  33. def tearDown(self):
  34. shutil.rmtree(self.tmppath)
  35. class MockRepository:
  36. class _Location:
  37. orig = '/some/place'
  38. _location = _Location()
  39. id = bytes(32)
  40. def _test_make_testdata(self):
  41. # modify tearDown to not kill the key file first, before using this
  42. os.environ['ATTIC_PASSPHRASE'] = 'passphrase'
  43. key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
  44. print("keyfile2_key_file: find the it in the filesystem, see location in test log output")
  45. print("keyfile2_cdata:", hexlify(key.encrypt(b'payload')))
  46. print("keyfile2_id:", hexlify(key.id_hash(b'payload')))
  47. assert False
  48. def test_plaintext(self):
  49. key = PlaintextKey.create(None, self.MockArgs())
  50. data = b'foo'
  51. self.assert_equal(hexlify(key.id_hash(data)), b'4c9137bc0dd3ddb31de4e138a49d7eb3')
  52. self.assert_equal(data, key.decrypt(key.id_hash(data), key.encrypt(data)))
  53. def test_keyfile(self):
  54. os.environ['ATTIC_PASSPHRASE'] = 'test'
  55. key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
  56. self.assert_equal(key.enc_iv, b'\0'*16)
  57. manifest = key.encrypt(b'XXX')
  58. self.assert_equal(key.extract_iv(manifest), b'\0'*16)
  59. manifest2 = key.encrypt(b'XXX')
  60. self.assert_not_equal(manifest, manifest2)
  61. self.assert_equal(key.decrypt(None, manifest), key.decrypt(None, manifest2))
  62. self.assert_equal(key.extract_iv(manifest2), b'\0'*15+b'\x01')
  63. iv = key.extract_iv(manifest)
  64. key2 = KeyfileKey.detect(self.MockRepository(), manifest)
  65. # we assume that the payload fits into one 16B AES block (which is given for b'XXX').
  66. iv_plus_1 = increment_iv(iv, 16)
  67. self.assert_equal(key2.enc_iv, iv_plus_1)
  68. # Key data sanity check
  69. self.assert_equal(len(set([key2.id_key, key2.enc_key, key2.enc_hmac_key])), 3)
  70. self.assert_equal(key2.chunk_seed == 0, False)
  71. data = b'foo'
  72. self.assert_equal(data, key2.decrypt(key.id_hash(data), key.encrypt(data)))
  73. def test_keyfile2(self):
  74. with open(os.path.join(os.environ['ATTIC_KEYS_DIR'], 'keyfile'), 'w') as fd:
  75. fd.write(self.keyfile2_key_file)
  76. os.environ['ATTIC_PASSPHRASE'] = 'passphrase'
  77. key = KeyfileKey.detect(self.MockRepository(), self.keyfile2_cdata)
  78. self.assert_equal(key.decrypt(self.keyfile2_id, self.keyfile2_cdata), b'payload')
  79. def test_passphrase(self):
  80. os.environ['ATTIC_PASSPHRASE'] = 'test'
  81. key = PassphraseKey.create(self.MockRepository(), self.MockArgs())
  82. self.assert_equal(key.enc_iv, b'\0'*16)
  83. self.assert_equal(hexlify(key.id_key), b'793b0717f9d8fb01c751a487e9b827897ceea62409870600013fbc6b4d8d7ca6')
  84. self.assert_equal(hexlify(key.enc_hmac_key), b'b885a05d329a086627412a6142aaeb9f6c54ab7950f996dd65587251f6bc0901')
  85. self.assert_equal(hexlify(key.enc_key), b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a')
  86. self.assert_equal(key.chunk_seed, -775740477)
  87. manifest = key.encrypt(b'XXX')
  88. self.assert_equal(key.extract_iv(manifest), b'\0'*16)
  89. manifest2 = key.encrypt(b'XXX')
  90. self.assert_not_equal(manifest, manifest2)
  91. self.assert_equal(key.decrypt(None, manifest), key.decrypt(None, manifest2))
  92. self.assert_equal(key.extract_iv(manifest2), b'\0'*15+b'\x01')
  93. iv = key.extract_iv(manifest)
  94. key2 = PassphraseKey.detect(self.MockRepository(), manifest)
  95. # we assume that the payload fits into one 16B AES block (which is given for b'XXX').
  96. iv_plus_1 = increment_iv(iv, 16)
  97. self.assert_equal(key2.enc_iv, iv_plus_1)
  98. self.assert_equal(key.id_key, key2.id_key)
  99. self.assert_equal(key.enc_hmac_key, key2.enc_hmac_key)
  100. self.assert_equal(key.enc_key, key2.enc_key)
  101. self.assert_equal(key.chunk_seed, key2.chunk_seed)
  102. data = b'foo'
  103. self.assert_equal(hexlify(key.id_hash(data)), b'a409d69859b8a07625f066e42cde0501')
  104. self.assert_equal(data, key2.decrypt(key2.id_hash(data), key.encrypt(data)))