mirror of https://github.com/borgbackup/borg.git
nonce manager: remove get/set iv, make it integer based
This commit is contained in:
parent
f34092e567
commit
58c2dafbe0
|
@ -358,8 +358,10 @@ class AESKeyBase(KeyBase):
|
|||
|
||||
def encrypt(self, chunk):
|
||||
data = self.compressor.compress(chunk)
|
||||
self.nonce_manager.ensure_reservation(self.cipher.block_count(len(data)))
|
||||
return self.cipher.encrypt(data, header=self.TYPE_STR)
|
||||
next_nonce = int.from_bytes(self.cipher.next_iv(), byteorder='big')
|
||||
next_nonce = self.nonce_manager.ensure_reservation(next_nonce, self.cipher.block_count(len(data)))
|
||||
iv = next_nonce.to_bytes(self.cipher.iv_len, byteorder='big')
|
||||
return self.cipher.encrypt(data, header=self.TYPE_STR, iv=iv)
|
||||
|
||||
def decrypt(self, id, data, decompress=True):
|
||||
if not (data[0] == self.TYPE or
|
||||
|
@ -401,7 +403,7 @@ class AESKeyBase(KeyBase):
|
|||
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)
|
||||
self.nonce_manager = NonceManager(self.repository, nonce)
|
||||
|
||||
|
||||
class Passphrase(str):
|
||||
|
|
|
@ -14,9 +14,8 @@ NONCE_SPACE_RESERVATION = 2**28 # This in units of AES blocksize (16 bytes)
|
|||
|
||||
|
||||
class NonceManager:
|
||||
def __init__(self, repository, cipher, manifest_nonce):
|
||||
def __init__(self, repository, manifest_nonce):
|
||||
self.repository = repository
|
||||
self.cipher = cipher
|
||||
self.end_of_nonce_reservation = None
|
||||
self.manifest_nonce = manifest_nonce
|
||||
self.nonce_file = os.path.join(get_security_dir(self.repository.id_str), 'nonce')
|
||||
|
@ -47,7 +46,15 @@ class NonceManager:
|
|||
def commit_repo_nonce_reservation(self, next_unreserved, start_nonce):
|
||||
self.repository.commit_nonce_reservation(next_unreserved, start_nonce)
|
||||
|
||||
def ensure_reservation(self, nonce_space_needed):
|
||||
def ensure_reservation(self, nonce, nonce_space_needed):
|
||||
"""
|
||||
Call this before doing encryption, give current, yet unused, integer IV as <nonce>
|
||||
and the amount of subsequent (counter-like) IVs needed as <nonce_space_needed>.
|
||||
Return value is the IV (counter) integer you shall use for encryption.
|
||||
|
||||
Note: this method may return the <nonce> you gave, if a reservation for it exists or
|
||||
can be established, so make sure you give a unused nonce.
|
||||
"""
|
||||
# Nonces may never repeat, even if a transaction aborts or the system crashes.
|
||||
# Therefore a part of the nonce space is reserved before any nonce is used for encryption.
|
||||
# As these reservations are committed to permanent storage before any nonce is used, this protects
|
||||
|
@ -64,20 +71,17 @@ class NonceManager:
|
|||
|
||||
if self.end_of_nonce_reservation:
|
||||
# we already got a reservation, if nonce_space_needed still fits everything is ok
|
||||
next_nonce_bytes = self.cipher.next_iv()
|
||||
next_nonce = int.from_bytes(next_nonce_bytes, byteorder='big')
|
||||
next_nonce = nonce
|
||||
assert next_nonce <= self.end_of_nonce_reservation
|
||||
if next_nonce + nonce_space_needed <= self.end_of_nonce_reservation:
|
||||
self.cipher.set_iv(next_nonce_bytes)
|
||||
return
|
||||
return next_nonce
|
||||
|
||||
repo_free_nonce = self.get_repo_free_nonce()
|
||||
local_free_nonce = self.get_local_free_nonce()
|
||||
free_nonce_space = max(x for x in (repo_free_nonce, local_free_nonce, self.manifest_nonce, self.end_of_nonce_reservation) if x is not None)
|
||||
reservation_end = free_nonce_space + nonce_space_needed + NONCE_SPACE_RESERVATION
|
||||
assert reservation_end < MAX_REPRESENTABLE_NONCE
|
||||
next_nonce_bytes = free_nonce_space.to_bytes(16, byteorder='big')
|
||||
self.cipher.set_iv(next_nonce_bytes)
|
||||
self.commit_repo_nonce_reservation(reservation_end, repo_free_nonce)
|
||||
self.commit_local_nonce_reservation(reservation_end, local_free_nonce)
|
||||
self.end_of_nonce_reservation = reservation_end
|
||||
return free_nonce_space
|
||||
|
|
|
@ -33,26 +33,6 @@ class TestNonceManager:
|
|||
def commit_nonce_reservation(self, next_unreserved, start_nonce):
|
||||
pytest.fail("commit_nonce_reservation should never be called on an old repository")
|
||||
|
||||
class MockCipher:
|
||||
def __init__(self, iv):
|
||||
self.iv_set = False # placeholder, this is never a valid iv
|
||||
self.iv = iv
|
||||
|
||||
def set_iv(self, iv):
|
||||
assert iv is not False
|
||||
self.iv_set = iv
|
||||
self.iv = iv
|
||||
|
||||
def next_iv(self):
|
||||
return self.iv
|
||||
|
||||
def expect_iv_and_advance(self, expected_iv, advance):
|
||||
expected_iv = expected_iv.to_bytes(16, byteorder='big')
|
||||
iv_set = self.iv_set
|
||||
assert iv_set == expected_iv
|
||||
self.iv_set = False
|
||||
self.iv = advance.to_bytes(16, byteorder='big')
|
||||
|
||||
def setUp(self):
|
||||
self.repository = None
|
||||
|
||||
|
@ -67,74 +47,70 @@ class TestNonceManager:
|
|||
def test_empty_cache_and_old_server(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x2000)
|
||||
self.repository = self.MockOldRepository()
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2013)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x2000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
|
||||
def test_empty_cache(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x2000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = 0x2000
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2013)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x2000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
|
||||
def test_empty_nonce(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x2000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = None
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x2000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
||||
# enough space in reservation
|
||||
manager.ensure_reservation(13)
|
||||
cipher.expect_iv_and_advance(0x2013, 0x2000 + 19 + 13)
|
||||
next_nonce = manager.ensure_reservation(0x2013, 13)
|
||||
assert next_nonce == 0x2013
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
||||
# just barely enough space in reservation
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2020, 0x2000 + 19 + 13 + 19)
|
||||
next_nonce = manager.ensure_reservation(0x2020, 19)
|
||||
assert next_nonce == 0x2020
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
||||
# no space in reservation
|
||||
manager.ensure_reservation(16)
|
||||
cipher.expect_iv_and_advance(0x2033, 0x2000 + 19 + 13 + 19 + 16)
|
||||
next_nonce = manager.ensure_reservation(0x2033, 16)
|
||||
assert next_nonce == 0x2033
|
||||
assert self.cache_nonce() == "0000000000002063"
|
||||
assert self.repository.next_free == 0x2063
|
||||
|
||||
# spans reservation boundary
|
||||
manager.ensure_reservation(64)
|
||||
cipher.expect_iv_and_advance(0x2063, 0x2000 + 19 + 13 + 19 + 16 + 64)
|
||||
next_nonce = manager.ensure_reservation(0x2043, 64)
|
||||
assert next_nonce == 0x2063
|
||||
assert self.cache_nonce() == "00000000000020c3"
|
||||
assert self.repository.next_free == 0x20c3
|
||||
|
||||
def test_sync_nonce(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x2000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = 0x2000
|
||||
self.set_cache_nonce("0000000000002000")
|
||||
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x2000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
@ -142,14 +118,13 @@ class TestNonceManager:
|
|||
def test_server_just_upgraded(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x2000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = None
|
||||
self.set_cache_nonce("0000000000002000")
|
||||
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x2000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
@ -157,13 +132,12 @@ class TestNonceManager:
|
|||
def test_transaction_abort_no_cache(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x1000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = 0x2000
|
||||
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x1000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
@ -171,27 +145,25 @@ class TestNonceManager:
|
|||
def test_transaction_abort_old_server(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x1000)
|
||||
self.repository = self.MockOldRepository()
|
||||
self.set_cache_nonce("0000000000002000")
|
||||
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x1000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
|
||||
def test_transaction_abort_on_other_client(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x1000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = 0x2000
|
||||
self.set_cache_nonce("0000000000001000")
|
||||
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x1000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
@ -199,14 +171,13 @@ class TestNonceManager:
|
|||
def test_interleaved(self, monkeypatch):
|
||||
monkeypatch.setattr(nonces, 'NONCE_SPACE_RESERVATION', 0x20)
|
||||
|
||||
cipher = self.MockCipher(0x2000)
|
||||
self.repository = self.MockRepository()
|
||||
self.repository.next_free = 0x2000
|
||||
self.set_cache_nonce("0000000000002000")
|
||||
|
||||
manager = NonceManager(self.repository, cipher, 0x2000)
|
||||
manager.ensure_reservation(19)
|
||||
cipher.expect_iv_and_advance(0x2000, 0x2000 + 19)
|
||||
manager = NonceManager(self.repository, 0x2000)
|
||||
next_nonce = manager.ensure_reservation(0x2000, 19)
|
||||
assert next_nonce == 0x2000
|
||||
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x2033
|
||||
|
@ -215,13 +186,13 @@ class TestNonceManager:
|
|||
self.repository.next_free = 0x4000
|
||||
|
||||
# enough space in reservation
|
||||
manager.ensure_reservation(12)
|
||||
cipher.expect_iv_and_advance(0x2013, 0x2000 + 19 + 12)
|
||||
next_nonce = manager.ensure_reservation(0x2013, 12)
|
||||
assert next_nonce == 0x2013
|
||||
assert self.cache_nonce() == "0000000000002033"
|
||||
assert self.repository.next_free == 0x4000
|
||||
|
||||
# spans reservation boundary
|
||||
manager.ensure_reservation(21)
|
||||
cipher.expect_iv_and_advance(0x4000, 0x4000 + 21)
|
||||
next_nonce = manager.ensure_reservation(0x201f, 21)
|
||||
assert next_nonce == 0x4000
|
||||
assert self.cache_nonce() == "0000000000004035"
|
||||
assert self.repository.next_free == 0x4035
|
||||
|
|
Loading…
Reference in New Issue