|
@@ -39,9 +39,6 @@ try:
|
|
|
from ..constants import * # NOQA
|
|
|
from ..compress import CompressionSpec
|
|
|
from ..crypto.key import key_creator, key_argument_names, tam_required_file
|
|
|
- from ..crypto.key import AESOCBRepoKey, CHPORepoKey, Blake2AESOCBRepoKey, Blake2CHPORepoKey
|
|
|
- from ..crypto.key import AESOCBKeyfileKey, CHPOKeyfileKey, Blake2AESOCBKeyfileKey, Blake2CHPOKeyfileKey
|
|
|
- from ..crypto.keymanager import KeyManager
|
|
|
from ..helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE
|
|
|
from ..helpers import Error, NoManifestError, set_ec
|
|
|
from ..helpers import positive_int_validator, location_validator, archivename_validator, ChunkerParams, Location
|
|
@@ -109,10 +106,11 @@ def get_func(args):
|
|
|
|
|
|
from .benchmarks import BenchmarkMixIn
|
|
|
from .debug import DebugMixIn
|
|
|
+from .keys import KeysMixIn
|
|
|
from .tar import TarMixIn
|
|
|
|
|
|
|
|
|
-class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn):
|
|
|
+class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn):
|
|
|
def __init__(self, lock_wait=None, prog=None):
|
|
|
self.exit_code = EXIT_SUCCESS
|
|
|
self.lock_wait = lock_wait
|
|
@@ -316,134 +314,6 @@ class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn):
|
|
|
return EXIT_WARNING
|
|
|
return EXIT_SUCCESS
|
|
|
|
|
|
- @with_repository(compatibility=(Manifest.Operation.CHECK,))
|
|
|
- def do_change_passphrase(self, args, repository, manifest, key):
|
|
|
- """Change repository key file passphrase"""
|
|
|
- if not hasattr(key, "change_passphrase"):
|
|
|
- print("This repository is not encrypted, cannot change the passphrase.")
|
|
|
- return EXIT_ERROR
|
|
|
- key.change_passphrase()
|
|
|
- logger.info("Key updated")
|
|
|
- if hasattr(key, "find_key"):
|
|
|
- # print key location to make backing it up easier
|
|
|
- logger.info("Key location: %s", key.find_key())
|
|
|
- return EXIT_SUCCESS
|
|
|
-
|
|
|
- @with_repository(exclusive=True, manifest=True, cache=True, compatibility=(Manifest.Operation.CHECK,))
|
|
|
- def do_change_location(self, args, repository, manifest, key, cache):
|
|
|
- """Change repository key location"""
|
|
|
- if not hasattr(key, "change_passphrase"):
|
|
|
- print("This repository is not encrypted, cannot change the key location.")
|
|
|
- return EXIT_ERROR
|
|
|
-
|
|
|
- if args.key_mode == "keyfile":
|
|
|
- if isinstance(key, AESOCBRepoKey):
|
|
|
- key_new = AESOCBKeyfileKey(repository)
|
|
|
- elif isinstance(key, CHPORepoKey):
|
|
|
- key_new = CHPOKeyfileKey(repository)
|
|
|
- elif isinstance(key, Blake2AESOCBRepoKey):
|
|
|
- key_new = Blake2AESOCBKeyfileKey(repository)
|
|
|
- elif isinstance(key, Blake2CHPORepoKey):
|
|
|
- key_new = Blake2CHPOKeyfileKey(repository)
|
|
|
- else:
|
|
|
- print("Change not needed or not supported.")
|
|
|
- return EXIT_WARNING
|
|
|
- if args.key_mode == "repokey":
|
|
|
- if isinstance(key, AESOCBKeyfileKey):
|
|
|
- key_new = AESOCBRepoKey(repository)
|
|
|
- elif isinstance(key, CHPOKeyfileKey):
|
|
|
- key_new = CHPORepoKey(repository)
|
|
|
- elif isinstance(key, Blake2AESOCBKeyfileKey):
|
|
|
- key_new = Blake2AESOCBRepoKey(repository)
|
|
|
- elif isinstance(key, Blake2CHPOKeyfileKey):
|
|
|
- key_new = Blake2CHPORepoKey(repository)
|
|
|
- else:
|
|
|
- print("Change not needed or not supported.")
|
|
|
- return EXIT_WARNING
|
|
|
-
|
|
|
- for name in (
|
|
|
- "repository_id",
|
|
|
- "enc_key",
|
|
|
- "enc_hmac_key",
|
|
|
- "id_key",
|
|
|
- "chunk_seed",
|
|
|
- "tam_required",
|
|
|
- "sessionid",
|
|
|
- "cipher",
|
|
|
- ):
|
|
|
- value = getattr(key, name)
|
|
|
- setattr(key_new, name, value)
|
|
|
-
|
|
|
- key_new.target = key_new.get_new_target(args)
|
|
|
- # save with same passphrase and algorithm
|
|
|
- key_new.save(key_new.target, key._passphrase, create=True, algorithm=key._encrypted_key_algorithm)
|
|
|
-
|
|
|
- # rewrite the manifest with the new key, so that the key-type byte of the manifest changes
|
|
|
- manifest.key = key_new
|
|
|
- manifest.write()
|
|
|
- repository.commit(compact=False)
|
|
|
-
|
|
|
- # we need to rewrite cache config and security key-type info,
|
|
|
- # so that the cached key-type will match the repo key-type.
|
|
|
- cache.begin_txn() # need to start a cache transaction, otherwise commit() does nothing.
|
|
|
- cache.key = key_new
|
|
|
- cache.commit()
|
|
|
-
|
|
|
- loc = key_new.find_key() if hasattr(key_new, "find_key") else None
|
|
|
- if args.keep:
|
|
|
- logger.info(f"Key copied to {loc}")
|
|
|
- else:
|
|
|
- key.remove(key.target) # remove key from current location
|
|
|
- logger.info(f"Key moved to {loc}")
|
|
|
-
|
|
|
- return EXIT_SUCCESS
|
|
|
-
|
|
|
- @with_repository(exclusive=True, compatibility=(Manifest.Operation.CHECK,))
|
|
|
- def do_change_algorithm(self, args, repository, manifest, key):
|
|
|
- """Change repository key algorithm"""
|
|
|
- if not hasattr(key, "change_passphrase"):
|
|
|
- print("This repository is not encrypted, cannot change the algorithm.")
|
|
|
- return EXIT_ERROR
|
|
|
- key.save(key.target, key._passphrase, algorithm=KEY_ALGORITHMS[args.algorithm])
|
|
|
- return EXIT_SUCCESS
|
|
|
-
|
|
|
- @with_repository(lock=False, exclusive=False, manifest=False, cache=False)
|
|
|
- def do_key_export(self, args, repository):
|
|
|
- """Export the repository key for backup"""
|
|
|
- manager = KeyManager(repository)
|
|
|
- manager.load_keyblob()
|
|
|
- if args.paper:
|
|
|
- manager.export_paperkey(args.path)
|
|
|
- else:
|
|
|
- try:
|
|
|
- if args.qr:
|
|
|
- manager.export_qr(args.path)
|
|
|
- else:
|
|
|
- manager.export(args.path)
|
|
|
- except IsADirectoryError:
|
|
|
- self.print_error(f"'{args.path}' must be a file, not a directory")
|
|
|
- return EXIT_ERROR
|
|
|
- return EXIT_SUCCESS
|
|
|
-
|
|
|
- @with_repository(lock=False, exclusive=False, manifest=False, cache=False)
|
|
|
- def do_key_import(self, args, repository):
|
|
|
- """Import the repository key from backup"""
|
|
|
- manager = KeyManager(repository)
|
|
|
- if args.paper:
|
|
|
- if args.path:
|
|
|
- self.print_error("with --paper import from file is not supported")
|
|
|
- return EXIT_ERROR
|
|
|
- manager.import_paperkey(args)
|
|
|
- else:
|
|
|
- if not args.path:
|
|
|
- self.print_error("input file to import key from expected")
|
|
|
- return EXIT_ERROR
|
|
|
- if args.path != "-" and not os.path.exists(args.path):
|
|
|
- self.print_error("input file does not exist: " + args.path)
|
|
|
- return EXIT_ERROR
|
|
|
- manager.import_keyfile(args)
|
|
|
- return EXIT_SUCCESS
|
|
|
-
|
|
|
@with_repository(fake="dry_run", exclusive=True, compatibility=(Manifest.Operation.WRITE,))
|
|
|
def do_create(self, args, repository, manifest=None, key=None):
|
|
|
"""Create new archive"""
|
|
@@ -3683,173 +3553,7 @@ class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn):
|
|
|
help="create the parent directories of the repository directory, if they are missing.",
|
|
|
)
|
|
|
|
|
|
- # borg key
|
|
|
- subparser = subparsers.add_parser(
|
|
|
- "key",
|
|
|
- parents=[mid_common_parser],
|
|
|
- add_help=False,
|
|
|
- description="Manage a keyfile or repokey of a repository",
|
|
|
- epilog="",
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help="manage repository key",
|
|
|
- )
|
|
|
-
|
|
|
- key_parsers = subparser.add_subparsers(title="required arguments", metavar="<command>")
|
|
|
- subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
|
|
|
-
|
|
|
- key_export_epilog = process_epilog(
|
|
|
- """
|
|
|
- If repository encryption is used, the repository is inaccessible
|
|
|
- without the key. This command allows one to backup this essential key.
|
|
|
- Note that the backup produced does not include the passphrase itself
|
|
|
- (i.e. the exported key stays encrypted). In order to regain access to a
|
|
|
- repository, one needs both the exported key and the original passphrase.
|
|
|
-
|
|
|
- There are three backup formats. The normal backup format is suitable for
|
|
|
- digital storage as a file. The ``--paper`` backup format is optimized
|
|
|
- for printing and typing in while importing, with per line checks to
|
|
|
- reduce problems with manual input. The ``--qr-html`` creates a printable
|
|
|
- HTML template with a QR code and a copy of the ``--paper``-formatted key.
|
|
|
-
|
|
|
- For repositories using keyfile encryption the key is saved locally
|
|
|
- on the system that is capable of doing backups. To guard against loss
|
|
|
- of this key, the key needs to be backed up independently of the main
|
|
|
- data backup.
|
|
|
-
|
|
|
- For repositories using the repokey encryption the key is saved in the
|
|
|
- repository in the config file. A backup is thus not strictly needed,
|
|
|
- but guards against the repository becoming inaccessible if the file
|
|
|
- is damaged for some reason.
|
|
|
-
|
|
|
- Examples::
|
|
|
-
|
|
|
- borg key export /path/to/repo > encrypted-key-backup
|
|
|
- borg key export --paper /path/to/repo > encrypted-key-backup.txt
|
|
|
- borg key export --qr-html /path/to/repo > encrypted-key-backup.html
|
|
|
- # Or pass the output file as an argument instead of redirecting stdout:
|
|
|
- borg key export /path/to/repo encrypted-key-backup
|
|
|
- borg key export --paper /path/to/repo encrypted-key-backup.txt
|
|
|
- borg key export --qr-html /path/to/repo encrypted-key-backup.html
|
|
|
-
|
|
|
-
|
|
|
- """
|
|
|
- )
|
|
|
- subparser = key_parsers.add_parser(
|
|
|
- "export",
|
|
|
- parents=[common_parser],
|
|
|
- add_help=False,
|
|
|
- description=self.do_key_export.__doc__,
|
|
|
- epilog=key_export_epilog,
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help="export repository key for backup",
|
|
|
- )
|
|
|
- subparser.set_defaults(func=self.do_key_export)
|
|
|
- subparser.add_argument("path", metavar="PATH", nargs="?", type=str, help="where to store the backup")
|
|
|
- subparser.add_argument(
|
|
|
- "--paper",
|
|
|
- dest="paper",
|
|
|
- action="store_true",
|
|
|
- help="Create an export suitable for printing and later type-in",
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--qr-html",
|
|
|
- dest="qr",
|
|
|
- action="store_true",
|
|
|
- help="Create an html file suitable for printing and later type-in or qr scan",
|
|
|
- )
|
|
|
-
|
|
|
- key_import_epilog = process_epilog(
|
|
|
- """
|
|
|
- This command restores a key previously backed up with the export command.
|
|
|
-
|
|
|
- If the ``--paper`` option is given, the import will be an interactive
|
|
|
- process in which each line is checked for plausibility before
|
|
|
- proceeding to the next line. For this format PATH must not be given.
|
|
|
-
|
|
|
- For repositories using keyfile encryption, the key file which ``borg key
|
|
|
- import`` writes to depends on several factors. If the ``BORG_KEY_FILE``
|
|
|
- environment variable is set and non-empty, ``borg key import`` creates
|
|
|
- or overwrites that file named by ``$BORG_KEY_FILE``. Otherwise, ``borg
|
|
|
- key import`` searches in the ``$BORG_KEYS_DIR`` directory for a key file
|
|
|
- associated with the repository. If a key file is found in
|
|
|
- ``$BORG_KEYS_DIR``, ``borg key import`` overwrites it; otherwise, ``borg
|
|
|
- key import`` creates a new key file in ``$BORG_KEYS_DIR``.
|
|
|
- """
|
|
|
- )
|
|
|
- subparser = key_parsers.add_parser(
|
|
|
- "import",
|
|
|
- parents=[common_parser],
|
|
|
- add_help=False,
|
|
|
- description=self.do_key_import.__doc__,
|
|
|
- epilog=key_import_epilog,
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help="import repository key from backup",
|
|
|
- )
|
|
|
- subparser.set_defaults(func=self.do_key_import)
|
|
|
- subparser.add_argument(
|
|
|
- "path", metavar="PATH", nargs="?", type=str, help="path to the backup ('-' to read from stdin)"
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--paper",
|
|
|
- dest="paper",
|
|
|
- action="store_true",
|
|
|
- help="interactively import from a backup done with ``--paper``",
|
|
|
- )
|
|
|
-
|
|
|
- change_passphrase_epilog = process_epilog(
|
|
|
- """
|
|
|
- The key files used for repository encryption are optionally passphrase
|
|
|
- protected. This command can be used to change this passphrase.
|
|
|
-
|
|
|
- Please note that this command only changes the passphrase, but not any
|
|
|
- secret protected by it (like e.g. encryption/MAC keys or chunker seed).
|
|
|
- Thus, changing the passphrase after passphrase and borg key got compromised
|
|
|
- does not protect future (nor past) backups to the same repository.
|
|
|
- """
|
|
|
- )
|
|
|
- subparser = key_parsers.add_parser(
|
|
|
- "change-passphrase",
|
|
|
- parents=[common_parser],
|
|
|
- add_help=False,
|
|
|
- description=self.do_change_passphrase.__doc__,
|
|
|
- epilog=change_passphrase_epilog,
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help="change repository passphrase",
|
|
|
- )
|
|
|
- subparser.set_defaults(func=self.do_change_passphrase)
|
|
|
-
|
|
|
- change_location_epilog = process_epilog(
|
|
|
- """
|
|
|
- Change the location of a borg key. The key can be stored at different locations:
|
|
|
-
|
|
|
- - keyfile: locally, usually in the home directory
|
|
|
- - repokey: inside the repo (in the repo config)
|
|
|
-
|
|
|
- Please note:
|
|
|
-
|
|
|
- This command does NOT change the crypto algorithms, just the key location,
|
|
|
- thus you must ONLY give the key location (keyfile or repokey).
|
|
|
- """
|
|
|
- )
|
|
|
- subparser = key_parsers.add_parser(
|
|
|
- "change-location",
|
|
|
- parents=[common_parser],
|
|
|
- add_help=False,
|
|
|
- description=self.do_change_location.__doc__,
|
|
|
- epilog=change_location_epilog,
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help="change key location",
|
|
|
- )
|
|
|
- subparser.set_defaults(func=self.do_change_location)
|
|
|
- subparser.add_argument(
|
|
|
- "key_mode", metavar="KEY_LOCATION", choices=("repokey", "keyfile"), help="select key location"
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--keep",
|
|
|
- dest="keep",
|
|
|
- action="store_true",
|
|
|
- help="keep the key also at the current location (default: remove it)",
|
|
|
- )
|
|
|
+ self.build_parser_keys(subparsers, common_parser, mid_common_parser)
|
|
|
|
|
|
# borg list
|
|
|
list_epilog = (
|