Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,33 @@ set_target_properties(lib_util PROPERTIES
)
add_library(binsrv::lib_util ALIAS lib_util)

# OpenSSL++ files
set(opensslpp_source_files
src/opensslpp/cipher_context_fwd.hpp
src/opensslpp/cipher_context.hpp
src/opensslpp/cipher_context.cpp

src/opensslpp/crypto_rng.hpp
src/opensslpp/crypto_rng.cpp

src/opensslpp/core_error_fwd.hpp
src/opensslpp/core_error.hpp
src/opensslpp/core_error.cpp
)
add_library(lib_opensslpp STATIC ${opensslpp_source_files})
target_link_libraries(lib_opensslpp
PRIVATE
binlog_server_compiler_flags
OpenSSL::Crypto
)
# it is not possible to propagate CXX_EXTENSIONS and CXX_STANDARD_REQUIRED
# via interface library (binlog_server_compiler_flags)
set_target_properties(lib_opensslpp PROPERTIES
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
add_library(binsrv::lib_opensslpp ALIAS lib_opensslpp)

# mysql wrapper library files
set(easymysql_source_files
src/easymysql/core_error_helpers_private.hpp
Expand Down
319 changes: 319 additions & 0 deletions src/opensslpp/cipher_context.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
// Copyright (c) 2023-2024 Percona and/or its affiliates.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

#include "opensslpp/cipher_context.hpp"

#include <array>
#include <cassert>
#include <cstddef>
#include <string>
#include <type_traits>
#include <utility>

#include <openssl/evp.h>
#include <openssl/types.h>

#include "opensslpp/core_error.hpp"

#include "util/byte_span_fwd.hpp"
#include "util/exception_location_helpers.hpp"

namespace opensslpp {

struct cipher_context::native_helper {
[[nodiscard]] static auto deimpl(auto &impl) noexcept {
using cast_type = std::conditional_t<
std::is_const_v<std::remove_reference_t<decltype(impl)>>,
const EVP_CIPHER_CTX, EVP_CIPHER_CTX>;
return static_cast<cast_type *>(impl.get());
}

[[nodiscard]] static const auto *
get_native_cipher_by_name(const std::string &cipher_name) {
const auto *evp_cipher{EVP_get_cipherbyname(cipher_name.c_str())};
if (evp_cipher == nullptr) {
util::exception_location().raise<core_error>("unknown cipher name");
}
const auto mode{EVP_CIPHER_get_mode(evp_cipher)};
switch (mode) {
case EVP_CIPH_ECB_MODE:
case EVP_CIPH_CBC_MODE:
case EVP_CIPH_CTR_MODE:
case EVP_CIPH_GCM_MODE:
break;
default:
// EVP_CIPH_CFB_MODE
// EVP_CIPH_OFB_MODE
// EVP_CIPH_CCM_MODE
// EVP_CIPH_XTS_MODE
// EVP_CIPH_WRAP_MODE
// EVP_CIPH_OCB_MODE
// EVP_CIPH_SIV_MODE
// EVP_CIPH_STREAM_CIPHER
util::exception_location().raise<core_error>("unsupported cipher mode");
}
return evp_cipher;
}

[[nodiscard]] static std::size_t
get_block_size_in_bytes_internal(const EVP_CIPHER *cipher) {
assert(cipher != nullptr);
return static_cast<std::size_t>(EVP_CIPHER_get_block_size(cipher));
}
[[nodiscard]] static std::size_t
get_key_size_in_bytes_internal(const EVP_CIPHER *cipher) {
assert(cipher != nullptr);
return static_cast<std::size_t>(EVP_CIPHER_get_key_length(cipher));
}
[[nodiscard]] static std::size_t
get_iv_size_in_bytes_internal(const EVP_CIPHER *cipher) {
assert(cipher != nullptr);
return static_cast<std::size_t>(EVP_CIPHER_get_iv_length(cipher));
}
};

void cipher_context::impl_deleter::operator()(void *cipher_ctx) const noexcept {
if (cipher_ctx != nullptr) {
EVP_CIPHER_CTX_free(static_cast<EVP_CIPHER_CTX *>(cipher_ctx));
}
}

cipher_context::cipher_context(cipher_context_mode_type mode,
const std::string &cipher_name,
util::const_byte_span key,
util::const_byte_span ivec,
util::const_byte_span tag)
: impl_{EVP_CIPHER_CTX_new()} {
if (!impl_) {
util::exception_location().raise<core_error>(
"cannot create cipher context");
}
const auto *evp_cipher{native_helper::get_native_cipher_by_name(cipher_name)};
if (std::size(key) !=
native_helper::get_key_size_in_bytes_internal(evp_cipher)) {
util::exception_location().raise<core_error>(
"invalid key size for the specified cipher");
}
if (std::size(ivec) !=
native_helper::get_iv_size_in_bytes_internal(evp_cipher)) {
util::exception_location().raise<core_error>(
"invalid iv size for the specified cipher");
}
if (mode == cipher_context_mode_type::encryption) {
if (!tag.empty()) {
util::exception_location().raise<core_error>(
"tag must not be specified for encryption cipher context");
}
}

if (EVP_CipherInit_ex(
native_helper::deimpl(impl_), // context
evp_cipher, // cipher
nullptr, // engine
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<const unsigned char *>(std::data(key)), // key
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<const unsigned char *>(std::data(ivec)), // iv
(mode == cipher_context_mode_type::encryption ? 1 : 0) // enc
) == 0) {
util::exception_location().raise<core_error>(
"cannot initialize cipher context");
}

if (EVP_CIPHER_CTX_set_padding(native_helper::deimpl(impl_), 0) == 0) {
util::exception_location().raise<core_error>(
"cannot disable padding for cipher context");
}

if (mode == cipher_context_mode_type::decryption) {
if (get_tag_size_in_bytes() != std::size(tag)) {
util::exception_location().raise<core_error>(
"invalid tag size for the specified cipher");
}
void *tag_ptr{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
const_cast<void *>(static_cast<const void *>(std::data(tag)))};
if (EVP_CIPHER_CTX_ctrl(native_helper::deimpl(impl_), // context
EVP_CTRL_GCM_SET_TAG, // type
static_cast<int>(std::size(tag)), // length
tag_ptr // tag
) == 0) {
util::exception_location().raise<core_error>(
"cannot set tag for cipher context");
}
}
}

[[nodiscard]] cipher_context_mode_type
cipher_context::get_mode() const noexcept {
assert(!is_empty());
return (EVP_CIPHER_CTX_encrypting(native_helper::deimpl(impl_))
? cipher_context_mode_type::encryption
: cipher_context_mode_type::decryption);
}

[[nodiscard]] std::size_t
cipher_context::get_block_size_in_bytes() const noexcept {
assert(!is_empty());
return static_cast<std::size_t>(
EVP_CIPHER_CTX_get_block_size(native_helper::deimpl(impl_)));
}

[[nodiscard]] std::size_t
cipher_context::get_key_size_in_bytes() const noexcept {
assert(!is_empty());
return static_cast<std::size_t>(
EVP_CIPHER_CTX_get_key_length(native_helper::deimpl(impl_)));
}

[[nodiscard]] std::size_t
cipher_context::get_iv_size_in_bytes() const noexcept {
assert(!is_empty());
return static_cast<std::size_t>(
EVP_CIPHER_CTX_get_iv_length(native_helper::deimpl(impl_)));
}

[[nodiscard]] std::size_t
cipher_context::get_tag_size_in_bytes() const noexcept {
assert(!is_empty());
return static_cast<std::size_t>(
EVP_CIPHER_CTX_get_tag_length(native_helper::deimpl(impl_)));
}

[[nodiscard]] std::size_t
cipher_context::get_block_size_in_bytes(const std::string &cipher_name) {
return native_helper::get_block_size_in_bytes_internal(
native_helper::get_native_cipher_by_name(cipher_name));
}

[[nodiscard]] std::size_t
cipher_context::get_key_size_in_bytes(const std::string &cipher_name) {
return native_helper::get_key_size_in_bytes_internal(
native_helper::get_native_cipher_by_name(cipher_name));
}

[[nodiscard]] std::size_t
cipher_context::get_iv_size_in_bytes(const std::string &cipher_name) {
return native_helper::get_iv_size_in_bytes_internal(
native_helper::get_native_cipher_by_name(cipher_name));
}

void cipher_context::update(util::const_byte_span input,
util::byte_span output) {
assert(!is_empty());

if (std::size(input) % get_block_size_in_bytes() != 0U) {
util::exception_location().raise<core_error>(
"in cipher context update input size is not a multiple of the block "
"size");
}

// in all modes supported by us ('XXX-ECB' with padding disabled, 'XXX-CBC'
// with padding disabled, 'XXX-CTR', and'XXX-GCM') the output length needs to
// be of the same size as the input
if (std::size(output) != std::size(input)) {
util::exception_location().raise<core_error>(
"in cipher context update the output size does not match the input "
"size");
}

if (!std::in_range<int>(std::size(input))) {
util::exception_location().raise<core_error>(
"in cipher context update input size is out of range");
}
const auto input_length_native{static_cast<int>(std::size(input))};
int output_length_native{0};
if (EVP_CipherUpdate(
native_helper::deimpl(impl_), // context
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<unsigned char *>(std::data(output)), // output
&output_length_native, // output length
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<const unsigned char *>(std::data(input)), // input
input_length_native // input length
) == 0) {
util::exception_location().raise<core_error>(
"cannot update cipher context");
}
if (!std::in_range<std::size_t>(output_length_native)) {
util::exception_location().raise<core_error>(
"in cipher context update output size is out of range");
}
const auto output_length{static_cast<std::size_t>(output_length_native)};
if (output_length != std::size(output)) {
util::exception_location().raise<core_error>(
"in cipher context update the actual output size does not match the "
"expected output size");
}
}

void cipher_context::finalize(util::byte_span output_tag) {
assert(!is_empty());

const auto mode{get_mode()};
if (mode == cipher_context_mode_type::decryption) {
if (!output_tag.empty()) {
util::exception_location().raise<core_error>(
"in cipher context finalize the output tag must only be specified "
"for the encryption mode");
}
} else {
// cipher_context_mode_type::encryption mode
if (std::size(output_tag) != get_tag_size_in_bytes()) {
util::exception_location().raise<core_error>(
"in cipher context finalize the output tag size does not match the "
"expected tag size");
}
}

using fake_buffer_type = std::array<std::byte, EVP_MAX_BLOCK_LENGTH>;
fake_buffer_type fake_buffer;
int output_length_native{0};
if (EVP_CipherFinal_ex(
native_helper::deimpl(impl_),
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<unsigned char *>(std::data(fake_buffer)),
&output_length_native) == 0) {
util::exception_location().raise<core_error>(
"cannot finalize cipher context");
}
if (!std::in_range<std::size_t>(output_length_native)) {
util::exception_location().raise<core_error>(
"in cipher context finalize output size is out of range");
}
const auto output_length{static_cast<std::size_t>(output_length_native)};
if (output_length != 0U) {
util::exception_location().raise<core_error>(
"in cipher context finalize the actual output size is not zero");
}

if (mode == cipher_context_mode_type::encryption) {
const auto tag_length_native{static_cast<int>(std::size(output_tag))};
void *const tag_ptr{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
const_cast<void *>(static_cast<const void *>(std::data(output_tag)))};
if (EVP_CIPHER_CTX_ctrl(native_helper::deimpl(impl_), // context
EVP_CTRL_GCM_GET_TAG, // type
tag_length_native, // length
tag_ptr // tag
) == 0) {
util::exception_location().raise<core_error>(
"cannot get tag from cipher context");
}
}
impl_.reset();
}

} // namespace opensslpp
Loading
Loading