Browse Source

Merge pull request #6412 from ThomasWaldmann/remove-passphrasekey

remove PassphraseKey code and borg key migrate-to-repokey command
TW 3 năm trước cách đây
mục cha
commit
0714339d3f
6 tập tin đã thay đổi với 14 bổ sung156 xóa
  1. 2 2
      docs/faq.rst
  2. 1 8
      docs/internals/security.rst
  3. 0 14
      docs/usage/upgrade.rst
  4. 1 44
      src/borg/archiver.py
  5. 9 62
      src/borg/crypto/key.py
  6. 1 26
      src/borg/testsuite/key.py

+ 2 - 2
docs/faq.rst

@@ -1289,8 +1289,8 @@ There are some caveats:
   This means that data added by Borg won't deduplicate with the existing data
   stored by Attic. The effect is lessened if the files cache is used with Borg.
 - Repositories in "passphrase" mode *must* be migrated to "repokey" mode using
-  :ref:`borg_key_migrate-to-repokey`. Borg does not support the "passphrase" mode
-  any other way.
+  "borg key migrate-to-repokey" (only available in borg <= 1.2.x). Borg does not
+  support the "passphrase" mode in any other way.
 
 Why is my backup bigger than with attic?
 ----------------------------------------

+ 1 - 8
docs/internals/security.rst

@@ -129,7 +129,7 @@ which is generally seen as the most robust way to create an authenticated
 encryption scheme from encryption and message authentication primitives.
 
 Every operation (encryption, MAC / authentication, chunk ID derivation)
-uses independent, random keys generated by `os.urandom`_ [#]_.
+uses independent, random keys generated by `os.urandom`_.
 
 Borg does not support unauthenticated encryption -- only authenticated encryption
 schemes are supported. No unauthenticated encryption schemes will be added
@@ -208,13 +208,6 @@ untrusted, but a trusted synchronization channel exists between
 clients, the security database could be synchronized between them over
 said trusted channel. This is not part of Borg's functionality.
 
-.. [#] Using the :ref:`borg key migrate-to-repokey <borg_key_migrate-to-repokey>`
-       command a user can convert repositories created using Attic in "passphrase"
-       mode to "repokey" mode. In this case the keys were directly derived from
-       the user's passphrase at some point using PBKDF2.
-
-       Borg does not support "passphrase" mode otherwise any more.
-
 .. _key_encryption:
 
 Offline key security

+ 0 - 14
docs/usage/upgrade.rst

@@ -14,17 +14,3 @@ Examples
     converting borg 0.xx to borg current
     no key file found for repository
 
-.. _borg_key_migrate-to-repokey:
-
-Upgrading a passphrase encrypted attic repo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-attic offered a "passphrase" encryption mode, but this was removed in borg 1.0
-and replaced by the "repokey" mode (which stores the passphrase-protected
-encryption key into the repository config).
-
-Thus, to upgrade a "passphrase" attic repo to a "repokey" borg repo, 2 steps
-are needed, in this order:
-
-- borg upgrade repo
-- borg key migrate-to-repokey repo

+ 1 - 44
src/borg/archiver.py

@@ -44,7 +44,7 @@ try:
     from .cache import Cache, assert_secure, SecurityManager
     from .constants import *  # NOQA
     from .compress import CompressionSpec
-    from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required, RepoKey, PassphraseKey
+    from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required, RepoKey
     from .crypto.keymanager import KeyManager
     from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE
     from .helpers import Error, NoManifestError, set_ec
@@ -400,22 +400,6 @@ class Archiver:
             manager.import_keyfile(args)
         return EXIT_SUCCESS
 
-    @with_repository(manifest=False)
-    def do_migrate_to_repokey(self, args, repository):
-        """Migrate passphrase -> repokey"""
-        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
-        logger.info('Key updated')
-        return EXIT_SUCCESS
-
     def do_benchmark_crud(self, args):
         """Benchmark Create, Read, Update, Delete for archives."""
         def measurement_run(repo, path):
@@ -4266,33 +4250,6 @@ class Archiver:
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
                                type=location_validator(archive=False))
 
-        migrate_to_repokey_epilog = process_epilog("""
-        This command migrates a repository from passphrase mode (removed in Borg 1.0)
-        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 = key_parsers.add_parser('migrate-to-repokey', parents=[common_parser], add_help=False,
-                                          description=self.do_migrate_to_repokey.__doc__,
-                                          epilog=migrate_to_repokey_epilog,
-                                          formatter_class=argparse.RawDescriptionHelpFormatter,
-                                          help='migrate passphrase-mode repository to repokey')
-        subparser.set_defaults(func=self.do_migrate_to_repokey)
-        subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
-                               type=location_validator(archive=False))
-
         # borg list
         list_epilog = process_epilog("""
         This command lists the contents of a repository or an archive.

+ 9 - 62
src/borg/crypto/key.py

@@ -119,9 +119,7 @@ def key_argument_names():
 def identify_key(manifest_data):
     key_type = manifest_data[0]
     if key_type == PassphraseKey.TYPE:
-        # we just dispatch to repokey mode and assume the passphrase was migrated to a repokey.
-        # see also comment in PassphraseKey class.
-        return RepoKey
+        return RepoKey  # see comment in PassphraseKey class.
 
     for key in AVAILABLE_KEY_TYPES:
         if key.TYPE == key_type:
@@ -327,8 +325,7 @@ class ID_BLAKE2b_256:
     def id_hash(self, data):
         return blake2b_256(self.id_key, data)
 
-    def init_from_random_data(self, data=None):
-        assert data is None  # PassphraseKey is the only caller using *data*
+    def init_from_random_data(self):
         super().init_from_random_data()
         self.enc_hmac_key = random_blake2b_256_key()
         self.id_key = random_blake2b_256_key()
@@ -347,8 +344,6 @@ class ID_HMAC_SHA_256:
 
 class AESKeyBase(KeyBase):
     """
-    Common base class shared by KeyfileKey and PassphraseKey
-
     Chunks are encrypted using 256bit AES in Counter Mode (CTR)
 
     Payload layout: TYPE(1) + HMAC(32) + NONCE(8) + CIPHERTEXT
@@ -386,9 +381,8 @@ class AESKeyBase(KeyBase):
         self.assert_id(id, data)
         return data
 
-    def init_from_random_data(self, data=None):
-        if data is None:
-            data = os.urandom(100)
+    def init_from_random_data(self):
+        data = os.urandom(100)
         self.enc_key = data[0:32]
         self.enc_hmac_key = data[32:64]
         self.id_key = data[64:96]
@@ -523,59 +517,13 @@ class Passphrase(str):
         return pbkdf2_hmac('sha256', self.encode('utf-8'), salt, iterations, length)
 
 
-class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase):
-    # This mode was killed in borg 1.0, see: https://github.com/borgbackup/borg/issues/97
-    # Reasons:
-    # - you can never ever change your passphrase for existing repos.
-    # - you can never ever use a different iterations count for existing repos.
-    # "Killed" means:
-    # - there is no automatic dispatch to this class via type byte
-    # - --encryption=passphrase is an invalid argument now
-    # This class is kept for a while to support migration from passphrase to repokey mode.
+class PassphraseKey:
+    # this is only a stub, repos with this mode could not be created any more since borg 1.0, see #97.
+    # in borg 1.3 all of its code and also the "borg key migrate-to-repokey" command was removed.
+    # if you still need to, you can use "borg key migrate-to-repokey" with borg 1.0, 1.1 and 1.2.
+    # Nowadays, we just dispatch this to RepoKey and assume the passphrase was migrated to a repokey.
     TYPE = 0x01
     NAME = 'passphrase'
-    ARG_NAME = None
-    STORAGE = KeyBlobStorage.NO_STORAGE
-
-    iterations = 100000  # must not be changed ever!
-
-    @classmethod
-    def create(cls, repository, args):
-        key = cls(repository)
-        logger.warning('WARNING: "passphrase" mode is unsupported since borg 1.0.')
-        passphrase = Passphrase.new(allow_empty=False)
-        key.init(repository, passphrase)
-        return key
-
-    @classmethod
-    def detect(cls, repository, manifest_data):
-        prompt = 'Enter passphrase for %s: ' % repository._location.canonical_path()
-        key = cls(repository)
-        passphrase = Passphrase.env_passphrase()
-        if passphrase is None:
-            passphrase = Passphrase.getpass(prompt)
-        for retry in range(1, 3):
-            key.init(repository, passphrase)
-            try:
-                key.decrypt(None, manifest_data)
-                key.init_ciphers(manifest_data)
-                key._passphrase = passphrase
-                return key
-            except IntegrityError:
-                passphrase = Passphrase.getpass(prompt)
-        else:
-            raise PasswordRetriesExceeded
-
-    def change_passphrase(self):
-        class ImmutablePassphraseError(Error):
-            """The passphrase for this encryption key type can't be changed."""
-
-        raise ImmutablePassphraseError
-
-    def init(self, repository, passphrase):
-        self.init_from_random_data(passphrase.kdf(repository.id, self.iterations, 100))
-        self.init_ciphers()
-        self.tam_required = False
 
 
 class KeyfileKeyBase(AESKeyBase):
@@ -888,7 +836,6 @@ class Blake2AuthenticatedKey(ID_BLAKE2b_256, AuthenticatedKeyBase):
 
 AVAILABLE_KEY_TYPES = (
     PlaintextKey,
-    PassphraseKey,
     KeyfileKey, RepoKey, AuthenticatedKey,
     Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey,
 )

+ 1 - 26
src/borg/testsuite/key.py

@@ -7,7 +7,7 @@ from binascii import hexlify, unhexlify
 import pytest
 
 from ..crypto.key import Passphrase, PasswordRetriesExceeded, bin_to_hex
-from ..crypto.key import PlaintextKey, PassphraseKey, AuthenticatedKey, RepoKey, KeyfileKey, \
+from ..crypto.key import PlaintextKey, AuthenticatedKey, RepoKey, KeyfileKey, \
     Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey
 from ..crypto.key import ID_HMAC_SHA_256, ID_BLAKE2b_256
 from ..crypto.key import TAMRequiredError, TAMInvalid, TAMUnsupportedSuiteError, UnsupportedManifestError
@@ -182,31 +182,6 @@ class TestKey:
         key = Blake2KeyfileKey.detect(self.MockRepository(), self.keyfile_blake2_cdata)
         assert key.decrypt(self.keyfile_blake2_id, self.keyfile_blake2_cdata) == b'payload'
 
-    def test_passphrase(self, keys_dir, monkeypatch):
-        monkeypatch.setenv('BORG_PASSPHRASE', 'test')
-        key = PassphraseKey.create(self.MockRepository(), None)
-        assert key.cipher.next_iv() == 0
-        assert hexlify(key.id_key) == b'793b0717f9d8fb01c751a487e9b827897ceea62409870600013fbc6b4d8d7ca6'
-        assert hexlify(key.enc_hmac_key) == b'b885a05d329a086627412a6142aaeb9f6c54ab7950f996dd65587251f6bc0901'
-        assert hexlify(key.enc_key) == b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a'
-        assert key.chunk_seed == -775740477
-        manifest = key.encrypt(b'ABC')
-        assert key.cipher.extract_iv(manifest) == 0
-        manifest2 = key.encrypt(b'ABC')
-        assert manifest != manifest2
-        assert key.decrypt(None, manifest) == key.decrypt(None, manifest2)
-        assert key.cipher.extract_iv(manifest2) == 1
-        iv = key.cipher.extract_iv(manifest)
-        key2 = PassphraseKey.detect(self.MockRepository(), manifest)
-        assert key2.cipher.next_iv() == iv + key2.cipher.block_count(len(manifest))
-        assert key.id_key == key2.id_key
-        assert key.enc_hmac_key == key2.enc_hmac_key
-        assert key.enc_key == key2.enc_key
-        assert key.chunk_seed == key2.chunk_seed
-        chunk = b'foo'
-        assert hexlify(key.id_hash(chunk)) == b'818217cf07d37efad3860766dcdf1d21e401650fed2d76ed1d797d3aae925990'
-        assert chunk == key2.decrypt(key2.id_hash(chunk), key.encrypt(chunk))
-
     def _corrupt_byte(self, key, data, offset):
         data = bytearray(data)
         data[offset] ^= 1