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
This commit is contained in:
Martin Stein 2021-04-17 13:25:40 +02:00 committed by Norman Feske
parent 94701eec09
commit a661aa79de
6 changed files with 395 additions and 90 deletions

View File

@ -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

View File

@ -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 <util/string.h>
/* OpenSSL includes */
namespace Openssl {
#include <openssl/aes.h>
}
/* local includes */
#include <aes_256.h>
#include <integer.h>
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 <typename OBJECT_TYPE>
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;
}
}

View File

@ -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 <base/stdint.h>
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_ */

View File

@ -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 <integer.h>
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;
}

View File

@ -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 <base/stdint.h>
namespace Integer {
Genode::uint64_t u64_swap_byte_order(Genode::uint64_t input);
}
#endif /* _INTEGER_H_ */

View File

@ -20,89 +20,15 @@
/* OpenSSL includes */
#include <openssl/sha.h>
#include <openssl/aes.h>
/* CBE includes */
#include <cbe/vfs/io_job.h>
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 <typename T, typename S>
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 <aes_256.h>
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)
{