From f41dec67e1975173d184f677551ae3beb9491553 Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Sat, 10 Apr 2021 12:18:22 +0200 Subject: [PATCH] vfs/cbe_trust_anchor: use secure private key A private key of 256 bits is generated pseudo-randomly using the jitterentropy VFS plugin on initialization. The private key is stored in the key file encrypted via AES256 using the SHA256 hash of the users passphrase. When unlocking the CBE device, the encrypted private key is read from the key file and decrypted with the hash of the users passphrase. Ref #4032 --- .../gems/src/lib/vfs/cbe_trust_anchor/vfs.cc | 229 ++++++++++++++++-- 1 file changed, 203 insertions(+), 26 deletions(-) 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 f7a3bb72f8..6178048df6 100644 --- a/repos/gems/src/lib/vfs/cbe_trust_anchor/vfs.cc +++ b/repos/gems/src/lib/vfs/cbe_trust_anchor/vfs.cc @@ -20,10 +20,90 @@ /* 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); +} + + +enum { PRIVATE_KEY_SIZE = 32 }; + static void xor_bytes(unsigned char const *p, int p_len, unsigned char *k, int k_len) @@ -96,15 +176,24 @@ class Trust_anchor }; Job _job { Job::NONE }; - enum class Job_state { NONE, PENDING, IN_PROGRESS, FINAL_SYNC, COMPLETE }; + enum class Job_state + { + NONE, + INIT_READ_JITTERENTROPY_PENDING, + INIT_READ_JITTERENTROPY_IN_PROGRESS, + PENDING, + IN_PROGRESS, + FINAL_SYNC, + COMPLETE + }; + Job_state _job_state { Job_state::NONE }; bool _job_success { false }; struct Private_key { - enum { KEY_LEN = 32 }; - unsigned char value[KEY_LEN] { }; + unsigned char value[PRIVATE_KEY_SIZE] { }; }; Private_key _private_key { }; @@ -128,7 +217,7 @@ class Trust_anchor void _xcrypt_key(Private_key const &priv_key, Key &key) { - xor_bytes(priv_key.value, (int)Private_key::KEY_LEN, + xor_bytes(priv_key.value, (int)PRIVATE_KEY_SIZE, key.value, (int)Key::KEY_LEN); } @@ -218,18 +307,14 @@ class Trust_anchor if (!_read_key_file_finished()) { break; } + if (_key_io_job_buffer.size == PRIVATE_KEY_SIZE) { - if (_key_io_job_buffer.size == _passphrase_hash_buffer.size && - Genode::memcmp(_key_io_job_buffer.base, - _passphrase_hash_buffer.base, - _passphrase_hash_buffer.size) == 0) { - - Genode::memset(_private_key.value, 0xa5, - sizeof (_private_key.value)); - - Genode::memcpy(_private_key.value, - _key_io_job_buffer.buffer, - _key_io_job_buffer.size); + Aes_cbc::decrypt_without_iv( + _private_key.value, + PRIVATE_KEY_SIZE, + (unsigned char *)_key_io_job_buffer.base, + (unsigned char *)_passphrase_hash_buffer.base, + _passphrase_hash_buffer.size); _job_state = Job_state::COMPLETE; _job_success = true; @@ -259,6 +344,41 @@ class Trust_anchor bool progress = false; switch (_job_state) { + case Job_state::INIT_READ_JITTERENTROPY_PENDING: + { + if (!_open_private_key_file_and_queue_read()) { + break; + } + _job_state = Job_state::INIT_READ_JITTERENTROPY_IN_PROGRESS; + progress = true; + } + [[fallthrough]]; + case Job_state::INIT_READ_JITTERENTROPY_IN_PROGRESS: + { + if (!_read_private_key_file_finished()) { + break; + } + if (_private_key_io_job_buffer.size != (size_t)PRIVATE_KEY_SIZE) { + class Bad_private_key_io_buffer_size { }; + throw Bad_private_key_io_buffer_size { }; + } + Genode::memcpy( + _private_key.value, + _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( + (unsigned char *)_key_io_job_buffer.base, + _key_io_job_buffer.size, + (unsigned char *)_private_key_io_job_buffer.base, + (unsigned char *)_passphrase_hash_buffer.base, + _passphrase_hash_buffer.size); + + _job_state = Job_state::PENDING; + progress = true; + } + [[fallthrough]]; case Job_state::PENDING: { if (!_open_key_file_and_write(_base_path)) { @@ -267,13 +387,6 @@ class Trust_anchor return true; } - /* copy passphrase to key object */ - size_t const key_len = - Genode::min(_key_io_job_buffer.size, - sizeof (_private_key.value)); - Genode::memset(_private_key.value, 0xa5, sizeof (_private_key.value)); - Genode::memcpy(_private_key.value, _key_io_job_buffer.buffer, key_len); - _job_state = Job_state::IN_PROGRESS; progress |= true; } @@ -478,6 +591,22 @@ class Trust_anchor Genode::Constructible _jitterentropy_io_job { }; Jitterentropy_io_job_buffer _jitterentropy_io_job_buffer { }; + + struct Private_key_io_job_buffer : Util::Io_job::Buffer + { + char buffer[PRIVATE_KEY_SIZE] { }; + + Private_key_io_job_buffer() + { + Buffer::base = buffer; + Buffer::size = sizeof (buffer); + } + }; + + Vfs::Vfs_handle *_private_key_handle { nullptr }; + Genode::Constructible _private_key_io_job { }; + Private_key_io_job_buffer _private_key_io_job_buffer { }; + /* key */ Vfs::Vfs_handle *_key_handle { nullptr }; @@ -485,7 +614,7 @@ class Trust_anchor struct Key_io_job_buffer : Util::Io_job::Buffer { - char buffer[SHA256_DIGEST_LENGTH] { }; + char buffer[PRIVATE_KEY_SIZE] { }; Key_io_job_buffer() { @@ -517,6 +646,35 @@ class Trust_anchor return false; } + bool _open_private_key_file_and_queue_read() + { + Path file_path = "/dev/jitterentropy"; + using Result = Vfs::Directory_service::Open_result; + + Result const res = + _vfs_env.root_dir().open(file_path.string(), + Vfs::Directory_service::OPEN_MODE_RDONLY, + (Vfs::Vfs_handle **)&_private_key_handle, + _vfs_env.alloc()); + if (res != Result::OPEN_OK) { + Genode::error("could not open '", file_path.string(), "'"); + return false; + } + + _private_key_handle->handler(&_io_response_handler); + _private_key_io_job.construct(*_private_key_handle, Util::Io_job::Operation::READ, + _private_key_io_job_buffer, 0, + Util::Io_job::Partial_result::ALLOW); + + if (_private_key_io_job->execute() && _private_key_io_job->completed()) { + _close_handle(&_private_key_handle); + _private_key_io_job_buffer.size = _private_key_io_job->current_offset(); + _private_key_io_job.destruct(); + return true; + } + return true; + } + bool _open_jitterentropy_file_and_queue_read() { Path file_path = "/dev/jitterentropy"; @@ -575,6 +733,25 @@ class Trust_anchor return true; } + bool _read_private_key_file_finished() + { + if (!_private_key_io_job.constructed()) { + return true; + } + + // XXX trigger sync + + bool const progress = _private_key_io_job->execute(); + bool const completed = _private_key_io_job->completed(); + if (completed) { + _close_handle(&_private_key_handle); + _private_key_io_job_buffer.size = _private_key_io_job->current_offset(); + _private_key_io_job.destruct(); + } + + return progress && completed; + } + bool _read_jitterentropy_file_finished() { if (!_jitterentropy_io_job.constructed()) { @@ -852,12 +1029,12 @@ class Trust_anchor return false; } SHA256((unsigned char const *)src, len, - (unsigned char *)_key_io_job_buffer.base); + (unsigned char *)_passphrase_hash_buffer.base); - _key_io_job_buffer.size = SHA256_DIGEST_LENGTH; + _passphrase_hash_buffer.size = SHA256_DIGEST_LENGTH; _job = Job::INIT; - _job_state = Job_state::PENDING; + _job_state = Job_state::INIT_READ_JITTERENTROPY_PENDING; return true; }