Browse Source

refactor to cipher.extract_iv

position and length of iv depends on cipher
Thomas Waldmann 8 years ago
parent
commit
e9bbf9307d
3 changed files with 36 additions and 23 deletions
  1. 17 15
      src/borg/crypto/key.py
  2. 11 0
      src/borg/crypto/low_level.pyx
  3. 8 8
      src/borg/testsuite/key.py

+ 17 - 15
src/borg/crypto/key.py

@@ -373,13 +373,6 @@ class AESKeyBase(KeyBase):
         self.assert_id(id, data)
         return data
 
-    def extract_nonce(self, payload):
-        if not (payload[0] == self.TYPE or
-            payload[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)):
-            raise IntegrityError('Manifest: Invalid encryption envelope')
-        nonce = bytes_to_long(payload[33:41])
-        return nonce
-
     def init_from_random_data(self, data=None):
         if data is None:
             data = os.urandom(100)
@@ -391,10 +384,21 @@ class AESKeyBase(KeyBase):
         if self.chunk_seed & 0x80000000:
             self.chunk_seed = self.chunk_seed - 0xffffffff - 1
 
-    def init_ciphers(self, manifest_nonce=0):
-        self.cipher = CIPHERSUITE(mac_key=self.enc_hmac_key, enc_key=self.enc_key,
-                                  iv=manifest_nonce.to_bytes(16, byteorder='big'))
-        self.nonce_manager = NonceManager(self.repository, self.cipher, manifest_nonce)
+    def init_ciphers(self, manifest_data=None):
+        self.cipher = CIPHERSUITE(mac_key=self.enc_hmac_key, enc_key=self.enc_key)
+        if manifest_data is None:
+            nonce = 0
+        else:
+            if not (manifest_data[0] == self.TYPE or
+                    manifest_data[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)):
+                raise IntegrityError('Invalid encryption envelope')
+            # manifest_blocks is a safe upper bound on the amount of cipher blocks needed
+            # to encrypt the manifest. depending on the ciphersuite and overhead, it might
+            # be a bit too high, but that does not matter.
+            manifest_blocks = num_cipher_blocks(len(manifest_data))
+            nonce = self.cipher.extract_iv(manifest_data) + manifest_blocks
+        self.cipher.set_iv(nonce.to_bytes(16, byteorder='big'))
+        self.nonce_manager = NonceManager(self.repository, self.cipher, nonce)
 
 
 class Passphrase(str):
@@ -514,8 +518,7 @@ class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase):
             key.init(repository, passphrase)
             try:
                 key.decrypt(None, manifest_data)
-                num_blocks = num_cipher_blocks(len(manifest_data) - 41)
-                key.init_ciphers(key.extract_nonce(manifest_data) + num_blocks)
+                key.init_ciphers(manifest_data)
                 key._passphrase = passphrase
                 return key
             except IntegrityError:
@@ -554,8 +557,7 @@ class KeyfileKeyBase(AESKeyBase):
         else:
             if not key.load(target, passphrase):
                 raise PassphraseWrong
-        num_blocks = num_cipher_blocks(len(manifest_data) - 41)
-        key.init_ciphers(key.extract_nonce(manifest_data) + num_blocks)
+        key.init_ciphers(manifest_data)
         key._passphrase = passphrase
         return key
 

+ 11 - 0
src/borg/crypto/low_level.pyx

@@ -233,6 +233,9 @@ class UNENCRYPTED:
     def next_iv(self):
         return self.iv
 
+    def extract_iv(self, envelope):
+        return 0
+
 
 cdef class AES256_CTR_HMAC_SHA256:
     # Layout: HEADER + HMAC 32 + IV 8 + CT (same as attic / borg < 1.2 IF HEADER = TYPE_BYTE, no AAD)
@@ -395,6 +398,10 @@ cdef class AES256_CTR_HMAC_SHA256:
         for i in range(self.iv_len_short):
             iv_out[i] = iv[(self.iv_len-self.iv_len_short)+i]
 
+    def extract_iv(self, envelope):
+        offset = 1 + self.mac_len
+        return bytes_to_long(envelope[offset:offset+self.iv_len_short])
+
 
 ctypedef const EVP_CIPHER * (* CIPHER)()
 
@@ -565,6 +572,10 @@ cdef class _AEAD_BASE:
         for i in range(self.iv_len):
             iv_out[i] = iv[i]
 
+    def extract_iv(self, envelope):
+        offset = 1 + self.mac_len  # XXX 1 -> self.header_len
+        return bytes_to_long(envelope[offset:offset+self.iv_len])
+
 
 cdef class _AES_BASE(_AEAD_BASE):
     def __init__(self, *args, **kwargs):

+ 8 - 8
src/borg/testsuite/key.py

@@ -119,12 +119,12 @@ class TestKey:
         key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
         assert bytes_to_long(key.cipher.next_iv(), 8) == 0
         manifest = key.encrypt(b'ABC')
-        assert key.extract_nonce(manifest) == 0
+        assert key.cipher.extract_iv(manifest) == 0
         manifest2 = key.encrypt(b'ABC')
         assert manifest != manifest2
         assert key.decrypt(None, manifest) == key.decrypt(None, manifest2)
-        assert key.extract_nonce(manifest2) == 1
-        iv = key.extract_nonce(manifest)
+        assert key.cipher.extract_iv(manifest2) == 1
+        iv = key.cipher.extract_iv(manifest)
         key2 = KeyfileKey.detect(self.MockRepository(), manifest)
         assert bytes_to_long(key2.cipher.next_iv(), 8) >= iv + key2.cipher.block_count(len(manifest) - KeyfileKey.PAYLOAD_OVERHEAD)
         # Key data sanity check
@@ -140,7 +140,7 @@ class TestKey:
             fd.write("0000000000002000")
         key = KeyfileKey.create(repository, self.MockArgs())
         data = key.encrypt(b'ABC')
-        assert key.extract_nonce(data) == 0x2000
+        assert key.cipher.extract_iv(data) == 0x2000
         assert key.decrypt(None, data) == b'ABC'
 
     def test_keyfile_kfenv(self, tmpdir, monkeypatch):
@@ -192,14 +192,14 @@ class TestKey:
         assert hexlify(key.enc_key) == b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a'
         assert key.chunk_seed == -775740477
         manifest = key.encrypt(b'ABC')
-        assert key.extract_nonce(manifest) == 0
+        assert key.cipher.extract_iv(manifest) == 0
         manifest2 = key.encrypt(b'ABC')
         assert manifest != manifest2
         assert key.decrypt(None, manifest) == key.decrypt(None, manifest2)
-        assert key.extract_nonce(manifest2) == 1
-        iv = key.extract_nonce(manifest)
+        assert key.cipher.extract_iv(manifest2) == 1
+        iv = key.cipher.extract_iv(manifest)
         key2 = PassphraseKey.detect(self.MockRepository(), manifest)
-        assert bytes_to_long(key2.cipher.next_iv(), 8) == iv + key2.cipher.block_count(len(manifest) - PassphraseKey.PAYLOAD_OVERHEAD)
+        assert bytes_to_long(key2.cipher.next_iv(), 8) == iv + key2.cipher.block_count(len(manifest))
         assert key.id_key == key2.id_key
         assert key.enc_hmac_key == key2.enc_hmac_key
         assert key.enc_key == key2.enc_key