From 68ef5e8a4bd780a93bd0bb33a819c68511f5f9ed Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 4 Dec 2016 05:20:19 +0100 Subject: [PATCH] allow different MACs, implement blake2b MAC --- src/borg/crypto/low_level.pyx | 124 ++++++++++++++++++++++++++-------- src/borg/testsuite/key.py | 8 +-- 2 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/borg/crypto/low_level.pyx b/src/borg/crypto/low_level.pyx index c72e2a358..96d15b296 100644 --- a/src/borg/crypto/low_level.pyx +++ b/src/borg/crypto/low_level.pyx @@ -223,12 +223,10 @@ class UNENCRYPTED: return 0 -cdef class AES256_CTR_HMAC_SHA256: - # Layout: HEADER + HMAC 32 + IV 8 + CT (same as attic / borg < 1.2 IF HEADER = TYPE_BYTE, no AAD) +cdef class AES256_CTR_BASE: + # Layout: HEADER + MAC 32 + IV 8 + CT (same as attic / borg < 1.2 IF HEADER = TYPE_BYTE, no AAD) cdef EVP_CIPHER_CTX *ctx - cdef HMAC_CTX *hmac_ctx - cdef unsigned char *mac_key cdef unsigned char *enc_key cdef int cipher_blk_len cdef int iv_len, iv_len_short @@ -245,7 +243,6 @@ cdef class AES256_CTR_HMAC_SHA256: def __init__(self, mac_key, enc_key, iv=None, header_len=1, aad_offset=1): self.requirements_check() - assert isinstance(mac_key, bytes) and len(mac_key) == 32 assert isinstance(enc_key, bytes) and len(enc_key) == 32 self.cipher_blk_len = 16 self.iv_len = sizeof(self.iv) @@ -254,7 +251,6 @@ cdef class AES256_CTR_HMAC_SHA256: self.aad_offset = aad_offset self.header_len = header_len self.mac_len = 32 - self.mac_key = mac_key self.enc_key = enc_key if iv is not None: self.set_iv(iv) @@ -263,11 +259,19 @@ cdef class AES256_CTR_HMAC_SHA256: def __cinit__(self, mac_key, enc_key, iv=None, header_len=1, aad_offset=1): self.ctx = EVP_CIPHER_CTX_new() - self.hmac_ctx = HMAC_CTX_new() def __dealloc__(self): EVP_CIPHER_CTX_free(self.ctx) - HMAC_CTX_free(self.hmac_ctx) + + cdef mac_compute(self, const unsigned char *data1, int data1_len, + const unsigned char *data2, int data2_len, + const unsigned char *mac_buf): + raise NotImplementedError + + cdef mac_verify(self, const unsigned char *data1, int data1_len, + const unsigned char *data2, int data2_len, + const unsigned char *mac_buf, const unsigned char *mac_wanted): + raise NotImplementedError def encrypt(self, data, header=b'', iv=None): """ @@ -309,14 +313,9 @@ cdef class AES256_CTR_HMAC_SHA256: if not rc: raise CryptoError('EVP_EncryptFinal_ex failed') offset += olen - if not HMAC_Init_ex(self.hmac_ctx, self.mac_key, self.mac_len, EVP_sha256(), NULL): - raise CryptoError('HMAC_Init_ex failed') - if not HMAC_Update(self.hmac_ctx, hdata.buf+aoffset, alen): - raise CryptoError('HMAC_Update failed') - if not HMAC_Update(self.hmac_ctx, odata+hlen+self.mac_len, offset-hlen-self.mac_len): - raise CryptoError('HMAC_Update failed') - if not HMAC_Final(self.hmac_ctx, odata+hlen, NULL): - raise CryptoError('HMAC_Final failed') + self.mac_compute( hdata.buf+aoffset, alen, + odata+hlen+self.mac_len, offset-hlen-self.mac_len, + odata+hlen) self.blocks += self.block_count(ilen) return odata[:offset] finally: @@ -338,20 +337,13 @@ cdef class AES256_CTR_HMAC_SHA256: raise MemoryError cdef int olen cdef int offset - cdef unsigned char hmac_buf[32] - assert sizeof(hmac_buf) == self.mac_len + cdef unsigned char mac_buf[32] + assert sizeof(mac_buf) == self.mac_len cdef Py_buffer idata = ro_buffer(envelope) try: - if not HMAC_Init_ex(self.hmac_ctx, self.mac_key, self.mac_len, EVP_sha256(), NULL): - raise CryptoError('HMAC_Init_ex failed') - if not HMAC_Update(self.hmac_ctx, idata.buf+aoffset, alen): - raise CryptoError('HMAC_Update failed') - if not HMAC_Update(self.hmac_ctx, idata.buf+hlen+self.mac_len, ilen-hlen-self.mac_len): - raise CryptoError('HMAC_Update failed') - if not HMAC_Final(self.hmac_ctx, hmac_buf, NULL): - raise CryptoError('HMAC_Final failed') - if CRYPTO_memcmp(hmac_buf, idata.buf+hlen, self.mac_len): - raise IntegrityError('HMAC Authentication failed') + self.mac_verify( idata.buf+aoffset, alen, + idata.buf+hlen+self.mac_len, ilen-hlen-self.mac_len, + mac_buf, idata.buf+hlen) iv = self.fetch_iv( idata.buf+hlen+self.mac_len) self.set_iv(iv) if not EVP_DecryptInit_ex(self.ctx, EVP_aes_256_ctr(), NULL, self.enc_key, iv): @@ -405,7 +397,81 @@ cdef class AES256_CTR_HMAC_SHA256: return bytes_to_long(envelope[offset:offset+self.iv_len_short]) -AES256_CTR_BLAKE2b = AES256_CTR_HMAC_SHA256 # TODO this is a dummy +cdef class AES256_CTR_HMAC_SHA256(AES256_CTR_BASE): + cdef HMAC_CTX *hmac_ctx + cdef unsigned char *mac_key + + def __init__(self, mac_key, enc_key, iv=None, header_len=1, aad_offset=1): + assert isinstance(mac_key, bytes) and len(mac_key) == 32 + self.mac_key = mac_key + super().__init__(mac_key, enc_key, iv=iv, header_len=header_len, aad_offset=aad_offset) + + def __cinit__(self, mac_key, enc_key, iv=None, header_len=1, aad_offset=1): + self.hmac_ctx = HMAC_CTX_new() + + def __dealloc__(self): + HMAC_CTX_free(self.hmac_ctx) + + cdef mac_compute(self, const unsigned char *data1, int data1_len, + const unsigned char *data2, int data2_len, + const unsigned char *mac_buf): + if not HMAC_Init_ex(self.hmac_ctx, self.mac_key, self.mac_len, EVP_sha256(), NULL): + raise CryptoError('HMAC_Init_ex failed') + if not HMAC_Update(self.hmac_ctx, data1, data1_len): + raise CryptoError('HMAC_Update failed') + if not HMAC_Update(self.hmac_ctx, data2, data2_len): + raise CryptoError('HMAC_Update failed') + if not HMAC_Final(self.hmac_ctx, mac_buf, NULL): + raise CryptoError('HMAC_Final failed') + + cdef mac_verify(self, const unsigned char *data1, int data1_len, + const unsigned char *data2, int data2_len, + const unsigned char *mac_buf, const unsigned char *mac_wanted): + self.mac_compute(data1, data1_len, data2, data2_len, mac_buf) + if CRYPTO_memcmp(mac_buf, mac_wanted, self.mac_len): + raise IntegrityError('MAC Authentication failed') + + +cdef class AES256_CTR_BLAKE2b(AES256_CTR_BASE): + cdef unsigned char *mac_key + + def __init__(self, mac_key, enc_key, iv=None, header_len=1, aad_offset=1): + assert isinstance(mac_key, bytes) and len(mac_key) == 128 + self.mac_key = mac_key + super().__init__(mac_key, enc_key, iv=iv, header_len=header_len, aad_offset=aad_offset) + + def __cinit__(self, mac_key, enc_key, iv=None, header_len=1, aad_offset=1): + pass + + def __dealloc__(self): + pass + + cdef mac_compute(self, const unsigned char *data1, int data1_len, + const unsigned char *data2, int data2_len, + const unsigned char *mac_buf): + cdef blake2b_state state + cdef int rc + rc = blake2b_init(&state, self.mac_len) + if rc == -1: + raise Exception('blake2b_init() failed') + with nogil: + rc = blake2b_update(&state, self.mac_key, 128) + if rc != -1: + rc = blake2b_update(&state, data1, data1_len) + if rc != -1: + rc = blake2b_update(&state, data2, data2_len) + if rc == -1: + raise Exception('blake2b_update() failed') + rc = blake2b_final(&state, mac_buf, self.mac_len) + if rc == -1: + raise Exception('blake2b_final() failed') + + cdef mac_verify(self, const unsigned char *data1, int data1_len, + const unsigned char *data2, int data2_len, + const unsigned char *mac_buf, const unsigned char *mac_wanted): + self.mac_compute(data1, data1_len, data2, data2_len, mac_buf) + if CRYPTO_memcmp(mac_buf, mac_wanted, self.mac_len): + raise IntegrityError('MAC Authentication failed') ctypedef const EVP_CIPHER * (* CIPHER)() diff --git a/src/borg/testsuite/key.py b/src/borg/testsuite/key.py index d5c9debb7..075311744 100644 --- a/src/borg/testsuite/key.py +++ b/src/borg/testsuite/key.py @@ -77,10 +77,9 @@ class TestKey: KeyfileKey, RepoKey, AuthenticatedKey, - # TODO temporarily disabled for branch merging XXX - #Blake2KeyfileKey, - #Blake2RepoKey, - #Blake2AuthenticatedKey, + Blake2KeyfileKey, + Blake2RepoKey, + Blake2AuthenticatedKey, )) def key(self, request, monkeypatch): monkeypatch.setenv('BORG_PASSPHRASE', 'test') @@ -176,7 +175,6 @@ class TestKey: key = KeyfileKey.detect(self.MockRepository(), self.keyfile2_cdata) assert key.decrypt(self.keyfile2_id, self.keyfile2_cdata) == b'payload' - @pytest.mark.skip("temporarily disabled for branch merge") # TODO def test_keyfile_blake2(self, monkeypatch, keys_dir): with keys_dir.join('keyfile').open('w') as fd: fd.write(self.keyfile_blake2_key_file)