From 30514731685d2af8dee240e9e93182719a296bda Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 2 Jul 2023 22:29:05 +0200 Subject: [PATCH 1/2] BORG_WORKAROUNDS=authenticated_no_key to extract from authenticated repos without key, fixes #7700 --- docs/usage/general/environment.rst.inc | 16 ++++++++++++++++ src/borg/archiver/rcreate_cmd.py | 2 ++ src/borg/crypto/key.py | 19 +++++++++++++++++++ src/borg/repoobj.py | 11 ++++++++--- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/docs/usage/general/environment.rst.inc b/docs/usage/general/environment.rst.inc index 0ac9092843..6d1ccfaacd 100644 --- a/docs/usage/general/environment.rst.inc +++ b/docs/usage/general/environment.rst.inc @@ -104,6 +104,22 @@ General: caused EROFS. You will need this to make archives from volume shadow copies in WSL1 (Windows Subsystem for Linux 1). + authenticated_no_key + Work around a lost passphrase or key for an ``authenticated`` mode repository + (these are only authenticated, but not encrypted). + If the key is missing in the repository config, add ``key = anything`` there. + + This workaround is **only** 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 you do not use the workaround any more. + Output formatting: BORG_LIST_FORMAT Giving the default value for ``borg list --format=X``. diff --git a/src/borg/archiver/rcreate_cmd.py b/src/borg/archiver/rcreate_cmd.py index 9e0504a014..a3c0ea1503 100644 --- a/src/borg/archiver/rcreate_cmd.py +++ b/src/borg/archiver/rcreate_cmd.py @@ -152,6 +152,8 @@ def build_parser_rcreate(self, subparsers, common_parser, mid_common_parser): If you do **not** want to encrypt the contents of your backups, but still want to detect malicious tampering use an `authenticated` mode. It's like `repokey` minus encryption. + To normally work with ``authenticated`` repos, you will need the passphrase, but + there is an emergency workaround, see ``BORG_WORKAROUNDS=authenticated_no_key`` docs. Creating a related repository +++++++++++++++++++++++++++++ diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 9386805b05..a45c6c7953 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -20,6 +20,7 @@ from ..helpers import bin_to_hex from ..helpers.passphrase import Passphrase, PasswordRetriesExceeded, PassphraseWrong from ..helpers import msgpack +from ..helpers import workarounds from ..item import Key, EncryptedKey, want_bytes from ..manifest import Manifest from ..platform import SaveFile @@ -30,6 +31,9 @@ from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b, AES256_OCB, CHACHA20_POLY1305 from . import low_level +# workaround for lost passphrase or key in "authenticated" or "authenticated-blake2" mode +AUTHENTICATED_NO_KEY = "authenticated_no_key" in workarounds + class UnsupportedPayloadError(Error): """Unsupported payload type {}. A newer version is required to access this repository.""" @@ -267,6 +271,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() @@ -800,6 +806,19 @@ class AuthenticatedKeyBase(AESKeyBase, FlexiKey): # 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 diff --git a/src/borg/repoobj.py b/src/borg/repoobj.py index edfa070ae6..557d4a6040 100644 --- a/src/borg/repoobj.py +++ b/src/borg/repoobj.py @@ -1,8 +1,11 @@ from struct import Struct -from .helpers import msgpack +from .helpers import msgpack, workarounds from .compress import Compressor, LZ4_COMPRESSOR, get_compressor +# workaround for lost passphrase or key in "authenticated" or "authenticated-blake2" mode +AUTHENTICATED_NO_KEY = "authenticated_no_key" in workarounds + class RepoObj: meta_len_hdr = Struct(" Date: Thu, 20 Jul 2023 17:54:33 +0200 Subject: [PATCH 2/2] bugfix: skip TAM check with BORG_WORKAROUNDS=authenticated_no_key This is an emergency workaround for authenticated repos if the user has lost the borg key. We can't compute the TAM key without the borg key, so just skip all the TAM stuff. --- src/borg/crypto/key.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index a45c6c7953..338888b3af 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -246,6 +246,8 @@ def unpack_and_verify_manifest(self, data, force_tam_not_required=False): unpacker = get_limited_unpacker("manifest") unpacker.feed(data) unpacked = unpacker.unpack() + if AUTHENTICATED_NO_KEY: + return unpacked, True # True is a lie. if "tam" not in unpacked: if tam_required: raise TAMRequiredError(self.repository._location.canonical_path()) @@ -271,8 +273,6 @@ 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()