diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 7f309e605de..874aeeb307b 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -24,6 +24,7 @@ from ..helpers import bin_to_hex from ..helpers import prepare_subprocess_env from ..helpers import msgpack +from ..helpers import workarounds from ..item import Key, EncryptedKey from ..platform import SaveFile @@ -32,6 +33,21 @@ from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b +# "authenticated" or "authenticated-blake2" mode: user lost key or passphrase! +# usually (with encrypted repos) this means "game over", +# but for not-encrypted, just "authenticated" repos, we can still extract data, +# if we do not try to check the authentication (which we can't, because we lost +# the key or passphrase). +# if the key is missing in the repository config, add "key = anything" there. +# this workaround is **only** intended for emergencies and **only** to extract data +# from an affected repository (read-only access): +# BORG_WORKAROUNDS=authenticated_no_key borg extract repo::archive +# after you have extracted all data you need, you MUST delete the repository: +# BORG_WORKAROUNDS=authenticated_no_key borg delete repo +# Now you can init a fresh repo. Make sure NOT to use the workaround any more. +AUTHENTICATED_NO_KEY = 'authenticated_no_key' in workarounds + + class NoPassphraseFailure(Error): """can not acquire a passphrase: {}""" @@ -253,6 +269,8 @@ def unpack_and_verify_manifest(self, data, force_tam_not_required=False): offset = data.index(tam_hmac) data[offset:offset + 64] = bytes(64) tam_key = self._tam_key(tam_salt, context=b'manifest') + if AUTHENTICATED_NO_KEY: + return unpacked, True # True is a lie. calculated_hmac = hmac.digest(tam_key, data, 'sha512') if not hmac.compare_digest(calculated_hmac, tam_hmac): raise TAMInvalid() @@ -874,6 +892,19 @@ class AuthenticatedKeyBase(RepoKey): # It's only authenticated, not encrypted. logically_encrypted = False + def _load(self, key_data, passphrase): + if AUTHENTICATED_NO_KEY: + # fake _load if we have no key or passphrase + NOPE = bytes(32) # 256 bit all-zero + self.repository_id = NOPE + self.enc_key = NOPE + self.enc_hmac_key = NOPE + self.id_key = NOPE + self.chunk_seed = 0 + self.tam_required = False + return True + return super()._load(key_data, passphrase) + def load(self, target, passphrase): success = super().load(target, passphrase) self.logically_encrypted = False @@ -899,6 +930,8 @@ def decrypt(self, id, data, decompress=True): if not decompress: return payload data = self.decompress(payload) + if AUTHENTICATED_NO_KEY: + return data self.assert_id(id, data) return data