mirror of
https://github.com/borgbackup/borg.git
synced 2024-12-25 17:27:31 +00:00
crypto: AEAD key classes
also: cleanup class structure: less inheritance, more mixins. define type bytes using the 4:4 split upper 4 bits are ciphersuite: 0 == legacy AES-CTR based stuff 1+ == new AEAD stuff lower 4 bits are keytype: legacy: a bit mixed up, as it was... new stuff: 0=keyfile 1=repokey, ...
This commit is contained in:
parent
9633273622
commit
0f6f278b0f
2 changed files with 164 additions and 16 deletions
|
@ -111,6 +111,8 @@ class KeyBlobStorage:
|
|||
|
||||
|
||||
class KeyType:
|
||||
# legacy crypto
|
||||
# upper 4 bits are ciphersuite, 0 == legacy AES-CTR
|
||||
KEYFILE = 0x00
|
||||
# repos with PASSPHRASE 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.
|
||||
|
@ -123,6 +125,16 @@ class KeyType:
|
|||
BLAKE2REPO = 0x05
|
||||
BLAKE2AUTHENTICATED = 0x06
|
||||
AUTHENTICATED = 0x07
|
||||
# new crypto
|
||||
# upper 4 bits are ciphersuite, lower 4 bits are keytype
|
||||
AESOCBKEYFILE = 0x10
|
||||
AESOCBREPO = 0x11
|
||||
CHPOKEYFILE = 0x20
|
||||
CHPOREPO = 0x21
|
||||
BLAKE2AESOCBKEYFILE = 0x30
|
||||
BLAKE2AESOCBREPO = 0x31
|
||||
BLAKE2CHPOKEYFILE = 0x40
|
||||
BLAKE2CHPOREPO = 0x41
|
||||
|
||||
|
||||
REPOSITORY_README = """This is a Borg Backup repository.
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
from .nonces import NonceManager
|
||||
from .low_level import AES, bytes_to_long, long_to_bytes, bytes_to_int, num_cipher_blocks, hmac_sha256, blake2b_256, hkdf_hmac_sha512
|
||||
from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b
|
||||
from .low_level import AES256_CTR_HMAC_SHA256, AES256_CTR_BLAKE2b, AES256_OCB, CHACHA20_POLY1305
|
||||
|
||||
|
||||
class UnsupportedPayloadError(Error):
|
||||
|
@ -336,7 +336,7 @@ class AESKeyBase(KeyBase):
|
|||
|
||||
PAYLOAD_OVERHEAD = 1 + 32 + 8 # TYPE + HMAC + NONCE
|
||||
|
||||
CIPHERSUITE = AES256_CTR_HMAC_SHA256
|
||||
CIPHERSUITE = None # override in derived class
|
||||
|
||||
logically_encrypted = True
|
||||
|
||||
|
@ -383,7 +383,7 @@ def init_ciphers(self, manifest_data=None):
|
|||
self.nonce_manager = NonceManager(self.repository, nonce)
|
||||
|
||||
|
||||
class FlexiKeyBase(AESKeyBase):
|
||||
class FlexiKeyBase:
|
||||
@classmethod
|
||||
def detect(cls, repository, manifest_data):
|
||||
key = cls(repository)
|
||||
|
@ -496,9 +496,7 @@ def get_new_target(self, args):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
class FlexiKey(ID_HMAC_SHA_256, FlexiKeyBase):
|
||||
TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE}
|
||||
|
||||
class FlexiKey(FlexiKeyBase):
|
||||
FILE_ID = 'BORG_KEY'
|
||||
|
||||
def sanity_check(self, filename, id):
|
||||
|
@ -625,40 +623,43 @@ def remove(self, target):
|
|||
raise TypeError('Unsupported borg key storage type')
|
||||
|
||||
|
||||
class KeyfileKey(FlexiKey):
|
||||
class KeyfileKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE}
|
||||
TYPE = KeyType.KEYFILE
|
||||
NAME = 'key file'
|
||||
ARG_NAME = 'keyfile'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
CIPHERSUITE = AES256_CTR_HMAC_SHA256
|
||||
|
||||
|
||||
class RepoKey(FlexiKey):
|
||||
class RepoKey(ID_HMAC_SHA_256, AESKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.KEYFILE, KeyType.REPO, KeyType.PASSPHRASE}
|
||||
TYPE = KeyType.REPO
|
||||
NAME = 'repokey'
|
||||
ARG_NAME = 'repokey'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
CIPHERSUITE = AES256_CTR_HMAC_SHA256
|
||||
|
||||
|
||||
class Blake2FlexiKey(ID_BLAKE2b_256, FlexiKey):
|
||||
class Blake2KeyfileKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.BLAKE2KEYFILE, KeyType.BLAKE2REPO}
|
||||
CIPHERSUITE = AES256_CTR_BLAKE2b
|
||||
|
||||
|
||||
class Blake2KeyfileKey(Blake2FlexiKey):
|
||||
TYPE = KeyType.BLAKE2KEYFILE
|
||||
NAME = 'key file BLAKE2b'
|
||||
ARG_NAME = 'keyfile-blake2'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
CIPHERSUITE = AES256_CTR_BLAKE2b
|
||||
|
||||
|
||||
class Blake2RepoKey(Blake2FlexiKey):
|
||||
class Blake2RepoKey(ID_BLAKE2b_256, AESKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.BLAKE2KEYFILE, KeyType.BLAKE2REPO}
|
||||
TYPE = KeyType.BLAKE2REPO
|
||||
NAME = 'repokey BLAKE2b'
|
||||
ARG_NAME = 'repokey-blake2'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
CIPHERSUITE = AES256_CTR_BLAKE2b
|
||||
|
||||
|
||||
class AuthenticatedKeyBase(FlexiKey):
|
||||
class AuthenticatedKeyBase(AESKeyBase, FlexiKey):
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
|
||||
# It's only authenticated, not encrypted.
|
||||
|
@ -691,7 +692,7 @@ def decrypt(self, id, data, decompress=True):
|
|||
return data
|
||||
|
||||
|
||||
class AuthenticatedKey(AuthenticatedKeyBase):
|
||||
class AuthenticatedKey(ID_HMAC_SHA_256, AuthenticatedKeyBase):
|
||||
TYPE = KeyType.AUTHENTICATED
|
||||
TYPES_ACCEPTABLE = {TYPE}
|
||||
NAME = 'authenticated'
|
||||
|
@ -705,8 +706,143 @@ class Blake2AuthenticatedKey(ID_BLAKE2b_256, AuthenticatedKeyBase):
|
|||
ARG_NAME = 'authenticated-blake2'
|
||||
|
||||
|
||||
# ------------ new crypto ------------
|
||||
|
||||
|
||||
class AEADKeyBase(KeyBase):
|
||||
"""
|
||||
Chunks are encrypted and authenticated using some AEAD ciphersuite
|
||||
|
||||
Payload layout: TYPE(1) + SESSIONID(24) + NONCE(12) + MAC(16) + CIPHERTEXT
|
||||
^------------- AAD ---------------^
|
||||
"""
|
||||
|
||||
PAYLOAD_OVERHEAD = 1 + 24 + 12 + 16 # TYPE + SESSIONID + NONCE + MAC
|
||||
|
||||
CIPHERSUITE = None # override in subclass
|
||||
|
||||
logically_encrypted = True
|
||||
|
||||
def encrypt(self, chunk):
|
||||
data = self.compressor.compress(chunk)
|
||||
header = self.TYPE_STR + self.sessionid
|
||||
iv = self.cipher.next_iv()
|
||||
return self.cipher.encrypt(data, header=header, iv=iv)
|
||||
|
||||
def decrypt(self, id, data, decompress=True):
|
||||
self.assert_type(data[0], id)
|
||||
sessionid = data[1:13] # XXX
|
||||
self.init_ciphers(salt=salt, context=context, iv=iv) # XXX
|
||||
try:
|
||||
payload = self.cipher.decrypt(data)
|
||||
except IntegrityError as e:
|
||||
raise IntegrityError(f"Chunk {bin_to_hex(id)}: Could not decrypt [{str(e)}]")
|
||||
if not decompress:
|
||||
return payload
|
||||
data = self.decompress(payload)
|
||||
self.assert_id(id, data)
|
||||
return data
|
||||
|
||||
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]
|
||||
self.chunk_seed = bytes_to_int(data[96:100])
|
||||
# Convert to signed int32
|
||||
if self.chunk_seed & 0x80000000:
|
||||
self.chunk_seed = self.chunk_seed - 0xffffffff - 1
|
||||
|
||||
def init_ciphers(self, salt=b'', context=b'', iv=0):
|
||||
key = hkdf_hmac_sha512(
|
||||
ikm=self.enc_key + self.enc_hmac_key,
|
||||
salt=salt,
|
||||
info=b'borg-crypto-' + context, # XXX
|
||||
output_length=32
|
||||
)
|
||||
self.cipher = self.CIPHERSUITE(key=key, header_len=1+24, aad_offset=0) # XXX
|
||||
self.cipher.set_iv(iv)
|
||||
|
||||
|
||||
class AESOCBKeyfileKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.AESOCBKEYFILE, KeyType.AESOCBREPO}
|
||||
TYPE = KeyType.AESOCBKEYFILE
|
||||
NAME = 'key file AES-OCB'
|
||||
ARG_NAME = 'keyfile-aes-ocb'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
CIPHERSUITE = AES256_OCB
|
||||
|
||||
|
||||
class AESOCBRepoKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.AESOCBKEYFILE, KeyType.AESOCBREPO}
|
||||
TYPE = KeyType.AESOCBREPO
|
||||
NAME = 'repokey AES-OCB'
|
||||
ARG_NAME = 'repokey-aes-ocb'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
CIPHERSUITE = AES256_OCB
|
||||
|
||||
|
||||
class CHPOKeyfileKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.CHPOKEYFILE, KeyType.CHPOREPO}
|
||||
TYPE = KeyType.CHPOKEYFILE
|
||||
NAME = 'key file ChaCha20-Poly1305'
|
||||
ARG_NAME = 'keyfile-chacha20-poly1305'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
CIPHERSUITE = CHACHA20_POLY1305
|
||||
|
||||
|
||||
class CHPORepoKey(ID_HMAC_SHA_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.CHPOKEYFILE, KeyType.CHPOREPO}
|
||||
TYPE = KeyType.CHPOREPO
|
||||
NAME = 'repokey ChaCha20-Poly1305'
|
||||
ARG_NAME = 'repokey-chacha20-poly1305'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
CIPHERSUITE = CHACHA20_POLY1305
|
||||
|
||||
|
||||
class Blake2AESOCBKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.BLAKE2AESOCBKEYFILE, KeyType.BLAKE2AESOCBREPO}
|
||||
TYPE = KeyType.BLAKE2AESOCBKEYFILE
|
||||
NAME = 'key file Blake2b AES-OCB'
|
||||
ARG_NAME = 'keyfile-blake2-aes-ocb'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
CIPHERSUITE = AES256_OCB
|
||||
|
||||
|
||||
class Blake2AESOCBRepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.BLAKE2AESOCBKEYFILE, KeyType.BLAKE2AESOCBREPO}
|
||||
TYPE = KeyType.BLAKE2AESOCBREPO
|
||||
NAME = 'repokey Blake2b AES-OCB'
|
||||
ARG_NAME = 'repokey-blake2-aes-ocb'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
CIPHERSUITE = AES256_OCB
|
||||
|
||||
|
||||
class Blake2CHPOKeyfileKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.BLAKE2CHPOKEYFILE, KeyType.BLAKE2CHPOREPO}
|
||||
TYPE = KeyType.BLAKE2CHPOKEYFILE
|
||||
NAME = 'key file Blake2b ChaCha20-Poly1305'
|
||||
ARG_NAME = 'keyfile-blake2-chacha20-poly1305'
|
||||
STORAGE = KeyBlobStorage.KEYFILE
|
||||
CIPHERSUITE = CHACHA20_POLY1305
|
||||
|
||||
|
||||
class Blake2CHPORepoKey(ID_BLAKE2b_256, AEADKeyBase, FlexiKey):
|
||||
TYPES_ACCEPTABLE = {KeyType.BLAKE2CHPOKEYFILE, KeyType.BLAKE2CHPOREPO}
|
||||
TYPE = KeyType.BLAKE2CHPOREPO
|
||||
NAME = 'repokey Blake2b ChaCha20-Poly1305'
|
||||
ARG_NAME = 'repokey-blake2-chacha20-poly1305'
|
||||
STORAGE = KeyBlobStorage.REPO
|
||||
CIPHERSUITE = CHACHA20_POLY1305
|
||||
|
||||
|
||||
AVAILABLE_KEY_TYPES = (
|
||||
PlaintextKey,
|
||||
KeyfileKey, RepoKey, AuthenticatedKey,
|
||||
Blake2KeyfileKey, Blake2RepoKey, Blake2AuthenticatedKey,
|
||||
# new crypto
|
||||
AESOCBKeyfileKey, AESOCBRepoKey,
|
||||
CHPOKeyfileKey, CHPORepoKey,
|
||||
Blake2AESOCBKeyfileKey, Blake2AESOCBRepoKey,
|
||||
Blake2CHPOKeyfileKey, Blake2CHPORepoKey,
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue