Browse Source

minor key.encrypt api change/cleanup

we already have .decrypt(id, data, ...).
i changed .encrypt(chunk) to .encrypt(id, data).

the old borg crypto won't really need or use the id,
but the new AEAD crypto will authenticate the id in future.
Thomas Waldmann 3 years ago
parent
commit
d3b78a6cf5

+ 3 - 3
src/borg/archive.py

@@ -1789,7 +1789,7 @@ class ArchiveChecker:
 
         def add_callback(chunk):
             id_ = self.key.id_hash(chunk)
-            cdata = self.key.encrypt(chunk)
+            cdata = self.key.encrypt(id_, chunk)
             add_reference(id_, len(chunk), len(cdata), cdata)
             return id_
 
@@ -1811,7 +1811,7 @@ class ArchiveChecker:
             def replacement_chunk(size):
                 chunk = Chunk(None, allocation=CH_ALLOC, size=size)
                 chunk_id, data = cached_hash(chunk, self.key.id_hash)
-                cdata = self.key.encrypt(data)
+                cdata = self.key.encrypt(chunk_id, data)
                 csize = len(cdata)
                 return chunk_id, size, csize, cdata
 
@@ -1998,7 +1998,7 @@ class ArchiveChecker:
                 archive.items = items_buffer.chunks
                 data = msgpack.packb(archive.as_dict())
                 new_archive_id = self.key.id_hash(data)
-                cdata = self.key.encrypt(data)
+                cdata = self.key.encrypt(new_archive_id, data)
                 add_reference(new_archive_id, len(data), len(cdata), cdata)
                 self.manifest.archives[info.name] = (new_archive_id, info.ts)
             pi.finish()

+ 2 - 2
src/borg/cache.py

@@ -942,7 +942,7 @@ class LocalCache(CacheStatsMixin):
         refcount = self.seen_chunk(id, size)
         if refcount and not overwrite:
             return self.chunk_incref(id, stats)
-        data = self.key.encrypt(chunk)
+        data = self.key.encrypt(id, chunk)
         csize = len(data)
         self.repository.put(id, data, wait=wait)
         self.chunks.add(id, 1, size, csize)
@@ -1107,7 +1107,7 @@ Chunk index:    {0.total_unique_chunks:20d}             unknown"""
         refcount = self.seen_chunk(id, size)
         if refcount:
             return self.chunk_incref(id, stats, size=size)
-        data = self.key.encrypt(chunk)
+        data = self.key.encrypt(id, chunk)
         csize = len(data)
         self.repository.put(id, data, wait=wait)
         self.chunks.add(id, 1, size, csize)

+ 9 - 9
src/borg/crypto/key.py

@@ -158,7 +158,7 @@ class KeyBase:
         """
         raise NotImplementedError
 
-    def encrypt(self, chunk):
+    def encrypt(self, id, data):
         pass
 
     def decrypt(self, id, data, decompress=True):
@@ -264,8 +264,8 @@ class PlaintextKey(KeyBase):
     def id_hash(self, data):
         return sha256(data).digest()
 
-    def encrypt(self, chunk):
-        data = self.compressor.compress(chunk)
+    def encrypt(self, id, data):
+        data = self.compressor.compress(data)
         return b''.join([self.TYPE_STR, data])
 
     def decrypt(self, id, data, decompress=True):
@@ -340,8 +340,8 @@ class AESKeyBase(KeyBase):
 
     logically_encrypted = True
 
-    def encrypt(self, chunk):
-        data = self.compressor.compress(chunk)
+    def encrypt(self, id, data):
+        data = self.compressor.compress(data)
         next_iv = self.nonce_manager.ensure_reservation(self.cipher.next_iv(),
                                                         self.cipher.block_count(len(data)))
         return self.cipher.encrypt(data, header=self.TYPE_STR, iv=next_iv)
@@ -678,8 +678,8 @@ class AuthenticatedKeyBase(AESKeyBase, FlexiKey):
         if manifest_data is not None:
             self.assert_type(manifest_data[0])
 
-    def encrypt(self, chunk):
-        data = self.compressor.compress(chunk)
+    def encrypt(self, id, data):
+        data = self.compressor.compress(data)
         return b''.join([self.TYPE_STR, data])
 
     def decrypt(self, id, data, decompress=True):
@@ -732,9 +732,9 @@ class AEADKeyBase(KeyBase):
 
     logically_encrypted = True
 
-    def encrypt(self, chunk):
+    def encrypt(self, id, data):
         # to encrypt new data in this session we use always self.cipher and self.sessionid
-        data = self.compressor.compress(chunk)
+        data = self.compressor.compress(data)
         reserved = b'\0'
         iv = self.cipher.next_iv()
         iv_48bit = iv.to_bytes(6, 'big')

+ 1 - 1
src/borg/helpers/manifest.py

@@ -261,4 +261,4 @@ class Manifest:
         self.tam_verified = True
         data = self.key.pack_and_authenticate_metadata(manifest.as_dict())
         self.id = self.key.id_hash(data)
-        self.repository.put(self.MANIFEST_ID, self.key.encrypt(data))
+        self.repository.put(self.MANIFEST_ID, self.key.encrypt(self.MANIFEST_ID, data))

+ 4 - 4
src/borg/testsuite/archiver.py

@@ -3806,7 +3806,7 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase):
                 'version': 1,
             })
             archive_id = key.id_hash(archive)
-            repository.put(archive_id, key.encrypt(archive))
+            repository.put(archive_id, key.encrypt(archive_id, archive))
             repository.commit(compact=False)
         self.cmd('check', self.repository_location, exit_code=1)
         self.cmd('check', '--repair', self.repository_location, exit_code=0)
@@ -3894,7 +3894,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase):
     def spoof_manifest(self, repository):
         with repository:
             _, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
-            repository.put(Manifest.MANIFEST_ID, key.encrypt(msgpack.packb({
+            repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb({
                 'version': 1,
                 'archives': {},
                 'config': {},
@@ -3907,7 +3907,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase):
         repository = Repository(self.repository_path, exclusive=True)
         with repository:
             manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
-            repository.put(Manifest.MANIFEST_ID, key.encrypt(msgpack.packb({
+            repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb({
                 'version': 1,
                 'archives': {},
                 'timestamp': (datetime.utcnow() + timedelta(days=1)).strftime(ISO_FORMAT),
@@ -3929,7 +3929,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase):
 
             manifest = msgpack.unpackb(key.decrypt(None, repository.get(Manifest.MANIFEST_ID)))
             del manifest[b'tam']
-            repository.put(Manifest.MANIFEST_ID, key.encrypt(msgpack.packb(manifest)))
+            repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb(manifest)))
             repository.commit(compact=False)
         output = self.cmd('list', '--debug', self.repository_location)
         assert 'archive1234' in output

+ 26 - 16
src/borg/testsuite/key.py

@@ -114,18 +114,21 @@ class TestKey:
     def test_plaintext(self):
         key = PlaintextKey.create(None, None)
         chunk = b'foo'
-        assert hexlify(key.id_hash(chunk)) == b'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
-        assert chunk == key.decrypt(key.id_hash(chunk), key.encrypt(chunk))
+        id = key.id_hash(chunk)
+        assert hexlify(id) == b'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
+        assert chunk == key.decrypt(id, key.encrypt(id, chunk))
 
     def test_keyfile(self, monkeypatch, keys_dir):
         monkeypatch.setenv('BORG_PASSPHRASE', 'test')
         key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
         assert key.cipher.next_iv() == 0
-        manifest = key.encrypt(b'ABC')
+        chunk = b'ABC'
+        id = key.id_hash(chunk)
+        manifest = key.encrypt(id, chunk)
         assert key.cipher.extract_iv(manifest) == 0
-        manifest2 = key.encrypt(b'ABC')
+        manifest2 = key.encrypt(id, chunk)
         assert manifest != manifest2
-        assert key.decrypt(None, manifest) == key.decrypt(None, manifest2)
+        assert key.decrypt(id, manifest) == key.decrypt(id, manifest2)
         assert key.cipher.extract_iv(manifest2) == 1
         iv = key.cipher.extract_iv(manifest)
         key2 = KeyfileKey.detect(self.MockRepository(), manifest)
@@ -134,7 +137,8 @@ class TestKey:
         assert len({key2.id_key, key2.enc_key, key2.enc_hmac_key}) == 3
         assert key2.chunk_seed != 0
         chunk = b'foo'
-        assert chunk == key2.decrypt(key.id_hash(chunk), key.encrypt(chunk))
+        id = key.id_hash(chunk)
+        assert chunk == key2.decrypt(id, key.encrypt(id, chunk))
 
     def test_keyfile_nonce_rollback_protection(self, monkeypatch, keys_dir):
         monkeypatch.setenv('BORG_PASSPHRASE', 'test')
@@ -142,9 +146,11 @@ class TestKey:
         with open(os.path.join(get_security_dir(repository.id_str), 'nonce'), "w") as fd:
             fd.write("0000000000002000")
         key = KeyfileKey.create(repository, self.MockArgs())
-        data = key.encrypt(b'ABC')
+        chunk = b'ABC'
+        id = key.id_hash(chunk)
+        data = key.encrypt(id, chunk)
         assert key.cipher.extract_iv(data) == 0x2000
-        assert key.decrypt(None, data) == b'ABC'
+        assert key.decrypt(id, data) == chunk
 
     def test_keyfile_kfenv(self, tmpdir, monkeypatch):
         keyfile = tmpdir.join('keyfile')
@@ -155,7 +161,7 @@ class TestKey:
         assert keyfile.exists()
         chunk = b'ABC'
         chunk_id = key.id_hash(chunk)
-        chunk_cdata = key.encrypt(chunk)
+        chunk_cdata = key.encrypt(chunk_id, chunk)
         key = KeyfileKey.detect(self.MockRepository(), chunk_cdata)
         assert chunk == key.decrypt(chunk_id, chunk_cdata)
         keyfile.remove()
@@ -212,18 +218,20 @@ class TestKey:
     def test_roundtrip(self, key):
         repository = key.repository
         plaintext = b'foo'
-        encrypted = key.encrypt(plaintext)
+        id = key.id_hash(plaintext)
+        encrypted = key.encrypt(id, plaintext)
         identified_key_class = identify_key(encrypted)
         assert identified_key_class == key.__class__
         loaded_key = identified_key_class.detect(repository, encrypted)
-        decrypted = loaded_key.decrypt(None, encrypted)
+        decrypted = loaded_key.decrypt(id, encrypted)
         assert decrypted == plaintext
 
     def test_decrypt_decompress(self, key):
         plaintext = b'123456789'
-        encrypted = key.encrypt(plaintext)
-        assert key.decrypt(None, encrypted, decompress=False) != plaintext
-        assert key.decrypt(None, encrypted) == plaintext
+        id = key.id_hash(plaintext)
+        encrypted = key.encrypt(id, plaintext)
+        assert key.decrypt(id, encrypted, decompress=False) != plaintext
+        assert key.decrypt(id, encrypted) == plaintext
 
     def test_assert_id(self, key):
         plaintext = b'123456789'
@@ -243,7 +251,8 @@ class TestKey:
         assert AuthenticatedKey.id_hash is ID_HMAC_SHA_256.id_hash
         assert len(key.id_key) == 32
         plaintext = b'123456789'
-        authenticated = key.encrypt(plaintext)
+        id = key.id_hash(plaintext)
+        authenticated = key.encrypt(id, plaintext)
         # 0x07 is the key TYPE, \x0000 identifies no compression.
         assert authenticated == b'\x07\x00\x00' + plaintext
 
@@ -253,7 +262,8 @@ class TestKey:
         assert Blake2AuthenticatedKey.id_hash is ID_BLAKE2b_256.id_hash
         assert len(key.id_key) == 128
         plaintext = b'123456789'
-        authenticated = key.encrypt(plaintext)
+        id = key.id_hash(plaintext)
+        authenticated = key.encrypt(id, plaintext)
         # 0x06 is the key TYPE, 0x0000 identifies no compression.
         assert authenticated == b'\x06\x00\x00' + plaintext
 

+ 1 - 1
src/borg/testsuite/remote.py

@@ -165,7 +165,7 @@ class TestRepositoryCache:
 
     def _put_encrypted_object(self, key, repository, data):
         id_ = key.id_hash(data)
-        repository.put(id_, key.encrypt(data))
+        repository.put(id_, key.encrypt(id_, data))
         return id_
 
     @pytest.fixture