Skip to content

Commit

Permalink
migrate-to-repokey command, dispatch passphrase type to repokey handler
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ThomasWaldmann committed Jan 24, 2016
1 parent b2dedee commit 2f9b643
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 9 deletions.
45 changes: 44 additions & 1 deletion borg/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions borg/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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())
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions borg/testsuite/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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')
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion borg/testsuite/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2f9b643

Please sign in to comment.