From a661aa79ded6bac583b6cc214fb0391bc2156daf Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Sat, 17 Apr 2021 13:25:40 +0200 Subject: [PATCH] vfs/cbe_trust_anchor: AES-key-wrap private key Instead of simply encrypting the private key with AES-256 when storing it to the 'encrypted_private_key' file, wrap it using the AES-key-wrap algorithm described in RFC 3394 "Advanced Encryption Standard (AES) Key Wrap Algorithm". This is more secure and enables us to directly check whether the passphrase entered by the user was correct or not. Ref #4032 --- .../mk/spec/x86_64/vfs_cbe_trust_anchor.mk | 5 +- .../src/lib/vfs/cbe_trust_anchor/aes_256.cc | 225 ++++++++++++++++++ .../src/lib/vfs/cbe_trust_anchor/aes_256.h | 71 ++++++ .../src/lib/vfs/cbe_trust_anchor/integer.cc | 28 +++ .../src/lib/vfs/cbe_trust_anchor/integer.h | 25 ++ .../gems/src/lib/vfs/cbe_trust_anchor/vfs.cc | 131 ++++------ 6 files changed, 395 insertions(+), 90 deletions(-) create mode 100644 repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.cc create mode 100644 repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.h create mode 100644 repos/gems/src/lib/vfs/cbe_trust_anchor/integer.cc create mode 100644 repos/gems/src/lib/vfs/cbe_trust_anchor/integer.h diff --git a/repos/gems/lib/mk/spec/x86_64/vfs_cbe_trust_anchor.mk b/repos/gems/lib/mk/spec/x86_64/vfs_cbe_trust_anchor.mk index 8edb1c8571..9ac5ac1d93 100644 --- a/repos/gems/lib/mk/spec/x86_64/vfs_cbe_trust_anchor.mk +++ b/repos/gems/lib/mk/spec/x86_64/vfs_cbe_trust_anchor.mk @@ -1,7 +1,10 @@ OPENSSL_DIR = $(call select_from_ports,openssl) -SRC_CC = vfs.cc +SRC_CC += vfs.cc +SRC_CC += aes_256.cc +SRC_CC += integer.cc +INC_DIR += $(REP_DIR)/src/lib/vfs/cbe_trust_anchor INC_DIR += $(OPENSSL_DIR)/include LIBS += libcrypto diff --git a/repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.cc b/repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.cc new file mode 100644 index 0000000000..ca24e5e243 --- /dev/null +++ b/repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.cc @@ -0,0 +1,225 @@ +/* + * \brief Local variants of doing AES-256 and AES-256 key wrapping + * \author Martin Stein + * \date 2021-04-16 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include + +/* OpenSSL includes */ +namespace Openssl { + + #include +} + +/* local includes */ +#include +#include + +using namespace Genode; + + +namespace Aes_256 +{ + struct Initialization_vector + { + unsigned char values[16]; + }; +} + +namespace Aes_256_key_wrap { + + enum { KEY_PLAINTEXT_NR_OF_64_BIT_VALUES = KEY_PLAINTEXT_SIZE / 8 }; + enum { NR_OF_WRAPPING_STEPS = 6 }; + enum { INTEGRITY_CHECK_VALUE = 0xa6a6a6a6a6a6a6a6 }; +} + + +template +static void inline overwrite_object_with_zeroes(OBJECT_TYPE &object) +{ + memset(&object, 0, sizeof(object)); + + /* trigger compiler to not drop the memset */ + asm volatile(""::"r"(&object):"memory"); +} + + +void Aes_256::encrypt_with_zeroed_iv(unsigned char *ciphertext_base, + size_t ciphertext_size, + unsigned char const *plaintext_base, + unsigned char const *key_base, + size_t key_size) +{ + Openssl::AES_KEY aes_key; + if (AES_set_encrypt_key(key_base, key_size * 8, &aes_key)) { + class Failed_to_set_key { }; + throw Failed_to_set_key { }; + } + Aes_256::Initialization_vector iv { }; + memset(iv.values, 0, sizeof(iv.values)); + AES_cbc_encrypt( + plaintext_base, ciphertext_base, ciphertext_size, &aes_key, iv.values, + AES_ENCRYPT); + + /* ensure that relevant encryption data doesn't remain on the stack */ + overwrite_object_with_zeroes(aes_key); +} + + +void Aes_256::decrypt_with_zeroed_iv(unsigned char *plaintext_base, + size_t plaintext_size, + unsigned char const *ciphertext_base, + unsigned char const *key_base, + size_t key_size) +{ + Openssl::AES_KEY aes_key; + if (AES_set_decrypt_key(key_base, key_size * 8, &aes_key)) { + class Failed_to_set_key { }; + throw Failed_to_set_key { }; + } + Aes_256::Initialization_vector iv { }; + memset(iv.values, 0, sizeof(iv.values)); + AES_cbc_encrypt( + ciphertext_base, plaintext_base, plaintext_size, &aes_key, iv.values, + AES_DECRYPT); + + /* ensure that relevant encryption data doesn't remain on the stack */ + overwrite_object_with_zeroes(aes_key); +} + + +void Aes_256_key_wrap::wrap_key(unsigned char *ciphertext_uint8, + size_t ciphertext_size, + unsigned char const *key_plaintext_uint8, + size_t key_plaintext_size, + unsigned char const *key_encryption_key_uint8, + size_t key_encryption_key_size) +{ + if (ciphertext_size != CIPHERTEXT_SIZE) { + class Bad_ciphertext_size { }; + throw Bad_ciphertext_size { }; + } + if (key_plaintext_size != KEY_PLAINTEXT_SIZE) { + class Bad_key_plaintext_size { }; + throw Bad_key_plaintext_size { }; + } + if (key_encryption_key_size != KEY_ENCRYPTION_KEY_SIZE) { + class Bad_key_encryption_key_size { }; + throw Bad_key_encryption_key_size { }; + } + uint64_t *ciphertext { (uint64_t *)ciphertext_uint8 }; + uint64_t const *key_plaintext { (uint64_t const *)key_plaintext_uint8 }; + uint64_t const *key_encryption_key { + (uint64_t const *)key_encryption_key_uint8 }; + + ciphertext[0] = INTEGRITY_CHECK_VALUE; + memcpy( + &ciphertext[1], &key_plaintext[0], + ciphertext_size - sizeof(ciphertext[0])); + + for (unsigned step_idx = 0; + step_idx < NR_OF_WRAPPING_STEPS; + step_idx++) { + + for (unsigned value_idx = 1; + value_idx <= KEY_PLAINTEXT_NR_OF_64_BIT_VALUES; + value_idx++) { + + uint64_t encryption_input[2]; + encryption_input[0] = ciphertext[0]; + encryption_input[1] = ciphertext[value_idx]; + + uint64_t encryption_output[2]; + + Aes_256::encrypt_with_zeroed_iv( + (unsigned char *)encryption_output, + sizeof(encryption_output), + (unsigned char *)encryption_input, + (unsigned char *)key_encryption_key, + key_encryption_key_size); + + uint64_t const xor_operand { + Integer::u64_swap_byte_order( + ((uint64_t)KEY_PLAINTEXT_NR_OF_64_BIT_VALUES * step_idx) + + value_idx) }; + + ciphertext[0] = encryption_output[0] ^ xor_operand; + ciphertext[value_idx] = encryption_output[1]; + } + } +} + + +void Aes_256_key_wrap::unwrap_key(unsigned char *key_plaintext_uint8, + size_t key_plaintext_size, + bool &key_plaintext_corrupt, + unsigned char const *ciphertext_uint8, + size_t ciphertext_size, + unsigned char const *key_encryption_key_uint8, + size_t key_encryption_key_size) +{ + if (key_plaintext_size != KEY_PLAINTEXT_SIZE) { + class Bad_key_plaintext_size { }; + throw Bad_key_plaintext_size { }; + } + if (ciphertext_size != CIPHERTEXT_SIZE) { + class Bad_ciphertext_size { }; + throw Bad_ciphertext_size { }; + } + if (key_encryption_key_size != KEY_ENCRYPTION_KEY_SIZE) { + class Bad_key_encryption_key_size { }; + throw Bad_key_encryption_key_size { }; + } + uint64_t *key_plaintext { (uint64_t *)key_plaintext_uint8 }; + uint64_t const *ciphertext { (uint64_t const *)ciphertext_uint8 }; + uint64_t const *key_encryption_key { + (uint64_t const *)key_encryption_key_uint8 }; + + uint64_t integrity_check_value { ciphertext[0] }; + memcpy(&key_plaintext[0], &ciphertext[1], key_plaintext_size); + + for (signed step_idx = NR_OF_WRAPPING_STEPS - 1; + step_idx >= 0; + step_idx--) { + + for (unsigned value_idx = KEY_PLAINTEXT_NR_OF_64_BIT_VALUES; + value_idx >= 1; + value_idx--) { + + uint64_t const xor_operand { + Integer::u64_swap_byte_order( + ((uint64_t)KEY_PLAINTEXT_NR_OF_64_BIT_VALUES * step_idx) + + value_idx) }; + + uint64_t encryption_input[2]; + encryption_input[0] = integrity_check_value ^ xor_operand; + encryption_input[1] = key_plaintext[value_idx - 1]; + + uint64_t encryption_output[2]; + + Aes_256::decrypt_with_zeroed_iv( + (unsigned char *)encryption_output, + sizeof(encryption_output), + (unsigned char *)encryption_input, + (unsigned char *)key_encryption_key, + key_encryption_key_size); + + integrity_check_value = encryption_output[0]; + key_plaintext[value_idx - 1] = encryption_output[1]; + } + } + if (integrity_check_value == INTEGRITY_CHECK_VALUE) { + key_plaintext_corrupt = false; + } else { + key_plaintext_corrupt = true; + } +} diff --git a/repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.h b/repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.h new file mode 100644 index 0000000000..9bedb62145 --- /dev/null +++ b/repos/gems/src/lib/vfs/cbe_trust_anchor/aes_256.h @@ -0,0 +1,71 @@ +/* + * \brief Local variants of doing AES-256 and AES-256 key wrapping + * \author Martin Stein + * \date 2021-04-16 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _AES_256_H_ +#define _AES_256_H_ + +/* Genode includes */ +#include + +namespace Aes_256 +{ + void encrypt_with_zeroed_iv(unsigned char *ciphertext_base, + Genode::size_t ciphertext_size, + unsigned char const *plaintext_base, + unsigned char const *key_base, + Genode::size_t key_size); + + void decrypt_with_zeroed_iv(unsigned char *plaintext_base, + Genode::size_t plaintext_size, + unsigned char const *ciphertext_base, + unsigned char const *key_base, + Genode::size_t key_size); +} + +namespace Aes_256_key_wrap +{ + enum { KEY_PLAINTEXT_SIZE = 32 }; + enum { CIPHERTEXT_SIZE = 40 }; + enum { KEY_ENCRYPTION_KEY_SIZE = 32 }; + + /** + * Implementation of the "Key Wrap" algorithm (alternative indexing-based + * variant) defined in RFC 3394 "Advanced Encryption Standard (AES) Key + * Wrap Algorithm" paragraph 2.2.1, artificially tailored to a + * key-encryption-key (KEK) size of 256 bits and a key (key data) size of + * 256 bits. + */ + void wrap_key(unsigned char *ciphertext_uint8, + Genode::size_t ciphertext_size, + unsigned char const *key_plaintext_uint8, + Genode::size_t key_plaintext_size, + unsigned char const *key_encryption_key_uint8, + Genode::size_t key_encryption_key_size); + + /** + * Implementation of the "Key Unwrap" algorithm (alternative indexing-based + * variant) defined in RFC 3394 "Advanced Encryption Standard (AES) Key + * Wrap Algorithm" paragraph 2.2.2, artificially tailored to a + * key-encryption-key (KEK) size of 256 bits and a key (key data) size of + * 256 bits. + */ + void unwrap_key(unsigned char *key_plaintext_uint8, + Genode::size_t key_plaintext_size, + bool &key_plaintext_corrupt, + unsigned char const *ciphertext_uint8, + Genode::size_t ciphertext_size, + unsigned char const *key_encryption_key_uint8, + Genode::size_t key_encryption_key_size); +} + +#endif /* _AES_256_H_ */ diff --git a/repos/gems/src/lib/vfs/cbe_trust_anchor/integer.cc b/repos/gems/src/lib/vfs/cbe_trust_anchor/integer.cc new file mode 100644 index 0000000000..85fc63efd3 --- /dev/null +++ b/repos/gems/src/lib/vfs/cbe_trust_anchor/integer.cc @@ -0,0 +1,28 @@ +/* + * \brief Helper functions for modifying integer values + * \author Martin Stein + * \date 2021-04-16 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* local includes */ +#include + +using namespace Genode; + + +uint64_t Integer::u64_swap_byte_order(uint64_t input) +{ + uint64_t result { 0 }; + for (unsigned byte_idx = 0; byte_idx < 8; byte_idx++) { + uint8_t const byte { (uint8_t)(input >> (byte_idx * 8)) }; + result |= (uint64_t)byte << ((7 - byte_idx) * 8); + } + return result; +} diff --git a/repos/gems/src/lib/vfs/cbe_trust_anchor/integer.h b/repos/gems/src/lib/vfs/cbe_trust_anchor/integer.h new file mode 100644 index 0000000000..1547c42bdd --- /dev/null +++ b/repos/gems/src/lib/vfs/cbe_trust_anchor/integer.h @@ -0,0 +1,25 @@ +/* + * \brief Helper functions for modifying integer values + * \author Martin Stein + * \date 2021-04-16 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INTEGER_H_ +#define _INTEGER_H_ + +/* Genode includes */ +#include + +namespace Integer { + + Genode::uint64_t u64_swap_byte_order(Genode::uint64_t input); +} + +#endif /* _INTEGER_H_ */ diff --git a/repos/gems/src/lib/vfs/cbe_trust_anchor/vfs.cc b/repos/gems/src/lib/vfs/cbe_trust_anchor/vfs.cc index 9deaf20578..179c0d62da 100644 --- a/repos/gems/src/lib/vfs/cbe_trust_anchor/vfs.cc +++ b/repos/gems/src/lib/vfs/cbe_trust_anchor/vfs.cc @@ -20,89 +20,15 @@ /* OpenSSL includes */ #include -#include /* CBE includes */ #include -namespace Aes_cbc -{ - struct Iv - { - unsigned char values[16]; - }; - - void encrypt_without_iv(unsigned char *ciphertext_base, - size_t ciphertext_size, - unsigned char const *plaintext_base, - unsigned char const *key_base, - size_t key_size); - - void decrypt_without_iv(unsigned char *plaintext_base, - size_t plaintext_size, - unsigned char const *ciphertext_base, - unsigned char const *key_base, - size_t key_size); -} - - -/** - * Clean up crypto relevant data which would stay on the stack otherwise - */ -template -static void inline cleanup_crypto_data(T &t, S &s) -{ - Genode::memset(&t, 0, sizeof(t)); - Genode::memset(&s, 0, sizeof(s)); - - /* trigger compiler to not drop the memsets */ - asm volatile(""::"r"(&t),"r"(&s):"memory"); -} - - -void Aes_cbc::encrypt_without_iv(unsigned char *ciphertext_base, - size_t ciphertext_size, - unsigned char const *plaintext_base, - unsigned char const *key_base, - size_t key_size) -{ - AES_KEY aes_key; - if (AES_set_encrypt_key(key_base, key_size * 8, &aes_key)) { - class Failed_to_set_key { }; - throw Failed_to_set_key { }; - } - Aes_cbc::Iv iv { }; - Genode::memset(iv.values, 0, sizeof(iv.values)); - AES_cbc_encrypt( - plaintext_base, ciphertext_base, ciphertext_size, &aes_key, iv.values, - AES_ENCRYPT); - - cleanup_crypto_data(aes_key, iv); -} - - -void Aes_cbc::decrypt_without_iv(unsigned char *plaintext_base, - size_t plaintext_size, - unsigned char const *ciphertext_base, - unsigned char const *key_base, - size_t key_size) -{ - AES_KEY aes_key; - if (AES_set_decrypt_key(key_base, key_size * 8, &aes_key)) { - class Failed_to_set_key { }; - throw Failed_to_set_key { }; - } - Aes_cbc::Iv iv { }; - Genode::memset(iv.values, 0, sizeof(iv.values)); - AES_cbc_encrypt( - ciphertext_base, plaintext_base, plaintext_size, &aes_key, iv.values, - AES_DECRYPT); - - cleanup_crypto_data(aes_key, iv); -} - +/* local includes */ +#include enum { PRIVATE_KEY_SIZE = 32 }; +enum { PASSPHRASE_HASH_SIZE = 32 }; namespace Vfs_cbe_trust_anchor { @@ -215,7 +141,7 @@ class Trust_anchor Genode::memcpy( key_plaintext.value, _encrypt_key.value, Key::KEY_LEN); - Aes_cbc::encrypt_without_iv( + Aes_256::encrypt_with_zeroed_iv( _encrypt_key.value, Key::KEY_LEN, key_plaintext.value, @@ -247,7 +173,7 @@ class Trust_anchor Genode::memcpy( key_ciphertext.value, _decrypt_key.value, Key::KEY_LEN); - Aes_cbc::decrypt_without_iv( + Aes_256::decrypt_with_zeroed_iv( _decrypt_key.value, Key::KEY_LEN, key_ciphertext.value, @@ -336,21 +262,36 @@ class Trust_anchor if (!_read_key_file_finished()) { break; } - if (_key_io_job_buffer.size == PRIVATE_KEY_SIZE) { + if (_key_io_job_buffer.size == Aes_256_key_wrap::CIPHERTEXT_SIZE) { - Aes_cbc::decrypt_without_iv( + bool private_key_corrupt; + Aes_256_key_wrap::unwrap_key( _private_key.value, - PRIVATE_KEY_SIZE, + sizeof(_private_key.value), + private_key_corrupt, (unsigned char *)_key_io_job_buffer.base, + _key_io_job_buffer.size, (unsigned char *)_passphrase_hash_buffer.base, _passphrase_hash_buffer.size); - _job_state = Job_state::COMPLETE; - _job_success = true; + if (private_key_corrupt) { + + Genode::error("failed to unwrap the private key"); + _job_success = false; + + } else { + + _job_success = true; + } + _job_state = Job_state::COMPLETE; progress = true; } else { + Genode::error( + "content read from file 'encrypted_private_key' " + "has unexpected size"); + _job_state = Job_state::COMPLETE; _job_success = false; progress = true; @@ -396,11 +337,12 @@ class Trust_anchor _private_key_io_job_buffer.base, _private_key_io_job_buffer.size); - _key_io_job_buffer.size = PRIVATE_KEY_SIZE; - Aes_cbc::encrypt_without_iv( + _key_io_job_buffer.size = Aes_256_key_wrap::CIPHERTEXT_SIZE; + Aes_256_key_wrap::wrap_key( (unsigned char *)_key_io_job_buffer.base, _key_io_job_buffer.size, (unsigned char *)_private_key_io_job_buffer.base, + _private_key_io_job_buffer.size, (unsigned char *)_passphrase_hash_buffer.base, _passphrase_hash_buffer.size); @@ -643,7 +585,7 @@ class Trust_anchor struct Key_io_job_buffer : Util::Io_job::Buffer { - char buffer[PRIVATE_KEY_SIZE] { }; + char buffer[Aes_256_key_wrap::CIPHERTEXT_SIZE] { }; Key_io_job_buffer() { @@ -652,8 +594,19 @@ class Trust_anchor } }; - Key_io_job_buffer _key_io_job_buffer { }; - Key_io_job_buffer _passphrase_hash_buffer { }; + struct Passphrase_hash_buffer : Util::Io_job::Buffer + { + char buffer[PASSPHRASE_HASH_SIZE] { }; + + Passphrase_hash_buffer() + { + Buffer::base = buffer; + Buffer::size = sizeof (buffer); + } + }; + + Key_io_job_buffer _key_io_job_buffer { }; + Passphrase_hash_buffer _passphrase_hash_buffer { }; bool _check_key_file(Path const &path) {