Skip to content

Commit

Permalink
Merge pull request #7730 from ThomasWaldmann/authenticated_no_key-master
Browse files Browse the repository at this point in the history
BORG_WORKAROUNDS=authenticated_no_key to extract from authenticated repos without key, fixes #7700
  • Loading branch information
ThomasWaldmann committed Jul 20, 2023
2 parents 33645ad + d5ffa59 commit 989b4fe
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 3 deletions.
16 changes: 16 additions & 0 deletions docs/usage/general/environment.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down
2 changes: 2 additions & 0 deletions src/borg/archiver/rcreate_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
+++++++++++++++++++++++++++++
Expand Down
19 changes: 19 additions & 0 deletions src/borg/crypto/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."""
Expand Down Expand Up @@ -242,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())
Expand Down Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/borg/repoobj.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 989b4fe

Please sign in to comment.