mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-25 07:23:28 +00:00
BORG_WORKAROUNDS=authenticated_no_key to extract from authenticated repos without key, fixes #7700
This commit is contained in:
parent
33645ad38a
commit
3051473168
4 changed files with 45 additions and 3 deletions
|
@ -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``.
|
||||
|
|
|
@ -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
|
||||
+++++++++++++++++++++++++++++
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@ def parse(
|
|||
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 @@ def parse(
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue