diff --git a/include/ccf/crypto/sha256_hash.h b/include/ccf/crypto/sha256_hash.h index d7c2d23e2c81..bbf643c9e418 100644 --- a/include/ccf/crypto/sha256_hash.h +++ b/include/ccf/crypto/sha256_hash.h @@ -6,6 +6,8 @@ #include #include +#define FMT_HEADER_ONLY +#include namespace crypto { diff --git a/include/ccf/crypto/symmetric_key.h b/include/ccf/crypto/symmetric_key.h index 89ed00d5c79f..c32a03af536d 100644 --- a/include/ccf/crypto/symmetric_key.h +++ b/include/ccf/crypto/symmetric_key.h @@ -2,149 +2,68 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "ccf/crypto/entropy.h" #include "ccf/ds/buffer.h" -#include "ds/serialized.h" -#include "ds/thread_messaging.h" + +#include namespace crypto { - constexpr size_t GCM_SIZE_KEY = 32; + constexpr size_t GCM_DEFAULT_KEY_SIZE = 32; + constexpr size_t GCM_SIZE_TAG = 16; - constexpr size_t GCM_SIZE_IV = 12; - template struct GcmHeader { uint8_t tag[GCM_SIZE_TAG] = {}; - uint8_t iv[SIZE_IV] = {}; - - // 12 bytes IV with 8 LSB are unique sequence number - // and 4 MSB are 4 LSB of term (with last bit indicating a snapshot) - constexpr static uint8_t IV_DELIMITER = 8; - constexpr static size_t RAW_DATA_SIZE = sizeof(tag) + sizeof(iv); - - GcmHeader() = default; - GcmHeader(const uint8_t* data, size_t size) - { - if (size != RAW_DATA_SIZE) - { - throw std::logic_error("Incompatible IV size"); - } - - memcpy(tag, data, sizeof(tag)); - memcpy(iv, data + sizeof(tag), sizeof(iv)); - } - - GcmHeader(const std::vector& data) : - GcmHeader(data.data(), data.size()) - {} - - void set_iv_seq(uint64_t seq) - { - *reinterpret_cast(iv) = seq; - } - - void set_iv_term(uint64_t term) - { - if (term > 0x7FFFFFFF) - { - throw std::logic_error(fmt::format( - "term should fit in 31 bits of IV. Value is: 0x{0:x}", term)); - } - - *reinterpret_cast(iv + IV_DELIMITER) = - static_cast(term); - } - - uint64_t get_term() const - { - return *reinterpret_cast(iv + IV_DELIMITER); - } - - void set_iv_snapshot(bool is_snapshot) - { - // Set very last bit in IV - iv[SIZE_IV - 1] |= (is_snapshot << ((sizeof(uint8_t) * 8) - 1)); - } - void set_iv(uint8_t* iv_, size_t size) - { - if (size != SIZE_IV) - { - throw std::logic_error( - fmt::format("Specified IV is not of size {}", SIZE_IV)); - } + // Size does not change after construction + std::vector iv; - memcpy(iv, iv_, size); - } + GcmHeader(size_t iv_size); - CBuffer get_iv() const - { - return {iv, SIZE_IV}; - } + void set_iv(const uint8_t* data, size_t size); + CBuffer get_iv() const; - uint64_t get_iv_int() const - { - return *reinterpret_cast(iv); - } + size_t serialised_size() const; + std::vector serialise(); - std::vector serialise() - { - auto space = RAW_DATA_SIZE; - std::vector serial_hdr(space); + void deserialise(const std::vector& ser); + void deserialise(const uint8_t*& data, size_t& size); + }; - auto data_ = serial_hdr.data(); - serialized::write(data_, space, tag, sizeof(tag)); - serialized::write(data_, space, iv, sizeof(iv)); + template + struct FixedSizeGcmHeader : public GcmHeader + { + static constexpr size_t IV_SIZE = IV_BYTES; - return serial_hdr; - } + FixedSizeGcmHeader() : GcmHeader(IV_SIZE) {} - void deserialise(const std::vector& ser) + static size_t serialised_size() { - auto data = ser.data(); - auto size = ser.size(); - - deserialise(data, size); + return GCM_SIZE_TAG + IV_SIZE; } - void deserialise(const uint8_t*& data, size_t& size) + void set_random_iv(EntropyPtr entropy = crypto::create_entropy()) { - memcpy( - tag, serialized::read(data, size, GCM_SIZE_TAG).data(), GCM_SIZE_TAG); - memcpy(iv, serialized::read(data, size, SIZE_IV).data(), SIZE_IV); + iv = entropy->random(IV_SIZE); } }; + // GcmHeader with 12-byte (96-bit) IV + using StandardGcmHeader = FixedSizeGcmHeader<12>; + struct GcmCipher { - GcmHeader<> hdr; + StandardGcmHeader hdr; std::vector cipher; - GcmCipher() {} - GcmCipher(size_t size) : cipher(size) {} - - std::vector serialise() - { - std::vector serial; - auto space = GcmHeader<>::RAW_DATA_SIZE + cipher.size(); - serial.resize(space); + GcmCipher(); + GcmCipher(size_t size); - auto data_ = serial.data(); - serialized::write(data_, space, hdr.tag, sizeof(hdr.tag)); - serialized::write(data_, space, hdr.iv, sizeof(hdr.iv)); - serialized::write(data_, space, cipher.data(), cipher.size()); + std::vector serialise(); - return serial; - } - - void deserialise(const std::vector& serial) - { - auto size = serial.size(); - auto data_ = serial.data(); - hdr = serialized::read(data_, size, GcmHeader<>::RAW_DATA_SIZE); - cipher = serialized::read(data_, size, size); - } + void deserialise(const std::vector& serial); }; class KeyAesGcm @@ -169,6 +88,7 @@ namespace crypto CBuffer aad, uint8_t* plain) const = 0; + // Key size in bits virtual size_t key_size() const = 0; }; @@ -180,7 +100,7 @@ namespace crypto inline void check_supported_aes_key_size(size_t num_bits) { if (num_bits != 128 && num_bits != 192 && num_bits != 256) - throw std::runtime_error("unsupported key size"); + throw std::runtime_error("Unsupported key size"); } /** Default initialization vector for AES-GCM (12 zeroes) */ diff --git a/src/crypto/symmetric_key.cpp b/src/crypto/symmetric_key.cpp index 16098b546822..6a90ea7c11a4 100644 --- a/src/crypto/symmetric_key.cpp +++ b/src/crypto/symmetric_key.cpp @@ -5,9 +5,95 @@ #include "ccf/crypto/rsa_key_pair.h" #include "ccf/crypto/symmetric_key.h" +#include "ds/serialized.h" + +#define FMT_HEADER_ONLY +#include namespace crypto { + /// GcmHeader implementation + GcmHeader::GcmHeader(size_t iv_size) + { + iv.resize(iv_size); + } + + void GcmHeader::set_iv(const uint8_t* data, size_t size) + { + if (size != iv.size()) + { + throw std::logic_error( + fmt::format("Specified IV is not of size {}", iv.size())); + } + + memcpy(iv.data(), data, size); + } + + CBuffer GcmHeader::get_iv() const + { + return {iv.data(), iv.size()}; + } + + size_t GcmHeader::serialised_size() const + { + return sizeof(tag) + iv.size(); + } + + std::vector GcmHeader::serialise() + { + auto space = serialised_size(); + std::vector serial_hdr(space); + + auto data_ = serial_hdr.data(); + serialized::write(data_, space, tag, sizeof(tag)); + serialized::write(data_, space, iv.data(), iv.size()); + + return serial_hdr; + } + + void GcmHeader::deserialise(const std::vector& ser) + { + auto data = ser.data(); + auto size = ser.size(); + + deserialise(data, size); + } + + void GcmHeader::deserialise(const uint8_t*& data, size_t& size) + { + memcpy( + tag, serialized::read(data, size, GCM_SIZE_TAG).data(), GCM_SIZE_TAG); + iv = serialized::read(data, size, iv.size()); + } + + /// GcmCipher implementation + GcmCipher::GcmCipher() = default; + + GcmCipher::GcmCipher(size_t size) : cipher(size) {} + + std::vector GcmCipher::serialise() + { + std::vector serial; + auto space = hdr.serialised_size() + cipher.size(); + serial.resize(space); + + auto data_ = serial.data(); + serialized::write(data_, space, hdr.tag, sizeof(hdr.tag)); + serialized::write(data_, space, hdr.iv.data(), hdr.iv.size()); + serialized::write(data_, space, cipher.data(), cipher.size()); + + return serial; + } + + void GcmCipher::deserialise(const std::vector& serial) + { + auto data = serial.data(); + auto size = serial.size(); + hdr.deserialise(data, size); + cipher = serialized::read(data, size, size); + } + + /// Free function implementation std::unique_ptr make_key_aes_gcm(CBuffer rawKey) { return std::make_unique(rawKey); diff --git a/src/crypto/test/bench.cpp b/src/crypto/test/bench.cpp index ad117d9cbaeb..c04deee5e2e6 100644 --- a/src/crypto/test/bench.cpp +++ b/src/crypto/test/bench.cpp @@ -96,7 +96,8 @@ template static void benchmark_hmac(picobench::state& s) { const auto contents = make_contents(); - const auto key = crypto::create_entropy()->random(crypto::GCM_SIZE_KEY); + const auto key = + crypto::create_entropy()->random(crypto::GCM_DEFAULT_KEY_SIZE); s.start_timer(); for (auto _ : s) diff --git a/src/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index 2fa484320a53..94886cdd79c6 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -425,7 +425,7 @@ TEST_CASE("Create sign and verify certificates") } while (corrupt_csr); } -static const vector& getRawKey() +static const vector& get_raw_key() { static const vector v(16, '$'); return v; @@ -433,23 +433,32 @@ static const vector& getRawKey() TEST_CASE("ExtendedIv0") { - auto k = crypto::make_key_aes_gcm(getRawKey()); + auto k = crypto::make_key_aes_gcm(get_raw_key()); // setup plain text unsigned char rawP[100]; memset(rawP, 'x', sizeof(rawP)); Buffer p{rawP, sizeof(rawP)}; + // test large IV - GcmHeader<1234> h; + using LargeIVGcmHeader = FixedSizeGcmHeader<1234>; + LargeIVGcmHeader h; + + SUBCASE("Null IV") {} + + SUBCASE("Random IV") + { + h.set_random_iv(); + } + k->encrypt(h.get_iv(), p, nullb, p.p, h.tag); - auto k2 = crypto::make_key_aes_gcm(getRawKey()); + auto k2 = crypto::make_key_aes_gcm(get_raw_key()); REQUIRE(k2->decrypt(h.get_iv(), h.tag, p, nullb, p.p)); } TEST_CASE("AES Key wrap with padding") { - auto key = getRawKey(); - GcmHeader<1234> h; + auto key = get_raw_key(); std::vector aad(123, 'y'); std::vector key_to_wrap = create_entropy()->random(997); @@ -465,7 +474,7 @@ TEST_CASE("AES Key wrap with padding") TEST_CASE("CKM_RSA_PKCS_OAEP") { - auto key = getRawKey(); + auto key = get_raw_key(); auto rsa_kp = make_rsa_key_pair(); auto rsa_pk = make_rsa_public_key(rsa_kp->public_key_pem()); @@ -499,7 +508,7 @@ TEST_CASE("CKM_RSA_AES_KEY_WRAP") TEST_CASE("AES-GCM convenience functions") { EntropyPtr entropy = create_entropy(); - std::vector key = entropy->random(GCM_SIZE_KEY); + std::vector key = entropy->random(GCM_DEFAULT_KEY_SIZE); auto encrypted = aes_gcm_encrypt(key, contents); auto decrypted = aes_gcm_decrypt(key, encrypted); REQUIRE(decrypted == contents); diff --git a/src/indexing/enclave_lfs_access.h b/src/indexing/enclave_lfs_access.h index 8b4716b8c77b..83a29344fc9f 100644 --- a/src/indexing/enclave_lfs_access.h +++ b/src/indexing/enclave_lfs_access.h @@ -107,8 +107,7 @@ namespace ccf::indexing crypto::GcmCipher gcm(contents.size()); // Use a random IV for each call - auto iv = entropy_src->random(crypto::GCM_SIZE_IV); - gcm.hdr.set_iv(iv.data(), iv.size()); + gcm.hdr.set_random_iv(); encryption_key->encrypt( gcm.hdr.get_iv(), contents, nullb, gcm.cipher.data(), gcm.hdr.tag); @@ -127,8 +126,8 @@ namespace ccf::indexing { // Generate a fresh random key. Only this specific instance, in this // enclave, can read these files! - encryption_key = - crypto::make_key_aes_gcm(entropy_src->random(crypto::GCM_SIZE_KEY)); + encryption_key = crypto::make_key_aes_gcm( + entropy_src->random(crypto::GCM_DEFAULT_KEY_SIZE)); } void register_message_handlers( diff --git a/src/kv/encryptor.h b/src/kv/encryptor.h index f84796d7d86e..4262e92e2b42 100644 --- a/src/kv/encryptor.h +++ b/src/kv/encryptor.h @@ -30,7 +30,10 @@ namespace kv hdr.set_iv_seq(tx_id.version); hdr.set_iv_term(tx_id.term); - hdr.set_iv_snapshot(entry_type == EntryType::Snapshot); + if (entry_type == EntryType::Snapshot) + { + hdr.set_iv_is_snapshot(); + } } public: @@ -38,12 +41,14 @@ namespace kv size_t get_header_length() override { - return S::RAW_DATA_SIZE; + return S::serialised_size(); } uint64_t get_term(const uint8_t* data, size_t size) override { - return S(data, size).get_term(); + S s; + s.deserialise(data, size); + return s.get_term(); } /** diff --git a/src/node/channels.h b/src/node/channels.h index 6d427b53daac..4492365ec0d3 100644 --- a/src/node/channels.h +++ b/src/node/channels.h @@ -13,6 +13,7 @@ #include "ds/hex.h" #include "ds/serialized.h" #include "ds/state_machine.h" +#include "ds/thread_messaging.h" #include "enclave/enclave_time.h" #include "entities.h" #include "node_types.h" @@ -36,7 +37,7 @@ namespace ccf { using SendNonce = uint64_t; - using GcmHdr = crypto::GcmHeader; + using GcmHdr = crypto::FixedSizeGcmHeader; struct RecvNonce { @@ -59,7 +60,7 @@ namespace ccf static inline RecvNonce get_nonce(const GcmHdr& header) { - return RecvNonce(header.get_iv_int()); + return *reinterpret_cast(header.iv.data()); } enum ChannelStatus @@ -193,7 +194,7 @@ namespace ccf { status.expect(ESTABLISHED); - RecvNonce recv_nonce(header.get_iv_int()); + auto recv_nonce = get_nonce(header); auto tid = recv_nonce.tid; assert(tid < threading::ThreadMessaging::max_num_threads); @@ -847,7 +848,8 @@ namespace ccf (size_t)nonce.nonce); GcmHdr gcm_hdr; - gcm_hdr.set_iv_seq(nonce.get_val()); + const auto nonce_n = nonce.get_val(); + gcm_hdr.set_iv((const uint8_t*)&nonce_n, sizeof(nonce_n)); std::vector cipher(plain.n); assert(send_key); @@ -918,10 +920,10 @@ namespace ccf const uint8_t* data_ = data; size_t size_ = size; - serialized::skip(data_, size_, (size_ - sizeof(GcmHdr))); GcmHdr hdr; + serialized::skip(data_, size_, (size_ - hdr.serialised_size())); hdr.deserialise(data_, size_); - size -= sizeof(GcmHdr); + size -= hdr.serialised_size(); if (!verify_or_decrypt(hdr, {data, size})) { diff --git a/src/node/encryptor.h b/src/node/encryptor.h index fe11cf510dd4..81ec71f6da63 100644 --- a/src/node/encryptor.h +++ b/src/node/encryptor.h @@ -8,6 +8,44 @@ namespace ccf { - using NodeEncryptor = - kv::TxEncryptor>; + // Extends 12-byte IV GcmHeader with interpretation of those bytes as term, + // seqno, and snapshot indicator: + // - 8 LSB are unique sequence number + // - 4 MSB (except final bit) are the 4 LSB of term + // - Final bit indicates a snapshot + struct TxGcmHeader : public crypto::StandardGcmHeader + { + using crypto::StandardGcmHeader::StandardGcmHeader; + constexpr static uint8_t IV_DELIMITER = 8; + + void set_iv_seq(uint64_t seq) + { + *reinterpret_cast(iv.data()) = seq; + } + + void set_iv_term(uint64_t term) + { + if (term > 0x7FFFFFFF) + { + throw std::logic_error(fmt::format( + "term should fit in 31 bits of IV. Value is: 0x{0:x}", term)); + } + + *reinterpret_cast(iv.data() + IV_DELIMITER) = + static_cast(term); + } + + uint64_t get_term() const + { + return *reinterpret_cast(iv.data() + IV_DELIMITER); + } + + void set_iv_is_snapshot() + { + // Set very last bit in IV + iv.back() |= (1 << ((sizeof(uint8_t) * 8) - 1)); + } + }; + + using NodeEncryptor = kv::TxEncryptor; } \ No newline at end of file diff --git a/src/node/ledger_secret.h b/src/node/ledger_secret.h index e9b86d9af5b7..d56b8689d437 100644 --- a/src/node/ledger_secret.h +++ b/src/node/ledger_secret.h @@ -76,7 +76,7 @@ namespace ccf inline LedgerSecretPtr make_ledger_secret() { return std::make_shared( - crypto::create_entropy()->random(crypto::GCM_SIZE_KEY)); + crypto::create_entropy()->random(crypto::GCM_DEFAULT_KEY_SIZE)); } inline std::vector decrypt_previous_ledger_secret_raw( diff --git a/src/node/share_manager.h b/src/node/share_manager.h index 6c7ac90daa90..a08fd2e31ce2 100644 --- a/src/node/share_manager.h +++ b/src/node/share_manager.h @@ -20,7 +20,7 @@ namespace ccf class LedgerSecretWrappingKey { private: - static constexpr auto KZ_KEY_SIZE = crypto::GCM_SIZE_KEY; + static constexpr auto KZ_KEY_SIZE = crypto::GCM_DEFAULT_KEY_SIZE; std::vector data; // Referred to as "kz" in TR bool has_wrapped = false; @@ -195,8 +195,7 @@ namespace ccf crypto::GcmCipher encrypted_previous_ls( previous_ledger_secret->second->raw_key.size()); - auto iv = crypto::create_entropy()->random(crypto::GCM_SIZE_IV); - encrypted_previous_ls.hdr.set_iv(iv.data(), iv.size()); + encrypted_previous_ls.hdr.set_random_iv(); latest_ledger_secret->key->encrypt( encrypted_previous_ls.hdr.get_iv(), @@ -226,8 +225,7 @@ namespace ccf // Submitted recovery shares are encrypted with the latest ledger secret. crypto::GcmCipher encrypted_submitted_share(submitted_share.size()); - auto iv = crypto::create_entropy()->random(crypto::GCM_SIZE_IV); - encrypted_submitted_share.hdr.set_iv(iv.data(), iv.size()); + encrypted_submitted_share.hdr.set_random_iv(); current_ledger_secret->key->encrypt( encrypted_submitted_share.hdr.get_iv(), diff --git a/src/node/test/channels.cpp b/src/node/test/channels.cpp index bd3df0d20258..15ce4d1b7a9a 100644 --- a/src/node/test/channels.cpp +++ b/src/node/test/channels.cpp @@ -305,7 +305,7 @@ TEST_CASE("Client/Server key exchange") initiator_signature = msgs[0].data(); auto md = msgs[1].data(); - REQUIRE(md.size() == msg.size() + sizeof(GcmHdr)); + REQUIRE(md.size() == msg.size() + GcmHdr::serialised_size()); REQUIRE(memcmp(md.data(), msg.data(), msg.size()) == 0); queued_msg = msgs[1]; // save for later