BORG_WORKAROUNDS=authenticated_no_key to extract from authenticated repos without key, fixes #7700

This commit is contained in:
Thomas Waldmann 2023-07-02 22:29:05 +02:00
parent 33645ad38a
commit 3051473168
No known key found for this signature in database
GPG Key ID: 243ACFA951F78E01
4 changed files with 45 additions and 3 deletions

View File

@ -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``.

View File

@ -152,6 +152,8 @@ class RCreateMixIn:
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
+++++++++++++++++++++++++++++

View File

@ -20,6 +20,7 @@ from ..helpers import get_limited_unpacker
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 AES, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2
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 @@ class KeyBase:
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

View File

@ -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("<H") # 16bit unsigned int
@ -110,7 +113,8 @@ class RepoObj:
compressor_cls, compression_level = Compressor.detect(compr_hdr)
compressor = compressor_cls(level=compression_level)
meta, data = compressor.decompress(dict(meta_compressed), data_compressed[:psize])
self.key.assert_id(id, data)
if not AUTHENTICATED_NO_KEY:
self.key.assert_id(id, data)
else:
meta, data = None, None
return meta_compressed if want_compressed else meta, data_compressed if want_compressed else data
@ -170,7 +174,8 @@ class RepoObj1: # legacy
meta_compressed["csize"] = len(data_compressed)
if decompress:
meta, data = compressor.decompress(None, data_compressed)
self.key.assert_id(id, data)
if not AUTHENTICATED_NO_KEY:
self.key.assert_id(id, data)
else:
meta, data = None, None
return meta_compressed if want_compressed else meta, data_compressed if want_compressed else data