mirror of
https://github.com/borgbackup/borg.git
synced 2024-12-25 01:06:50 +00:00
Redo key_creator, key_factory, centralise key knowledge (#2272)
* key: put key metadata (name, storage) into key classses * keymanager: use key-declared storage types
This commit is contained in:
parent
1f861ec78e
commit
fc41c98a86
2 changed files with 62 additions and 45 deletions
|
@ -87,45 +87,38 @@ class TAMUnsupportedSuiteError(IntegrityError):
|
|||
traceback = False
|
||||
|
||||
|
||||
class KeyBlobStorage:
|
||||
NO_STORAGE = 'no_storage'
|
||||
KEYFILE = 'keyfile'
|
||||
REPO = 'repository'
|
||||
|
||||
|
||||
def key_creator(repository, args):
|
||||
if args.encryption == 'keyfile':
|
||||
return KeyfileKey.create(repository, args)
|
||||
elif args.encryption == 'repokey':
|
||||
return RepoKey.create(repository, args)
|
||||
elif args.encryption == 'keyfile-blake2':
|
||||
return Blake2KeyfileKey.create(repository, args)
|
||||
elif args.encryption == 'repokey-blake2':
|
||||
return Blake2RepoKey.create(repository, args)
|
||||
elif args.encryption == 'authenticated':
|
||||
return AuthenticatedKey.create(repository, args)
|
||||
elif args.encryption == 'none':
|
||||
return PlaintextKey.create(repository, args)
|
||||
for key in AVAILABLE_KEY_TYPES:
|
||||
if key.ARG_NAME == args.encryption:
|
||||
return key.create(repository, args)
|
||||
else:
|
||||
raise ValueError('Invalid encryption mode "%s"' % args.encryption)
|
||||
|
||||
|
||||
def key_factory(repository, manifest_data):
|
||||
def identify_key(manifest_data):
|
||||
key_type = manifest_data[0]
|
||||
if key_type == KeyfileKey.TYPE:
|
||||
return KeyfileKey.detect(repository, manifest_data)
|
||||
elif key_type == RepoKey.TYPE:
|
||||
return RepoKey.detect(repository, manifest_data)
|
||||
elif key_type == PassphraseKey.TYPE:
|
||||
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.detect(repository, manifest_data)
|
||||
elif key_type == PlaintextKey.TYPE:
|
||||
return PlaintextKey.detect(repository, manifest_data)
|
||||
elif key_type == Blake2KeyfileKey.TYPE:
|
||||
return Blake2KeyfileKey.detect(repository, manifest_data)
|
||||
elif key_type == Blake2RepoKey.TYPE:
|
||||
return Blake2RepoKey.detect(repository, manifest_data)
|
||||
elif key_type == AuthenticatedKey.TYPE:
|
||||
return AuthenticatedKey.detect(repository, manifest_data)
|
||||
return RepoKey
|
||||
|
||||
for key in AVAILABLE_KEY_TYPES:
|
||||
if key.TYPE == key_type:
|
||||
return key
|
||||
else:
|
||||
raise UnsupportedPayloadError(key_type)
|
||||
|
||||
|
||||
def key_factory(repository, manifest_data):
|
||||
return identify_key(manifest_data).detect(repository, manifest_data)
|
||||
|
||||
|
||||
def tam_required_file(repository):
|
||||
security_dir = get_security_dir(bin_to_hex(repository.id))
|
||||
return os.path.join(security_dir, 'tam_required')
|
||||
|
@ -139,6 +132,13 @@ def tam_required(repository):
|
|||
class KeyBase:
|
||||
TYPE = None # override in subclasses
|
||||
|
||||
# Human-readable name
|
||||
NAME = 'UNDEFINED'
|
||||
# Name used in command line / API (e.g. borg init --encryption=...)
|
||||
ARG_NAME = 'UNDEFINED'
|
||||
# Storage type (no key blob storage / keyfile / repo)
|
||||
STORAGE = KeyBlobStorage.NO_STORAGE
|
||||
|
||||
def __init__(self, repository):
|
||||
self.TYPE_STR = bytes([self.TYPE])
|
||||
self.repository = repository
|
||||
|
@ -236,6 +236,8 @@ def unpack_and_verify_manifest(self, data, force_tam_not_required=False):
|
|||
class PlaintextKey(KeyBase):
|
||||
TYPE = 0x02
|
||||
NAME = 'plaintext'
|
||||
ARG_NAME = 'none'
|
||||
STORAGE = KeyBlobStorage.NO_STORAGE
|
||||
|
||||
chunk_seed = 0
|
||||
|
||||
|
@ -466,6 +468,9 @@ class PassphraseKey(ID_HMAC_SHA_256, AESKeyBase):
|
|||
# This class is kept for a while to support migration from passphrase to repokey mode.
|
||||
TYPE = 0x01
|
||||
NAME = 'passphrase'
|
||||
ARG_NAME = None
|
||||
STORAGE = KeyBlobStorage.NO_STORAGE
|
||||
|
||||
iterations = 100000 # must not be changed ever!
|
||||
|
||||
@classmethod
|
||||
|
@ -623,6 +628,9 @@ def get_new_target(self, args):
|
|||
class KeyfileKey(ID_HMAC_SHA_256, KeyfileKeyBase):
|
||||
TYPE = 0x00
|
||||
NAME = 'key file'
|
||||
ARG_NAME = 'keyfile'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
|
||||
FILE_ID = 'BORG_KEY'
|
||||
|
||||
def sanity_check(self, filename, id):
|
||||
|
@ -683,6 +691,8 @@ def save(self, target, passphrase):
|
|||
class RepoKey(ID_HMAC_SHA_256, KeyfileKeyBase):
|
||||
TYPE = 0x03
|
||||
NAME = 'repokey'
|
||||
ARG_NAME = 'repokey'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
|
||||
def find_key(self):
|
||||
loc = self.repository._location.canonical_path()
|
||||
|
@ -715,6 +725,9 @@ def save(self, target, passphrase):
|
|||
class Blake2KeyfileKey(ID_BLAKE2b_256, KeyfileKey):
|
||||
TYPE = 0x04
|
||||
NAME = 'key file BLAKE2b'
|
||||
ARG_NAME = 'keyfile-blake2'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
|
||||
FILE_ID = 'BORG_KEY'
|
||||
MAC = blake2b_256
|
||||
|
||||
|
@ -722,12 +735,17 @@ class Blake2KeyfileKey(ID_BLAKE2b_256, KeyfileKey):
|
|||
class Blake2RepoKey(ID_BLAKE2b_256, RepoKey):
|
||||
TYPE = 0x05
|
||||
NAME = 'repokey BLAKE2b'
|
||||
ARG_NAME = 'repokey-blake2'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
|
||||
MAC = blake2b_256
|
||||
|
||||
|
||||
class AuthenticatedKey(ID_BLAKE2b_256, RepoKey):
|
||||
TYPE = 0x06
|
||||
NAME = 'authenticated BLAKE2b'
|
||||
ARG_NAME = 'authenticated'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
|
||||
def encrypt(self, chunk):
|
||||
chunk = self.compress(chunk)
|
||||
|
@ -742,3 +760,11 @@ def decrypt(self, id, data, decompress=True):
|
|||
data = self.compressor.decompress(payload)
|
||||
self.assert_id(id, data)
|
||||
return Chunk(data)
|
||||
|
||||
|
||||
AVAILABLE_KEY_TYPES = (
|
||||
PlaintextKey,
|
||||
PassphraseKey,
|
||||
KeyfileKey, RepoKey,
|
||||
Blake2KeyfileKey, Blake2RepoKey, AuthenticatedKey,
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from hashlib import sha256
|
||||
import pkgutil
|
||||
|
||||
from .key import KeyfileKey, RepoKey, PassphraseKey, KeyfileNotFoundError, PlaintextKey
|
||||
from .key import KeyfileKey, KeyfileNotFoundError, KeyBlobStorage, identify_key
|
||||
from .helpers import Manifest, NoManifestError, Error, yes, bin_to_hex
|
||||
from .repository import Repository
|
||||
|
||||
|
@ -31,10 +31,6 @@ def sha256_truncated(data, num):
|
|||
return h.hexdigest()[:num]
|
||||
|
||||
|
||||
KEYBLOB_LOCAL = 'local'
|
||||
KEYBLOB_REPO = 'repo'
|
||||
|
||||
|
||||
class KeyManager:
|
||||
def __init__(self, repository):
|
||||
self.repository = repository
|
||||
|
@ -42,32 +38,27 @@ def __init__(self, repository):
|
|||
self.keyblob_storage = None
|
||||
|
||||
try:
|
||||
cdata = self.repository.get(Manifest.MANIFEST_ID)
|
||||
manifest_data = self.repository.get(Manifest.MANIFEST_ID)
|
||||
except Repository.ObjectNotFound:
|
||||
raise NoManifestError
|
||||
|
||||
key_type = cdata[0]
|
||||
if key_type == KeyfileKey.TYPE:
|
||||
self.keyblob_storage = KEYBLOB_LOCAL
|
||||
elif key_type == RepoKey.TYPE or key_type == PassphraseKey.TYPE:
|
||||
self.keyblob_storage = KEYBLOB_REPO
|
||||
elif key_type == PlaintextKey.TYPE:
|
||||
key = identify_key(manifest_data)
|
||||
self.keyblob_storage = key.STORAGE
|
||||
if self.keyblob_storage == KeyBlobStorage.NO_STORAGE:
|
||||
raise UnencryptedRepo()
|
||||
else:
|
||||
raise UnknownKeyType(key_type)
|
||||
|
||||
def load_keyblob(self):
|
||||
if self.keyblob_storage == KEYBLOB_LOCAL:
|
||||
if self.keyblob_storage == KeyBlobStorage.KEYFILE:
|
||||
k = KeyfileKey(self.repository)
|
||||
target = k.find_key()
|
||||
with open(target, 'r') as fd:
|
||||
self.keyblob = ''.join(fd.readlines()[1:])
|
||||
|
||||
elif self.keyblob_storage == KEYBLOB_REPO:
|
||||
elif self.keyblob_storage == KeyBlobStorage.REPO:
|
||||
self.keyblob = self.repository.load_key().decode()
|
||||
|
||||
def store_keyblob(self, args):
|
||||
if self.keyblob_storage == KEYBLOB_LOCAL:
|
||||
if self.keyblob_storage == KeyBlobStorage.KEYFILE:
|
||||
k = KeyfileKey(self.repository)
|
||||
try:
|
||||
target = k.find_key()
|
||||
|
@ -75,7 +66,7 @@ def store_keyblob(self, args):
|
|||
target = k.get_new_target(args)
|
||||
|
||||
self.store_keyfile(target)
|
||||
elif self.keyblob_storage == KEYBLOB_REPO:
|
||||
elif self.keyblob_storage == KeyBlobStorage.REPO:
|
||||
self.repository.save_key(self.keyblob.encode('utf-8'))
|
||||
|
||||
def get_keyfile_data(self):
|
||||
|
|
Loading…
Reference in a new issue