1
0
Fork 0
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:
Thomas Waldmann 2016-01-15 06:34:09 +01:00
parent b2dedee3c8
commit 2f9b643edb
4 changed files with 56 additions and 9 deletions

View file

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

View file

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

View file

@ -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()

View file

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