Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rcreate: remove legacy crypto for new repos, fixes #6490 #6821

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion setup_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ class build_man(Command):

'key_change-passphrase': 'key',
'key_change-location': 'key',
'key_change-algorithm': 'key',
'key_export': 'key',
'key_import': 'key',
'key_migrate-to-repokey': 'key',
Expand Down
98 changes: 23 additions & 75 deletions src/borg/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
from .compress import CompressionSpec, ZLIB, ZLIB_legacy, ObfuscateSize
from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required
from .crypto.key import RepoKey, KeyfileKey, Blake2RepoKey, Blake2KeyfileKey, FlexiKey
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
Expand Down Expand Up @@ -503,28 +505,32 @@ def do_change_location(self, args, repository, manifest, key, cache):
return EXIT_ERROR

if args.key_mode == 'keyfile':
if isinstance(key, RepoKey):
key_new = KeyfileKey(repository)
elif isinstance(key, Blake2RepoKey):
key_new = Blake2KeyfileKey(repository)
elif isinstance(key, (KeyfileKey, Blake2KeyfileKey)):
print(f"Location already is {args.key_mode}")
return EXIT_SUCCESS
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:
raise Error("Unsupported key type")
print("Change not needed or not supported.")
return EXIT_WARNING
if args.key_mode == 'repokey':
if isinstance(key, KeyfileKey):
key_new = RepoKey(repository)
elif isinstance(key, Blake2KeyfileKey):
key_new = Blake2RepoKey(repository)
elif isinstance(key, (RepoKey, Blake2RepoKey)):
print(f"Location already is {args.key_mode}")
return EXIT_SUCCESS
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:
raise Error("Unsupported key type")
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', 'nonce_manager', 'cipher'):
'tam_required', 'sessionid', 'cipher'):
value = getattr(key, name)
setattr(key_new, name, value)

Expand Down Expand Up @@ -4374,22 +4380,6 @@ def define_borg_mount(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.

Key derivation functions
++++++++++++++++++++++++

- ``--key-algorithm argon2`` is the default and is recommended.
The key encryption key is derived from your passphrase via argon2-id.
Argon2 is considered more modern and secure than pbkdf2.

Our implementation of argon2-based key algorithm follows the cryptographic best practices:

- It derives two separate keys from your passphrase: one to encrypt your key and another one
to sign it. ``--key-algorithm pbkdf2`` uses the same key for both.

- It uses encrypt-then-mac instead of encrypt-and-mac used by ``--key-algorithm pbkdf2``

Neither is inherently linked to the key derivation function, but since we were going
to break backwards compatibility anyway we took the opportunity to fix all 3 issues at once.
""")
subparser = subparsers.add_parser('rcreate', parents=[common_parser], add_help=False,
description=self.do_rcreate.__doc__, epilog=rcreate_epilog,
Expand All @@ -4412,8 +4402,6 @@ def define_borg_mount(parser):
help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
subparser.add_argument('--make-parent-dirs', dest='make_parent_dirs', action='store_true',
help='create the parent directories of the repository directory, if they are missing.')
subparser.add_argument('--key-algorithm', dest='key_algorithm', default='argon2', choices=list(KEY_ALGORITHMS),
help='the algorithm we use to derive a key encryption key from your passphrase. Default: argon2')

# borg key
subparser = subparsers.add_parser('key', parents=[mid_common_parser], add_help=False,
Expand Down Expand Up @@ -4538,46 +4526,6 @@ def define_borg_mount(parser):
subparser.add_argument('--keep', dest='keep', action='store_true',
help='keep the key also at the current location (default: remove it)')

change_algorithm_epilog = process_epilog("""
Change the algorithm we use to encrypt and authenticate the borg key.

Important: In a `repokey` mode (e.g. repokey-blake2) all users share the same key.
In this mode upgrading to `argon2` will make it impossible to access the repo for users who use an old version of borg.
We recommend upgrading to the latest stable version.

Important: In a `keyfile` mode (e.g. keyfile-blake2) each user has their own key (in ``~/.config/borg/keys``).
In this mode this command will only change the key used by the current user.
If you want to upgrade to `argon2` to strengthen security, you will have to upgrade each user's key individually.

Your repository is encrypted and authenticated with a key that is randomly generated by ``borg init``.
The key is encrypted and authenticated with your passphrase.

We currently support two choices:

1. argon2 - recommended. This algorithm is used by default when initialising a new repository.
The key encryption key is derived from your passphrase via argon2-id.
Argon2 is considered more modern and secure than pbkdf2.
2. pbkdf2 - the legacy algorithm. Use this if you want to access your repo via old versions of borg.
The key encryption key is derived from your passphrase via PBKDF2-HMAC-SHA256.

Examples::

# Upgrade an existing key to argon2
borg key change-algorithm /path/to/repo argon2
# Downgrade to pbkdf2 - use this if upgrading borg is not an option
borg key change-algorithm /path/to/repo pbkdf2


""")
subparser = key_parsers.add_parser('change-algorithm', parents=[common_parser], add_help=False,
description=self.do_change_algorithm.__doc__,
epilog=change_algorithm_epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
help='change key algorithm')
subparser.set_defaults(func=self.do_change_algorithm)
subparser.add_argument('algorithm', metavar='ALGORITHM', choices=list(KEY_ALGORITHMS),
help='select key algorithm')

# borg list
list_epilog = process_epilog("""
This command lists the contents of an archive.
Expand Down
23 changes: 15 additions & 8 deletions src/borg/crypto/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def identify_key(manifest_data):
if key_type == KeyType.PASSPHRASE: # legacy, see comment in KeyType class.
return RepoKey

for key in AVAILABLE_KEY_TYPES:
for key in LEGACY_KEY_TYPES + AVAILABLE_KEY_TYPES:
if key.TYPE == key_type:
return key
else:
Expand Down Expand Up @@ -620,7 +620,7 @@ def create(cls, repository, args, *, other_key=None):
passphrase = Passphrase.new(allow_empty=True)
key.init_ciphers()
target = key.get_new_target(args)
key.save(target, passphrase, create=True, algorithm=KEY_ALGORITHMS[args.key_algorithm])
key.save(target, passphrase, create=True, algorithm=KEY_ALGORITHMS['argon2'])
logger.info('Key in "%s" created.' % target)
logger.info('Keep this key safe. Your data will be inaccessible without it.')
return key
Expand Down Expand Up @@ -977,7 +977,7 @@ class CHPORepoKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
class Blake2AESOCBKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
TYPES_ACCEPTABLE = {KeyType.BLAKE2AESOCBKEYFILE, KeyType.BLAKE2AESOCBREPO}
TYPE = KeyType.BLAKE2AESOCBKEYFILE
NAME = 'key file Blake2b AES-OCB'
NAME = 'key file BLAKE2b AES-OCB'
ARG_NAME = 'keyfile-blake2-aes-ocb'
STORAGE = KeyBlobStorage.KEYFILE
CIPHERSUITE = AES256_OCB
Expand All @@ -986,7 +986,7 @@ class Blake2AESOCBKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
class Blake2AESOCBRepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
TYPES_ACCEPTABLE = {KeyType.BLAKE2AESOCBKEYFILE, KeyType.BLAKE2AESOCBREPO}
TYPE = KeyType.BLAKE2AESOCBREPO
NAME = 'repokey Blake2b AES-OCB'
NAME = 'repokey BLAKE2b AES-OCB'
ARG_NAME = 'repokey-blake2-aes-ocb'
STORAGE = KeyBlobStorage.REPO
CIPHERSUITE = AES256_OCB
Expand All @@ -995,7 +995,7 @@ class Blake2AESOCBRepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
class Blake2CHPOKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
TYPES_ACCEPTABLE = {KeyType.BLAKE2CHPOKEYFILE, KeyType.BLAKE2CHPOREPO}
TYPE = KeyType.BLAKE2CHPOKEYFILE
NAME = 'key file Blake2b ChaCha20-Poly1305'
NAME = 'key file BLAKE2b ChaCha20-Poly1305'
ARG_NAME = 'keyfile-blake2-chacha20-poly1305'
STORAGE = KeyBlobStorage.KEYFILE
CIPHERSUITE = CHACHA20_POLY1305
Expand All @@ -1004,16 +1004,23 @@ class Blake2CHPOKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
class Blake2CHPORepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
TYPES_ACCEPTABLE = {KeyType.BLAKE2CHPOKEYFILE, KeyType.BLAKE2CHPOREPO}
TYPE = KeyType.BLAKE2CHPOREPO
NAME = 'repokey Blake2b ChaCha20-Poly1305'
NAME = 'repokey BLAKE2b ChaCha20-Poly1305'
ARG_NAME = 'repokey-blake2-chacha20-poly1305'
STORAGE = KeyBlobStorage.REPO
CIPHERSUITE = CHACHA20_POLY1305


LEGACY_KEY_TYPES = (
# legacy (AES-CTR based) crypto
KeyfileKey, RepoKey,
Blake2KeyfileKey, Blake2RepoKey,
)

AVAILABLE_KEY_TYPES = (
# these are available encryption modes for new repositories
# not encrypted modes
PlaintextKey,
KeyfileKey, RepoKey, AuthenticatedKey,
Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey,
AuthenticatedKey, Blake2AuthenticatedKey,
# new crypto
AESOCBKeyfileKey, AESOCBRepoKey,
CHPOKeyfileKey, CHPORepoKey,
Expand Down
10 changes: 5 additions & 5 deletions src/borg/crypto/keymanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ..helpers import Manifest, NoManifestError, Error, yes, bin_to_hex, dash_open
from ..repository import Repository

from .key import KeyfileKey, KeyfileNotFoundError, RepoKeyNotFoundError, KeyBlobStorage, identify_key
from .key import CHPOKeyfileKey, KeyfileNotFoundError, RepoKeyNotFoundError, KeyBlobStorage, identify_key


class UnencryptedRepo(Error):
Expand Down Expand Up @@ -50,7 +50,7 @@ def __init__(self, repository):

def load_keyblob(self):
if self.keyblob_storage == KeyBlobStorage.KEYFILE:
k = KeyfileKey(self.repository)
k = CHPOKeyfileKey(self.repository)
target = k.find_key()
with open(target) as fd:
self.keyblob = ''.join(fd.readlines()[1:])
Expand All @@ -65,15 +65,15 @@ def load_keyblob(self):

def store_keyblob(self, args):
if self.keyblob_storage == KeyBlobStorage.KEYFILE:
k = KeyfileKey(self.repository)
k = CHPOKeyfileKey(self.repository)
target = k.get_existing_or_new_target(args)

self.store_keyfile(target)
elif self.keyblob_storage == KeyBlobStorage.REPO:
self.repository.save_key(self.keyblob.encode('utf-8'))

def get_keyfile_data(self):
data = f'{KeyfileKey.FILE_ID} {bin_to_hex(self.repository.id)}\n'
data = f'{CHPOKeyfileKey.FILE_ID} {bin_to_hex(self.repository.id)}\n'
data += self.keyblob
if not self.keyblob.endswith('\n'):
data += '\n'
Expand Down Expand Up @@ -136,7 +136,7 @@ def grouped(s):
fd.write(export)

def import_keyfile(self, args):
file_id = KeyfileKey.FILE_ID
file_id = CHPOKeyfileKey.FILE_ID
first_line = file_id + ' ' + bin_to_hex(self.repository.id) + '\n'
with dash_open(args.path, 'r') as fd:
file_first_line = fd.read(len(first_line))
Expand Down
2 changes: 1 addition & 1 deletion src/borg/crypto/low_level.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ cdef class CHACHA20_POLY1305(_AEAD_BASE):
super().__init__(key, iv=iv, header_len=header_len, aad_offset=aad_offset)


cdef class AES:
cdef class AES: # legacy
"""A thin wrapper around the OpenSSL EVP cipher API - for legacy code, like key file encryption"""
cdef CIPHER cipher
cdef EVP_CIPHER_CTX *ctx
Expand Down
Loading