mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-23 22:51:35 +00:00
migrate-to-repokey command, dispatch passphrase type to repokey handler
every chunk has the encryption key type as first byte and we do not want to rewrite the whole repo to change the passphrase type to repokey type. thus we simply dispatch this type to repokey handler. if there is a repokey that contains the same secrets as they were derived from the passphrase, it will just work. if there is none yet, one needs to run migrate-to-repokey command to create it.
This commit is contained in:
parent
b2dedee3c8
commit
2f9b643edb
4 changed files with 56 additions and 9 deletions
|
@ -26,7 +26,7 @@
|
|||
from .upgrader import AtticRepositoryUpgrader
|
||||
from .repository import Repository
|
||||
from .cache import Cache
|
||||
from .key import key_creator
|
||||
from .key import key_creator, RepoKey, PassphraseKey
|
||||
from .archive import Archive, ArchiveChecker, CHUNKER_PARAMS
|
||||
from .remote import RepositoryServer, RemoteRepository, cache_if_remote
|
||||
|
||||
|
@ -124,6 +124,23 @@ def do_change_passphrase(self, args):
|
|||
key.change_passphrase()
|
||||
return EXIT_SUCCESS
|
||||
|
||||
def do_migrate_to_repokey(self, args):
|
||||
"""Migrate passphrase -> repokey"""
|
||||
repository = self.open_repository(args)
|
||||
manifest_data = repository.get(Manifest.MANIFEST_ID)
|
||||
key_old = PassphraseKey.detect(repository, manifest_data)
|
||||
key_new = RepoKey(repository)
|
||||
key_new.target = repository
|
||||
key_new.repository_id = repository.id
|
||||
key_new.enc_key = key_old.enc_key
|
||||
key_new.enc_hmac_key = key_old.enc_hmac_key
|
||||
key_new.id_key = key_old.id_key
|
||||
key_new.chunk_seed = key_old.chunk_seed
|
||||
key_new.change_passphrase() # option to change key protection passphrase, save
|
||||
return EXIT_SUCCESS
|
||||
|
||||
return EXIT_SUCCESS
|
||||
|
||||
def do_create(self, args):
|
||||
"""Create new archive"""
|
||||
matcher = PatternMatcher(fallback=True)
|
||||
|
@ -873,6 +890,32 @@ def build_parser(self, args=None, prog=None):
|
|||
subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
|
||||
type=location_validator(archive=False))
|
||||
|
||||
migrate_to_repokey_epilog = textwrap.dedent("""
|
||||
This command migrates a repository from passphrase mode (not supported any
|
||||
more) to repokey mode.
|
||||
|
||||
You will be first asked for the repository passphrase (to open it in passphrase
|
||||
mode). This is the same passphrase as you used to use for this repo before 1.0.
|
||||
|
||||
It will then derive the different secrets from this passphrase.
|
||||
|
||||
Then you will be asked for a new passphrase (twice, for safety). This
|
||||
passphrase will be used to protect the repokey (which contains these same
|
||||
secrets in encrypted form). You may use the same passphrase as you used to
|
||||
use, but you may also use a different one.
|
||||
|
||||
After migrating to repokey mode, you can change the passphrase at any time.
|
||||
But please note: the secrets will always stay the same and they could always
|
||||
be derived from your (old) passphrase-mode passphrase.
|
||||
""")
|
||||
subparser = subparsers.add_parser('migrate-to-repokey', parents=[common_parser],
|
||||
description=self.do_migrate_to_repokey.__doc__,
|
||||
epilog=migrate_to_repokey_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
subparser.set_defaults(func=self.do_migrate_to_repokey)
|
||||
subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
|
||||
type=location_validator(archive=False))
|
||||
|
||||
create_epilog = textwrap.dedent("""
|
||||
This command creates a backup archive containing all files found while recursively
|
||||
traversing all paths specified. The archive will consume almost no disk space for
|
||||
|
|
12
borg/key.py
12
borg/key.py
|
@ -47,8 +47,10 @@ def key_factory(repository, manifest_data):
|
|||
return KeyfileKey.detect(repository, manifest_data)
|
||||
elif key_type == RepoKey.TYPE:
|
||||
return RepoKey.detect(repository, manifest_data)
|
||||
elif key_type == PassphraseKey.TYPE: # deprecated, kill in 1.x
|
||||
return PassphraseKey.detect(repository, manifest_data)
|
||||
elif key_type == PassphraseKey.TYPE:
|
||||
# this mode was killed in borg 1.0, see: https://github.com/borgbackup/borg/issues/97
|
||||
# we just dispatch to repokey mode and assume the passphrase was migrated to a repokey.
|
||||
return RepoKey.detect(repository, manifest_data)
|
||||
elif key_type == PlaintextKey.TYPE:
|
||||
return PlaintextKey.detect(repository, manifest_data)
|
||||
else:
|
||||
|
@ -132,7 +134,8 @@ def encrypt(self, data):
|
|||
return b''.join((self.TYPE_STR, hmac, data))
|
||||
|
||||
def decrypt(self, id, data):
|
||||
if data[0] != self.TYPE:
|
||||
if not (data[0] == self.TYPE or \
|
||||
data[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)):
|
||||
raise IntegrityError('Invalid encryption envelope')
|
||||
hmac_given = memoryview(data)[1:33]
|
||||
hmac_computed = memoryview(HMAC(self.enc_hmac_key, memoryview(data)[33:], sha256).digest())
|
||||
|
@ -148,7 +151,8 @@ def decrypt(self, id, data):
|
|||
return data
|
||||
|
||||
def extract_nonce(self, payload):
|
||||
if payload[0] != self.TYPE:
|
||||
if not (payload[0] == self.TYPE or \
|
||||
payload[0] == PassphraseKey.TYPE and isinstance(self, RepoKey)):
|
||||
raise IntegrityError('Invalid encryption envelope')
|
||||
nonce = bytes_to_long(payload[33:41])
|
||||
return nonce
|
||||
|
|
|
@ -426,7 +426,7 @@ def test_unusual_filenames(self):
|
|||
def test_repository_swap_detection(self):
|
||||
self.create_test_files()
|
||||
os.environ['BORG_PASSPHRASE'] = 'passphrase'
|
||||
self.cmd('init', '--encryption=passphrase', self.repository_location)
|
||||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||
repository_id = self._extract_repository_id(self.repository_path)
|
||||
self.cmd('create', self.repository_location + '::test', 'input')
|
||||
shutil.rmtree(self.repository_path)
|
||||
|
@ -442,7 +442,7 @@ def test_repository_swap_detection2(self):
|
|||
self.create_test_files()
|
||||
self.cmd('init', '--encryption=none', self.repository_location + '_unencrypted')
|
||||
os.environ['BORG_PASSPHRASE'] = 'passphrase'
|
||||
self.cmd('init', '--encryption=passphrase', self.repository_location + '_encrypted')
|
||||
self.cmd('init', '--encryption=repokey', self.repository_location + '_encrypted')
|
||||
self.cmd('create', self.repository_location + '_encrypted::test', 'input')
|
||||
shutil.rmtree(self.repository_path + '_encrypted')
|
||||
os.rename(self.repository_path + '_unencrypted', self.repository_path + '_encrypted')
|
||||
|
@ -986,7 +986,7 @@ def test_aes_counter_uniqueness_keyfile(self):
|
|||
self.verify_aes_counter_uniqueness('keyfile')
|
||||
|
||||
def test_aes_counter_uniqueness_passphrase(self):
|
||||
self.verify_aes_counter_uniqueness('passphrase')
|
||||
self.verify_aes_counter_uniqueness('repokey')
|
||||
|
||||
def test_debug_dump_archive_items(self):
|
||||
self.create_test_files()
|
||||
|
|
|
@ -25,7 +25,7 @@ def repo_url(request, tmpdir):
|
|||
tmpdir.remove(rec=1)
|
||||
|
||||
|
||||
@pytest.fixture(params=["none", "passphrase"])
|
||||
@pytest.fixture(params=["none", "repokey"])
|
||||
def repo(request, cmd, repo_url):
|
||||
cmd('init', '--encryption', request.param, repo_url)
|
||||
return repo_url
|
||||
|
|
Loading…
Reference in a new issue