From 1bdab9cb7e53caa5bd896930d23bd3b11c4c84aa Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 10:32:43 -1000 Subject: [PATCH 1/8] Keep pre-warmed encryption state between operations Addresses #55 by caching cipher contexts to avoid redundant key schedule computation on every seal/open call. Changes: - Add CipherState class to crypto.h with RAII-managed cipher handle - KeyRecord now holds a std::unique_ptr for cached state - All three backends (OpenSSL 3.x, OpenSSL 1.1, BoringSSL) implement: - GCM suites: EVP_CIPHER_CTX cached and reset with new nonce only - CTR+HMAC suites: Both AES-CTR and HMAC contexts cached The key optimization is that EVP_EncryptInit_ex/EVP_DecryptInit_ex with nullptr for cipher preserves the key schedule while updating the nonce. Similarly, HMAC_Init_ex with nullptr key preserves the HMAC key state. Co-Authored-By: Claude Opus 4.5 --- include/sframe/sframe.h | 22 +- src/crypto.h | 37 ++++ src/crypto_boringssl.cpp | 405 +++++++++++++++++++++++++++++++++++++ src/crypto_openssl11.cpp | 406 +++++++++++++++++++++++++++++++++++++ src/crypto_openssl3.cpp | 427 +++++++++++++++++++++++++++++++++++++++ src/sframe.cpp | 48 ++++- test/vectors.cpp | 2 +- 7 files changed, 1341 insertions(+), 6 deletions(-) diff --git a/include/sframe/sframe.h b/include/sframe/sframe.h index dcceb72..9a9856a 100644 --- a/include/sframe/sframe.h +++ b/include/sframe/sframe.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -26,6 +27,9 @@ namespace SFRAME_NAMESPACE { +// Forward declaration for pre-warmed cipher state +struct CipherState; + struct crypto_error : std::runtime_error { crypto_error(); @@ -86,18 +90,32 @@ enum struct KeyUsage struct KeyRecord { + static constexpr size_t max_key_size = 48; + static constexpr size_t max_salt_size = 12; + static KeyRecord from_base_key(CipherSuite suite, KeyID key_id, KeyUsage usage, input_bytes base_key); - static constexpr size_t max_key_size = 48; - static constexpr size_t max_salt_size = 12; + KeyRecord(owned_bytes key, + owned_bytes salt, + KeyUsage usage, + Counter counter, + std::unique_ptr cipher); + ~KeyRecord(); + + KeyRecord(KeyRecord&&) noexcept; + KeyRecord& operator=(KeyRecord&&) noexcept; + + KeyRecord(const KeyRecord&) = delete; + KeyRecord& operator=(const KeyRecord&) = delete; owned_bytes key; owned_bytes salt; KeyUsage usage; Counter counter; + std::unique_ptr cipher; // Pre-warmed cipher state (AEAD only) }; // Context applies the full SFrame transform. It tracks a counter for each key diff --git a/src/crypto.h b/src/crypto.h index eadd4bf..d861eb3 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -2,8 +2,45 @@ #include +#include + namespace SFRAME_NAMESPACE { +/// +/// CipherState - pre-warmed cipher state for efficient repeated operations +/// +/// Holds a pre-initialized cipher context so that expensive key schedule +/// computation happens once at construction, not on every seal/open call. +/// + +struct CipherHandle; + +struct CipherState +{ + static CipherState create_seal(CipherSuite suite, input_bytes key); + static CipherState create_open(CipherSuite suite, input_bytes key); + + output_bytes seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt); + + output_bytes open(input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct); + +private: + struct Deleter + { + void operator()(CipherHandle* h) const; + }; + + std::unique_ptr handle; + + explicit CipherState(CipherHandle* h); +}; + size_t cipher_digest_size(CipherSuite suite); size_t diff --git a/src/crypto_boringssl.cpp b/src/crypto_boringssl.cpp index 4bb6ba3..295af3a 100644 --- a/src/crypto_boringssl.cpp +++ b/src/crypto_boringssl.cpp @@ -11,6 +11,411 @@ namespace SFRAME_NAMESPACE { +/// +/// Forward declarations +/// + +static const EVP_CIPHER* +openssl_cipher(CipherSuite suite); + +static const EVP_MD* +openssl_digest_type(CipherSuite suite); + +static bool +is_ctr_hmac_suite(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return true; + default: + return false; + } +} + +/// +/// CipherState - pre-warmed cipher context +/// + +struct CipherHandle +{ + EVP_CIPHER_CTX* ctx; + HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) + CipherSuite suite; + bool is_seal; + + CipherHandle(CipherSuite suite_in, input_bytes key, bool seal) + : ctx(EVP_CIPHER_CTX_new()) + , hmac_ctx(nullptr) + , suite(suite_in) + , is_seal(seal) + { + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode) + if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + // Initialize HMAC context + hmac_ctx = HMAC_CTX_new(); + if (hmac_ctx == nullptr) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + const auto* md = openssl_digest_type(suite); + if (1 != HMAC_Init_ex(hmac_ctx, auth_key.data(), auth_key.size(), md, nullptr)) { + HMAC_CTX_free(hmac_ctx); + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } else { + // GCM: use full key + if (seal) { + if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } else { + if (1 != EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } + } + } + + ~CipherHandle() + { + if (hmac_ctx != nullptr) { + HMAC_CTX_free(hmac_ctx); + } + if (ctx != nullptr) { + EVP_CIPHER_CTX_free(ctx); + } + } + + CipherHandle(const CipherHandle&) = delete; + CipherHandle& operator=(const CipherHandle&) = delete; +}; + +void +CipherState::Deleter::operator()(CipherHandle* h) const +{ + delete h; +} + +CipherState::CipherState(CipherHandle* h) + : handle(h) +{ +} + +CipherState +CipherState::create_seal(CipherSuite suite, input_bytes key) +{ + return CipherState(new CipherHandle(suite, key, true)); +} + +CipherState +CipherState::create_open(CipherSuite suite, input_bytes key) +{ + return CipherState(new CipherHandle(suite, key, false)); +} + +static output_bytes +seal_ctr_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Pad nonce to 16 bytes for AES-CTR + auto padded_nonce = owned_bytes<16>(0); + padded_nonce.append(nonce); + padded_nonce.resize(16); + + // Reset AES-CTR context with new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + throw crypto_error(); + } + + // Encrypt with AES-CTR + auto inner_ct = ct.subspan(0, pt.size()); + int outlen = 0; + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate( + handle->ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + // Compute HMAC tag using cached context + // Reset HMAC context (key is preserved from init) + if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + throw crypto_error(); + } + + // Build length block + auto len_block = owned_bytes<24>(); + auto len_view = output_bytes(len_block); + encode_uint(aad.size(), len_view.first(8)); + encode_uint(inner_ct.size(), len_view.first(16).last(8)); + encode_uint(tag_size, len_view.last(8)); + + if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + throw crypto_error(); + } + + auto mac_buf = owned_bytes<64>(); + unsigned int mac_size = mac_buf.size(); + if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + std::copy(mac_buf.begin(), mac_buf.begin() + tag_size, tag.begin()); + + return ct.subspan(0, pt.size() + tag_size); +} + +static output_bytes +seal_aead_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != EVP_EncryptUpdate( + handle->ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate( + handle->ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + handle->ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + return ct.subspan(0, pt.size() + tag_size); +} + +output_bytes +CipherState::seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + if (is_ctr_hmac_suite(handle->suite)) { + return seal_ctr_cached(handle.get(), nonce, ct, aad, pt); + } + return seal_aead_cached(handle.get(), nonce, ct, aad, pt); +} + +static output_bytes +open_ctr_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + auto inner_ct_size = ct.size() - tag_size; + if (pt.size() < inner_ct_size) { + throw buffer_too_small_error("Plaintext buffer too small"); + } + + auto inner_ct = ct.subspan(0, inner_ct_size); + auto tag = ct.subspan(inner_ct_size, tag_size); + + // Verify HMAC tag using cached context + // Reset HMAC context (key is preserved from init) + if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + throw crypto_error(); + } + + // Build length block + auto len_block = owned_bytes<24>(); + auto len_view = output_bytes(len_block); + encode_uint(aad.size(), len_view.first(8)); + encode_uint(inner_ct.size(), len_view.first(16).last(8)); + encode_uint(tag_size, len_view.last(8)); + + if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + throw crypto_error(); + } + + auto mac_buf = owned_bytes<64>(); + unsigned int mac_size = mac_buf.size(); + if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + throw crypto_error(); + } + + if (CRYPTO_memcmp(mac_buf.data(), tag.data(), tag_size) != 0) { + throw authentication_error(); + } + + // Decrypt with AES-CTR + // Pad nonce to 16 bytes for AES-CTR + auto padded_nonce = owned_bytes<16>(0); + padded_nonce.append(nonce); + padded_nonce.resize(16); + + // Reset AES-CTR context with new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto inner_ct_size_int = static_cast(inner_ct_size); + if (1 != EVP_EncryptUpdate( + handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + return pt.subspan(0, inner_ct_size); +} + +static output_bytes +open_aead_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + auto inner_ct_size = ct.size() - tag_size; + if (pt.size() < inner_ct_size) { + throw buffer_too_small_error("Plaintext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_DecryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + auto tag = ct.subspan(inner_ct_size, tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + handle->ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + int out_size; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != EVP_DecryptUpdate( + handle->ctx, nullptr, &out_size, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto inner_ct_size_int = static_cast(inner_ct_size); + if (1 != EVP_DecryptUpdate( + handle->ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_DecryptFinal(handle->ctx, nullptr, &out_size)) { + throw authentication_error(); + } + + return pt.subspan(0, inner_ct_size); +} + +output_bytes +CipherState::open(input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + if (is_ctr_hmac_suite(handle->suite)) { + return open_ctr_cached(handle.get(), nonce, pt, aad, ct); + } + return open_aead_cached(handle.get(), nonce, pt, aad, ct); +} + /// /// Convert between native identifiers / errors and OpenSSL ones /// diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index d68e841..cffe2cd 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -9,6 +9,412 @@ namespace SFRAME_NAMESPACE { +/// +/// Forward declarations +/// + +static const EVP_CIPHER* +openssl_cipher(CipherSuite suite); + +static const EVP_MD* +openssl_digest_type(CipherSuite suite); + +static bool +is_ctr_hmac_suite(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return true; + default: + return false; + } +} + +/// +/// CipherState - pre-warmed cipher context +/// + +struct CipherHandle +{ + EVP_CIPHER_CTX* ctx; + HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) + CipherSuite suite; + bool is_seal; + + CipherHandle(CipherSuite suite_in, input_bytes key, bool seal) + : ctx(EVP_CIPHER_CTX_new()) + , hmac_ctx(nullptr) + , suite(suite_in) + , is_seal(seal) + { + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode) + if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + // Initialize HMAC context + hmac_ctx = HMAC_CTX_new(); + if (hmac_ctx == nullptr) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + const auto* md = openssl_digest_type(suite); + auto key_size = static_cast(auth_key.size()); + if (1 != HMAC_Init_ex(hmac_ctx, auth_key.data(), key_size, md, nullptr)) { + HMAC_CTX_free(hmac_ctx); + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } else { + // GCM: use full key + if (seal) { + if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } else { + if (1 != EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } + } + } + + ~CipherHandle() + { + if (hmac_ctx != nullptr) { + HMAC_CTX_free(hmac_ctx); + } + if (ctx != nullptr) { + EVP_CIPHER_CTX_free(ctx); + } + } + + CipherHandle(const CipherHandle&) = delete; + CipherHandle& operator=(const CipherHandle&) = delete; +}; + +void +CipherState::Deleter::operator()(CipherHandle* h) const +{ + delete h; +} + +CipherState::CipherState(CipherHandle* h) + : handle(h) +{ +} + +CipherState +CipherState::create_seal(CipherSuite suite, input_bytes key) +{ + return CipherState(new CipherHandle(suite, key, true)); +} + +CipherState +CipherState::create_open(CipherSuite suite, input_bytes key) +{ + return CipherState(new CipherHandle(suite, key, false)); +} + +static output_bytes +seal_ctr_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Pad nonce to 16 bytes for AES-CTR + auto padded_nonce = owned_bytes<16>(0); + padded_nonce.append(nonce); + padded_nonce.resize(16); + + // Reset AES-CTR context with new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + throw crypto_error(); + } + + // Encrypt with AES-CTR + auto inner_ct = ct.subspan(0, pt.size()); + int outlen = 0; + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate( + handle->ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + // Compute HMAC tag using cached context + // Reset HMAC context (key is preserved from init) + if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + throw crypto_error(); + } + + // Build length block + auto len_block = owned_bytes<24>(); + auto len_view = output_bytes(len_block); + encode_uint(aad.size(), len_view.first(8)); + encode_uint(inner_ct.size(), len_view.first(16).last(8)); + encode_uint(tag_size, len_view.last(8)); + + if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + throw crypto_error(); + } + + auto mac_buf = owned_bytes<64>(); + unsigned int mac_size = mac_buf.size(); + if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + std::copy(mac_buf.begin(), mac_buf.begin() + tag_size, tag.begin()); + + return ct.subspan(0, pt.size() + tag_size); +} + +static output_bytes +seal_aead_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != EVP_EncryptUpdate( + handle->ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate( + handle->ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + handle->ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + return ct.subspan(0, pt.size() + tag_size); +} + +output_bytes +CipherState::seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + if (is_ctr_hmac_suite(handle->suite)) { + return seal_ctr_cached(handle.get(), nonce, ct, aad, pt); + } + return seal_aead_cached(handle.get(), nonce, ct, aad, pt); +} + +static output_bytes +open_ctr_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + auto inner_ct_size = ct.size() - tag_size; + if (pt.size() < inner_ct_size) { + throw buffer_too_small_error("Plaintext buffer too small"); + } + + auto inner_ct = ct.subspan(0, inner_ct_size); + auto tag = ct.subspan(inner_ct_size, tag_size); + + // Verify HMAC tag using cached context + // Reset HMAC context (key is preserved from init) + if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + throw crypto_error(); + } + + // Build length block + auto len_block = owned_bytes<24>(); + auto len_view = output_bytes(len_block); + encode_uint(aad.size(), len_view.first(8)); + encode_uint(inner_ct.size(), len_view.first(16).last(8)); + encode_uint(tag_size, len_view.last(8)); + + if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + throw crypto_error(); + } + if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + throw crypto_error(); + } + + auto mac_buf = owned_bytes<64>(); + unsigned int mac_size = mac_buf.size(); + if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + throw crypto_error(); + } + + if (CRYPTO_memcmp(mac_buf.data(), tag.data(), tag_size) != 0) { + throw authentication_error(); + } + + // Decrypt with AES-CTR + // Pad nonce to 16 bytes for AES-CTR + auto padded_nonce = owned_bytes<16>(0); + padded_nonce.append(nonce); + padded_nonce.resize(16); + + // Reset AES-CTR context with new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto inner_ct_size_int = static_cast(inner_ct_size); + if (1 != EVP_EncryptUpdate( + handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + return pt.subspan(0, inner_ct_size); +} + +static output_bytes +open_aead_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + auto inner_ct_size = ct.size() - tag_size; + if (pt.size() < inner_ct_size) { + throw buffer_too_small_error("Plaintext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_DecryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + auto tag = ct.subspan(inner_ct_size, tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + handle->ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + int out_size; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != EVP_DecryptUpdate( + handle->ctx, nullptr, &out_size, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto inner_ct_size_int = static_cast(inner_ct_size); + if (1 != EVP_DecryptUpdate( + handle->ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_DecryptFinal(handle->ctx, nullptr, &out_size)) { + throw authentication_error(); + } + + return pt.subspan(0, inner_ct_size); +} + +output_bytes +CipherState::open(input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + if (is_ctr_hmac_suite(handle->suite)) { + return open_ctr_cached(handle.get(), nonce, pt, aad, ct); + } + return open_aead_cached(handle.get(), nonce, pt, aad, ct); +} + /// /// Scoped pointers for OpenSSL objects /// diff --git a/src/crypto_openssl3.cpp b/src/crypto_openssl3.cpp index d1bda30..206ace0 100644 --- a/src/crypto_openssl3.cpp +++ b/src/crypto_openssl3.cpp @@ -11,6 +11,433 @@ namespace SFRAME_NAMESPACE { +/// +/// Forward declarations +/// + +static const EVP_CIPHER* +openssl_cipher(CipherSuite suite); + +static std::string +openssl_digest_name(CipherSuite suite); + +static bool +is_ctr_hmac_suite(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return true; + default: + return false; + } +} + +/// +/// CipherState - pre-warmed cipher context +/// + +struct CipherHandle +{ + EVP_CIPHER_CTX* ctx; + EVP_MAC* mac; // For CTR+HMAC (null for GCM) + EVP_MAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) + CipherSuite suite; + bool is_seal; + + CipherHandle(CipherSuite suite_in, input_bytes key, bool seal) + : ctx(EVP_CIPHER_CTX_new()) + , mac(nullptr) + , hmac_ctx(nullptr) + , suite(suite_in) + , is_seal(seal) + { + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode) + if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + // Initialize HMAC context + mac = EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr); + if (mac == nullptr) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + hmac_ctx = EVP_MAC_CTX_new(mac); + if (hmac_ctx == nullptr) { + EVP_MAC_free(mac); + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + + auto digest_name = openssl_digest_name(suite); + std::array params = { + OSSL_PARAM_construct_utf8_string( + OSSL_ALG_PARAM_DIGEST, digest_name.data(), 0), + OSSL_PARAM_construct_end() + }; + + if (1 != + EVP_MAC_init(hmac_ctx, auth_key.data(), auth_key.size(), params.data())) { + EVP_MAC_CTX_free(hmac_ctx); + EVP_MAC_free(mac); + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } else { + // GCM: use full key + if (seal) { + if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } else { + if (1 != EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + EVP_CIPHER_CTX_free(ctx); + throw crypto_error(); + } + } + } + } + + ~CipherHandle() + { + if (hmac_ctx != nullptr) { + EVP_MAC_CTX_free(hmac_ctx); + } + if (mac != nullptr) { + EVP_MAC_free(mac); + } + if (ctx != nullptr) { + EVP_CIPHER_CTX_free(ctx); + } + } + + CipherHandle(const CipherHandle&) = delete; + CipherHandle& operator=(const CipherHandle&) = delete; +}; + +void +CipherState::Deleter::operator()(CipherHandle* h) const +{ + delete h; +} + +CipherState::CipherState(CipherHandle* h) + : handle(h) +{ +} + +CipherState +CipherState::create_seal(CipherSuite suite, input_bytes key) +{ + return CipherState(new CipherHandle(suite, key, true)); +} + +CipherState +CipherState::create_open(CipherSuite suite, input_bytes key) +{ + return CipherState(new CipherHandle(suite, key, false)); +} + +static output_bytes +seal_ctr_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Pad nonce to 16 bytes for AES-CTR + auto padded_nonce = owned_bytes<16>(0); + padded_nonce.append(nonce); + padded_nonce.resize(16); + + // Reset AES-CTR context with new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + throw crypto_error(); + } + + // Encrypt with AES-CTR + auto inner_ct = ct.subspan(0, pt.size()); + int outlen = 0; + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate( + handle->ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + // Compute HMAC tag using cached context + // Reset HMAC context (key is preserved from init) + if (1 != EVP_MAC_init(handle->hmac_ctx, nullptr, 0, nullptr)) { + throw crypto_error(); + } + + // Build length block + auto len_block = owned_bytes<24>(); + auto len_view = output_bytes(len_block); + encode_uint(aad.size(), len_view.first(8)); + encode_uint(inner_ct.size(), len_view.first(16).last(8)); + encode_uint(tag_size, len_view.last(8)); + + if (1 != EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { + throw crypto_error(); + } + if (1 != EVP_MAC_update(handle->hmac_ctx, nonce.data(), nonce.size())) { + throw crypto_error(); + } + if (1 != EVP_MAC_update(handle->hmac_ctx, aad.data(), aad.size())) { + throw crypto_error(); + } + if (1 != EVP_MAC_update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + throw crypto_error(); + } + + size_t mac_size = 0; + auto mac_buf = owned_bytes<64>(); + if (1 != + EVP_MAC_final(handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + std::copy(mac_buf.begin(), mac_buf.begin() + tag_size, tag.begin()); + + return ct.subspan(0, pt.size() + tag_size); +} + +static output_bytes +seal_aead_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != EVP_EncryptUpdate( + handle->ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate( + handle->ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + handle->ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + return ct.subspan(0, pt.size() + tag_size); +} + +output_bytes +CipherState::seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + if (is_ctr_hmac_suite(handle->suite)) { + return seal_ctr_cached(handle.get(), nonce, ct, aad, pt); + } + return seal_aead_cached(handle.get(), nonce, ct, aad, pt); +} + +static output_bytes +open_ctr_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + auto inner_ct_size = ct.size() - tag_size; + if (pt.size() < inner_ct_size) { + throw buffer_too_small_error("Plaintext buffer too small"); + } + + auto inner_ct = ct.subspan(0, inner_ct_size); + auto tag = ct.subspan(inner_ct_size, tag_size); + + // Verify HMAC tag using cached context + // Reset HMAC context (key is preserved from init) + if (1 != EVP_MAC_init(handle->hmac_ctx, nullptr, 0, nullptr)) { + throw crypto_error(); + } + + // Build length block + auto len_block = owned_bytes<24>(); + auto len_view = output_bytes(len_block); + encode_uint(aad.size(), len_view.first(8)); + encode_uint(inner_ct.size(), len_view.first(16).last(8)); + encode_uint(tag_size, len_view.last(8)); + + if (1 != EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { + throw crypto_error(); + } + if (1 != EVP_MAC_update(handle->hmac_ctx, nonce.data(), nonce.size())) { + throw crypto_error(); + } + if (1 != EVP_MAC_update(handle->hmac_ctx, aad.data(), aad.size())) { + throw crypto_error(); + } + if (1 != EVP_MAC_update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + throw crypto_error(); + } + + size_t mac_size = 0; + auto mac_buf = owned_bytes<64>(); + if (1 != + EVP_MAC_final(handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + throw crypto_error(); + } + + if (CRYPTO_memcmp(mac_buf.data(), tag.data(), tag_size) != 0) { + throw authentication_error(); + } + + // Decrypt with AES-CTR + // Pad nonce to 16 bytes for AES-CTR + auto padded_nonce = owned_bytes<16>(0); + padded_nonce.append(nonce); + padded_nonce.resize(16); + + // Reset AES-CTR context with new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto inner_ct_size_int = static_cast(inner_ct_size); + if (1 != EVP_EncryptUpdate( + handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + return pt.subspan(0, inner_ct_size); +} + +static output_bytes +open_aead_cached(CipherHandle* handle, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + auto tag_size = cipher_overhead(handle->suite); + if (ct.size() < tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + auto inner_ct_size = ct.size() - tag_size; + if (pt.size() < inner_ct_size) { + throw buffer_too_small_error("Plaintext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_DecryptInit_ex( + handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + auto tag = ct.subspan(inner_ct_size, tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + handle->ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + int out_size; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != EVP_DecryptUpdate( + handle->ctx, nullptr, &out_size, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto inner_ct_size_int = static_cast(inner_ct_size); + if (1 != EVP_DecryptUpdate( + handle->ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_DecryptFinal(handle->ctx, nullptr, &out_size)) { + throw authentication_error(); + } + + return pt.subspan(0, inner_ct_size); +} + +output_bytes +CipherState::open(input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) +{ + if (is_ctr_hmac_suite(handle->suite)) { + return open_ctr_cached(handle.get(), nonce, pt, aad, ct); + } + return open_aead_cached(handle.get(), nonce, pt, aad, ct); +} + /// /// Convert between native identifiers / errors and OpenSSL ones /// diff --git a/src/sframe.cpp b/src/sframe.cpp index c4510d4..254fe6a 100644 --- a/src/sframe.cpp +++ b/src/sframe.cpp @@ -23,6 +23,25 @@ authentication_error::authentication_error() /// KeyRecord /// +KeyRecord::KeyRecord(owned_bytes key_in, + owned_bytes salt_in, + KeyUsage usage_in, + Counter counter_in, + std::unique_ptr cipher_in) + : key(std::move(key_in)) + , salt(std::move(salt_in)) + , usage(usage_in) + , counter(counter_in) + , cipher(std::move(cipher_in)) +{ +} + +KeyRecord::~KeyRecord() = default; + +KeyRecord::KeyRecord(KeyRecord&&) noexcept = default; +KeyRecord& +KeyRecord::operator=(KeyRecord&&) noexcept = default; + static auto from_ascii(const char* str, size_t len) { @@ -79,7 +98,18 @@ KeyRecord::from_base_key(CipherSuite suite, auto key = hkdf_expand(suite, secret, key_label, key_size); auto salt = hkdf_expand(suite, secret, salt_label, nonce_size); - return KeyRecord{ key, salt, usage, 0 }; + // Create pre-warmed cipher state + std::unique_ptr cipher_state; + if (usage == KeyUsage::protect) { + cipher_state = + std::make_unique(CipherState::create_seal(suite, key)); + } else { + cipher_state = + std::make_unique(CipherState::create_open(suite, key)); + } + + return KeyRecord( + std::move(key), std::move(salt), usage, 0, std::move(cipher_state)); } /// @@ -171,10 +201,16 @@ Context::protect_inner(const Header& header, throw buffer_too_small_error("Ciphertext too small for cipher overhead"); } - const auto& key_and_salt = keys.at(header.key_id); + auto& key_and_salt = keys.at(header.key_id); const auto aad = form_aad(header, metadata); const auto nonce = form_nonce(header.counter, key_and_salt.salt); + + // Use pre-warmed cipher state if available (AEAD suites) + if (key_and_salt.cipher) { + return key_and_salt.cipher->seal(nonce, ciphertext, aad, plaintext); + } + return seal(suite, key_and_salt.key, nonce, ciphertext, aad, plaintext); } @@ -192,10 +228,16 @@ Context::unprotect_inner(const Header& header, throw buffer_too_small_error("Plaintext too small for decrypted value"); } - const auto& key_and_salt = keys.at(header.key_id); + auto& key_and_salt = keys.at(header.key_id); const auto aad = form_aad(header, metadata); const auto nonce = form_nonce(header.counter, key_and_salt.salt); + + // Use pre-warmed cipher state if available (AEAD suites) + if (key_and_salt.cipher) { + return key_and_salt.cipher->open(nonce, plaintext, aad, ciphertext); + } + return open(suite, key_and_salt.key, nonce, plaintext, aad, ciphertext); } diff --git a/test/vectors.cpp b/test/vectors.cpp index a3e1c45..470ab2d 100644 --- a/test/vectors.cpp +++ b/test/vectors.cpp @@ -48,7 +48,7 @@ from_json(const json& j, HexBytes& b) } } -static void +[[maybe_unused]] static void to_json(json& /* j */, const HexBytes& /* p */) { // Included just so that macros work From 3cefbad02219771aa774ca92feb51817ab4d1714 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 10:35:31 -1000 Subject: [PATCH 2/8] clang-format Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 8 ++++++++ src/crypto_boringssl.cpp | 19 ++++++++++++------- src/crypto_openssl11.cpp | 16 ++++++++++------ src/crypto_openssl3.cpp | 36 +++++++++++++++++++++--------------- 4 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..121e7a1 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(make test)", + "Bash(make format)" + ] + } +} diff --git a/src/crypto_boringssl.cpp b/src/crypto_boringssl.cpp index 295af3a..ab3fdde 100644 --- a/src/crypto_boringssl.cpp +++ b/src/crypto_boringssl.cpp @@ -41,7 +41,7 @@ is_ctr_hmac_suite(CipherSuite suite) struct CipherHandle { EVP_CIPHER_CTX* ctx; - HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) + HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) CipherSuite suite; bool is_seal; @@ -64,7 +64,8 @@ struct CipherHandle auto auth_key = key.subspan(enc_key_size); // Initialize AES-CTR context (always encrypt for CTR mode) - if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { + if (1 != + EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } @@ -77,7 +78,8 @@ struct CipherHandle } const auto* md = openssl_digest_type(suite); - if (1 != HMAC_Init_ex(hmac_ctx, auth_key.data(), auth_key.size(), md, nullptr)) { + if (1 != HMAC_Init_ex( + hmac_ctx, auth_key.data(), auth_key.size(), md, nullptr)) { HMAC_CTX_free(hmac_ctx); EVP_CIPHER_CTX_free(ctx); throw crypto_error(); @@ -85,12 +87,14 @@ struct CipherHandle } else { // GCM: use full key if (seal) { - if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + if (1 != + EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } } else { - if (1 != EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + if (1 != + EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } @@ -339,8 +343,9 @@ open_ctr_cached(CipherHandle* handle, int outlen = 0; auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != EVP_EncryptUpdate( - handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + if (1 != + EVP_EncryptUpdate( + handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { throw crypto_error(); } diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index cffe2cd..0d586f7 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -39,7 +39,7 @@ is_ctr_hmac_suite(CipherSuite suite) struct CipherHandle { EVP_CIPHER_CTX* ctx; - HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) + HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) CipherSuite suite; bool is_seal; @@ -62,7 +62,8 @@ struct CipherHandle auto auth_key = key.subspan(enc_key_size); // Initialize AES-CTR context (always encrypt for CTR mode) - if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { + if (1 != + EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } @@ -84,12 +85,14 @@ struct CipherHandle } else { // GCM: use full key if (seal) { - if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + if (1 != + EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } } else { - if (1 != EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + if (1 != + EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } @@ -338,8 +341,9 @@ open_ctr_cached(CipherHandle* handle, int outlen = 0; auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != EVP_EncryptUpdate( - handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + if (1 != + EVP_EncryptUpdate( + handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { throw crypto_error(); } diff --git a/src/crypto_openssl3.cpp b/src/crypto_openssl3.cpp index 206ace0..5b3c582 100644 --- a/src/crypto_openssl3.cpp +++ b/src/crypto_openssl3.cpp @@ -41,8 +41,8 @@ is_ctr_hmac_suite(CipherSuite suite) struct CipherHandle { EVP_CIPHER_CTX* ctx; - EVP_MAC* mac; // For CTR+HMAC (null for GCM) - EVP_MAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) + EVP_MAC* mac; // For CTR+HMAC (null for GCM) + EVP_MAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) CipherSuite suite; bool is_seal; @@ -66,7 +66,8 @@ struct CipherHandle auto auth_key = key.subspan(enc_key_size); // Initialize AES-CTR context (always encrypt for CTR mode) - if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { + if (1 != + EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } @@ -92,8 +93,8 @@ struct CipherHandle OSSL_PARAM_construct_end() }; - if (1 != - EVP_MAC_init(hmac_ctx, auth_key.data(), auth_key.size(), params.data())) { + if (1 != EVP_MAC_init( + hmac_ctx, auth_key.data(), auth_key.size(), params.data())) { EVP_MAC_CTX_free(hmac_ctx); EVP_MAC_free(mac); EVP_CIPHER_CTX_free(ctx); @@ -102,12 +103,14 @@ struct CipherHandle } else { // GCM: use full key if (seal) { - if (1 != EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + if (1 != + EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } } else { - if (1 != EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { + if (1 != + EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { EVP_CIPHER_CTX_free(ctx); throw crypto_error(); } @@ -204,7 +207,8 @@ seal_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != + EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { throw crypto_error(); } if (1 != EVP_MAC_update(handle->hmac_ctx, nonce.data(), nonce.size())) { @@ -219,8 +223,8 @@ seal_ctr_cached(CipherHandle* handle, size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != - EVP_MAC_final(handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != EVP_MAC_final( + handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -323,7 +327,8 @@ open_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != + EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { throw crypto_error(); } if (1 != EVP_MAC_update(handle->hmac_ctx, nonce.data(), nonce.size())) { @@ -338,8 +343,8 @@ open_ctr_cached(CipherHandle* handle, size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != - EVP_MAC_final(handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != EVP_MAC_final( + handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -361,8 +366,9 @@ open_ctr_cached(CipherHandle* handle, int outlen = 0; auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != EVP_EncryptUpdate( - handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + if (1 != + EVP_EncryptUpdate( + handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { throw crypto_error(); } From 0a6e71312d9ba1614bb052275ccabf7653c5a827 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 10:36:40 -1000 Subject: [PATCH 3/8] Add .claude to gitignore Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 8 -------- .gitignore | 1 + 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 121e7a1..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(make test)", - "Bash(make format)" - ] - } -} diff --git a/.gitignore b/.gitignore index 81c95f5..1d96a7d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ build *.exe *.out *.app +.claude/ From dc77df745330269b23a28b520883bbf90881e2d7 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 10:41:08 -1000 Subject: [PATCH 4/8] Hold CipherState by value in KeyRecord Co-Authored-By: Claude Opus 4.5 --- include/sframe/sframe.h | 42 ++++++++++++++++++++++++++++++++++++----- src/crypto.h | 37 ------------------------------------ src/sframe.cpp | 27 ++++++-------------------- 3 files changed, 43 insertions(+), 63 deletions(-) diff --git a/include/sframe/sframe.h b/include/sframe/sframe.h index 9a9856a..5a72cf0 100644 --- a/include/sframe/sframe.h +++ b/include/sframe/sframe.h @@ -27,9 +27,6 @@ namespace SFRAME_NAMESPACE { -// Forward declaration for pre-warmed cipher state -struct CipherState; - struct crypto_error : std::runtime_error { crypto_error(); @@ -88,6 +85,41 @@ enum struct KeyUsage unprotect, }; +/// +/// CipherState - pre-warmed cipher state for efficient repeated operations +/// +/// Holds a pre-initialized cipher context so that expensive key schedule +/// computation happens once at construction, not on every seal/open call. +/// + +struct CipherHandle; + +struct CipherState +{ + static CipherState create_seal(CipherSuite suite, input_bytes key); + static CipherState create_open(CipherSuite suite, input_bytes key); + + output_bytes seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt); + + output_bytes open(input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct); + +private: + struct Deleter + { + void operator()(CipherHandle* h) const; + }; + + std::unique_ptr handle; + + explicit CipherState(CipherHandle* h); +}; + struct KeyRecord { static constexpr size_t max_key_size = 48; @@ -102,7 +134,7 @@ struct KeyRecord owned_bytes salt, KeyUsage usage, Counter counter, - std::unique_ptr cipher); + CipherState cipher); ~KeyRecord(); KeyRecord(KeyRecord&&) noexcept; @@ -115,7 +147,7 @@ struct KeyRecord owned_bytes salt; KeyUsage usage; Counter counter; - std::unique_ptr cipher; // Pre-warmed cipher state (AEAD only) + CipherState cipher; // Pre-warmed cipher state }; // Context applies the full SFrame transform. It tracks a counter for each key diff --git a/src/crypto.h b/src/crypto.h index d861eb3..eadd4bf 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -2,45 +2,8 @@ #include -#include - namespace SFRAME_NAMESPACE { -/// -/// CipherState - pre-warmed cipher state for efficient repeated operations -/// -/// Holds a pre-initialized cipher context so that expensive key schedule -/// computation happens once at construction, not on every seal/open call. -/// - -struct CipherHandle; - -struct CipherState -{ - static CipherState create_seal(CipherSuite suite, input_bytes key); - static CipherState create_open(CipherSuite suite, input_bytes key); - - output_bytes seal(input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt); - - output_bytes open(input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct); - -private: - struct Deleter - { - void operator()(CipherHandle* h) const; - }; - - std::unique_ptr handle; - - explicit CipherState(CipherHandle* h); -}; - size_t cipher_digest_size(CipherSuite suite); size_t diff --git a/src/sframe.cpp b/src/sframe.cpp index 254fe6a..12acc08 100644 --- a/src/sframe.cpp +++ b/src/sframe.cpp @@ -27,7 +27,7 @@ KeyRecord::KeyRecord(owned_bytes key_in, owned_bytes salt_in, KeyUsage usage_in, Counter counter_in, - std::unique_ptr cipher_in) + CipherState cipher_in) : key(std::move(key_in)) , salt(std::move(salt_in)) , usage(usage_in) @@ -99,14 +99,9 @@ KeyRecord::from_base_key(CipherSuite suite, auto salt = hkdf_expand(suite, secret, salt_label, nonce_size); // Create pre-warmed cipher state - std::unique_ptr cipher_state; - if (usage == KeyUsage::protect) { - cipher_state = - std::make_unique(CipherState::create_seal(suite, key)); - } else { - cipher_state = - std::make_unique(CipherState::create_open(suite, key)); - } + auto cipher_state = (usage == KeyUsage::protect) + ? CipherState::create_seal(suite, key) + : CipherState::create_open(suite, key); return KeyRecord( std::move(key), std::move(salt), usage, 0, std::move(cipher_state)); @@ -206,12 +201,7 @@ Context::protect_inner(const Header& header, const auto aad = form_aad(header, metadata); const auto nonce = form_nonce(header.counter, key_and_salt.salt); - // Use pre-warmed cipher state if available (AEAD suites) - if (key_and_salt.cipher) { - return key_and_salt.cipher->seal(nonce, ciphertext, aad, plaintext); - } - - return seal(suite, key_and_salt.key, nonce, ciphertext, aad, plaintext); + return key_and_salt.cipher.seal(nonce, ciphertext, aad, plaintext); } output_bytes @@ -233,12 +223,7 @@ Context::unprotect_inner(const Header& header, const auto aad = form_aad(header, metadata); const auto nonce = form_nonce(header.counter, key_and_salt.salt); - // Use pre-warmed cipher state if available (AEAD suites) - if (key_and_salt.cipher) { - return key_and_salt.cipher->open(nonce, plaintext, aad, ciphertext); - } - - return open(suite, key_and_salt.key, nonce, plaintext, aad, ciphertext); + return key_and_salt.cipher.open(nonce, plaintext, aad, ciphertext); } /// From ce4a43ea7745432cdab70c608558056a4a951b69 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 11:03:58 -1000 Subject: [PATCH 5/8] Simplify CipherHandle to be a direct typedef for EVP_CIPHER_CTX - CipherHandle is now EVP_CIPHER_CTX via reinterpret_cast - Add separate HmacHandle struct for HMAC state - CipherState members are now cipher_handle and hmac_handle - Use unique_ptr throughout to avoid raw pointer temporaries - Fix BoringSSL endif comment Co-Authored-By: Claude Opus 4.5 --- include/sframe/sframe.h | 15 +- src/crypto_boringssl.cpp | 305 ++++++++++++++++++--------------- src/crypto_openssl11.cpp | 303 ++++++++++++++++++--------------- src/crypto_openssl3.cpp | 352 ++++++++++++++++++++++----------------- 4 files changed, 555 insertions(+), 420 deletions(-) diff --git a/include/sframe/sframe.h b/include/sframe/sframe.h index 5a72cf0..dfc7092 100644 --- a/include/sframe/sframe.h +++ b/include/sframe/sframe.h @@ -92,7 +92,9 @@ enum struct KeyUsage /// computation happens once at construction, not on every seal/open call. /// +// Opaque handles - defined by each crypto backend struct CipherHandle; +struct HmacHandle; struct CipherState { @@ -110,14 +112,21 @@ struct CipherState input_bytes ct); private: - struct Deleter + struct CipherDeleter { void operator()(CipherHandle* h) const; }; - std::unique_ptr handle; + struct HmacDeleter + { + void operator()(HmacHandle* h) const; + }; + + std::unique_ptr cipher_handle; + std::unique_ptr hmac_handle; // null for GCM + CipherSuite suite; - explicit CipherState(CipherHandle* h); + CipherState(CipherHandle* cipher, HmacHandle* hmac, CipherSuite suite); }; struct KeyRecord diff --git a/src/crypto_boringssl.cpp b/src/crypto_boringssl.cpp index ab3fdde..0641713 100644 --- a/src/crypto_boringssl.cpp +++ b/src/crypto_boringssl.cpp @@ -35,118 +35,157 @@ is_ctr_hmac_suite(CipherSuite suite) } /// -/// CipherState - pre-warmed cipher context +/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle holds HMAC state /// -struct CipherHandle +// HmacHandle for BoringSSL wraps HMAC_CTX +struct HmacHandle { - EVP_CIPHER_CTX* ctx; - HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) - CipherSuite suite; - bool is_seal; - - CipherHandle(CipherSuite suite_in, input_bytes key, bool seal) - : ctx(EVP_CIPHER_CTX_new()) - , hmac_ctx(nullptr) - , suite(suite_in) - , is_seal(seal) - { - if (ctx == nullptr) { - throw crypto_error(); - } + HMAC_CTX* ctx; +}; - auto cipher = openssl_cipher(suite); - - if (is_ctr_hmac_suite(suite)) { - // CTR+HMAC: key is split into enc_key and auth_key - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Initialize AES-CTR context (always encrypt for CTR mode) - if (1 != - EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - // Initialize HMAC context - hmac_ctx = HMAC_CTX_new(); - if (hmac_ctx == nullptr) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - const auto* md = openssl_digest_type(suite); - if (1 != HMAC_Init_ex( - hmac_ctx, auth_key.data(), auth_key.size(), md, nullptr)) { - HMAC_CTX_free(hmac_ctx); - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } else { - // GCM: use full key - if (seal) { - if (1 != - EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } else { - if (1 != - EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } - } - } +// Cast helpers - CipherHandle is EVP_CIPHER_CTX +static EVP_CIPHER_CTX* +cipher_ctx(CipherHandle* h) +{ + return reinterpret_cast(h); +} - ~CipherHandle() - { - if (hmac_ctx != nullptr) { - HMAC_CTX_free(hmac_ctx); - } - if (ctx != nullptr) { - EVP_CIPHER_CTX_free(ctx); - } - } +static CipherHandle* +to_cipher_handle(EVP_CIPHER_CTX* ctx) +{ + return reinterpret_cast(ctx); +} - CipherHandle(const CipherHandle&) = delete; - CipherHandle& operator=(const CipherHandle&) = delete; -}; +void +CipherState::CipherDeleter::operator()(CipherHandle* h) const +{ + EVP_CIPHER_CTX_free(cipher_ctx(h)); +} void -CipherState::Deleter::operator()(CipherHandle* h) const +CipherState::HmacDeleter::operator()(HmacHandle* h) const { - delete h; + if (h != nullptr) { + HMAC_CTX_free(h->ctx); + delete h; + } } -CipherState::CipherState(CipherHandle* h) - : handle(h) +CipherState::CipherState(CipherHandle* cipher, + HmacHandle* hmac, + CipherSuite suite_in) + : cipher_handle(cipher) + , hmac_handle(hmac) + , suite(suite_in) { } CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - return CipherState(new CipherHandle(suite, key, true)); + std::unique_ptr ctx( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + std::unique_ptr hmac; + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode) + if (1 != EVP_EncryptInit_ex( + ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + throw crypto_error(); + } + + // Initialize HMAC + hmac.reset(new HmacHandle()); + hmac->ctx = HMAC_CTX_new(); + if (hmac->ctx == nullptr) { + throw crypto_error(); + } + + const auto* md = openssl_digest_type(suite); + if (1 != HMAC_Init_ex( + hmac->ctx, auth_key.data(), auth_key.size(), md, nullptr)) { + throw crypto_error(); + } + } else { + // GCM: use full key + if (1 != + EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + throw crypto_error(); + } + } + + return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - return CipherState(new CipherHandle(suite, key, false)); + std::unique_ptr ctx( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + std::unique_ptr hmac; + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode - CTR is + // symmetric) + if (1 != EVP_EncryptInit_ex( + ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + throw crypto_error(); + } + + // Initialize HMAC + hmac.reset(new HmacHandle()); + hmac->ctx = HMAC_CTX_new(); + if (hmac->ctx == nullptr) { + throw crypto_error(); + } + + const auto* md = openssl_digest_type(suite); + if (1 != HMAC_Init_ex( + hmac->ctx, auth_key.data(), auth_key.size(), md, nullptr)) { + throw crypto_error(); + } + } else { + // GCM: use full key + if (1 != + EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + throw crypto_error(); + } + } + + return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); } static output_bytes -seal_ctr_cached(CipherHandle* handle, +seal_ctr_cached(EVP_CIPHER_CTX* ctx, + HmacHandle* hmac, + CipherSuite suite, input_bytes nonce, output_bytes ct, input_bytes aad, input_bytes pt) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -157,8 +196,8 @@ seal_ctr_cached(CipherHandle* handle, padded_nonce.resize(16); // Reset AES-CTR context with new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + if (1 != + EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { throw crypto_error(); } @@ -167,17 +206,17 @@ seal_ctr_cached(CipherHandle* handle, int outlen = 0; auto pt_size_int = static_cast(pt.size()); if (1 != EVP_EncryptUpdate( - handle->ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { + ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } // Compute HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -188,22 +227,22 @@ seal_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -214,39 +253,38 @@ seal_ctr_cached(CipherHandle* handle, } static output_bytes -seal_aead_cached(CipherHandle* handle, +seal_aead_cached(EVP_CIPHER_CTX* ctx, + CipherSuite suite, input_bytes nonce, output_bytes ct, input_bytes aad, input_bytes pt) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } // Reset context and set new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { throw crypto_error(); } int outlen = 0; auto aad_size_int = static_cast(aad.size()); if (aad.size() > 0) { - if (1 != EVP_EncryptUpdate( - handle->ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + if (1 != + EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { throw crypto_error(); } } auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate( - handle->ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } @@ -254,7 +292,7 @@ seal_aead_cached(CipherHandle* handle, auto tag_ptr = const_cast(static_cast(tag.data())); auto tag_size_downcast = static_cast(tag.size()); if (1 != EVP_CIPHER_CTX_ctrl( - handle->ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { throw crypto_error(); } @@ -267,20 +305,23 @@ CipherState::seal(input_bytes nonce, input_bytes aad, input_bytes pt) { - if (is_ctr_hmac_suite(handle->suite)) { - return seal_ctr_cached(handle.get(), nonce, ct, aad, pt); + auto* ctx = cipher_ctx(cipher_handle.get()); + if (is_ctr_hmac_suite(suite)) { + return seal_ctr_cached(ctx, hmac_handle.get(), suite, nonce, ct, aad, pt); } - return seal_aead_cached(handle.get(), nonce, ct, aad, pt); + return seal_aead_cached(ctx, suite, nonce, ct, aad, pt); } static output_bytes -open_ctr_cached(CipherHandle* handle, +open_ctr_cached(EVP_CIPHER_CTX* ctx, + HmacHandle* hmac, + CipherSuite suite, input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -295,7 +336,7 @@ open_ctr_cached(CipherHandle* handle, // Verify HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -306,22 +347,22 @@ open_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -336,20 +377,19 @@ open_ctr_cached(CipherHandle* handle, padded_nonce.resize(16); // Reset AES-CTR context with new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + if (1 != + EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { throw crypto_error(); } int outlen = 0; auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != - EVP_EncryptUpdate( - handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + if (1 != EVP_EncryptUpdate( + ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } @@ -357,13 +397,14 @@ open_ctr_cached(CipherHandle* handle, } static output_bytes -open_aead_cached(CipherHandle* handle, +open_aead_cached(EVP_CIPHER_CTX* ctx, + CipherSuite suite, input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -374,8 +415,7 @@ open_aead_cached(CipherHandle* handle, } // Reset context and set new nonce (key is preserved) - if (1 != EVP_DecryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + if (1 != EVP_DecryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { throw crypto_error(); } @@ -383,26 +423,26 @@ open_aead_cached(CipherHandle* handle, auto tag_ptr = const_cast(static_cast(tag.data())); auto tag_size_downcast = static_cast(tag.size()); if (1 != EVP_CIPHER_CTX_ctrl( - handle->ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { + ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { throw crypto_error(); } int out_size; auto aad_size_int = static_cast(aad.size()); if (aad.size() > 0) { - if (1 != EVP_DecryptUpdate( - handle->ctx, nullptr, &out_size, aad.data(), aad_size_int)) { + if (1 != + EVP_DecryptUpdate(ctx, nullptr, &out_size, aad.data(), aad_size_int)) { throw crypto_error(); } } auto inner_ct_size_int = static_cast(inner_ct_size); if (1 != EVP_DecryptUpdate( - handle->ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { + ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { throw crypto_error(); } - if (1 != EVP_DecryptFinal(handle->ctx, nullptr, &out_size)) { + if (1 != EVP_DecryptFinal(ctx, nullptr, &out_size)) { throw authentication_error(); } @@ -415,10 +455,11 @@ CipherState::open(input_bytes nonce, input_bytes aad, input_bytes ct) { - if (is_ctr_hmac_suite(handle->suite)) { - return open_ctr_cached(handle.get(), nonce, pt, aad, ct); + auto* ctx = cipher_ctx(cipher_handle.get()); + if (is_ctr_hmac_suite(suite)) { + return open_ctr_cached(ctx, hmac_handle.get(), suite, nonce, pt, aad, ct); } - return open_aead_cached(handle.get(), nonce, pt, aad, ct); + return open_aead_cached(ctx, suite, nonce, pt, aad, ct); } /// @@ -840,4 +881,4 @@ open(CipherSuite suite, } // namespace SFRAME_NAMESPACE -#endif // defined(OPENSSL_3) +#endif // defined(BORINGSSL) diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index 0d586f7..268cc37 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -33,118 +33,157 @@ is_ctr_hmac_suite(CipherSuite suite) } /// -/// CipherState - pre-warmed cipher context +/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle holds HMAC state /// -struct CipherHandle +// HmacHandle for OpenSSL 1.1 wraps HMAC_CTX +struct HmacHandle { - EVP_CIPHER_CTX* ctx; - HMAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) - CipherSuite suite; - bool is_seal; - - CipherHandle(CipherSuite suite_in, input_bytes key, bool seal) - : ctx(EVP_CIPHER_CTX_new()) - , hmac_ctx(nullptr) - , suite(suite_in) - , is_seal(seal) - { - if (ctx == nullptr) { - throw crypto_error(); - } + HMAC_CTX* ctx; +}; - auto cipher = openssl_cipher(suite); - - if (is_ctr_hmac_suite(suite)) { - // CTR+HMAC: key is split into enc_key and auth_key - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Initialize AES-CTR context (always encrypt for CTR mode) - if (1 != - EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - // Initialize HMAC context - hmac_ctx = HMAC_CTX_new(); - if (hmac_ctx == nullptr) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - const auto* md = openssl_digest_type(suite); - auto key_size = static_cast(auth_key.size()); - if (1 != HMAC_Init_ex(hmac_ctx, auth_key.data(), key_size, md, nullptr)) { - HMAC_CTX_free(hmac_ctx); - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } else { - // GCM: use full key - if (seal) { - if (1 != - EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } else { - if (1 != - EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } - } - } +// Cast helpers - CipherHandle is EVP_CIPHER_CTX +static EVP_CIPHER_CTX* +cipher_ctx(CipherHandle* h) +{ + return reinterpret_cast(h); +} - ~CipherHandle() - { - if (hmac_ctx != nullptr) { - HMAC_CTX_free(hmac_ctx); - } - if (ctx != nullptr) { - EVP_CIPHER_CTX_free(ctx); - } - } +static CipherHandle* +to_cipher_handle(EVP_CIPHER_CTX* ctx) +{ + return reinterpret_cast(ctx); +} - CipherHandle(const CipherHandle&) = delete; - CipherHandle& operator=(const CipherHandle&) = delete; -}; +void +CipherState::CipherDeleter::operator()(CipherHandle* h) const +{ + EVP_CIPHER_CTX_free(cipher_ctx(h)); +} void -CipherState::Deleter::operator()(CipherHandle* h) const +CipherState::HmacDeleter::operator()(HmacHandle* h) const { - delete h; + if (h != nullptr) { + HMAC_CTX_free(h->ctx); + delete h; + } } -CipherState::CipherState(CipherHandle* h) - : handle(h) +CipherState::CipherState(CipherHandle* cipher, + HmacHandle* hmac, + CipherSuite suite_in) + : cipher_handle(cipher) + , hmac_handle(hmac) + , suite(suite_in) { } CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - return CipherState(new CipherHandle(suite, key, true)); + std::unique_ptr ctx( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + std::unique_ptr hmac; + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode) + if (1 != EVP_EncryptInit_ex( + ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + throw crypto_error(); + } + + // Initialize HMAC + hmac.reset(new HmacHandle()); + hmac->ctx = HMAC_CTX_new(); + if (hmac->ctx == nullptr) { + throw crypto_error(); + } + + const auto* md = openssl_digest_type(suite); + auto key_size = static_cast(auth_key.size()); + if (1 != HMAC_Init_ex(hmac->ctx, auth_key.data(), key_size, md, nullptr)) { + throw crypto_error(); + } + } else { + // GCM: use full key + if (1 != + EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + throw crypto_error(); + } + } + + return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - return CipherState(new CipherHandle(suite, key, false)); + std::unique_ptr ctx( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + std::unique_ptr hmac; + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode - CTR is + // symmetric) + if (1 != EVP_EncryptInit_ex( + ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + throw crypto_error(); + } + + // Initialize HMAC + hmac.reset(new HmacHandle()); + hmac->ctx = HMAC_CTX_new(); + if (hmac->ctx == nullptr) { + throw crypto_error(); + } + + const auto* md = openssl_digest_type(suite); + auto key_size = static_cast(auth_key.size()); + if (1 != HMAC_Init_ex(hmac->ctx, auth_key.data(), key_size, md, nullptr)) { + throw crypto_error(); + } + } else { + // GCM: use full key + if (1 != + EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + throw crypto_error(); + } + } + + return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); } static output_bytes -seal_ctr_cached(CipherHandle* handle, +seal_ctr_cached(EVP_CIPHER_CTX* ctx, + HmacHandle* hmac, + CipherSuite suite, input_bytes nonce, output_bytes ct, input_bytes aad, input_bytes pt) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -155,8 +194,8 @@ seal_ctr_cached(CipherHandle* handle, padded_nonce.resize(16); // Reset AES-CTR context with new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + if (1 != + EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { throw crypto_error(); } @@ -165,17 +204,17 @@ seal_ctr_cached(CipherHandle* handle, int outlen = 0; auto pt_size_int = static_cast(pt.size()); if (1 != EVP_EncryptUpdate( - handle->ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { + ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } // Compute HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -186,22 +225,22 @@ seal_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -212,39 +251,38 @@ seal_ctr_cached(CipherHandle* handle, } static output_bytes -seal_aead_cached(CipherHandle* handle, +seal_aead_cached(EVP_CIPHER_CTX* ctx, + CipherSuite suite, input_bytes nonce, output_bytes ct, input_bytes aad, input_bytes pt) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } // Reset context and set new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { throw crypto_error(); } int outlen = 0; auto aad_size_int = static_cast(aad.size()); if (aad.size() > 0) { - if (1 != EVP_EncryptUpdate( - handle->ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + if (1 != + EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { throw crypto_error(); } } auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate( - handle->ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } @@ -252,7 +290,7 @@ seal_aead_cached(CipherHandle* handle, auto tag_ptr = const_cast(static_cast(tag.data())); auto tag_size_downcast = static_cast(tag.size()); if (1 != EVP_CIPHER_CTX_ctrl( - handle->ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { throw crypto_error(); } @@ -265,20 +303,23 @@ CipherState::seal(input_bytes nonce, input_bytes aad, input_bytes pt) { - if (is_ctr_hmac_suite(handle->suite)) { - return seal_ctr_cached(handle.get(), nonce, ct, aad, pt); + auto* ctx = cipher_ctx(cipher_handle.get()); + if (is_ctr_hmac_suite(suite)) { + return seal_ctr_cached(ctx, hmac_handle.get(), suite, nonce, ct, aad, pt); } - return seal_aead_cached(handle.get(), nonce, ct, aad, pt); + return seal_aead_cached(ctx, suite, nonce, ct, aad, pt); } static output_bytes -open_ctr_cached(CipherHandle* handle, +open_ctr_cached(EVP_CIPHER_CTX* ctx, + HmacHandle* hmac, + CipherSuite suite, input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -293,7 +334,7 @@ open_ctr_cached(CipherHandle* handle, // Verify HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(handle->hmac_ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -304,22 +345,22 @@ open_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(handle->hmac_ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -334,20 +375,19 @@ open_ctr_cached(CipherHandle* handle, padded_nonce.resize(16); // Reset AES-CTR context with new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + if (1 != + EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { throw crypto_error(); } int outlen = 0; auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != - EVP_EncryptUpdate( - handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + if (1 != EVP_EncryptUpdate( + ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } @@ -355,13 +395,14 @@ open_ctr_cached(CipherHandle* handle, } static output_bytes -open_aead_cached(CipherHandle* handle, +open_aead_cached(EVP_CIPHER_CTX* ctx, + CipherSuite suite, input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -372,8 +413,7 @@ open_aead_cached(CipherHandle* handle, } // Reset context and set new nonce (key is preserved) - if (1 != EVP_DecryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + if (1 != EVP_DecryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { throw crypto_error(); } @@ -381,26 +421,26 @@ open_aead_cached(CipherHandle* handle, auto tag_ptr = const_cast(static_cast(tag.data())); auto tag_size_downcast = static_cast(tag.size()); if (1 != EVP_CIPHER_CTX_ctrl( - handle->ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { + ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { throw crypto_error(); } int out_size; auto aad_size_int = static_cast(aad.size()); if (aad.size() > 0) { - if (1 != EVP_DecryptUpdate( - handle->ctx, nullptr, &out_size, aad.data(), aad_size_int)) { + if (1 != + EVP_DecryptUpdate(ctx, nullptr, &out_size, aad.data(), aad_size_int)) { throw crypto_error(); } } auto inner_ct_size_int = static_cast(inner_ct_size); if (1 != EVP_DecryptUpdate( - handle->ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { + ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { throw crypto_error(); } - if (1 != EVP_DecryptFinal(handle->ctx, nullptr, &out_size)) { + if (1 != EVP_DecryptFinal(ctx, nullptr, &out_size)) { throw authentication_error(); } @@ -413,10 +453,11 @@ CipherState::open(input_bytes nonce, input_bytes aad, input_bytes ct) { - if (is_ctr_hmac_suite(handle->suite)) { - return open_ctr_cached(handle.get(), nonce, pt, aad, ct); + auto* ctx = cipher_ctx(cipher_handle.get()); + if (is_ctr_hmac_suite(suite)) { + return open_ctr_cached(ctx, hmac_handle.get(), suite, nonce, pt, aad, ct); } - return open_aead_cached(handle.get(), nonce, pt, aad, ct); + return open_aead_cached(ctx, suite, nonce, pt, aad, ct); } /// diff --git a/src/crypto_openssl3.cpp b/src/crypto_openssl3.cpp index 5b3c582..61a9ae1 100644 --- a/src/crypto_openssl3.cpp +++ b/src/crypto_openssl3.cpp @@ -35,137 +35,181 @@ is_ctr_hmac_suite(CipherSuite suite) } /// -/// CipherState - pre-warmed cipher context +/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle holds HMAC state /// -struct CipherHandle +// HmacHandle for OpenSSL 3.x holds both the MAC algorithm and context +struct HmacHandle { - EVP_CIPHER_CTX* ctx; - EVP_MAC* mac; // For CTR+HMAC (null for GCM) - EVP_MAC_CTX* hmac_ctx; // For CTR+HMAC (null for GCM) - CipherSuite suite; - bool is_seal; - - CipherHandle(CipherSuite suite_in, input_bytes key, bool seal) - : ctx(EVP_CIPHER_CTX_new()) - , mac(nullptr) - , hmac_ctx(nullptr) - , suite(suite_in) - , is_seal(seal) - { - if (ctx == nullptr) { - throw crypto_error(); - } + EVP_MAC* mac; + EVP_MAC_CTX* ctx; +}; - auto cipher = openssl_cipher(suite); - - if (is_ctr_hmac_suite(suite)) { - // CTR+HMAC: key is split into enc_key and auth_key - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Initialize AES-CTR context (always encrypt for CTR mode) - if (1 != - EVP_EncryptInit_ex(ctx, cipher, nullptr, enc_key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - // Initialize HMAC context - mac = EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr); - if (mac == nullptr) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - hmac_ctx = EVP_MAC_CTX_new(mac); - if (hmac_ctx == nullptr) { - EVP_MAC_free(mac); - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - - auto digest_name = openssl_digest_name(suite); - std::array params = { - OSSL_PARAM_construct_utf8_string( - OSSL_ALG_PARAM_DIGEST, digest_name.data(), 0), - OSSL_PARAM_construct_end() - }; - - if (1 != EVP_MAC_init( - hmac_ctx, auth_key.data(), auth_key.size(), params.data())) { - EVP_MAC_CTX_free(hmac_ctx); - EVP_MAC_free(mac); - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } else { - // GCM: use full key - if (seal) { - if (1 != - EVP_EncryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } else { - if (1 != - EVP_DecryptInit_ex(ctx, cipher, nullptr, key.data(), nullptr)) { - EVP_CIPHER_CTX_free(ctx); - throw crypto_error(); - } - } - } - } +// Cast helpers - CipherHandle is EVP_CIPHER_CTX +static EVP_CIPHER_CTX* +cipher_ctx(CipherHandle* h) +{ + return reinterpret_cast(h); +} - ~CipherHandle() - { - if (hmac_ctx != nullptr) { - EVP_MAC_CTX_free(hmac_ctx); - } - if (mac != nullptr) { - EVP_MAC_free(mac); - } - if (ctx != nullptr) { - EVP_CIPHER_CTX_free(ctx); - } - } +static CipherHandle* +to_cipher_handle(EVP_CIPHER_CTX* ctx) +{ + return reinterpret_cast(ctx); +} - CipherHandle(const CipherHandle&) = delete; - CipherHandle& operator=(const CipherHandle&) = delete; -}; +void +CipherState::CipherDeleter::operator()(CipherHandle* h) const +{ + EVP_CIPHER_CTX_free(cipher_ctx(h)); +} void -CipherState::Deleter::operator()(CipherHandle* h) const +CipherState::HmacDeleter::operator()(HmacHandle* h) const { - delete h; + if (h != nullptr) { + EVP_MAC_CTX_free(h->ctx); + EVP_MAC_free(h->mac); + delete h; + } } -CipherState::CipherState(CipherHandle* h) - : handle(h) +CipherState::CipherState(CipherHandle* cipher, + HmacHandle* hmac, + CipherSuite suite_in) + : cipher_handle(cipher) + , hmac_handle(hmac) + , suite(suite_in) { } CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - return CipherState(new CipherHandle(suite, key, true)); + std::unique_ptr ctx( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + std::unique_ptr hmac; + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode) + if (1 != EVP_EncryptInit_ex( + ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + throw crypto_error(); + } + + // Initialize HMAC + hmac.reset(new HmacHandle()); + hmac->mac = EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr); + if (hmac->mac == nullptr) { + throw crypto_error(); + } + + hmac->ctx = EVP_MAC_CTX_new(hmac->mac); + if (hmac->ctx == nullptr) { + throw crypto_error(); + } + + auto digest_name = openssl_digest_name(suite); + std::array params = { + OSSL_PARAM_construct_utf8_string( + OSSL_ALG_PARAM_DIGEST, digest_name.data(), 0), + OSSL_PARAM_construct_end() + }; + + if (1 != EVP_MAC_init( + hmac->ctx, auth_key.data(), auth_key.size(), params.data())) { + throw crypto_error(); + } + } else { + // GCM: use full key + if (1 != + EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + throw crypto_error(); + } + } + + return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - return CipherState(new CipherHandle(suite, key, false)); + std::unique_ptr ctx( + EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + if (ctx == nullptr) { + throw crypto_error(); + } + + auto cipher = openssl_cipher(suite); + std::unique_ptr hmac; + + if (is_ctr_hmac_suite(suite)) { + // CTR+HMAC: key is split into enc_key and auth_key + auto enc_key_size = cipher_enc_key_size(suite); + auto enc_key = key.first(enc_key_size); + auto auth_key = key.subspan(enc_key_size); + + // Initialize AES-CTR context (always encrypt for CTR mode - CTR is + // symmetric) + if (1 != EVP_EncryptInit_ex( + ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + throw crypto_error(); + } + + // Initialize HMAC + hmac.reset(new HmacHandle()); + hmac->mac = EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr); + if (hmac->mac == nullptr) { + throw crypto_error(); + } + + hmac->ctx = EVP_MAC_CTX_new(hmac->mac); + if (hmac->ctx == nullptr) { + throw crypto_error(); + } + + auto digest_name = openssl_digest_name(suite); + std::array params = { + OSSL_PARAM_construct_utf8_string( + OSSL_ALG_PARAM_DIGEST, digest_name.data(), 0), + OSSL_PARAM_construct_end() + }; + + if (1 != EVP_MAC_init( + hmac->ctx, auth_key.data(), auth_key.size(), params.data())) { + throw crypto_error(); + } + } else { + // GCM: use full key + if (1 != + EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + throw crypto_error(); + } + } + + return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); } static output_bytes -seal_ctr_cached(CipherHandle* handle, +seal_ctr_cached(EVP_CIPHER_CTX* ctx, + HmacHandle* hmac, + CipherSuite suite, input_bytes nonce, output_bytes ct, input_bytes aad, input_bytes pt) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -176,8 +220,8 @@ seal_ctr_cached(CipherHandle* handle, padded_nonce.resize(16); // Reset AES-CTR context with new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + if (1 != + EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { throw crypto_error(); } @@ -186,17 +230,17 @@ seal_ctr_cached(CipherHandle* handle, int outlen = 0; auto pt_size_int = static_cast(pt.size()); if (1 != EVP_EncryptUpdate( - handle->ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { + ctx, inner_ct.data(), &outlen, pt.data(), pt_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } // Compute HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != EVP_MAC_init(handle->hmac_ctx, nullptr, 0, nullptr)) { + if (1 != EVP_MAC_init(hmac->ctx, nullptr, 0, nullptr)) { throw crypto_error(); } @@ -207,24 +251,23 @@ seal_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != - EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != EVP_MAC_update(hmac->ctx, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(handle->hmac_ctx, nonce.data(), nonce.size())) { + if (1 != EVP_MAC_update(hmac->ctx, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(handle->hmac_ctx, aad.data(), aad.size())) { + if (1 != EVP_MAC_update(hmac->ctx, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + if (1 != EVP_MAC_update(hmac->ctx, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != EVP_MAC_final( - handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != + EVP_MAC_final(hmac->ctx, mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -235,39 +278,38 @@ seal_ctr_cached(CipherHandle* handle, } static output_bytes -seal_aead_cached(CipherHandle* handle, +seal_aead_cached(EVP_CIPHER_CTX* ctx, + CipherSuite suite, input_bytes nonce, output_bytes ct, input_bytes aad, input_bytes pt) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } // Reset context and set new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { throw crypto_error(); } int outlen = 0; auto aad_size_int = static_cast(aad.size()); if (aad.size() > 0) { - if (1 != EVP_EncryptUpdate( - handle->ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + if (1 != + EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { throw crypto_error(); } } auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate( - handle->ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } @@ -275,7 +317,7 @@ seal_aead_cached(CipherHandle* handle, auto tag_ptr = const_cast(static_cast(tag.data())); auto tag_size_downcast = static_cast(tag.size()); if (1 != EVP_CIPHER_CTX_ctrl( - handle->ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { throw crypto_error(); } @@ -288,20 +330,23 @@ CipherState::seal(input_bytes nonce, input_bytes aad, input_bytes pt) { - if (is_ctr_hmac_suite(handle->suite)) { - return seal_ctr_cached(handle.get(), nonce, ct, aad, pt); + auto* ctx = cipher_ctx(cipher_handle.get()); + if (is_ctr_hmac_suite(suite)) { + return seal_ctr_cached(ctx, hmac_handle.get(), suite, nonce, ct, aad, pt); } - return seal_aead_cached(handle.get(), nonce, ct, aad, pt); + return seal_aead_cached(ctx, suite, nonce, ct, aad, pt); } static output_bytes -open_ctr_cached(CipherHandle* handle, +open_ctr_cached(EVP_CIPHER_CTX* ctx, + HmacHandle* hmac, + CipherSuite suite, input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -316,7 +361,7 @@ open_ctr_cached(CipherHandle* handle, // Verify HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != EVP_MAC_init(handle->hmac_ctx, nullptr, 0, nullptr)) { + if (1 != EVP_MAC_init(hmac->ctx, nullptr, 0, nullptr)) { throw crypto_error(); } @@ -327,24 +372,23 @@ open_ctr_cached(CipherHandle* handle, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != - EVP_MAC_update(handle->hmac_ctx, len_block.data(), len_block.size())) { + if (1 != EVP_MAC_update(hmac->ctx, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(handle->hmac_ctx, nonce.data(), nonce.size())) { + if (1 != EVP_MAC_update(hmac->ctx, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(handle->hmac_ctx, aad.data(), aad.size())) { + if (1 != EVP_MAC_update(hmac->ctx, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(handle->hmac_ctx, inner_ct.data(), inner_ct.size())) { + if (1 != EVP_MAC_update(hmac->ctx, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != EVP_MAC_final( - handle->hmac_ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != + EVP_MAC_final(hmac->ctx, mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -359,20 +403,19 @@ open_ctr_cached(CipherHandle* handle, padded_nonce.resize(16); // Reset AES-CTR context with new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { + if (1 != + EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, padded_nonce.data())) { throw crypto_error(); } int outlen = 0; auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != - EVP_EncryptUpdate( - handle->ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { + if (1 != EVP_EncryptUpdate( + ctx, pt.data(), &outlen, inner_ct.data(), inner_ct_size_int)) { throw crypto_error(); } - if (1 != EVP_EncryptFinal(handle->ctx, nullptr, &outlen)) { + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { throw crypto_error(); } @@ -380,13 +423,14 @@ open_ctr_cached(CipherHandle* handle, } static output_bytes -open_aead_cached(CipherHandle* handle, +open_aead_cached(EVP_CIPHER_CTX* ctx, + CipherSuite suite, input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto tag_size = cipher_overhead(handle->suite); + auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { throw buffer_too_small_error("Ciphertext buffer too small"); } @@ -397,8 +441,7 @@ open_aead_cached(CipherHandle* handle, } // Reset context and set new nonce (key is preserved) - if (1 != EVP_DecryptInit_ex( - handle->ctx, nullptr, nullptr, nullptr, nonce.data())) { + if (1 != EVP_DecryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { throw crypto_error(); } @@ -406,26 +449,26 @@ open_aead_cached(CipherHandle* handle, auto tag_ptr = const_cast(static_cast(tag.data())); auto tag_size_downcast = static_cast(tag.size()); if (1 != EVP_CIPHER_CTX_ctrl( - handle->ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { + ctx, EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { throw crypto_error(); } int out_size; auto aad_size_int = static_cast(aad.size()); if (aad.size() > 0) { - if (1 != EVP_DecryptUpdate( - handle->ctx, nullptr, &out_size, aad.data(), aad_size_int)) { + if (1 != + EVP_DecryptUpdate(ctx, nullptr, &out_size, aad.data(), aad_size_int)) { throw crypto_error(); } } auto inner_ct_size_int = static_cast(inner_ct_size); if (1 != EVP_DecryptUpdate( - handle->ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { + ctx, pt.data(), &out_size, ct.data(), inner_ct_size_int)) { throw crypto_error(); } - if (1 != EVP_DecryptFinal(handle->ctx, nullptr, &out_size)) { + if (1 != EVP_DecryptFinal(ctx, nullptr, &out_size)) { throw authentication_error(); } @@ -438,10 +481,11 @@ CipherState::open(input_bytes nonce, input_bytes aad, input_bytes ct) { - if (is_ctr_hmac_suite(handle->suite)) { - return open_ctr_cached(handle.get(), nonce, pt, aad, ct); + auto* ctx = cipher_ctx(cipher_handle.get()); + if (is_ctr_hmac_suite(suite)) { + return open_ctr_cached(ctx, hmac_handle.get(), suite, nonce, pt, aad, ct); } - return open_aead_cached(handle.get(), nonce, pt, aad, ct); + return open_aead_cached(ctx, suite, nonce, pt, aad, ct); } /// From 2d698461c1402ffc03a15dac4d1d4cef22533e3a Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 11:18:33 -1000 Subject: [PATCH 6/8] Refactor CipherState to use consolidated Deleter and direct casts - Consolidate CipherDeleter and HmacDeleter into single Deleter struct with overloaded operator() - For OpenSSL 1.1 and BoringSSL, HmacHandle is now a direct cast to HMAC_CTX (like CipherHandle to EVP_CIPHER_CTX) - For OpenSSL 3.x, HmacHandle struct uses unique_ptr for members - Move scoped typedefs to top of files, remove duplicates - Use scoped_evp_ctx and scoped_hmac_ctx in create_seal/create_open Co-Authored-By: Claude Opus 4.5 --- include/sframe/sframe.h | 10 ++-- src/crypto_boringssl.cpp | 96 +++++++++++++++++++------------------- src/crypto_openssl11.cpp | 99 ++++++++++++++++++++-------------------- src/crypto_openssl3.cpp | 72 +++++++++++++++-------------- 4 files changed, 142 insertions(+), 135 deletions(-) diff --git a/include/sframe/sframe.h b/include/sframe/sframe.h index dfc7092..3c00bef 100644 --- a/include/sframe/sframe.h +++ b/include/sframe/sframe.h @@ -112,18 +112,14 @@ struct CipherState input_bytes ct); private: - struct CipherDeleter + struct Deleter { void operator()(CipherHandle* h) const; - }; - - struct HmacDeleter - { void operator()(HmacHandle* h) const; }; - std::unique_ptr cipher_handle; - std::unique_ptr hmac_handle; // null for GCM + std::unique_ptr cipher_handle; + std::unique_ptr hmac_handle; // null for GCM CipherSuite suite; CipherState(CipherHandle* cipher, HmacHandle* hmac, CipherSuite suite); diff --git a/src/crypto_boringssl.cpp b/src/crypto_boringssl.cpp index 0641713..d19e109 100644 --- a/src/crypto_boringssl.cpp +++ b/src/crypto_boringssl.cpp @@ -35,16 +35,18 @@ is_ctr_hmac_suite(CipherSuite suite) } /// -/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle holds HMAC state +/// Scoped pointers for OpenSSL objects /// -// HmacHandle for BoringSSL wraps HMAC_CTX -struct HmacHandle -{ - HMAC_CTX* ctx; -}; +using scoped_evp_cipher_ctx = + std::unique_ptr; +using scoped_hmac_ctx = std::unique_ptr; -// Cast helpers - CipherHandle is EVP_CIPHER_CTX +/// +/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle is HMAC_CTX +/// + +// Cast helpers static EVP_CIPHER_CTX* cipher_ctx(CipherHandle* h) { @@ -57,19 +59,28 @@ to_cipher_handle(EVP_CIPHER_CTX* ctx) return reinterpret_cast(ctx); } +static HMAC_CTX* +hmac_ctx(HmacHandle* h) +{ + return reinterpret_cast(h); +} + +static HmacHandle* +to_hmac_handle(HMAC_CTX* ctx) +{ + return reinterpret_cast(ctx); +} + void -CipherState::CipherDeleter::operator()(CipherHandle* h) const +CipherState::Deleter::operator()(CipherHandle* h) const { EVP_CIPHER_CTX_free(cipher_ctx(h)); } void -CipherState::HmacDeleter::operator()(HmacHandle* h) const +CipherState::Deleter::operator()(HmacHandle* h) const { - if (h != nullptr) { - HMAC_CTX_free(h->ctx); - delete h; - } + HMAC_CTX_free(hmac_ctx(h)); } CipherState::CipherState(CipherHandle* cipher, @@ -84,14 +95,13 @@ CipherState::CipherState(CipherHandle* cipher, CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - std::unique_ptr ctx( - EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + scoped_evp_cipher_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -106,15 +116,14 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) } // Initialize HMAC - hmac.reset(new HmacHandle()); - hmac->ctx = HMAC_CTX_new(); - if (hmac->ctx == nullptr) { + hmac.reset(HMAC_CTX_new()); + if (hmac == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); if (1 != HMAC_Init_ex( - hmac->ctx, auth_key.data(), auth_key.size(), md, nullptr)) { + hmac.get(), auth_key.data(), auth_key.size(), md, nullptr)) { throw crypto_error(); } } else { @@ -125,20 +134,20 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) } } - return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); + return CipherState( + to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - std::unique_ptr ctx( - EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + scoped_evp_cipher_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -154,15 +163,14 @@ CipherState::create_open(CipherSuite suite, input_bytes key) } // Initialize HMAC - hmac.reset(new HmacHandle()); - hmac->ctx = HMAC_CTX_new(); - if (hmac->ctx == nullptr) { + hmac.reset(HMAC_CTX_new()); + if (hmac == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); if (1 != HMAC_Init_ex( - hmac->ctx, auth_key.data(), auth_key.size(), md, nullptr)) { + hmac.get(), auth_key.data(), auth_key.size(), md, nullptr)) { throw crypto_error(); } } else { @@ -173,7 +181,8 @@ CipherState::create_open(CipherSuite suite, input_bytes key) } } - return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); + return CipherState( + to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); } static output_bytes @@ -216,7 +225,7 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, // Compute HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -227,22 +236,22 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -336,7 +345,7 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, // Verify HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -347,22 +356,22 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -562,8 +571,6 @@ compute_tag(CipherSuite suite, input_bytes ct, size_t tag_size) { - using scoped_hmac_ctx = std::unique_ptr; - auto ctx = scoped_hmac_ctx(HMAC_CTX_new(), HMAC_CTX_free); const auto md = openssl_digest_type(suite); @@ -610,9 +617,6 @@ compute_tag(CipherSuite suite, return tag; } -using scoped_evp_cipher_ctx = - std::unique_ptr; - static void ctr_crypt(CipherSuite suite, input_bytes key, diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index 268cc37..d8ab621 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -33,16 +33,18 @@ is_ctr_hmac_suite(CipherSuite suite) } /// -/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle holds HMAC state +/// Scoped pointers for OpenSSL objects /// -// HmacHandle for OpenSSL 1.1 wraps HMAC_CTX -struct HmacHandle -{ - HMAC_CTX* ctx; -}; +using scoped_evp_ctx = + std::unique_ptr; +using scoped_hmac_ctx = std::unique_ptr; -// Cast helpers - CipherHandle is EVP_CIPHER_CTX +/// +/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle is HMAC_CTX +/// + +// Cast helpers static EVP_CIPHER_CTX* cipher_ctx(CipherHandle* h) { @@ -55,19 +57,28 @@ to_cipher_handle(EVP_CIPHER_CTX* ctx) return reinterpret_cast(ctx); } +static HMAC_CTX* +hmac_ctx(HmacHandle* h) +{ + return reinterpret_cast(h); +} + +static HmacHandle* +to_hmac_handle(HMAC_CTX* ctx) +{ + return reinterpret_cast(ctx); +} + void -CipherState::CipherDeleter::operator()(CipherHandle* h) const +CipherState::Deleter::operator()(CipherHandle* h) const { EVP_CIPHER_CTX_free(cipher_ctx(h)); } void -CipherState::HmacDeleter::operator()(HmacHandle* h) const +CipherState::Deleter::operator()(HmacHandle* h) const { - if (h != nullptr) { - HMAC_CTX_free(h->ctx); - delete h; - } + HMAC_CTX_free(hmac_ctx(h)); } CipherState::CipherState(CipherHandle* cipher, @@ -82,14 +93,13 @@ CipherState::CipherState(CipherHandle* cipher, CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - std::unique_ptr ctx( - EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + scoped_evp_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -104,15 +114,14 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) } // Initialize HMAC - hmac.reset(new HmacHandle()); - hmac->ctx = HMAC_CTX_new(); - if (hmac->ctx == nullptr) { + hmac.reset(HMAC_CTX_new()); + if (hmac == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); auto key_size = static_cast(auth_key.size()); - if (1 != HMAC_Init_ex(hmac->ctx, auth_key.data(), key_size, md, nullptr)) { + if (1 != HMAC_Init_ex(hmac.get(), auth_key.data(), key_size, md, nullptr)) { throw crypto_error(); } } else { @@ -123,20 +132,20 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) } } - return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); + return CipherState( + to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - std::unique_ptr ctx( - EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); + scoped_evp_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); if (ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -152,15 +161,14 @@ CipherState::create_open(CipherSuite suite, input_bytes key) } // Initialize HMAC - hmac.reset(new HmacHandle()); - hmac->ctx = HMAC_CTX_new(); - if (hmac->ctx == nullptr) { + hmac.reset(HMAC_CTX_new()); + if (hmac == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); auto key_size = static_cast(auth_key.size()); - if (1 != HMAC_Init_ex(hmac->ctx, auth_key.data(), key_size, md, nullptr)) { + if (1 != HMAC_Init_ex(hmac.get(), auth_key.data(), key_size, md, nullptr)) { throw crypto_error(); } } else { @@ -171,7 +179,8 @@ CipherState::create_open(CipherSuite suite, input_bytes key) } } - return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); + return CipherState( + to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); } static output_bytes @@ -214,7 +223,7 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, // Compute HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -225,22 +234,22 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -334,7 +343,7 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, // Verify HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac->ctx, nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -345,22 +354,22 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac->ctx, len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac->ctx, inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac->ctx, mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -460,14 +469,6 @@ CipherState::open(input_bytes nonce, return open_aead_cached(ctx, suite, nonce, pt, aad, ct); } -/// -/// Scoped pointers for OpenSSL objects -/// - -using scoped_evp_ctx = - std::unique_ptr; -using scoped_hmac_ctx = std::unique_ptr; - /// /// Convert between native identifiers / errors and OpenSSL ones /// diff --git a/src/crypto_openssl3.cpp b/src/crypto_openssl3.cpp index 61a9ae1..74780b0 100644 --- a/src/crypto_openssl3.cpp +++ b/src/crypto_openssl3.cpp @@ -41,8 +41,14 @@ is_ctr_hmac_suite(CipherSuite suite) // HmacHandle for OpenSSL 3.x holds both the MAC algorithm and context struct HmacHandle { - EVP_MAC* mac; - EVP_MAC_CTX* ctx; + std::unique_ptr mac; + std::unique_ptr ctx; + + HmacHandle() + : mac(nullptr, EVP_MAC_free) + , ctx(nullptr, EVP_MAC_CTX_free) + { + } }; // Cast helpers - CipherHandle is EVP_CIPHER_CTX @@ -59,19 +65,15 @@ to_cipher_handle(EVP_CIPHER_CTX* ctx) } void -CipherState::CipherDeleter::operator()(CipherHandle* h) const +CipherState::Deleter::operator()(CipherHandle* h) const { EVP_CIPHER_CTX_free(cipher_ctx(h)); } void -CipherState::HmacDeleter::operator()(HmacHandle* h) const +CipherState::Deleter::operator()(HmacHandle* h) const { - if (h != nullptr) { - EVP_MAC_CTX_free(h->ctx); - EVP_MAC_free(h->mac); - delete h; - } + delete h; // unique_ptr members clean up automatically } CipherState::CipherState(CipherHandle* cipher, @@ -93,7 +95,7 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + std::unique_ptr hmac; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -109,12 +111,12 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) // Initialize HMAC hmac.reset(new HmacHandle()); - hmac->mac = EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr); + hmac->mac.reset(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); if (hmac->mac == nullptr) { throw crypto_error(); } - hmac->ctx = EVP_MAC_CTX_new(hmac->mac); + hmac->ctx.reset(EVP_MAC_CTX_new(hmac->mac.get())); if (hmac->ctx == nullptr) { throw crypto_error(); } @@ -126,8 +128,9 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) OSSL_PARAM_construct_end() }; - if (1 != EVP_MAC_init( - hmac->ctx, auth_key.data(), auth_key.size(), params.data())) { + if (1 != + EVP_MAC_init( + hmac->ctx.get(), auth_key.data(), auth_key.size(), params.data())) { throw crypto_error(); } } else { @@ -151,7 +154,7 @@ CipherState::create_open(CipherSuite suite, input_bytes key) } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + std::unique_ptr hmac; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -168,12 +171,12 @@ CipherState::create_open(CipherSuite suite, input_bytes key) // Initialize HMAC hmac.reset(new HmacHandle()); - hmac->mac = EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr); + hmac->mac.reset(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); if (hmac->mac == nullptr) { throw crypto_error(); } - hmac->ctx = EVP_MAC_CTX_new(hmac->mac); + hmac->ctx.reset(EVP_MAC_CTX_new(hmac->mac.get())); if (hmac->ctx == nullptr) { throw crypto_error(); } @@ -185,8 +188,9 @@ CipherState::create_open(CipherSuite suite, input_bytes key) OSSL_PARAM_construct_end() }; - if (1 != EVP_MAC_init( - hmac->ctx, auth_key.data(), auth_key.size(), params.data())) { + if (1 != + EVP_MAC_init( + hmac->ctx.get(), auth_key.data(), auth_key.size(), params.data())) { throw crypto_error(); } } else { @@ -240,7 +244,7 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, // Compute HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != EVP_MAC_init(hmac->ctx, nullptr, 0, nullptr)) { + if (1 != EVP_MAC_init(hmac->ctx.get(), nullptr, 0, nullptr)) { throw crypto_error(); } @@ -251,23 +255,24 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != EVP_MAC_update(hmac->ctx, len_block.data(), len_block.size())) { + if (1 != + EVP_MAC_update(hmac->ctx.get(), len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx, nonce.data(), nonce.size())) { + if (1 != EVP_MAC_update(hmac->ctx.get(), nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx, aad.data(), aad.size())) { + if (1 != EVP_MAC_update(hmac->ctx.get(), aad.data(), aad.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx, inner_ct.data(), inner_ct.size())) { + if (1 != EVP_MAC_update(hmac->ctx.get(), inner_ct.data(), inner_ct.size())) { throw crypto_error(); } size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != - EVP_MAC_final(hmac->ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != EVP_MAC_final( + hmac->ctx.get(), mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -361,7 +366,7 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, // Verify HMAC tag using cached context // Reset HMAC context (key is preserved from init) - if (1 != EVP_MAC_init(hmac->ctx, nullptr, 0, nullptr)) { + if (1 != EVP_MAC_init(hmac->ctx.get(), nullptr, 0, nullptr)) { throw crypto_error(); } @@ -372,23 +377,24 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != EVP_MAC_update(hmac->ctx, len_block.data(), len_block.size())) { + if (1 != + EVP_MAC_update(hmac->ctx.get(), len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx, nonce.data(), nonce.size())) { + if (1 != EVP_MAC_update(hmac->ctx.get(), nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx, aad.data(), aad.size())) { + if (1 != EVP_MAC_update(hmac->ctx.get(), aad.data(), aad.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx, inner_ct.data(), inner_ct.size())) { + if (1 != EVP_MAC_update(hmac->ctx.get(), inner_ct.data(), inner_ct.size())) { throw crypto_error(); } size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != - EVP_MAC_final(hmac->ctx, mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != EVP_MAC_final( + hmac->ctx.get(), mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } From 2518f1415da5302caa00de75ccc48c80ebd1b5ad Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 11:54:21 -1000 Subject: [PATCH 7/8] Eliminate reinterpret_cast and consolidate duplicate AEAD code - Define CipherHandle and HmacHandle as actual structs wrapping unique_ptr members (no more reinterpret_cast) - Remove forward declarations, move helper functions earlier in file - Consolidate seal_ctr/open_ctr/seal_aead/open_aead to take contexts - Stateless seal/open now create temporary CipherState and delegate - Removes ~840 lines of duplicate code Co-Authored-By: Claude Opus 4.5 --- src/crypto_boringssl.cpp | 753 ++++++++++++--------------------------- src/crypto_openssl11.cpp | 723 ++++++++++++------------------------- src/crypto_openssl3.cpp | 747 ++++++++++++-------------------------- 3 files changed, 691 insertions(+), 1532 deletions(-) diff --git a/src/crypto_boringssl.cpp b/src/crypto_boringssl.cpp index d19e109..1752fb0 100644 --- a/src/crypto_boringssl.cpp +++ b/src/crypto_boringssl.cpp @@ -12,75 +12,105 @@ namespace SFRAME_NAMESPACE { /// -/// Forward declarations +/// Scoped pointers for OpenSSL objects /// -static const EVP_CIPHER* -openssl_cipher(CipherSuite suite); +using scoped_evp_cipher_ctx = + std::unique_ptr; +using scoped_hmac_ctx = std::unique_ptr; -static const EVP_MD* -openssl_digest_type(CipherSuite suite); +/// +/// Convert between native identifiers / errors and OpenSSL ones +/// -static bool -is_ctr_hmac_suite(CipherSuite suite) +crypto_error::crypto_error() + : std::runtime_error(ERR_error_string(ERR_get_error(), nullptr)) +{ +} + +static const EVP_MD* +openssl_digest_type(CipherSuite suite) { switch (suite) { case CipherSuite::AES_128_CTR_HMAC_SHA256_80: case CipherSuite::AES_128_CTR_HMAC_SHA256_64: case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - return true; + case CipherSuite::AES_GCM_128_SHA256: + return EVP_sha256(); + + case CipherSuite::AES_GCM_256_SHA512: + return EVP_sha512(); + default: - return false; + throw unsupported_ciphersuite_error(); } } -/// -/// Scoped pointers for OpenSSL objects -/// +static const EVP_CIPHER* +openssl_cipher(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return EVP_aes_128_ctr(); -using scoped_evp_cipher_ctx = - std::unique_ptr; -using scoped_hmac_ctx = std::unique_ptr; + case CipherSuite::AES_GCM_128_SHA256: + return EVP_aes_128_gcm(); -/// -/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle is HMAC_CTX -/// + case CipherSuite::AES_GCM_256_SHA512: + return EVP_aes_256_gcm(); -// Cast helpers -static EVP_CIPHER_CTX* -cipher_ctx(CipherHandle* h) -{ - return reinterpret_cast(h); + default: + throw unsupported_ciphersuite_error(); + } } -static CipherHandle* -to_cipher_handle(EVP_CIPHER_CTX* ctx) +static bool +is_ctr_hmac_suite(CipherSuite suite) { - return reinterpret_cast(ctx); + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return true; + default: + return false; + } } -static HMAC_CTX* -hmac_ctx(HmacHandle* h) +/// +/// CipherHandle and HmacHandle definitions +/// + +struct CipherHandle { - return reinterpret_cast(h); -} + scoped_evp_cipher_ctx ctx; + CipherHandle() + : ctx(nullptr, EVP_CIPHER_CTX_free) + { + } +}; -static HmacHandle* -to_hmac_handle(HMAC_CTX* ctx) +struct HmacHandle { - return reinterpret_cast(ctx); -} + scoped_hmac_ctx ctx; + HmacHandle() + : ctx(nullptr, HMAC_CTX_free) + { + } +}; void CipherState::Deleter::operator()(CipherHandle* h) const { - EVP_CIPHER_CTX_free(cipher_ctx(h)); + delete h; } void CipherState::Deleter::operator()(HmacHandle* h) const { - HMAC_CTX_free(hmac_ctx(h)); + delete h; } CipherState::CipherState(CipherHandle* cipher, @@ -95,13 +125,14 @@ CipherState::CipherState(CipherHandle* cipher, CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - scoped_evp_cipher_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx == nullptr) { + auto cipher_h = std::make_unique(); + cipher_h->ctx.reset(EVP_CIPHER_CTX_new()); + if (cipher_h->ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); + std::unique_ptr hmac_h; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -111,43 +142,45 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) // Initialize AES-CTR context (always encrypt for CTR mode) if (1 != EVP_EncryptInit_ex( - ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + cipher_h->ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { throw crypto_error(); } // Initialize HMAC - hmac.reset(HMAC_CTX_new()); - if (hmac == nullptr) { + hmac_h = std::make_unique(); + hmac_h->ctx.reset(HMAC_CTX_new()); + if (hmac_h->ctx == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); - if (1 != HMAC_Init_ex( - hmac.get(), auth_key.data(), auth_key.size(), md, nullptr)) { + if (1 != + HMAC_Init_ex( + hmac_h->ctx.get(), auth_key.data(), auth_key.size(), md, nullptr)) { throw crypto_error(); } } else { // GCM: use full key - if (1 != - EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + if (1 != EVP_EncryptInit_ex( + cipher_h->ctx.get(), cipher, nullptr, key.data(), nullptr)) { throw crypto_error(); } } - return CipherState( - to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); + return CipherState(cipher_h.release(), hmac_h.release(), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - scoped_evp_cipher_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx == nullptr) { + auto cipher_h = std::make_unique(); + cipher_h->ctx.reset(EVP_CIPHER_CTX_new()); + if (cipher_h->ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); + std::unique_ptr hmac_h; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -158,41 +191,46 @@ CipherState::create_open(CipherSuite suite, input_bytes key) // Initialize AES-CTR context (always encrypt for CTR mode - CTR is // symmetric) if (1 != EVP_EncryptInit_ex( - ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + cipher_h->ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { throw crypto_error(); } // Initialize HMAC - hmac.reset(HMAC_CTX_new()); - if (hmac == nullptr) { + hmac_h = std::make_unique(); + hmac_h->ctx.reset(HMAC_CTX_new()); + if (hmac_h->ctx == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); - if (1 != HMAC_Init_ex( - hmac.get(), auth_key.data(), auth_key.size(), md, nullptr)) { + if (1 != + HMAC_Init_ex( + hmac_h->ctx.get(), auth_key.data(), auth_key.size(), md, nullptr)) { throw crypto_error(); } } else { // GCM: use full key - if (1 != - EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + if (1 != EVP_DecryptInit_ex( + cipher_h->ctx.get(), cipher, nullptr, key.data(), nullptr)) { throw crypto_error(); } } - return CipherState( - to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); + return CipherState(cipher_h.release(), hmac_h.release(), suite); } +/// +/// AEAD Algorithms - CTR+HMAC +/// + static output_bytes -seal_ctr_cached(EVP_CIPHER_CTX* ctx, - HmacHandle* hmac, - CipherSuite suite, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) +seal_ctr(EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac, + CipherSuite suite, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) { auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { @@ -223,9 +261,9 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, throw crypto_error(); } - // Compute HMAC tag using cached context + // Compute HMAC tag // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -236,22 +274,22 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -262,73 +300,13 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, } static output_bytes -seal_aead_cached(EVP_CIPHER_CTX* ctx, - CipherSuite suite, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - // Reset context and set new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != - EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { - throw crypto_error(); - } - - if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { - throw crypto_error(); - } - - auto tag = ct.subspan(pt.size(), tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - return ct.subspan(0, pt.size() + tag_size); -} - -output_bytes -CipherState::seal(input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto* ctx = cipher_ctx(cipher_handle.get()); - if (is_ctr_hmac_suite(suite)) { - return seal_ctr_cached(ctx, hmac_handle.get(), suite, nonce, ct, aad, pt); - } - return seal_aead_cached(ctx, suite, nonce, ct, aad, pt); -} - -static output_bytes -open_ctr_cached(EVP_CIPHER_CTX* ctx, - HmacHandle* hmac, - CipherSuite suite, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) +open_ctr(EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac, + CipherSuite suite, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { @@ -343,9 +321,9 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, auto inner_ct = ct.subspan(0, inner_ct_size); auto tag = ct.subspan(inner_ct_size, tag_size); - // Verify HMAC tag using cached context + // Verify HMAC tag // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -356,22 +334,22 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -405,13 +383,64 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, return pt.subspan(0, inner_ct_size); } +/// +/// AEAD Algorithms - GCM +/// + +static output_bytes +seal_aead(EVP_CIPHER_CTX* ctx, + CipherSuite suite, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != + EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + return ct.subspan(0, pt.size() + tag_size); +} + static output_bytes -open_aead_cached(EVP_CIPHER_CTX* ctx, - CipherSuite suite, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) +open_aead(EVP_CIPHER_CTX* ctx, + CipherSuite suite, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { @@ -458,64 +487,72 @@ open_aead_cached(EVP_CIPHER_CTX* ctx, return pt.subspan(0, inner_ct_size); } +/// +/// CipherState seal/open methods +/// + +output_bytes +CipherState::seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + if (is_ctr_hmac_suite(suite)) { + return seal_ctr(cipher_handle->ctx.get(), + hmac_handle->ctx.get(), + suite, + nonce, + ct, + aad, + pt); + } + return seal_aead(cipher_handle->ctx.get(), suite, nonce, ct, aad, pt); +} + output_bytes CipherState::open(input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto* ctx = cipher_ctx(cipher_handle.get()); if (is_ctr_hmac_suite(suite)) { - return open_ctr_cached(ctx, hmac_handle.get(), suite, nonce, pt, aad, ct); - } - return open_aead_cached(ctx, suite, nonce, pt, aad, ct); + return open_ctr(cipher_handle->ctx.get(), + hmac_handle->ctx.get(), + suite, + nonce, + pt, + aad, + ct); + } + return open_aead(cipher_handle->ctx.get(), suite, nonce, pt, aad, ct); } /// -/// Convert between native identifiers / errors and OpenSSL ones +/// Stateless seal/open (used by test vectors) /// -crypto_error::crypto_error() - : std::runtime_error(ERR_error_string(ERR_get_error(), nullptr)) +output_bytes +seal(CipherSuite suite, + input_bytes key, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) { + auto state = CipherState::create_seal(suite, key); + return state.seal(nonce, ct, aad, pt); } -static const EVP_MD* -openssl_digest_type(CipherSuite suite) +output_bytes +open(CipherSuite suite, + input_bytes key, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - case CipherSuite::AES_GCM_128_SHA256: - return EVP_sha256(); - - case CipherSuite::AES_GCM_256_SHA512: - return EVP_sha512(); - - default: - throw unsupported_ciphersuite_error(); - } -} - -static const EVP_CIPHER* -openssl_cipher(CipherSuite suite) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - return EVP_aes_128_ctr(); - - case CipherSuite::AES_GCM_128_SHA256: - return EVP_aes_128_gcm(); - - case CipherSuite::AES_GCM_256_SHA512: - return EVP_aes_256_gcm(); - - default: - throw unsupported_ciphersuite_error(); - } + auto state = CipherState::create_open(suite, key); + return state.open(nonce, pt, aad, ct); } /// @@ -559,330 +596,6 @@ hkdf_expand(CipherSuite suite, input_bytes prk, input_bytes info, size_t size) return out; } -/// -/// AEAD Algorithms -/// - -static owned_bytes<64> -compute_tag(CipherSuite suite, - input_bytes auth_key, - input_bytes nonce, - input_bytes aad, - input_bytes ct, - size_t tag_size) -{ - auto ctx = scoped_hmac_ctx(HMAC_CTX_new(), HMAC_CTX_free); - const auto md = openssl_digest_type(suite); - - // Guard against sending nullptr to HMAC_Init_ex - const auto* key_data = auth_key.data(); - auto key_size = static_cast(auth_key.size()); - const auto non_null_zero_length_key = uint8_t(0); - if (key_data == nullptr) { - key_data = &non_null_zero_length_key; - } - - if (1 != HMAC_Init_ex(ctx.get(), key_data, key_size, md, nullptr)) { - throw crypto_error(); - } - - auto len_block = owned_bytes<24>(); - auto len_view = output_bytes(len_block); - encode_uint(aad.size(), len_view.first(8)); - encode_uint(ct.size(), len_view.first(16).last(8)); - encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(ctx.get(), len_block.data(), len_block.size())) { - throw crypto_error(); - } - - if (1 != HMAC_Update(ctx.get(), nonce.data(), nonce.size())) { - throw crypto_error(); - } - - if (1 != HMAC_Update(ctx.get(), aad.data(), aad.size())) { - throw crypto_error(); - } - - if (1 != HMAC_Update(ctx.get(), ct.data(), ct.size())) { - throw crypto_error(); - } - - auto tag = owned_bytes<64>(); - auto size = static_cast(EVP_MD_size(md)); - if (1 != HMAC_Final(ctx.get(), tag.data(), &size)) { - throw crypto_error(); - } - - tag.resize(tag_size); - return tag; -} - -static void -ctr_crypt(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes out, - input_bytes in) -{ - if (out.size() != in.size()) { - throw buffer_too_small_error("CTR size mismatch"); - } - - auto ctx = scoped_evp_cipher_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto padded_nonce = owned_bytes<16>(0); - padded_nonce.append(nonce); - padded_nonce.resize(16); - - auto cipher = openssl_cipher(suite); - if (1 != - EVP_EncryptInit(ctx.get(), cipher, key.data(), padded_nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto in_size_int = static_cast(in.size()); - if (1 != EVP_EncryptUpdate( - ctx.get(), out.data(), &outlen, in.data(), in_size_int)) { - throw crypto_error(); - } - - if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { - throw crypto_error(); - } -} - -static output_bytes -seal_ctr(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - // Split the key into enc and auth subkeys - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Encrypt with AES-CM - auto inner_ct = ct.subspan(0, pt.size()); - ctr_crypt(suite, enc_key, nonce, inner_ct, pt); - - // Authenticate with truncated HMAC - auto mac = compute_tag(suite, auth_key, nonce, aad, inner_ct, tag_size); - auto tag = ct.subspan(pt.size(), tag_size); - std::copy(mac.begin(), mac.begin() + tag_size, tag.begin()); - - return ct.subspan(0, pt.size() + tag_size); -} - -static output_bytes -seal_aead(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto ctx = scoped_evp_cipher_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto cipher = openssl_cipher(suite); - if (1 != EVP_EncryptInit(ctx.get(), cipher, key.data(), nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != EVP_EncryptUpdate( - ctx.get(), nullptr, &outlen, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate( - ctx.get(), ct.data(), &outlen, pt.data(), pt_size_int)) { - throw crypto_error(); - } - - // Providing nullptr as an argument is safe here because this - // function never writes with GCM; it only computes the tag - if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { - throw crypto_error(); - } - - auto tag = ct.subspan(pt.size(), tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - return ct.subspan(0, pt.size() + tag_size); -} - -output_bytes -seal(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: { - return seal_ctr(suite, key, nonce, ct, aad, pt); - } - - case CipherSuite::AES_GCM_128_SHA256: - case CipherSuite::AES_GCM_256_SHA512: { - return seal_aead(suite, key, nonce, ct, aad, pt); - } - } - - throw unsupported_ciphersuite_error(); -} - -static output_bytes -open_ctr(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto inner_ct_size = ct.size() - tag_size; - auto inner_ct = ct.subspan(0, inner_ct_size); - auto tag = ct.subspan(inner_ct_size, tag_size); - - // Split the key into enc and auth subkeys - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Authenticate with truncated HMAC - auto mac = compute_tag(suite, auth_key, nonce, aad, inner_ct, tag_size); - if (CRYPTO_memcmp(mac.data(), tag.data(), tag.size()) != 0) { - throw authentication_error(); - } - - // Decrypt with AES-CTR - const auto pt_out = pt.first(inner_ct_size); - ctr_crypt(suite, enc_key, nonce, pt_out, ct.first(inner_ct_size)); - - return pt_out; -} - -static output_bytes -open_aead(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto inner_ct_size = ct.size() - tag_size; - if (pt.size() < inner_ct_size) { - throw buffer_too_small_error("Plaintext buffer too small"); - } - - auto ctx = scoped_evp_cipher_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto cipher = openssl_cipher(suite); - if (1 != EVP_DecryptInit(ctx.get(), cipher, key.data(), nonce.data())) { - throw crypto_error(); - } - - auto tag = ct.subspan(inner_ct_size, tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx.get(), EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - int out_size; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != EVP_DecryptUpdate( - ctx.get(), nullptr, &out_size, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != EVP_DecryptUpdate( - ctx.get(), pt.data(), &out_size, ct.data(), inner_ct_size_int)) { - throw crypto_error(); - } - - // Providing nullptr as an argument is safe here because this - // function never writes with GCM; it only verifies the tag - if (1 != EVP_DecryptFinal(ctx.get(), nullptr, &out_size)) { - throw authentication_error(); - } - - return pt.subspan(0, inner_ct_size); -} - -output_bytes -open(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: { - return open_ctr(suite, key, nonce, pt, aad, ct); - } - - case CipherSuite::AES_GCM_128_SHA256: - case CipherSuite::AES_GCM_256_SHA512: { - return open_aead(suite, key, nonce, pt, aad, ct); - } - } - - throw unsupported_ciphersuite_error(); -} - } // namespace SFRAME_NAMESPACE #endif // defined(BORINGSSL) diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index d8ab621..6a4c41d 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -10,75 +10,105 @@ namespace SFRAME_NAMESPACE { /// -/// Forward declarations +/// Scoped pointers for OpenSSL objects /// -static const EVP_CIPHER* -openssl_cipher(CipherSuite suite); +using scoped_evp_ctx = + std::unique_ptr; +using scoped_hmac_ctx = std::unique_ptr; -static const EVP_MD* -openssl_digest_type(CipherSuite suite); +/// +/// Convert between native identifiers / errors and OpenSSL ones +/// -static bool -is_ctr_hmac_suite(CipherSuite suite) +crypto_error::crypto_error() + : std::runtime_error(ERR_error_string(ERR_get_error(), nullptr)) +{ +} + +static const EVP_MD* +openssl_digest_type(CipherSuite suite) { switch (suite) { case CipherSuite::AES_128_CTR_HMAC_SHA256_80: case CipherSuite::AES_128_CTR_HMAC_SHA256_64: case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - return true; + case CipherSuite::AES_GCM_128_SHA256: + return EVP_sha256(); + + case CipherSuite::AES_GCM_256_SHA512: + return EVP_sha512(); + default: - return false; + throw unsupported_ciphersuite_error(); } } -/// -/// Scoped pointers for OpenSSL objects -/// +static const EVP_CIPHER* +openssl_cipher(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return EVP_aes_128_ctr(); -using scoped_evp_ctx = - std::unique_ptr; -using scoped_hmac_ctx = std::unique_ptr; + case CipherSuite::AES_GCM_128_SHA256: + return EVP_aes_128_gcm(); -/// -/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle is HMAC_CTX -/// + case CipherSuite::AES_GCM_256_SHA512: + return EVP_aes_256_gcm(); -// Cast helpers -static EVP_CIPHER_CTX* -cipher_ctx(CipherHandle* h) -{ - return reinterpret_cast(h); + default: + throw unsupported_ciphersuite_error(); + } } -static CipherHandle* -to_cipher_handle(EVP_CIPHER_CTX* ctx) +static bool +is_ctr_hmac_suite(CipherSuite suite) { - return reinterpret_cast(ctx); + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return true; + default: + return false; + } } -static HMAC_CTX* -hmac_ctx(HmacHandle* h) +/// +/// CipherHandle and HmacHandle definitions +/// + +struct CipherHandle { - return reinterpret_cast(h); -} + scoped_evp_ctx ctx; + CipherHandle() + : ctx(nullptr, EVP_CIPHER_CTX_free) + { + } +}; -static HmacHandle* -to_hmac_handle(HMAC_CTX* ctx) +struct HmacHandle { - return reinterpret_cast(ctx); -} + scoped_hmac_ctx ctx; + HmacHandle() + : ctx(nullptr, HMAC_CTX_free) + { + } +}; void CipherState::Deleter::operator()(CipherHandle* h) const { - EVP_CIPHER_CTX_free(cipher_ctx(h)); + delete h; } void CipherState::Deleter::operator()(HmacHandle* h) const { - HMAC_CTX_free(hmac_ctx(h)); + delete h; } CipherState::CipherState(CipherHandle* cipher, @@ -93,13 +123,14 @@ CipherState::CipherState(CipherHandle* cipher, CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - scoped_evp_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx == nullptr) { + auto cipher_h = std::make_unique(); + cipher_h->ctx.reset(EVP_CIPHER_CTX_new()); + if (cipher_h->ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); + std::unique_ptr hmac_h; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -109,43 +140,45 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) // Initialize AES-CTR context (always encrypt for CTR mode) if (1 != EVP_EncryptInit_ex( - ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + cipher_h->ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { throw crypto_error(); } // Initialize HMAC - hmac.reset(HMAC_CTX_new()); - if (hmac == nullptr) { + hmac_h = std::make_unique(); + hmac_h->ctx.reset(HMAC_CTX_new()); + if (hmac_h->ctx == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); auto key_size = static_cast(auth_key.size()); - if (1 != HMAC_Init_ex(hmac.get(), auth_key.data(), key_size, md, nullptr)) { + if (1 != HMAC_Init_ex( + hmac_h->ctx.get(), auth_key.data(), key_size, md, nullptr)) { throw crypto_error(); } } else { // GCM: use full key - if (1 != - EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + if (1 != EVP_EncryptInit_ex( + cipher_h->ctx.get(), cipher, nullptr, key.data(), nullptr)) { throw crypto_error(); } } - return CipherState( - to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); + return CipherState(cipher_h.release(), hmac_h.release(), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - scoped_evp_ctx ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx == nullptr) { + auto cipher_h = std::make_unique(); + cipher_h->ctx.reset(EVP_CIPHER_CTX_new()); + if (cipher_h->ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - scoped_hmac_ctx hmac(nullptr, HMAC_CTX_free); + std::unique_ptr hmac_h; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -156,41 +189,46 @@ CipherState::create_open(CipherSuite suite, input_bytes key) // Initialize AES-CTR context (always encrypt for CTR mode - CTR is // symmetric) if (1 != EVP_EncryptInit_ex( - ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + cipher_h->ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { throw crypto_error(); } // Initialize HMAC - hmac.reset(HMAC_CTX_new()); - if (hmac == nullptr) { + hmac_h = std::make_unique(); + hmac_h->ctx.reset(HMAC_CTX_new()); + if (hmac_h->ctx == nullptr) { throw crypto_error(); } const auto* md = openssl_digest_type(suite); auto key_size = static_cast(auth_key.size()); - if (1 != HMAC_Init_ex(hmac.get(), auth_key.data(), key_size, md, nullptr)) { + if (1 != HMAC_Init_ex( + hmac_h->ctx.get(), auth_key.data(), key_size, md, nullptr)) { throw crypto_error(); } } else { // GCM: use full key - if (1 != - EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + if (1 != EVP_DecryptInit_ex( + cipher_h->ctx.get(), cipher, nullptr, key.data(), nullptr)) { throw crypto_error(); } } - return CipherState( - to_cipher_handle(ctx.release()), to_hmac_handle(hmac.release()), suite); + return CipherState(cipher_h.release(), hmac_h.release(), suite); } +/// +/// AEAD Algorithms - CTR+HMAC +/// + static output_bytes -seal_ctr_cached(EVP_CIPHER_CTX* ctx, - HmacHandle* hmac, - CipherSuite suite, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) +seal_ctr(EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac, + CipherSuite suite, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) { auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { @@ -221,9 +259,9 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, throw crypto_error(); } - // Compute HMAC tag using cached context + // Compute HMAC tag // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -234,22 +272,22 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -260,73 +298,13 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, } static output_bytes -seal_aead_cached(EVP_CIPHER_CTX* ctx, - CipherSuite suite, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - // Reset context and set new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != - EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { - throw crypto_error(); - } - - if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { - throw crypto_error(); - } - - auto tag = ct.subspan(pt.size(), tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - return ct.subspan(0, pt.size() + tag_size); -} - -output_bytes -CipherState::seal(input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto* ctx = cipher_ctx(cipher_handle.get()); - if (is_ctr_hmac_suite(suite)) { - return seal_ctr_cached(ctx, hmac_handle.get(), suite, nonce, ct, aad, pt); - } - return seal_aead_cached(ctx, suite, nonce, ct, aad, pt); -} - -static output_bytes -open_ctr_cached(EVP_CIPHER_CTX* ctx, - HmacHandle* hmac, - CipherSuite suite, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) +open_ctr(EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac, + CipherSuite suite, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { @@ -341,9 +319,9 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, auto inner_ct = ct.subspan(0, inner_ct_size); auto tag = ct.subspan(inner_ct_size, tag_size); - // Verify HMAC tag using cached context + // Verify HMAC tag // Reset HMAC context (key is preserved from init) - if (1 != HMAC_Init_ex(hmac_ctx(hmac), nullptr, 0, nullptr, nullptr)) { + if (1 != HMAC_Init_ex(hmac, nullptr, 0, nullptr, nullptr)) { throw crypto_error(); } @@ -354,22 +332,22 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != HMAC_Update(hmac_ctx(hmac), len_block.data(), len_block.size())) { + if (1 != HMAC_Update(hmac, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), nonce.data(), nonce.size())) { + if (1 != HMAC_Update(hmac, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), aad.data(), aad.size())) { + if (1 != HMAC_Update(hmac, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != HMAC_Update(hmac_ctx(hmac), inner_ct.data(), inner_ct.size())) { + if (1 != HMAC_Update(hmac, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } auto mac_buf = owned_bytes<64>(); unsigned int mac_size = mac_buf.size(); - if (1 != HMAC_Final(hmac_ctx(hmac), mac_buf.data(), &mac_size)) { + if (1 != HMAC_Final(hmac, mac_buf.data(), &mac_size)) { throw crypto_error(); } @@ -403,13 +381,64 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, return pt.subspan(0, inner_ct_size); } +/// +/// AEAD Algorithms - GCM +/// + +static output_bytes +seal_aead(EVP_CIPHER_CTX* ctx, + CipherSuite suite, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != + EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + return ct.subspan(0, pt.size() + tag_size); +} + static output_bytes -open_aead_cached(EVP_CIPHER_CTX* ctx, - CipherSuite suite, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) +open_aead(EVP_CIPHER_CTX* ctx, + CipherSuite suite, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { @@ -456,68 +485,76 @@ open_aead_cached(EVP_CIPHER_CTX* ctx, return pt.subspan(0, inner_ct_size); } +/// +/// CipherState seal/open methods +/// + +output_bytes +CipherState::seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + if (is_ctr_hmac_suite(suite)) { + return seal_ctr(cipher_handle->ctx.get(), + hmac_handle->ctx.get(), + suite, + nonce, + ct, + aad, + pt); + } + return seal_aead(cipher_handle->ctx.get(), suite, nonce, ct, aad, pt); +} + output_bytes CipherState::open(input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto* ctx = cipher_ctx(cipher_handle.get()); if (is_ctr_hmac_suite(suite)) { - return open_ctr_cached(ctx, hmac_handle.get(), suite, nonce, pt, aad, ct); - } - return open_aead_cached(ctx, suite, nonce, pt, aad, ct); + return open_ctr(cipher_handle->ctx.get(), + hmac_handle->ctx.get(), + suite, + nonce, + pt, + aad, + ct); + } + return open_aead(cipher_handle->ctx.get(), suite, nonce, pt, aad, ct); } /// -/// Convert between native identifiers / errors and OpenSSL ones +/// Stateless seal/open (used by test vectors) /// -crypto_error::crypto_error() - : std::runtime_error(ERR_error_string(ERR_get_error(), nullptr)) +output_bytes +seal(CipherSuite suite, + input_bytes key, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) { + auto state = CipherState::create_seal(suite, key); + return state.seal(nonce, ct, aad, pt); } -static const EVP_MD* -openssl_digest_type(CipherSuite suite) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - case CipherSuite::AES_GCM_128_SHA256: - return EVP_sha256(); - - case CipherSuite::AES_GCM_256_SHA512: - return EVP_sha512(); - - default: - throw unsupported_ciphersuite_error(); - } -} - -static const EVP_CIPHER* -openssl_cipher(CipherSuite suite) +output_bytes +open(CipherSuite suite, + input_bytes key, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - return EVP_aes_128_ctr(); - - case CipherSuite::AES_GCM_128_SHA256: - return EVP_aes_128_gcm(); - - case CipherSuite::AES_GCM_256_SHA512: - return EVP_aes_256_gcm(); - - default: - throw unsupported_ciphersuite_error(); - } + auto state = CipherState::create_open(suite, key); + return state.open(nonce, pt, aad, ct); } /// -/// HMAC +/// HMAC wrapper class for HKDF /// struct HMAC @@ -622,302 +659,6 @@ hkdf_expand(CipherSuite suite, input_bytes prk, input_bytes info, size_t size) return out; } -/// -/// AEAD Algorithms -/// - -static owned_bytes<64> -compute_tag(CipherSuite suite, - input_bytes auth_key, - input_bytes nonce, - input_bytes aad, - input_bytes ct, - size_t tag_size) -{ - auto len_block = owned_bytes<24>(); - auto len_view = output_bytes(len_block); - encode_uint(aad.size(), len_view.first(8)); - encode_uint(ct.size(), len_view.first(16).last(8)); - encode_uint(tag_size, len_view.last(8)); - - auto h = HMAC(suite, auth_key); - h.write(len_block); - h.write(nonce); - h.write(aad); - h.write(ct); - - auto tag = owned_bytes<64>(); - h.digest(tag); - tag.resize(tag_size); - return tag; -} - -static void -ctr_crypt(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes out, - input_bytes in) -{ - if (out.size() != in.size()) { - throw buffer_too_small_error("CTR size mismatch"); - } - - auto ctx = scoped_evp_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto padded_nonce = owned_bytes<16>(0); - padded_nonce.append(nonce); - padded_nonce.resize(16); - - auto cipher = openssl_cipher(suite); - if (1 != - EVP_EncryptInit(ctx.get(), cipher, key.data(), padded_nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto in_size_int = static_cast(in.size()); - if (1 != EVP_EncryptUpdate( - ctx.get(), out.data(), &outlen, in.data(), in_size_int)) { - throw crypto_error(); - } - - if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { - throw crypto_error(); - } -} - -static output_bytes -seal_ctr(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - // Split the key into enc and auth subkeys - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Encrypt with AES-CM - auto inner_ct = ct.subspan(0, pt.size()); - ctr_crypt(suite, enc_key, nonce, inner_ct, pt); - - // Authenticate with truncated HMAC - auto mac = compute_tag(suite, auth_key, nonce, aad, inner_ct, tag_size); - auto tag = ct.subspan(pt.size(), tag_size); - std::copy(mac.begin(), mac.begin() + tag_size, tag.begin()); - - return ct.subspan(0, pt.size() + tag_size); -} - -static output_bytes -seal_aead(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto ctx = scoped_evp_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto cipher = openssl_cipher(suite); - if (1 != EVP_EncryptInit(ctx.get(), cipher, key.data(), nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != EVP_EncryptUpdate( - ctx.get(), nullptr, &outlen, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate( - ctx.get(), ct.data(), &outlen, pt.data(), pt_size_int)) { - throw crypto_error(); - } - - // Providing nullptr as an argument is safe here because this - // function never writes with GCM; it only computes the tag - if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { - throw crypto_error(); - } - - auto tag = ct.subspan(pt.size(), tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - return ct.subspan(0, pt.size() + tag_size); -} - -output_bytes -seal(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: { - return seal_ctr(suite, key, nonce, ct, aad, pt); - } - - case CipherSuite::AES_GCM_128_SHA256: - case CipherSuite::AES_GCM_256_SHA512: { - return seal_aead(suite, key, nonce, ct, aad, pt); - } - } - - throw unsupported_ciphersuite_error(); -} - -static output_bytes -open_ctr(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto inner_ct_size = ct.size() - tag_size; - auto inner_ct = ct.subspan(0, inner_ct_size); - auto tag = ct.subspan(inner_ct_size, tag_size); - - // Split the key into enc and auth subkeys - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Authenticate with truncated HMAC - auto mac = compute_tag(suite, auth_key, nonce, aad, inner_ct, tag_size); - if (CRYPTO_memcmp(mac.data(), tag.data(), tag.size()) != 0) { - throw authentication_error(); - } - - // Decrypt with AES-CTR - const auto pt_out = pt.first(inner_ct_size); - ctr_crypt(suite, enc_key, nonce, pt_out, ct.first(inner_ct_size)); - - return pt_out; -} - -static output_bytes -open_aead(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto inner_ct_size = ct.size() - tag_size; - if (pt.size() < inner_ct_size) { - throw buffer_too_small_error("Plaintext buffer too small"); - } - - auto ctx = scoped_evp_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto cipher = openssl_cipher(suite); - if (1 != EVP_DecryptInit(ctx.get(), cipher, key.data(), nonce.data())) { - throw crypto_error(); - } - - auto tag = ct.subspan(inner_ct_size, tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx.get(), EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - int out_size; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != EVP_DecryptUpdate( - ctx.get(), nullptr, &out_size, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != EVP_DecryptUpdate( - ctx.get(), pt.data(), &out_size, ct.data(), inner_ct_size_int)) { - throw crypto_error(); - } - - // Providing nullptr as an argument is safe here because this - // function never writes with GCM; it only verifies the tag - if (1 != EVP_DecryptFinal(ctx.get(), nullptr, &out_size)) { - throw authentication_error(); - } - - return pt.subspan(0, inner_ct_size); -} - -output_bytes -open(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: { - return open_ctr(suite, key, nonce, pt, aad, ct); - } - - case CipherSuite::AES_GCM_128_SHA256: - case CipherSuite::AES_GCM_256_SHA512: { - return open_aead(suite, key, nonce, pt, aad, ct); - } - } - - throw unsupported_ciphersuite_error(); -} - } // namespace SFRAME_NAMESPACE #endif // defined(OPENSSL_1_1) diff --git a/src/crypto_openssl3.cpp b/src/crypto_openssl3.cpp index 74780b0..460a4dd 100644 --- a/src/crypto_openssl3.cpp +++ b/src/crypto_openssl3.cpp @@ -12,14 +12,58 @@ namespace SFRAME_NAMESPACE { /// -/// Forward declarations +/// Scoped pointers for OpenSSL objects /// +using scoped_evp_cipher_ctx = + std::unique_ptr; + +/// +/// Convert between native identifiers / errors and OpenSSL ones +/// + +crypto_error::crypto_error() + : std::runtime_error(ERR_error_string(ERR_get_error(), nullptr)) +{ +} + static const EVP_CIPHER* -openssl_cipher(CipherSuite suite); +openssl_cipher(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + return EVP_aes_128_ctr(); + + case CipherSuite::AES_GCM_128_SHA256: + return EVP_aes_128_gcm(); + + case CipherSuite::AES_GCM_256_SHA512: + return EVP_aes_256_gcm(); + + default: + throw unsupported_ciphersuite_error(); + } +} static std::string -openssl_digest_name(CipherSuite suite); +openssl_digest_name(CipherSuite suite) +{ + switch (suite) { + case CipherSuite::AES_128_CTR_HMAC_SHA256_80: + case CipherSuite::AES_128_CTR_HMAC_SHA256_64: + case CipherSuite::AES_128_CTR_HMAC_SHA256_32: + case CipherSuite::AES_GCM_128_SHA256: + return OSSL_DIGEST_NAME_SHA2_256; + + case CipherSuite::AES_GCM_256_SHA512: + return OSSL_DIGEST_NAME_SHA2_512; + + default: + throw unsupported_ciphersuite_error(); + } +} static bool is_ctr_hmac_suite(CipherSuite suite) @@ -35,9 +79,18 @@ is_ctr_hmac_suite(CipherSuite suite) } /// -/// CipherState - CipherHandle is EVP_CIPHER_CTX, HmacHandle holds HMAC state +/// CipherHandle and HmacHandle definitions /// +struct CipherHandle +{ + scoped_evp_cipher_ctx ctx; + CipherHandle() + : ctx(nullptr, EVP_CIPHER_CTX_free) + { + } +}; + // HmacHandle for OpenSSL 3.x holds both the MAC algorithm and context struct HmacHandle { @@ -51,29 +104,16 @@ struct HmacHandle } }; -// Cast helpers - CipherHandle is EVP_CIPHER_CTX -static EVP_CIPHER_CTX* -cipher_ctx(CipherHandle* h) -{ - return reinterpret_cast(h); -} - -static CipherHandle* -to_cipher_handle(EVP_CIPHER_CTX* ctx) -{ - return reinterpret_cast(ctx); -} - void CipherState::Deleter::operator()(CipherHandle* h) const { - EVP_CIPHER_CTX_free(cipher_ctx(h)); + delete h; } void CipherState::Deleter::operator()(HmacHandle* h) const { - delete h; // unique_ptr members clean up automatically + delete h; } CipherState::CipherState(CipherHandle* cipher, @@ -88,14 +128,14 @@ CipherState::CipherState(CipherHandle* cipher, CipherState CipherState::create_seal(CipherSuite suite, input_bytes key) { - std::unique_ptr ctx( - EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx == nullptr) { + auto cipher_h = std::make_unique(); + cipher_h->ctx.reset(EVP_CIPHER_CTX_new()); + if (cipher_h->ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + std::unique_ptr hmac_h; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -105,19 +145,19 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) // Initialize AES-CTR context (always encrypt for CTR mode) if (1 != EVP_EncryptInit_ex( - ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + cipher_h->ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { throw crypto_error(); } // Initialize HMAC - hmac.reset(new HmacHandle()); - hmac->mac.reset(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); - if (hmac->mac == nullptr) { + hmac_h = std::make_unique(); + hmac_h->mac.reset(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); + if (hmac_h->mac == nullptr) { throw crypto_error(); } - hmac->ctx.reset(EVP_MAC_CTX_new(hmac->mac.get())); - if (hmac->ctx == nullptr) { + hmac_h->ctx.reset(EVP_MAC_CTX_new(hmac_h->mac.get())); + if (hmac_h->ctx == nullptr) { throw crypto_error(); } @@ -130,31 +170,31 @@ CipherState::create_seal(CipherSuite suite, input_bytes key) if (1 != EVP_MAC_init( - hmac->ctx.get(), auth_key.data(), auth_key.size(), params.data())) { + hmac_h->ctx.get(), auth_key.data(), auth_key.size(), params.data())) { throw crypto_error(); } } else { // GCM: use full key - if (1 != - EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + if (1 != EVP_EncryptInit_ex( + cipher_h->ctx.get(), cipher, nullptr, key.data(), nullptr)) { throw crypto_error(); } } - return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); + return CipherState(cipher_h.release(), hmac_h.release(), suite); } CipherState CipherState::create_open(CipherSuite suite, input_bytes key) { - std::unique_ptr ctx( - EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx == nullptr) { + auto cipher_h = std::make_unique(); + cipher_h->ctx.reset(EVP_CIPHER_CTX_new()); + if (cipher_h->ctx == nullptr) { throw crypto_error(); } auto cipher = openssl_cipher(suite); - std::unique_ptr hmac; + std::unique_ptr hmac_h; if (is_ctr_hmac_suite(suite)) { // CTR+HMAC: key is split into enc_key and auth_key @@ -165,19 +205,19 @@ CipherState::create_open(CipherSuite suite, input_bytes key) // Initialize AES-CTR context (always encrypt for CTR mode - CTR is // symmetric) if (1 != EVP_EncryptInit_ex( - ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { + cipher_h->ctx.get(), cipher, nullptr, enc_key.data(), nullptr)) { throw crypto_error(); } // Initialize HMAC - hmac.reset(new HmacHandle()); - hmac->mac.reset(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); - if (hmac->mac == nullptr) { + hmac_h = std::make_unique(); + hmac_h->mac.reset(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); + if (hmac_h->mac == nullptr) { throw crypto_error(); } - hmac->ctx.reset(EVP_MAC_CTX_new(hmac->mac.get())); - if (hmac->ctx == nullptr) { + hmac_h->ctx.reset(EVP_MAC_CTX_new(hmac_h->mac.get())); + if (hmac_h->ctx == nullptr) { throw crypto_error(); } @@ -190,28 +230,32 @@ CipherState::create_open(CipherSuite suite, input_bytes key) if (1 != EVP_MAC_init( - hmac->ctx.get(), auth_key.data(), auth_key.size(), params.data())) { + hmac_h->ctx.get(), auth_key.data(), auth_key.size(), params.data())) { throw crypto_error(); } } else { // GCM: use full key - if (1 != - EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr)) { + if (1 != EVP_DecryptInit_ex( + cipher_h->ctx.get(), cipher, nullptr, key.data(), nullptr)) { throw crypto_error(); } } - return CipherState(to_cipher_handle(ctx.release()), hmac.release(), suite); + return CipherState(cipher_h.release(), hmac_h.release(), suite); } +/// +/// AEAD Algorithms - CTR+HMAC +/// + static output_bytes -seal_ctr_cached(EVP_CIPHER_CTX* ctx, - HmacHandle* hmac, - CipherSuite suite, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) +seal_ctr(EVP_CIPHER_CTX* ctx, + EVP_MAC_CTX* hmac, + CipherSuite suite, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) { auto tag_size = cipher_overhead(suite); if (ct.size() < pt.size() + tag_size) { @@ -242,9 +286,9 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, throw crypto_error(); } - // Compute HMAC tag using cached context + // Compute HMAC tag // Reset HMAC context (key is preserved from init) - if (1 != EVP_MAC_init(hmac->ctx.get(), nullptr, 0, nullptr)) { + if (1 != EVP_MAC_init(hmac, nullptr, 0, nullptr)) { throw crypto_error(); } @@ -255,24 +299,22 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != - EVP_MAC_update(hmac->ctx.get(), len_block.data(), len_block.size())) { + if (1 != EVP_MAC_update(hmac, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx.get(), nonce.data(), nonce.size())) { + if (1 != EVP_MAC_update(hmac, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx.get(), aad.data(), aad.size())) { + if (1 != EVP_MAC_update(hmac, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx.get(), inner_ct.data(), inner_ct.size())) { + if (1 != EVP_MAC_update(hmac, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != EVP_MAC_final( - hmac->ctx.get(), mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != EVP_MAC_final(hmac, mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -283,73 +325,13 @@ seal_ctr_cached(EVP_CIPHER_CTX* ctx, } static output_bytes -seal_aead_cached(EVP_CIPHER_CTX* ctx, - CipherSuite suite, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - // Reset context and set new nonce (key is preserved) - if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != - EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { - throw crypto_error(); - } - - if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { - throw crypto_error(); - } - - auto tag = ct.subspan(pt.size(), tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - return ct.subspan(0, pt.size() + tag_size); -} - -output_bytes -CipherState::seal(input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto* ctx = cipher_ctx(cipher_handle.get()); - if (is_ctr_hmac_suite(suite)) { - return seal_ctr_cached(ctx, hmac_handle.get(), suite, nonce, ct, aad, pt); - } - return seal_aead_cached(ctx, suite, nonce, ct, aad, pt); -} - -static output_bytes -open_ctr_cached(EVP_CIPHER_CTX* ctx, - HmacHandle* hmac, - CipherSuite suite, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) +open_ctr(EVP_CIPHER_CTX* ctx, + EVP_MAC_CTX* hmac, + CipherSuite suite, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { @@ -364,9 +346,9 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, auto inner_ct = ct.subspan(0, inner_ct_size); auto tag = ct.subspan(inner_ct_size, tag_size); - // Verify HMAC tag using cached context + // Verify HMAC tag // Reset HMAC context (key is preserved from init) - if (1 != EVP_MAC_init(hmac->ctx.get(), nullptr, 0, nullptr)) { + if (1 != EVP_MAC_init(hmac, nullptr, 0, nullptr)) { throw crypto_error(); } @@ -377,24 +359,22 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, encode_uint(inner_ct.size(), len_view.first(16).last(8)); encode_uint(tag_size, len_view.last(8)); - if (1 != - EVP_MAC_update(hmac->ctx.get(), len_block.data(), len_block.size())) { + if (1 != EVP_MAC_update(hmac, len_block.data(), len_block.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx.get(), nonce.data(), nonce.size())) { + if (1 != EVP_MAC_update(hmac, nonce.data(), nonce.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx.get(), aad.data(), aad.size())) { + if (1 != EVP_MAC_update(hmac, aad.data(), aad.size())) { throw crypto_error(); } - if (1 != EVP_MAC_update(hmac->ctx.get(), inner_ct.data(), inner_ct.size())) { + if (1 != EVP_MAC_update(hmac, inner_ct.data(), inner_ct.size())) { throw crypto_error(); } size_t mac_size = 0; auto mac_buf = owned_bytes<64>(); - if (1 != EVP_MAC_final( - hmac->ctx.get(), mac_buf.data(), &mac_size, mac_buf.size())) { + if (1 != EVP_MAC_final(hmac, mac_buf.data(), &mac_size, mac_buf.size())) { throw crypto_error(); } @@ -428,13 +408,64 @@ open_ctr_cached(EVP_CIPHER_CTX* ctx, return pt.subspan(0, inner_ct_size); } +/// +/// AEAD Algorithms - GCM +/// + +static output_bytes +seal_aead(EVP_CIPHER_CTX* ctx, + CipherSuite suite, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + auto tag_size = cipher_overhead(suite); + if (ct.size() < pt.size() + tag_size) { + throw buffer_too_small_error("Ciphertext buffer too small"); + } + + // Reset context and set new nonce (key is preserved) + if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce.data())) { + throw crypto_error(); + } + + int outlen = 0; + auto aad_size_int = static_cast(aad.size()); + if (aad.size() > 0) { + if (1 != + EVP_EncryptUpdate(ctx, nullptr, &outlen, aad.data(), aad_size_int)) { + throw crypto_error(); + } + } + + auto pt_size_int = static_cast(pt.size()); + if (1 != EVP_EncryptUpdate(ctx, ct.data(), &outlen, pt.data(), pt_size_int)) { + throw crypto_error(); + } + + if (1 != EVP_EncryptFinal(ctx, nullptr, &outlen)) { + throw crypto_error(); + } + + auto tag = ct.subspan(pt.size(), tag_size); + auto tag_ptr = const_cast(static_cast(tag.data())); + auto tag_size_downcast = static_cast(tag.size()); + if (1 != EVP_CIPHER_CTX_ctrl( + ctx, EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { + throw crypto_error(); + } + + return ct.subspan(0, pt.size() + tag_size); +} + static output_bytes -open_aead_cached(EVP_CIPHER_CTX* ctx, - CipherSuite suite, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) +open_aead(EVP_CIPHER_CTX* ctx, + CipherSuite suite, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { auto tag_size = cipher_overhead(suite); if (ct.size() < tag_size) { @@ -481,64 +512,72 @@ open_aead_cached(EVP_CIPHER_CTX* ctx, return pt.subspan(0, inner_ct_size); } +/// +/// CipherState seal/open methods +/// + +output_bytes +CipherState::seal(input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) +{ + if (is_ctr_hmac_suite(suite)) { + return seal_ctr(cipher_handle->ctx.get(), + hmac_handle->ctx.get(), + suite, + nonce, + ct, + aad, + pt); + } + return seal_aead(cipher_handle->ctx.get(), suite, nonce, ct, aad, pt); +} + output_bytes CipherState::open(input_bytes nonce, output_bytes pt, input_bytes aad, input_bytes ct) { - auto* ctx = cipher_ctx(cipher_handle.get()); if (is_ctr_hmac_suite(suite)) { - return open_ctr_cached(ctx, hmac_handle.get(), suite, nonce, pt, aad, ct); - } - return open_aead_cached(ctx, suite, nonce, pt, aad, ct); + return open_ctr(cipher_handle->ctx.get(), + hmac_handle->ctx.get(), + suite, + nonce, + pt, + aad, + ct); + } + return open_aead(cipher_handle->ctx.get(), suite, nonce, pt, aad, ct); } /// -/// Convert between native identifiers / errors and OpenSSL ones +/// Stateless seal/open (used by test vectors) /// -crypto_error::crypto_error() - : std::runtime_error(ERR_error_string(ERR_get_error(), nullptr)) +output_bytes +seal(CipherSuite suite, + input_bytes key, + input_bytes nonce, + output_bytes ct, + input_bytes aad, + input_bytes pt) { + auto state = CipherState::create_seal(suite, key); + return state.seal(nonce, ct, aad, pt); } -static const EVP_CIPHER* -openssl_cipher(CipherSuite suite) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - return EVP_aes_128_ctr(); - - case CipherSuite::AES_GCM_128_SHA256: - return EVP_aes_128_gcm(); - - case CipherSuite::AES_GCM_256_SHA512: - return EVP_aes_256_gcm(); - - default: - throw unsupported_ciphersuite_error(); - } -} - -static std::string -openssl_digest_name(CipherSuite suite) +output_bytes +open(CipherSuite suite, + input_bytes key, + input_bytes nonce, + output_bytes pt, + input_bytes aad, + input_bytes ct) { - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: - case CipherSuite::AES_GCM_128_SHA256: - return OSSL_DIGEST_NAME_SHA2_256; - - case CipherSuite::AES_GCM_256_SHA512: - return OSSL_DIGEST_NAME_SHA2_512; - - default: - throw unsupported_ciphersuite_error(); - } + auto state = CipherState::create_open(suite, key); + return state.open(nonce, pt, aad, ct); } /// @@ -617,340 +656,6 @@ hkdf_expand(CipherSuite suite, input_bytes prk, input_bytes info, size_t size) return out; } -/// -/// AEAD Algorithms -/// - -static owned_bytes<64> -compute_tag(CipherSuite suite, - input_bytes auth_key, - input_bytes nonce, - input_bytes aad, - input_bytes ct, - size_t tag_size) -{ - using scoped_evp_mac = std::unique_ptr; - using scoped_evp_mac_ctx = - std::unique_ptr; - - auto len_block = owned_bytes<24>(); - auto len_view = output_bytes(len_block); - encode_uint(aad.size(), len_view.first(8)); - encode_uint(ct.size(), len_view.first(16).last(8)); - encode_uint(tag_size, len_view.last(8)); - - auto digest_name = openssl_digest_name(suite); - std::array params = { - OSSL_PARAM_construct_utf8_string( - OSSL_ALG_PARAM_DIGEST, digest_name.data(), 0), - OSSL_PARAM_construct_end() - }; - - const auto mac = scoped_evp_mac( - EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr), EVP_MAC_free); - const auto ctx = - scoped_evp_mac_ctx(EVP_MAC_CTX_new(mac.get()), EVP_MAC_CTX_free); - - if (1 != EVP_MAC_init( - ctx.get(), auth_key.data(), auth_key.size(), params.data())) { - throw crypto_error(); - } - - if (1 != EVP_MAC_update(ctx.get(), len_block.data(), len_block.size())) { - throw crypto_error(); - } - - if (1 != EVP_MAC_update(ctx.get(), nonce.data(), nonce.size())) { - throw crypto_error(); - } - - if (1 != EVP_MAC_update(ctx.get(), aad.data(), aad.size())) { - throw crypto_error(); - } - - if (1 != EVP_MAC_update(ctx.get(), ct.data(), ct.size())) { - throw crypto_error(); - } - - size_t size = 0; - auto tag = owned_bytes<64>(); - if (1 != EVP_MAC_final(ctx.get(), tag.data(), &size, tag.size())) { - throw crypto_error(); - } - - tag.resize(tag_size); - return tag; -} - -using scoped_evp_cipher_ctx = - std::unique_ptr; - -static void -ctr_crypt(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes out, - input_bytes in) -{ - if (out.size() != in.size()) { - throw buffer_too_small_error("CTR size mismatch"); - } - - auto ctx = scoped_evp_cipher_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto padded_nonce = owned_bytes<16>(0); - padded_nonce.append(nonce); - padded_nonce.resize(16); - - auto cipher = openssl_cipher(suite); - if (1 != - EVP_EncryptInit(ctx.get(), cipher, key.data(), padded_nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto in_size_int = static_cast(in.size()); - if (1 != EVP_EncryptUpdate( - ctx.get(), out.data(), &outlen, in.data(), in_size_int)) { - throw crypto_error(); - } - - if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { - throw crypto_error(); - } -} - -static output_bytes -seal_ctr(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - // Split the key into enc and auth subkeys - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Encrypt with AES-CM - auto inner_ct = ct.subspan(0, pt.size()); - ctr_crypt(suite, enc_key, nonce, inner_ct, pt); - - // Authenticate with truncated HMAC - auto mac = compute_tag(suite, auth_key, nonce, aad, inner_ct, tag_size); - auto tag = ct.subspan(pt.size(), tag_size); - std::copy(mac.begin(), mac.begin() + tag_size, tag.begin()); - - return ct.subspan(0, pt.size() + tag_size); -} - -static output_bytes -seal_aead(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < pt.size() + tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto ctx = scoped_evp_cipher_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto cipher = openssl_cipher(suite); - if (1 != EVP_EncryptInit(ctx.get(), cipher, key.data(), nonce.data())) { - throw crypto_error(); - } - - int outlen = 0; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != EVP_EncryptUpdate( - ctx.get(), nullptr, &outlen, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto pt_size_int = static_cast(pt.size()); - if (1 != EVP_EncryptUpdate( - ctx.get(), ct.data(), &outlen, pt.data(), pt_size_int)) { - throw crypto_error(); - } - - // Providing nullptr as an argument is safe here because this - // function never writes with GCM; it only computes the tag - if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { - throw crypto_error(); - } - - auto tag = ct.subspan(pt.size(), tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - return ct.subspan(0, pt.size() + tag_size); -} - -output_bytes -seal(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes ct, - input_bytes aad, - input_bytes pt) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: { - return seal_ctr(suite, key, nonce, ct, aad, pt); - } - - case CipherSuite::AES_GCM_128_SHA256: - case CipherSuite::AES_GCM_256_SHA512: { - return seal_aead(suite, key, nonce, ct, aad, pt); - } - } - - throw unsupported_ciphersuite_error(); -} - -static output_bytes -open_ctr(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto inner_ct_size = ct.size() - tag_size; - auto inner_ct = ct.subspan(0, inner_ct_size); - auto tag = ct.subspan(inner_ct_size, tag_size); - - // Split the key into enc and auth subkeys - auto enc_key_size = cipher_enc_key_size(suite); - auto enc_key = key.first(enc_key_size); - auto auth_key = key.subspan(enc_key_size); - - // Authenticate with truncated HMAC - auto mac = compute_tag(suite, auth_key, nonce, aad, inner_ct, tag_size); - if (CRYPTO_memcmp(mac.data(), tag.data(), tag.size()) != 0) { - throw authentication_error(); - } - - // Decrypt with AES-CTR - const auto pt_out = pt.first(inner_ct_size); - ctr_crypt(suite, enc_key, nonce, pt_out, ct.first(inner_ct_size)); - - return pt_out; -} - -static output_bytes -open_aead(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - auto tag_size = cipher_overhead(suite); - if (ct.size() < tag_size) { - throw buffer_too_small_error("Ciphertext buffer too small"); - } - - auto inner_ct_size = ct.size() - tag_size; - if (pt.size() < inner_ct_size) { - throw buffer_too_small_error("Plaintext buffer too small"); - } - - auto ctx = scoped_evp_cipher_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); - if (ctx.get() == nullptr) { - throw crypto_error(); - } - - auto cipher = openssl_cipher(suite); - if (1 != EVP_DecryptInit(ctx.get(), cipher, key.data(), nonce.data())) { - throw crypto_error(); - } - - auto tag = ct.subspan(inner_ct_size, tag_size); - auto tag_ptr = const_cast(static_cast(tag.data())); - auto tag_size_downcast = static_cast(tag.size()); - if (1 != EVP_CIPHER_CTX_ctrl( - ctx.get(), EVP_CTRL_GCM_SET_TAG, tag_size_downcast, tag_ptr)) { - throw crypto_error(); - } - - int out_size; - auto aad_size_int = static_cast(aad.size()); - if (aad.size() > 0) { - if (1 != EVP_DecryptUpdate( - ctx.get(), nullptr, &out_size, aad.data(), aad_size_int)) { - throw crypto_error(); - } - } - - auto inner_ct_size_int = static_cast(inner_ct_size); - if (1 != EVP_DecryptUpdate( - ctx.get(), pt.data(), &out_size, ct.data(), inner_ct_size_int)) { - throw crypto_error(); - } - - // Providing nullptr as an argument is safe here because this - // function never writes with GCM; it only verifies the tag - if (1 != EVP_DecryptFinal(ctx.get(), nullptr, &out_size)) { - throw authentication_error(); - } - - return pt.subspan(0, inner_ct_size); -} - -output_bytes -open(CipherSuite suite, - input_bytes key, - input_bytes nonce, - output_bytes pt, - input_bytes aad, - input_bytes ct) -{ - switch (suite) { - case CipherSuite::AES_128_CTR_HMAC_SHA256_80: - case CipherSuite::AES_128_CTR_HMAC_SHA256_64: - case CipherSuite::AES_128_CTR_HMAC_SHA256_32: { - return open_ctr(suite, key, nonce, pt, aad, ct); - } - - case CipherSuite::AES_GCM_128_SHA256: - case CipherSuite::AES_GCM_256_SHA512: { - return open_aead(suite, key, nonce, pt, aad, ct); - } - } - - throw unsupported_ciphersuite_error(); -} - } // namespace SFRAME_NAMESPACE #endif // defined(OPENSSL_3) From 68d428e84ef084805c254eee15efcd8e241f862f Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 5 Mar 2026 11:59:56 -1000 Subject: [PATCH 8/8] Rename HMAC struct to HMACForHKDF to avoid OpenSSL 1.1 conflict The struct HMAC name conflicts with OpenSSL's HMAC function. Co-Authored-By: Claude Opus 4.5 --- src/crypto_openssl11.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index 6a4c41d..4a9cb0b 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -557,13 +557,13 @@ open(CipherSuite suite, /// HMAC wrapper class for HKDF /// -struct HMAC +struct HMACForHKDF { private: scoped_hmac_ctx ctx; public: - HMAC(CipherSuite suite, input_bytes key) + HMACForHKDF(CipherSuite suite, input_bytes key) : ctx(HMAC_CTX_new(), HMAC_CTX_free) { const auto type = openssl_digest_type(suite); @@ -617,7 +617,7 @@ struct HMAC owned_bytes hkdf_extract(CipherSuite suite, input_bytes salt, input_bytes ikm) { - auto h = HMAC(suite, salt); + auto h = HMACForHKDF(suite, salt); h.write(ikm); auto out = owned_bytes(); @@ -641,7 +641,7 @@ hkdf_expand(CipherSuite suite, input_bytes prk, input_bytes info, size_t size) auto counter = owned_bytes<1>(); counter[0] = 0x01; while (out.size() < size) { - auto h = HMAC(suite, prk); + auto h = HMACForHKDF(suite, prk); h.write(block); h.write(info); h.write(counter);