From 1301914694301949d48b3841dcadf60190ec5e1b Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 29 Sep 2026 17:06:39 +0300 Subject: [PATCH 01/38] move f to direct k2 header api --- .../kphp-light/stdlib/crypto-functions.txt | 1 - runtime-light/k2-platform/k2-api.h | 11 ++ runtime-light/k2-platform/k2-header.h | 8 + .../stdlib/crypto/crypto-functions.cpp | 167 +++++++++++++----- .../stdlib/crypto/crypto-functions.h | 6 +- 5 files changed, 149 insertions(+), 44 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index e91af71891..1f71036d4c 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -34,7 +34,6 @@ function openssl_get_cipher_methods($aliases ::: bool = false) ::: string[]; function openssl_cipher_iv_length($method ::: string) ::: int | false; -/** @kphp-extern-func-info interruptible */ function openssl_encrypt($data ::: string, $method ::: string, $key ::: string, $options ::: int = 0, $iv ::: string = '', string &$tag = TODO, $aad ::: string = '', $tag_length ::: int = 16) ::: string | false; diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 768c2512f5..d20bd8f1bd 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -261,6 +261,17 @@ inline int32_t component_access(std::string_view component_name) noexcept { return k2_component_access(component_name.size(), component_name.data()); } +inline std::expected openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view passphrase, std::string_view iv, + int64_t tag_size, std::string_view aad, std::string_view data, std::span encrypted_data, + std::span received_tag) noexcept { + if (auto error_code{k2_openssl_encrypt(algorithm, padding, passphrase.data(), passphrase.size(), iv.data(), iv.size(), tag_size, aad.data(), aad.size(), + data.data(), data.size(), encrypted_data.data(), encrypted_data.size(), received_tag.data(), received_tag.size())}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + return {}; +} + inline std::expected rpc_send_request(std::string_view actor_name, std::span request_buffer, k2::RpcKind rpc_kind) noexcept { k2::descriptor descriptor{}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 4a5b9a49b4..6e4790867d 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -98,6 +98,10 @@ enum UpdateStatus { NewDescriptor = 2, }; +enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3 }; + +enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; + enum RpcKind { TL_RPC = 0, }; @@ -514,6 +518,10 @@ int32_t k2_unlink(const char* path, size_t path_len); */ int32_t k2_component_access(size_t name_len, const char* name); +int32_t k2_openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, const char* passphrase, size_t passphrase_len, const char* iv, size_t iv_len, + int64_t tag_size, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, + size_t encrypted_data_len, char* received_tag, size_t received_tag_len); + /** * Try to send rpc request to actor. On success, write descriptor of the corresponding rpc query to `rpc_d`. * This descriptor should be later used to call `k2_rpc_get_response_size` and `k2_rpc_fetch_response`. diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 9c83b45f3a..fba74ed899 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -187,6 +187,33 @@ std::optional parse_cipher_algorithm(const string& method) return std::nullopt; } +std::optional parse_cipher_algorithm_test(const string& algorithm) noexcept { + using namespace std::string_view_literals; + std::string_view algorithm_sv{algorithm.c_str(), algorithm.size()}; + + const auto ichar_equals = [](char a, char b) { return std::tolower(a) == std::tolower(b); }; + + if (std::ranges::equal(algorithm_sv, AES_128_CBC, ichar_equals)) { + return AES128_CBC; + } else if (std::ranges::equal(algorithm_sv, AES_256_CBC, ichar_equals)) { + return AES256_CBC; + } else if (std::ranges::equal(algorithm_sv, AES_128_GCM, ichar_equals)) { + return AES128_GCM; + } else if (std::ranges::equal(algorithm_sv, AES_256_GCM, ichar_equals)) { + return AES256_GCM; + } + + return std::nullopt; +} + +bool is_gcm_algorithm_test(CipherAlgorithm algorithm) { + return algorithm == AES128_GCM || algorithm == AES256_GCM; +} + +bool is_aead_algorithm_test(CipherAlgorithm algorithm) { + return is_gcm_algorithm_test(algorithm); +} + bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { return algorithm == tl::AES128_GCM || algorithm == tl::AES256_GCM; } @@ -195,6 +222,24 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { return is_gcm_algorithm(algorithm); } +struct CipherSizeInfo { + int64_t cipher_text_size; + int64_t tag_size; +}; + +CipherSizeInfo calc_encrypted_size(CipherAlgorithm algorithm, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { + if (is_aead_algorithm_test(algorithm)) { + return {data_len, tag_size}; + } + + if (padding == PKCS7) { + static constexpr int64_t BLOCK_SIZE{16}; + return {(data_len + BLOCK_SIZE) & ~(BLOCK_SIZE - 1), 0}; + } + + return {data_len, 0}; +} + constexpr size_t GCM_NONCE_LEN = 12; constexpr size_t AES_BLOCK_LEN = 16; constexpr size_t AES_128_KEY_LEN = 16; @@ -224,6 +269,30 @@ int64_t algorithm_key_len(tl::CipherAlgorithm algorithm) noexcept { } } +int64_t algorithm_iv_len_test([[maybe_unused]] CipherAlgorithm algorithm) noexcept { + if (is_gcm_algorithm_test(algorithm)) { + return GCM_NONCE_LEN; + } + return AES_BLOCK_LEN; +} + +int64_t algorithm_key_len_test(CipherAlgorithm algorithm) noexcept { + switch (algorithm) { + case AES128_GCM: + case AES128_CBC: { + return AES_128_KEY_LEN; + } + case AES256_GCM: + case AES256_CBC: { + return AES_256_KEY_LEN; + } + default: { + kphp::log::warning("unexpected cipher algorithm"); + return 0; + } + } +} + enum class cipher_opts : int64_t { OPENSSL_RAW_DATA = 1, OPENSSL_ZERO_PADDING = 2, OPENSSL_DONT_ZERO_PAD_KEY = 4 }; Optional> algorithm_pad_key_iv(tl::CipherAlgorithm algorithm, const string& source_key, const string& source_iv, @@ -263,6 +332,43 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg return std::make_pair(key, iv); } +Optional> algorithm_pad_key_iv_test(CipherAlgorithm algorithm, const string& source_key, const string& source_iv, + int64_t options) noexcept { + const size_t iv_required_len = algorithm_iv_len_test(algorithm); + const size_t key_required_len = algorithm_key_len_test(algorithm); + auto iv = source_iv; + if (is_aead_algorithm_test(algorithm)) { + if (source_iv.empty()) { + kphp::log::warning("Setting of IV length for AEAD mode failed"); + return false; + } + } else { + if (iv.size() < iv_required_len) { + kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len); + iv.append(static_cast(iv_required_len - iv.size()), '\0'); + } else if (iv.size() > iv_required_len) { + kphp::log::warning("IV passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", iv.size(), iv_required_len); + iv.shrink(static_cast(iv_required_len)); + } + } + + auto key = source_key; + if (key.size() < key_required_len) { + if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) { + kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); + return false; + } + kphp::log::warning("passphrase passed is only {} bytes long, cipher expects an passphrase of precisely {} bytes, padding with \\0", key.size(), + key_required_len); + key.append(key_required_len - key.size(), '\0'); + } else if (key.size() > key_required_len) { + kphp::log::warning("passphrase passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", key.size(), key_required_len); + key.shrink(static_cast(key_required_len)); + } + + return std::make_pair(key, iv); +} + } // namespace array f$openssl_get_cipher_methods([[maybe_unused]] bool aliases) noexcept { @@ -281,20 +387,20 @@ Optional f$openssl_cipher_iv_length(const string& method) noexcept { return algorithm_iv_len(*algorithm); } -kphp::coro::task> f$openssl_encrypt(string data, string method, string source_key, int64_t options, string source_iv, - std::optional> tag, string aad, - [[maybe_unused]] int64_t tag_length) noexcept { - auto algorithm{parse_cipher_algorithm(method)}; +Optional f$openssl_encrypt(string data, string method, string source_key, int64_t options, string source_iv, + std::optional> tag, string aad, [[maybe_unused]] int64_t tag_length) noexcept { + auto algorithm{parse_cipher_algorithm_test(method)}; if (!algorithm) { kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); - co_return false; + return false; } - bool aead{is_aead_algorithm(*algorithm)}; + bool aead{is_aead_algorithm_test(*algorithm)}; if (aead && (!tag.has_value() || tag_length == 0)) { kphp::log::warning("A tag must be provided when using AEAD mode"); - co_return false; + return false; } + if (tag.has_value() && !aead) { kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); } @@ -305,50 +411,31 @@ kphp::coro::task> f$openssl_encrypt(string data, string method, kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); } - auto key_iv{algorithm_pad_key_iv(*algorithm, source_key, source_iv, options)}; + auto key_iv{algorithm_pad_key_iv_test(*algorithm, source_key, source_iv, options)}; if (!key_iv.has_value()) { - co_return false; + return false; } - tl::BlockPadding padding{tl::BlockPadding::PKCS7}; + BlockPadding padding{PKCS7}; if (options & static_cast(cipher_opts::OPENSSL_ZERO_PADDING)) { - padding = tl::BlockPadding::NO_PADDING; - } - tl::Encrypt encrypt{.algorithm = *algorithm, - .padding = padding, - .passphrase = {.value = {key_iv.val().first.c_str(), key_iv.val().first.size()}}, - .iv = {.value = {key_iv.val().second.c_str(), key_iv.val().second.size()}}, - .tag_size = {.value = tag_length}, - .aad = {.value = {aad.c_str(), aad.size()}}, - .data = {.value = {data.c_str(), data.size()}}}; - tl::storer tls{encrypt.footprint()}; - encrypt.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; + padding = NO_PADDING; } - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; - } - - tl::fetcher tlf{response_bytes}; - tl::Maybe> response{}; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; + auto encrypted_size{calc_encrypted_size(*algorithm, padding, tag_length, data.size())}; + string encrypted_data{encrypted_size.cipher_text_size}; + string received_tag{encrypted_size.tag_size}; + auto res{k2::openssl_encrypt(*algorithm, padding, {key_iv.val().first.c_str(), key_iv.val().first.size()}, + {key_iv.val().second.c_str(), key_iv.val().second.size()}, tag_length, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, + {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; + if (!res.has_value()) { + return false; } - string result{(*response.opt_value).value[0].value.data(), static_cast((*response.opt_value).value[0].value.size())}; - if (tag.has_value()) { - string received_tag{(*response.opt_value).value[1].value.data(), static_cast((*response.opt_value).value[1].value.size())}; tag.value().get() = std::move(received_tag); } - co_return (options & static_cast(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(result) : f$base64_encode(result); + + return (options & static_cast(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(encrypted_data) : f$base64_encode(encrypted_data); } kphp::coro::task> f$openssl_decrypt(string data, string method, string source_key, int64_t options, string source_iv, string tag, diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index d17f920338..2423872a10 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -26,9 +26,9 @@ array f$openssl_get_cipher_methods(bool aliases = false) noexcept; Optional f$openssl_cipher_iv_length(const string& method) noexcept; -kphp::coro::task> f$openssl_encrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, - std::optional> tag = {}, string aad = string{}, - int64_t tag_length = 16) noexcept; +Optional f$openssl_encrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, + std::optional> tag = {}, string aad = string{}, int64_t tag_length = 16) noexcept; + kphp::coro::task> f$openssl_decrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, string tag = string{}, string aad = string{}) noexcept; From 1f8d22983b73d115de65de617e35e59d9dc63b00 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 30 Sep 2026 14:29:26 +0300 Subject: [PATCH 02/38] refactor f arguments types --- runtime-light/stdlib/crypto/crypto-functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index fba74ed899..563d2e0a35 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -387,8 +387,8 @@ Optional f$openssl_cipher_iv_length(const string& method) noexcept { return algorithm_iv_len(*algorithm); } -Optional f$openssl_encrypt(string data, string method, string source_key, int64_t options, string source_iv, - std::optional> tag, string aad, [[maybe_unused]] int64_t tag_length) noexcept { +Optional f$openssl_encrypt(const string& data, const string& method, const string& source_key, int64_t options, const string& source_iv, + std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { auto algorithm{parse_cipher_algorithm_test(method)}; if (!algorithm) { kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); From 4d127d504e5fb3d39e2044c5a39094833b8a0a10 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 30 Sep 2026 14:35:34 +0300 Subject: [PATCH 03/38] refactor k2_openssl_encrypt arguments types --- runtime-light/k2-platform/k2-header.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 6e4790867d..17eb0000b6 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -518,8 +518,8 @@ int32_t k2_unlink(const char* path, size_t path_len); */ int32_t k2_component_access(size_t name_len, const char* name); -int32_t k2_openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, const char* passphrase, size_t passphrase_len, const char* iv, size_t iv_len, - int64_t tag_size, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, +int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* passphrase, size_t passphrase_len, const char* iv, + size_t iv_len, int64_t tag_size, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, char* received_tag, size_t received_tag_len); /** From f0c298bce299ce0b0491d0402700268f79e8544f Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 30 Sep 2026 15:46:56 +0300 Subject: [PATCH 04/38] refactor f arguments types --- runtime-light/stdlib/crypto/crypto-functions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 563d2e0a35..724b7853aa 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -387,8 +387,8 @@ Optional f$openssl_cipher_iv_length(const string& method) noexcept { return algorithm_iv_len(*algorithm); } -Optional f$openssl_encrypt(const string& data, const string& method, const string& source_key, int64_t options, const string& source_iv, - std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { +Optional f$openssl_encrypt(string data, string method, string source_key, int64_t options, string source_iv, + std::optional> tag, string aad, [[maybe_unused]] int64_t tag_length) noexcept { auto algorithm{parse_cipher_algorithm_test(method)}; if (!algorithm) { kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); @@ -422,8 +422,8 @@ Optional f$openssl_encrypt(const string& data, const string& method, con } auto encrypted_size{calc_encrypted_size(*algorithm, padding, tag_length, data.size())}; - string encrypted_data{encrypted_size.cipher_text_size}; - string received_tag{encrypted_size.tag_size}; + string encrypted_data{static_cast(encrypted_size.cipher_text_size), false}; + string received_tag{static_cast(encrypted_size.tag_size), false}; auto res{k2::openssl_encrypt(*algorithm, padding, {key_iv.val().first.c_str(), key_iv.val().first.size()}, {key_iv.val().second.c_str(), key_iv.val().second.size()}, tag_length, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; From 9354e1ff9741484237aa30a318f755f08bd8ed20 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Mon, 12 Oct 2026 18:31:48 +0300 Subject: [PATCH 05/38] improve parse_cipher_algorithm perf --- .../stdlib/crypto/crypto-functions.cpp | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 724b7853aa..3e79a69326 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -10,12 +10,13 @@ #include #include #include -#include #include +#include #include #include "common/crc32_generic.h" #include "common/tl/constants/common.h" +#include "runtime-common/core/allocator/platform-allocator.h" #include "runtime-common/core/allocator/script-allocator.h" #include "runtime-common/core/runtime-core.h" #include "runtime-common/core/std/containers.h" @@ -188,19 +189,28 @@ std::optional parse_cipher_algorithm(const string& method) } std::optional parse_cipher_algorithm_test(const string& algorithm) noexcept { - using namespace std::string_view_literals; - std::string_view algorithm_sv{algorithm.c_str(), algorithm.size()}; + static constexpr size_t MAX_ALGO_NAME_LEN = 32; + const size_t size{algorithm.size()}; + if (size > MAX_ALGO_NAME_LEN || size == 0) [[unlikely]] { + return std::nullopt; + } - const auto ichar_equals = [](char a, char b) { return std::tolower(a) == std::tolower(b); }; + std::array lowered; + std::transform(algorithm.c_str(), algorithm.c_str() + size, lowered.begin(), [](char c) noexcept -> char { + if (c >= 'A' && c <= 'Z') { + return c + ('a' - 'A'); + } + return c; + }); + + std::string_view lowered_view{lowered.data(), size}; + + static const auto ALGORITHMS = kphp::stl::unordered_map{ + {AES_128_CBC, AES128_CBC}, {AES_256_CBC, AES256_CBC}, {AES_128_GCM, AES128_GCM}, {AES_256_GCM, AES256_GCM}}; - if (std::ranges::equal(algorithm_sv, AES_128_CBC, ichar_equals)) { - return AES128_CBC; - } else if (std::ranges::equal(algorithm_sv, AES_256_CBC, ichar_equals)) { - return AES256_CBC; - } else if (std::ranges::equal(algorithm_sv, AES_128_GCM, ichar_equals)) { - return AES128_GCM; - } else if (std::ranges::equal(algorithm_sv, AES_256_GCM, ichar_equals)) { - return AES256_GCM; + auto it{ALGORITHMS.find(lowered_view)}; + if (it != ALGORITHMS.end()) [[likely]] { + return it->second; } return std::nullopt; @@ -387,8 +397,8 @@ Optional f$openssl_cipher_iv_length(const string& method) noexcept { return algorithm_iv_len(*algorithm); } -Optional f$openssl_encrypt(string data, string method, string source_key, int64_t options, string source_iv, - std::optional> tag, string aad, [[maybe_unused]] int64_t tag_length) noexcept { +Optional f$openssl_encrypt(const string& data, const string& method, const string& source_key, int64_t options, const string& source_iv, + std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { auto algorithm{parse_cipher_algorithm_test(method)}; if (!algorithm) { kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); From 7737d5967ea14cc932639188d3624cb7e576cc32 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Mon, 12 Oct 2026 18:58:23 +0300 Subject: [PATCH 06/38] improve is_*_algorithm function perf --- runtime-light/k2-platform/k2-header.h | 2 +- .../stdlib/crypto/crypto-functions.cpp | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 17eb0000b6..b8c79518df 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -98,7 +98,7 @@ enum UpdateStatus { NewDescriptor = 2, }; -enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3 }; +enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT = 4 }; enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 3e79a69326..8dadab92bb 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -216,12 +216,18 @@ std::optional parse_cipher_algorithm_test(const string& algorit return std::nullopt; } -bool is_gcm_algorithm_test(CipherAlgorithm algorithm) { - return algorithm == AES128_GCM || algorithm == AES256_GCM; -} - -bool is_aead_algorithm_test(CipherAlgorithm algorithm) { - return is_gcm_algorithm_test(algorithm); +bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { + static constexpr std::array IS_GCM_ALGO{true, true, false, false}; + static_assert(IS_GCM_ALGO.size() == CipherAlgorithm::COUNT, + "IS_GCM_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); + return IS_GCM_ALGO[algorithm]; +} + +bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { + static constexpr std::array IS_AEAD_ALGO{true, true, false, false}; + static_assert(IS_AEAD_ALGO.size() == CipherAlgorithm::COUNT, + "IS_AEAD_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); + return IS_AEAD_ALGO[algorithm]; } bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { From 2a5ccaa6a160386baa97cbb39d80bf5ecc5fc0d8 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Mon, 12 Oct 2026 19:00:52 +0300 Subject: [PATCH 07/38] improve is_*_algorithm function perf --- runtime-light/stdlib/crypto/crypto-functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 8dadab92bb..582d4f850d 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -217,14 +217,14 @@ std::optional parse_cipher_algorithm_test(const string& algorit } bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { - static constexpr std::array IS_GCM_ALGO{true, true, false, false}; + static constexpr std::array IS_GCM_ALGO = {true, true, false, false}; static_assert(IS_GCM_ALGO.size() == CipherAlgorithm::COUNT, "IS_GCM_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); return IS_GCM_ALGO[algorithm]; } bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { - static constexpr std::array IS_AEAD_ALGO{true, true, false, false}; + static constexpr std::array IS_AEAD_ALGO = {true, true, false, false}; static_assert(IS_AEAD_ALGO.size() == CipherAlgorithm::COUNT, "IS_AEAD_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); return IS_AEAD_ALGO[algorithm]; From 9e5244b081738841a74699d3a870c3c7252d2a80 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Mon, 12 Oct 2026 19:05:32 +0300 Subject: [PATCH 08/38] improve is_*_algorithm function perf --- runtime-light/stdlib/crypto/crypto-functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 582d4f850d..22d9129f8f 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -217,14 +217,14 @@ std::optional parse_cipher_algorithm_test(const string& algorit } bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { - static constexpr std::array IS_GCM_ALGO = {true, true, false, false}; + static constexpr std::array IS_GCM_ALGO = {/*AES128_GCM*/ true, /*AES256_GCM*/ true, /*AES128_CBC*/ false, /*AES256_CBC*/ false}; static_assert(IS_GCM_ALGO.size() == CipherAlgorithm::COUNT, "IS_GCM_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); return IS_GCM_ALGO[algorithm]; } bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { - static constexpr std::array IS_AEAD_ALGO = {true, true, false, false}; + static constexpr std::array IS_AEAD_ALGO = {/*AES128_GCM*/ true, /*AES256_GCM*/ true, /*AES128_CBC*/ false, /*AES256_CBC*/ false}; static_assert(IS_AEAD_ALGO.size() == CipherAlgorithm::COUNT, "IS_AEAD_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); return IS_AEAD_ALGO[algorithm]; From 5dbd856d7f7420c4b4d12ea96c198d683820855b Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Mon, 12 Oct 2026 19:18:14 +0300 Subject: [PATCH 09/38] improve is_*_algorithm function perf --- runtime-light/k2-platform/k2-header.h | 2 +- .../stdlib/crypto/crypto-functions.cpp | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index b8c79518df..17eb0000b6 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -98,7 +98,7 @@ enum UpdateStatus { NewDescriptor = 2, }; -enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT = 4 }; +enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3 }; enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 22d9129f8f..fb2dbc7875 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -217,17 +217,25 @@ std::optional parse_cipher_algorithm_test(const string& algorit } bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { - static constexpr std::array IS_GCM_ALGO = {/*AES128_GCM*/ true, /*AES256_GCM*/ true, /*AES128_CBC*/ false, /*AES256_CBC*/ false}; - static_assert(IS_GCM_ALGO.size() == CipherAlgorithm::COUNT, - "IS_GCM_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); - return IS_GCM_ALGO[algorithm]; + switch (algorithm) { + case AES128_GCM: + case AES256_GCM: + return true; + case AES128_CBC: + case AES256_CBC: + return false; + } } bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { - static constexpr std::array IS_AEAD_ALGO = {/*AES128_GCM*/ true, /*AES256_GCM*/ true, /*AES128_CBC*/ false, /*AES256_CBC*/ false}; - static_assert(IS_AEAD_ALGO.size() == CipherAlgorithm::COUNT, - "IS_AEAD_ALGO must have exactly CipherAlgorithm::COUNT entries - update it when adding a new cipher algorithm"); - return IS_AEAD_ALGO[algorithm]; + switch (algorithm) { + case AES128_GCM: + case AES256_GCM: + return true; + case AES128_CBC: + case AES256_CBC: + return false; + } } bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { From f68c88d6ad09d6b7be0856ab9f2333461f0dce78 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 13 Oct 2026 15:11:51 +0300 Subject: [PATCH 10/38] add cipher algo traits --- runtime-light/k2-platform/k2-header.h | 2 +- .../stdlib/crypto/crypto-functions.cpp | 83 ++++++++++--------- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 17eb0000b6..7790f17e18 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -98,7 +98,7 @@ enum UpdateStatus { NewDescriptor = 2, }; -enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3 }; +enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT }; enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index fb2dbc7875..f528429c0c 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -216,6 +216,28 @@ std::optional parse_cipher_algorithm_test(const string& algorit return std::nullopt; } +enum class encryption_mode { BLOCK, STREAM, AEAD }; + +struct cipher_algorithm_traits { + int64_t key_len; + int64_t iv_len; + int64_t block_size; + encryption_mode mode; +}; + +constexpr const cipher_algorithm_traits& get_cipher_algorithm_traits(CipherAlgorithm algorithm) noexcept { + static constexpr std::array traits{ + cipher_algorithm_traits{16, 12, 0, encryption_mode::AEAD}, // AES128_GCM + cipher_algorithm_traits{32, 12, 0, encryption_mode::AEAD}, // AES256_GCM + cipher_algorithm_traits{16, 16, 16, encryption_mode::BLOCK}, // AES128_CBC + cipher_algorithm_traits{32, 16, 16, encryption_mode::BLOCK} // AES256_CBC + }; + + static_assert(traits.size() == CipherAlgorithm::COUNT, "Update traits when adding a cipher algorithm"); + + return traits[algorithm]; +} + bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { switch (algorithm) { case AES128_GCM: @@ -224,6 +246,8 @@ bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { case AES128_CBC: case AES256_CBC: return false; + case COUNT: + std::unreachable(); } } @@ -235,6 +259,8 @@ bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { case AES128_CBC: case AES256_CBC: return false; + case COUNT: + std::unreachable(); } } @@ -246,22 +272,22 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { return is_gcm_algorithm(algorithm); } -struct CipherSizeInfo { - int64_t cipher_text_size; - int64_t tag_size; -}; - -CipherSizeInfo calc_encrypted_size(CipherAlgorithm algorithm, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { - if (is_aead_algorithm_test(algorithm)) { - return {data_len, tag_size}; - } +auto calc_encrypted_size(CipherAlgorithm algorithm, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { + struct cipher_algo_size_info { + int64_t cipher_text_size; + int64_t tag_size; + }; - if (padding == PKCS7) { - static constexpr int64_t BLOCK_SIZE{16}; - return {(data_len + BLOCK_SIZE) & ~(BLOCK_SIZE - 1), 0}; + const auto& algo_traits{get_cipher_algorithm_traits(algorithm)}; + switch (algo_traits.mode) { + case encryption_mode::BLOCK: + return padding == PKCS7 ? cipher_algo_size_info{(data_len + algo_traits.block_size) & ~(algo_traits.block_size - 1), 0} + : cipher_algo_size_info{data_len, 0}; + case encryption_mode::STREAM: + return cipher_algo_size_info{data_len, 0}; + case encryption_mode::AEAD: + return cipher_algo_size_info{data_len, tag_size}; } - - return {data_len, 0}; } constexpr size_t GCM_NONCE_LEN = 12; @@ -293,30 +319,6 @@ int64_t algorithm_key_len(tl::CipherAlgorithm algorithm) noexcept { } } -int64_t algorithm_iv_len_test([[maybe_unused]] CipherAlgorithm algorithm) noexcept { - if (is_gcm_algorithm_test(algorithm)) { - return GCM_NONCE_LEN; - } - return AES_BLOCK_LEN; -} - -int64_t algorithm_key_len_test(CipherAlgorithm algorithm) noexcept { - switch (algorithm) { - case AES128_GCM: - case AES128_CBC: { - return AES_128_KEY_LEN; - } - case AES256_GCM: - case AES256_CBC: { - return AES_256_KEY_LEN; - } - default: { - kphp::log::warning("unexpected cipher algorithm"); - return 0; - } - } -} - enum class cipher_opts : int64_t { OPENSSL_RAW_DATA = 1, OPENSSL_ZERO_PADDING = 2, OPENSSL_DONT_ZERO_PAD_KEY = 4 }; Optional> algorithm_pad_key_iv(tl::CipherAlgorithm algorithm, const string& source_key, const string& source_iv, @@ -358,8 +360,7 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg Optional> algorithm_pad_key_iv_test(CipherAlgorithm algorithm, const string& source_key, const string& source_iv, int64_t options) noexcept { - const size_t iv_required_len = algorithm_iv_len_test(algorithm); - const size_t key_required_len = algorithm_key_len_test(algorithm); + const auto& algo_traits{get_cipher_algorithm_traits(algorithm)}; auto iv = source_iv; if (is_aead_algorithm_test(algorithm)) { if (source_iv.empty()) { @@ -367,6 +368,7 @@ Optional> algorithm_pad_key_iv_test(CipherAlgorithm al return false; } } else { + int64_t iv_required_len{algo_traits.iv_len}; if (iv.size() < iv_required_len) { kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len); iv.append(static_cast(iv_required_len - iv.size()), '\0'); @@ -377,6 +379,7 @@ Optional> algorithm_pad_key_iv_test(CipherAlgorithm al } auto key = source_key; + int64_t key_required_len{algo_traits.key_len}; if (key.size() < key_required_len) { if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) { kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); From 0baf3133d58104e5be36487db138bed2bffd4dc3 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 13 Oct 2026 15:37:01 +0300 Subject: [PATCH 11/38] refactor f --- .../stdlib/crypto/crypto-functions.cpp | 76 +++++++------------ 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index f528429c0c..a56c55135b 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -238,30 +238,8 @@ constexpr const cipher_algorithm_traits& get_cipher_algorithm_traits(CipherAlgor return traits[algorithm]; } -bool is_gcm_algorithm_test(CipherAlgorithm algorithm) noexcept { - switch (algorithm) { - case AES128_GCM: - case AES256_GCM: - return true; - case AES128_CBC: - case AES256_CBC: - return false; - case COUNT: - std::unreachable(); - } -} - bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { - switch (algorithm) { - case AES128_GCM: - case AES256_GCM: - return true; - case AES128_CBC: - case AES256_CBC: - return false; - case COUNT: - std::unreachable(); - } + return get_cipher_algorithm_traits(algorithm).mode == encryption_mode::AEAD; } bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { @@ -273,20 +251,20 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { } auto calc_encrypted_size(CipherAlgorithm algorithm, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { - struct cipher_algo_size_info { - int64_t cipher_text_size; + struct encrypted_data_size_info { + int64_t encrypted_size; int64_t tag_size; }; const auto& algo_traits{get_cipher_algorithm_traits(algorithm)}; switch (algo_traits.mode) { case encryption_mode::BLOCK: - return padding == PKCS7 ? cipher_algo_size_info{(data_len + algo_traits.block_size) & ~(algo_traits.block_size - 1), 0} - : cipher_algo_size_info{data_len, 0}; + return padding == PKCS7 ? encrypted_data_size_info{(data_len + algo_traits.block_size) & ~(algo_traits.block_size - 1), 0} + : encrypted_data_size_info{data_len, 0}; case encryption_mode::STREAM: - return cipher_algo_size_info{data_len, 0}; + return encrypted_data_size_info{data_len, 0}; case encryption_mode::AEAD: - return cipher_algo_size_info{data_len, tag_size}; + return encrypted_data_size_info{data_len, tag_size}; } } @@ -358,14 +336,14 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg return std::make_pair(key, iv); } -Optional> algorithm_pad_key_iv_test(CipherAlgorithm algorithm, const string& source_key, const string& source_iv, - int64_t options) noexcept { +std::optional> algorithm_pad_key_iv_test(CipherAlgorithm algorithm, const string& source_key, const string& source_iv, + int64_t options) noexcept { const auto& algo_traits{get_cipher_algorithm_traits(algorithm)}; auto iv = source_iv; - if (is_aead_algorithm_test(algorithm)) { + if (algo_traits.mode == encryption_mode::AEAD) { if (source_iv.empty()) { kphp::log::warning("Setting of IV length for AEAD mode failed"); - return false; + return std::nullopt; } } else { int64_t iv_required_len{algo_traits.iv_len}; @@ -383,7 +361,7 @@ Optional> algorithm_pad_key_iv_test(CipherAlgorithm al if (key.size() < key_required_len) { if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) { kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); - return false; + return std::nullopt; } kphp::log::warning("passphrase passed is only {} bytes long, cipher expects an passphrase of precisely {} bytes, padding with \\0", key.size(), key_required_len); @@ -393,7 +371,7 @@ Optional> algorithm_pad_key_iv_test(CipherAlgorithm al key.shrink(static_cast(key_required_len)); } - return std::make_pair(key, iv); + return std::make_pair(std::move(key), std::move(iv)); } } // namespace @@ -417,29 +395,29 @@ Optional f$openssl_cipher_iv_length(const string& method) noexcept { Optional f$openssl_encrypt(const string& data, const string& method, const string& source_key, int64_t options, const string& source_iv, std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { auto algorithm{parse_cipher_algorithm_test(method)}; - if (!algorithm) { + if (!algorithm) [[unlikely]] { kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); return false; } - bool aead{is_aead_algorithm_test(*algorithm)}; - if (aead && (!tag.has_value() || tag_length == 0)) { + bool is_aead{is_aead_algorithm_test(*algorithm)}; + if (is_aead && (!tag || tag_length == 0)) [[unlikely]] { kphp::log::warning("A tag must be provided when using AEAD mode"); return false; } - if (tag.has_value() && !aead) { + if (tag && !is_aead) [[unlikely]] { kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); } - if (!aad.empty() && !aead) { + if (!aad.empty() && !is_aead) [[unlikely]] { kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); } - if (source_iv.empty()) { + if (source_iv.empty()) [[unlikely]] { kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); } auto key_iv{algorithm_pad_key_iv_test(*algorithm, source_key, source_iv, options)}; - if (!key_iv.has_value()) { + if (!key_iv) [[unlikely]] { return false; } @@ -449,17 +427,17 @@ Optional f$openssl_encrypt(const string& data, const string& method, con } auto encrypted_size{calc_encrypted_size(*algorithm, padding, tag_length, data.size())}; - string encrypted_data{static_cast(encrypted_size.cipher_text_size), false}; + string encrypted_data{static_cast(encrypted_size.encrypted_size), false}; string received_tag{static_cast(encrypted_size.tag_size), false}; - auto res{k2::openssl_encrypt(*algorithm, padding, {key_iv.val().first.c_str(), key_iv.val().first.size()}, - {key_iv.val().second.c_str(), key_iv.val().second.size()}, tag_length, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, - {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; - if (!res.has_value()) { + auto res{k2::openssl_encrypt(*algorithm, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, + tag_length, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, + {received_tag.buffer(), received_tag.size()})}; + if (!res) [[unlikely]] { return false; } - if (tag.has_value()) { - tag.value().get() = std::move(received_tag); + if (tag) { + (*tag).get() = std::move(received_tag); } return (options & static_cast(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(encrypted_data) : f$base64_encode(encrypted_data); From 646fc7b5ac7cdbbd4cce63e9fc9eaafb90b198cc Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 13 Oct 2026 15:55:18 +0300 Subject: [PATCH 12/38] refactor f --- runtime-light/k2-platform/k2-api.h | 1 + .../stdlib/crypto/crypto-functions.cpp | 56 +++++++++---------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index d20bd8f1bd..b51ba85d64 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -269,6 +269,7 @@ inline std::expected openssl_encrypt(CipherAlgorithm algorithm, B error_code != k2::errno_ok) { return std::unexpected{error_code}; } + return {}; } diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index a56c55135b..6b2abd4fd8 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -238,10 +238,6 @@ constexpr const cipher_algorithm_traits& get_cipher_algorithm_traits(CipherAlgor return traits[algorithm]; } -bool is_aead_algorithm_test(CipherAlgorithm algorithm) noexcept { - return get_cipher_algorithm_traits(algorithm).mode == encryption_mode::AEAD; -} - bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { return algorithm == tl::AES128_GCM || algorithm == tl::AES256_GCM; } @@ -250,16 +246,15 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { return is_gcm_algorithm(algorithm); } -auto calc_encrypted_size(CipherAlgorithm algorithm, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { +auto calc_encrypted_size(cipher_algorithm_traits algorithm_traits, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { struct encrypted_data_size_info { int64_t encrypted_size; int64_t tag_size; }; - const auto& algo_traits{get_cipher_algorithm_traits(algorithm)}; - switch (algo_traits.mode) { + switch (algorithm_traits.mode) { case encryption_mode::BLOCK: - return padding == PKCS7 ? encrypted_data_size_info{(data_len + algo_traits.block_size) & ~(algo_traits.block_size - 1), 0} + return padding == PKCS7 ? encrypted_data_size_info{(data_len + algorithm_traits.block_size) & ~(algorithm_traits.block_size - 1), 0} : encrypted_data_size_info{data_len, 0}; case encryption_mode::STREAM: return encrypted_data_size_info{data_len, 0}; @@ -336,37 +331,36 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg return std::make_pair(key, iv); } -std::optional> algorithm_pad_key_iv_test(CipherAlgorithm algorithm, const string& source_key, const string& source_iv, - int64_t options) noexcept { - const auto& algo_traits{get_cipher_algorithm_traits(algorithm)}; +std::optional> algorithm_pad_key_iv_test(const cipher_algorithm_traits& algorithm_traits, const string& source_key, + const string& source_iv, int64_t options) noexcept { auto iv = source_iv; - if (algo_traits.mode == encryption_mode::AEAD) { - if (source_iv.empty()) { + if (algorithm_traits.mode == encryption_mode::AEAD) { + if (source_iv.empty()) [[unlikely]] { kphp::log::warning("Setting of IV length for AEAD mode failed"); return std::nullopt; } } else { - int64_t iv_required_len{algo_traits.iv_len}; - if (iv.size() < iv_required_len) { + int64_t iv_required_len{algorithm_traits.iv_len}; + if (iv.size() < iv_required_len) [[unlikely]] { kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len); iv.append(static_cast(iv_required_len - iv.size()), '\0'); - } else if (iv.size() > iv_required_len) { + } else if (iv.size() > iv_required_len) [[unlikely]] { kphp::log::warning("IV passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", iv.size(), iv_required_len); iv.shrink(static_cast(iv_required_len)); } } auto key = source_key; - int64_t key_required_len{algo_traits.key_len}; - if (key.size() < key_required_len) { - if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) { + int64_t key_required_len{algorithm_traits.key_len}; + if (key.size() < key_required_len) [[unlikely]] { + if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) [[unlikely]] { kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); return std::nullopt; } kphp::log::warning("passphrase passed is only {} bytes long, cipher expects an passphrase of precisely {} bytes, padding with \\0", key.size(), key_required_len); key.append(key_required_len - key.size(), '\0'); - } else if (key.size() > key_required_len) { + } else if (key.size() > key_required_len) [[unlikely]] { kphp::log::warning("passphrase passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", key.size(), key_required_len); key.shrink(static_cast(key_required_len)); } @@ -400,23 +394,29 @@ Optional f$openssl_encrypt(const string& data, const string& method, con return false; } - bool is_aead{is_aead_algorithm_test(*algorithm)}; + const auto& algorithm_traits{get_cipher_algorithm_traits(*algorithm)}; + + bool is_aead{algorithm_traits.mode == encryption_mode::AEAD}; if (is_aead && (!tag || tag_length == 0)) [[unlikely]] { kphp::log::warning("A tag must be provided when using AEAD mode"); return false; } - if (tag && !is_aead) [[unlikely]] { - kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); - } - if (!aad.empty() && !is_aead) [[unlikely]] { - kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); + if (!is_aead) { + if (tag) [[unlikely]] { + kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); + } + + if (!aad.empty()) [[unlikely]] { + kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); + } } + if (source_iv.empty()) [[unlikely]] { kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); } - auto key_iv{algorithm_pad_key_iv_test(*algorithm, source_key, source_iv, options)}; + auto key_iv{algorithm_pad_key_iv_test(algorithm_traits, source_key, source_iv, options)}; if (!key_iv) [[unlikely]] { return false; } @@ -426,7 +426,7 @@ Optional f$openssl_encrypt(const string& data, const string& method, con padding = NO_PADDING; } - auto encrypted_size{calc_encrypted_size(*algorithm, padding, tag_length, data.size())}; + auto encrypted_size{calc_encrypted_size(algorithm_traits, padding, tag_length, data.size())}; string encrypted_data{static_cast(encrypted_size.encrypted_size), false}; string received_tag{static_cast(encrypted_size.tag_size), false}; auto res{k2::openssl_encrypt(*algorithm, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, From d8eda41e7788f71f2f91e33a0e79d5f6d4739619 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 13 Oct 2026 16:02:04 +0300 Subject: [PATCH 13/38] refactor f --- runtime-light/stdlib/crypto/crypto-functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 6b2abd4fd8..96617afc4f 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -246,7 +246,7 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { return is_gcm_algorithm(algorithm); } -auto calc_encrypted_size(cipher_algorithm_traits algorithm_traits, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { +auto calc_encrypted_size(const cipher_algorithm_traits& algorithm_traits, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { struct encrypted_data_size_info { int64_t encrypted_size; int64_t tag_size; From 73f419c270218adc2471dd42e7772935a8edf926 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 13 Oct 2026 17:25:59 +0300 Subject: [PATCH 14/38] rename k2_openssl_encrypt arguments --- runtime-light/k2-platform/k2-api.h | 8 ++++---- runtime-light/k2-platform/k2-header.h | 6 +++--- runtime-light/stdlib/crypto/crypto-functions.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index b51ba85d64..b83072b0f4 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -261,11 +261,11 @@ inline int32_t component_access(std::string_view component_name) noexcept { return k2_component_access(component_name.size(), component_name.data()); } -inline std::expected openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view passphrase, std::string_view iv, - int64_t tag_size, std::string_view aad, std::string_view data, std::span encrypted_data, +inline std::expected openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view key, std::string_view iv, + std::string_view aad, std::string_view data, std::span encrypted_data, std::span received_tag) noexcept { - if (auto error_code{k2_openssl_encrypt(algorithm, padding, passphrase.data(), passphrase.size(), iv.data(), iv.size(), tag_size, aad.data(), aad.size(), - data.data(), data.size(), encrypted_data.data(), encrypted_data.size(), received_tag.data(), received_tag.size())}; + if (auto error_code{k2_openssl_encrypt(algorithm, padding, key.data(), key.size(), iv.data(), iv.size(), aad.data(), aad.size(), data.data(), data.size(), + encrypted_data.data(), encrypted_data.size(), received_tag.data(), received_tag.size())}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 7790f17e18..945004c6dc 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -518,9 +518,9 @@ int32_t k2_unlink(const char* path, size_t path_len); */ int32_t k2_component_access(size_t name_len, const char* name); -int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* passphrase, size_t passphrase_len, const char* iv, - size_t iv_len, int64_t tag_size, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, - size_t encrypted_data_len, char* received_tag, size_t received_tag_len); +int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, + const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, + char* received_tag, size_t received_tag_len); /** * Try to send rpc request to actor. On success, write descriptor of the corresponding rpc query to `rpc_d`. diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 96617afc4f..622bc247a7 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -430,7 +430,7 @@ Optional f$openssl_encrypt(const string& data, const string& method, con string encrypted_data{static_cast(encrypted_size.encrypted_size), false}; string received_tag{static_cast(encrypted_size.tag_size), false}; auto res{k2::openssl_encrypt(*algorithm, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, - tag_length, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, + {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; if (!res) [[unlikely]] { return false; From 1ba746eb1932c791b92cc249a2879d48d4ab778a Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 15 Oct 2026 12:42:32 +0300 Subject: [PATCH 15/38] refactor f --- runtime-light/k2-platform/k2-api.h | 12 +++-- runtime-light/k2-platform/k2-header.h | 2 +- .../stdlib/crypto/crypto-functions.cpp | 49 +++++++++++-------- .../stdlib/crypto/crypto-functions.h | 4 +- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index b83072b0f4..dd4800c911 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -261,16 +261,18 @@ inline int32_t component_access(std::string_view component_name) noexcept { return k2_component_access(component_name.size(), component_name.data()); } -inline std::expected openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view key, std::string_view iv, - std::string_view aad, std::string_view data, std::span encrypted_data, - std::span received_tag) noexcept { +inline std::expected openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view key, std::string_view iv, + std::string_view aad, std::string_view data, std::span encrypted_data, + std::span received_tag) noexcept { + size_t encrypted_data_written{0}; if (auto error_code{k2_openssl_encrypt(algorithm, padding, key.data(), key.size(), iv.data(), iv.size(), aad.data(), aad.size(), data.data(), data.size(), - encrypted_data.data(), encrypted_data.size(), received_tag.data(), received_tag.size())}; + encrypted_data.data(), encrypted_data.size(), std::addressof(encrypted_data_written), received_tag.data(), + received_tag.size())}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } - return {}; + return {encrypted_data_written}; } inline std::expected rpc_send_request(std::string_view actor_name, std::span request_buffer, diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 945004c6dc..fb6d427b0c 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -520,7 +520,7 @@ int32_t k2_component_access(size_t name_len, const char* name); int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, - char* received_tag, size_t received_tag_len); + size_t* encrypted_data_written, char* received_tag, size_t received_tag_len); /** * Try to send rpc request to actor. On success, write descriptor of the corresponding rpc query to `rpc_d`. diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 622bc247a7..66e961d382 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -11,12 +11,10 @@ #include #include #include -#include #include #include "common/crc32_generic.h" #include "common/tl/constants/common.h" -#include "runtime-common/core/allocator/platform-allocator.h" #include "runtime-common/core/allocator/script-allocator.h" #include "runtime-common/core/runtime-core.h" #include "runtime-common/core/std/containers.h" @@ -189,7 +187,19 @@ std::optional parse_cipher_algorithm(const string& method) } std::optional parse_cipher_algorithm_test(const string& algorithm) noexcept { - static constexpr size_t MAX_ALGO_NAME_LEN = 32; + static constexpr auto ALGORITHMS = [] { + auto arr{std::array{std::pair{AES_128_CBC, AES128_CBC}, std::pair{AES_256_CBC, AES256_CBC}, std::pair{AES_128_GCM, AES128_GCM}, + std::pair{AES_256_GCM, AES256_GCM}}}; + std::ranges::sort(arr, std::less{}, &std::pair::first); + return arr; + }(); + + static_assert(ALGORITHMS.size() == CipherAlgorithm::COUNT); + + static constexpr size_t MAX_ALGO_NAME_LEN = + std::ranges::max( + ALGORITHMS, [](const auto& l, const auto& r) noexcept { return l.length() < r.length(); }, &std::pair::first) + .first.length(); const size_t size{algorithm.size()}; if (size > MAX_ALGO_NAME_LEN || size == 0) [[unlikely]] { return std::nullopt; @@ -204,13 +214,9 @@ std::optional parse_cipher_algorithm_test(const string& algorit }); std::string_view lowered_view{lowered.data(), size}; - - static const auto ALGORITHMS = kphp::stl::unordered_map{ - {AES_128_CBC, AES128_CBC}, {AES_256_CBC, AES256_CBC}, {AES_128_GCM, AES128_GCM}, {AES_256_GCM, AES256_GCM}}; - - auto it{ALGORITHMS.find(lowered_view)}; - if (it != ALGORITHMS.end()) [[likely]] { - return it->second; + auto rng{std::ranges::equal_range(ALGORITHMS, lowered_view, std::less{}, &std::pair::first)}; + if (!rng.empty()) [[likely]] { + return rng.front().second; } return std::nullopt; @@ -246,7 +252,7 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { return is_gcm_algorithm(algorithm); } -auto calc_encrypted_size(const cipher_algorithm_traits& algorithm_traits, BlockPadding padding, int64_t tag_size, int64_t data_len) noexcept { +auto calc_encrypted_size(const cipher_algorithm_traits& algorithm_traits, int64_t tag_size, int64_t data_len) noexcept { struct encrypted_data_size_info { int64_t encrypted_size; int64_t tag_size; @@ -254,8 +260,7 @@ auto calc_encrypted_size(const cipher_algorithm_traits& algorithm_traits, BlockP switch (algorithm_traits.mode) { case encryption_mode::BLOCK: - return padding == PKCS7 ? encrypted_data_size_info{(data_len + algorithm_traits.block_size) & ~(algorithm_traits.block_size - 1), 0} - : encrypted_data_size_info{data_len, 0}; + return encrypted_data_size_info{data_len + algorithm_traits.block_size, 0}; case encryption_mode::STREAM: return encrypted_data_size_info{data_len, 0}; case encryption_mode::AEAD: @@ -341,10 +346,10 @@ std::optional> algorithm_pad_key_iv_test(const cipher_ } } else { int64_t iv_required_len{algorithm_traits.iv_len}; - if (iv.size() < iv_required_len) [[unlikely]] { + if (iv.size() < iv_required_len) { kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len); iv.append(static_cast(iv_required_len - iv.size()), '\0'); - } else if (iv.size() > iv_required_len) [[unlikely]] { + } else if (iv.size() > iv_required_len) { kphp::log::warning("IV passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", iv.size(), iv_required_len); iv.shrink(static_cast(iv_required_len)); } @@ -352,7 +357,7 @@ std::optional> algorithm_pad_key_iv_test(const cipher_ auto key = source_key; int64_t key_required_len{algorithm_traits.key_len}; - if (key.size() < key_required_len) [[unlikely]] { + if (key.size() < key_required_len) { if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) [[unlikely]] { kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); return std::nullopt; @@ -360,7 +365,7 @@ std::optional> algorithm_pad_key_iv_test(const cipher_ kphp::log::warning("passphrase passed is only {} bytes long, cipher expects an passphrase of precisely {} bytes, padding with \\0", key.size(), key_required_len); key.append(key_required_len - key.size(), '\0'); - } else if (key.size() > key_required_len) [[unlikely]] { + } else if (key.size() > key_required_len) { kphp::log::warning("passphrase passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", key.size(), key_required_len); key.shrink(static_cast(key_required_len)); } @@ -403,16 +408,16 @@ Optional f$openssl_encrypt(const string& data, const string& method, con } if (!is_aead) { - if (tag) [[unlikely]] { + if (tag) { kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); } - if (!aad.empty()) [[unlikely]] { + if (!aad.empty()) { kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); } } - if (source_iv.empty()) [[unlikely]] { + if (source_iv.empty()) { kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); } @@ -426,7 +431,7 @@ Optional f$openssl_encrypt(const string& data, const string& method, con padding = NO_PADDING; } - auto encrypted_size{calc_encrypted_size(algorithm_traits, padding, tag_length, data.size())}; + auto encrypted_size{calc_encrypted_size(algorithm_traits, tag_length, data.size())}; string encrypted_data{static_cast(encrypted_size.encrypted_size), false}; string received_tag{static_cast(encrypted_size.tag_size), false}; auto res{k2::openssl_encrypt(*algorithm, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, @@ -436,6 +441,8 @@ Optional f$openssl_encrypt(const string& data, const string& method, con return false; } + encrypted_data.shrink(static_cast(*res)); + if (tag) { (*tag).get() = std::move(received_tag); } diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index 2423872a10..c418ca8af9 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -26,8 +26,8 @@ array f$openssl_get_cipher_methods(bool aliases = false) noexcept; Optional f$openssl_cipher_iv_length(const string& method) noexcept; -Optional f$openssl_encrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, - std::optional> tag = {}, string aad = string{}, int64_t tag_length = 16) noexcept; +Optional f$openssl_encrypt(const string& data, const string& method, const string& key, int64_t options = 0, const string& iv = string{}, + std::optional> tag = {}, const string& aad = string{}, int64_t tag_length = 16) noexcept; kphp::coro::task> f$openssl_decrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, string tag = string{}, string aad = string{}) noexcept; From 81dd01992ed28ccb2ba4220d9fc6ca08fe6345c3 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 15 Oct 2026 12:57:56 +0300 Subject: [PATCH 16/38] refactor f --- runtime-light/stdlib/crypto/crypto-functions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 66e961d382..ffdfa588e5 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -186,7 +186,7 @@ std::optional parse_cipher_algorithm(const string& method) return std::nullopt; } -std::optional parse_cipher_algorithm_test(const string& algorithm) noexcept { +std::optional parse_cipher_algorithm_test(std::string_view algorithm) noexcept { static constexpr auto ALGORITHMS = [] { auto arr{std::array{std::pair{AES_128_CBC, AES128_CBC}, std::pair{AES_256_CBC, AES256_CBC}, std::pair{AES_128_GCM, AES128_GCM}, std::pair{AES_256_GCM, AES256_GCM}}}; @@ -206,7 +206,7 @@ std::optional parse_cipher_algorithm_test(const string& algorit } std::array lowered; - std::transform(algorithm.c_str(), algorithm.c_str() + size, lowered.begin(), [](char c) noexcept -> char { + std::ranges::transform(algorithm, lowered.begin(), [](char c) noexcept -> char { if (c >= 'A' && c <= 'Z') { return c + ('a' - 'A'); } @@ -393,7 +393,7 @@ Optional f$openssl_cipher_iv_length(const string& method) noexcept { Optional f$openssl_encrypt(const string& data, const string& method, const string& source_key, int64_t options, const string& source_iv, std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { - auto algorithm{parse_cipher_algorithm_test(method)}; + auto algorithm{parse_cipher_algorithm_test({method.c_str(), method.size()})}; if (!algorithm) [[unlikely]] { kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); return false; From 6247bae04e83c6513725ea8fadb074bb94f2e492 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 15 Oct 2026 16:05:54 +0300 Subject: [PATCH 17/38] implement all hash functions via k2 header --- runtime-light/k2-platform/k2-api.h | 18 ++ runtime-light/k2-platform/k2-header.h | 9 +- .../stdlib/crypto/crypto-functions.cpp | 217 ++++++++++-------- .../stdlib/crypto/crypto-functions.h | 10 +- 4 files changed, 148 insertions(+), 106 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index dd4800c911..b7a29a3032 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -4,6 +4,7 @@ #pragma once +#include <__expected/expected.h> #include #include #include @@ -275,6 +276,23 @@ inline std::expected openssl_encrypt(CipherAlgorithm algorithm, return {encrypted_data_written}; } +inline std::expected hash(HashAlgorithm algorithm, std::string_view data, std::span hashed_data) noexcept { + if (auto error_code{k2_hash(algorithm, data.data(), data.size(), hashed_data.data(), hashed_data.size())}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {}; +} + +inline std::expected hash_hmac(HashAlgorithm algorithm, std::string_view data, std::string_view key, std::span hashed_data) noexcept { + if (auto error_code{k2_hash_hmac(algorithm, data.data(), data.size(), key.data(), key.size(), hashed_data.data(), hashed_data.size())}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {}; +} + inline std::expected rpc_send_request(std::string_view actor_name, std::span request_buffer, k2::RpcKind rpc_kind) noexcept { k2::descriptor descriptor{}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index fb6d427b0c..3b79c2ba90 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -98,10 +98,12 @@ enum UpdateStatus { NewDescriptor = 2, }; -enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT }; +enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT_CIPHER }; enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; +enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, RMD160 = 6, MD2 = 7, MD4 = 8, MD5 = 9, COUNT_HASH }; + enum RpcKind { TL_RPC = 0, }; @@ -522,6 +524,11 @@ int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding pad const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, size_t* encrypted_data_written, char* received_tag, size_t received_tag_len); +int32_t k2_hash(enum HashAlgorithm algorithm, const char* data, size_t data_len, char* hashed_data, size_t hashed_data_len); + +int32_t k2_hash_hmac(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* key, size_t key_len, char* hashed_data, + size_t hashed_data_len); + /** * Try to send rpc request to actor. On success, write descriptor of the corresponding rpc query to `rpc_d`. * This descriptor should be later used to call `k2_rpc_get_response_size` and `k2_rpc_fetch_response`. diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index ffdfa588e5..b277c417c8 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -186,35 +187,24 @@ std::optional parse_cipher_algorithm(const string& method) return std::nullopt; } -std::optional parse_cipher_algorithm_test(std::string_view algorithm) noexcept { - static constexpr auto ALGORITHMS = [] { - auto arr{std::array{std::pair{AES_128_CBC, AES128_CBC}, std::pair{AES_256_CBC, AES256_CBC}, std::pair{AES_128_GCM, AES128_GCM}, - std::pair{AES_256_GCM, AES256_GCM}}}; - std::ranges::sort(arr, std::less{}, &std::pair::first); - return arr; - }(); - - static_assert(ALGORITHMS.size() == CipherAlgorithm::COUNT); - - static constexpr size_t MAX_ALGO_NAME_LEN = - std::ranges::max( - ALGORITHMS, [](const auto& l, const auto& r) noexcept { return l.length() < r.length(); }, &std::pair::first) - .first.length(); - const size_t size{algorithm.size()}; - if (size > MAX_ALGO_NAME_LEN || size == 0) [[unlikely]] { - return std::nullopt; - } - - std::array lowered; - std::ranges::transform(algorithm, lowered.begin(), [](char c) noexcept -> char { - if (c >= 'A' && c <= 'Z') { - return c + ('a' - 'A'); - } - return c; - }); +constexpr auto CIPHER_ALGORITHMS = [] { + auto arr{std::array{std::pair{AES_128_CBC, AES128_CBC}, std::pair{AES_256_CBC, AES256_CBC}, std::pair{AES_128_GCM, AES128_GCM}, + std::pair{AES_256_GCM, AES256_GCM}}}; + std::ranges::sort(arr, std::less{}, &std::pair::first); + return arr; +}(); + +static_assert(CIPHER_ALGORITHMS.size() == CipherAlgorithm::COUNT_CIPHER); - std::string_view lowered_view{lowered.data(), size}; - auto rng{std::ranges::equal_range(ALGORITHMS, lowered_view, std::less{}, &std::pair::first)}; +std::optional parse_cipher_algorithm_test(std::string_view algorithm) noexcept { + auto lowered{std::ranges::transform_view{algorithm, [](char c) noexcept -> char { + if (c >= 'A' && c <= 'Z') { + return c + ('a' - 'A'); + } + return c; + }}}; + + auto rng{std::ranges::equal_range(CIPHER_ALGORITHMS, lowered, std::ranges::lexicographical_compare, &std::pair::first)}; if (!rng.empty()) [[likely]] { return rng.front().second; } @@ -239,7 +229,7 @@ constexpr const cipher_algorithm_traits& get_cipher_algorithm_traits(CipherAlgor cipher_algorithm_traits{32, 16, 16, encryption_mode::BLOCK} // AES256_CBC }; - static_assert(traits.size() == CipherAlgorithm::COUNT, "Update traits when adding a cipher algorithm"); + static_assert(traits.size() == CipherAlgorithm::COUNT_CIPHER); return traits[algorithm]; } @@ -376,30 +366,33 @@ std::optional> algorithm_pad_key_iv_test(const cipher_ } // namespace array f$openssl_get_cipher_methods([[maybe_unused]] bool aliases) noexcept { - array return_value{ - {std::make_pair(0, string{AES_128_CBC.data(), AES_128_CBC.size()}), std::make_pair(1, string{AES_256_CBC.data(), AES_256_CBC.size()}), - std::make_pair(2, string{AES_128_GCM.data(), AES_128_GCM.size()}), std::make_pair(3, string{AES_256_GCM.data(), AES_256_GCM.size()})}}; - return return_value; + array res{array_size{CIPHER_ALGORITHMS.size(), true}}; + for (auto [algo, _] : CIPHER_ALGORITHMS) { + res.push_back(string{algo.data(), static_cast(algo.size())}); + } + + return res; } -Optional f$openssl_cipher_iv_length(const string& method) noexcept { - auto algorithm{parse_cipher_algorithm(method)}; - if (!algorithm) { - kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); +Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept { + auto algo{parse_cipher_algorithm_test({algorithm.c_str(), algorithm.size()})}; + if (!algo) { + kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); return false; } - return algorithm_iv_len(*algorithm); + + return get_cipher_algorithm_traits(*algo).iv_len; } -Optional f$openssl_encrypt(const string& data, const string& method, const string& source_key, int64_t options, const string& source_iv, +Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& source_key, int64_t options, const string& source_iv, std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { - auto algorithm{parse_cipher_algorithm_test({method.c_str(), method.size()})}; - if (!algorithm) [[unlikely]] { - kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); + auto algo{parse_cipher_algorithm_test({algorithm.c_str(), algorithm.size()})}; + if (!algo) [[unlikely]] { + kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); return false; } - const auto& algorithm_traits{get_cipher_algorithm_traits(*algorithm)}; + const auto& algorithm_traits{get_cipher_algorithm_traits(*algo)}; bool is_aead{algorithm_traits.mode == encryption_mode::AEAD}; if (is_aead && (!tag || tag_length == 0)) [[unlikely]] { @@ -434,7 +427,7 @@ Optional f$openssl_encrypt(const string& data, const string& method, con auto encrypted_size{calc_encrypted_size(algorithm_traits, tag_length, data.size())}; string encrypted_data{static_cast(encrypted_size.encrypted_size), false}; string received_tag{static_cast(encrypted_size.tag_size), false}; - auto res{k2::openssl_encrypt(*algorithm, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, + auto res{k2::openssl_encrypt(*algo, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; if (!res) [[unlikely]] { @@ -652,91 +645,115 @@ kphp::coro::task f$openssl_private_decrypt(string data, mixed& result, str namespace { -constexpr std::array, 6> HASH_ALGOS = {{{"md5", tl::HashAlgorithm::MD5}, - {"sha1", tl::HashAlgorithm::SHA1}, - {"sha224", tl::HashAlgorithm::SHA224}, - {"sha256", tl::HashAlgorithm::SHA256}, - {"sha384", tl::HashAlgorithm::SHA384}, - {"sha512", tl::HashAlgorithm::SHA512}}}; - -std::optional parse_hash_algorithm(std::string_view user_algo) noexcept { - const auto* it{std::ranges::find_if( - HASH_ALGOS, - [user_algo = std::ranges::transform_view(user_algo, [](auto c) { return std::tolower(c); })](auto hash_algo) noexcept { - return std::ranges::equal(user_algo, hash_algo); - }, - [](const auto& hash_algo) noexcept { return hash_algo.first; })}; +constexpr std::string_view DSS1 = "dss1"; +constexpr std::string_view SHA1 = "sha1"; +constexpr std::string_view SHA224 = "sha224"; +constexpr std::string_view SHA256 = "sha256"; +constexpr std::string_view SHA384 = "sha384"; +constexpr std::string_view SHA512 = "sha512"; +constexpr std::string_view RMD160 = "rmd160"; +constexpr std::string_view MD2 = "md2"; +constexpr std::string_view MD4 = "md4"; +constexpr std::string_view MD5 = "md5"; + +constexpr auto HASH_ALGORITHMS = []() noexcept { + auto arr{std::array{std::pair{DSS1, HashAlgorithm::DSS1}, std::pair{SHA1, HashAlgorithm::SHA1}, std::pair{SHA224, HashAlgorithm::SHA224}, + std::pair{SHA256, HashAlgorithm::SHA256}, std::pair{SHA384, HashAlgorithm::SHA384}, std::pair{SHA512, HashAlgorithm::SHA512}, + std::pair{RMD160, HashAlgorithm::RMD160}, std::pair{MD2, HashAlgorithm::MD2}, std::pair{MD4, HashAlgorithm::MD4}, + std::pair{MD5, HashAlgorithm::MD5}}}; + + std::ranges::sort(arr, std::less{}, &std::pair::first); + return arr; +}(); + +static_assert(HASH_ALGORITHMS.size() == HashAlgorithm::COUNT_HASH); + +std::optional parse_hash_algorithm(std::string_view algorithm) noexcept { + auto lowered{std::ranges::transform_view{algorithm, [](char c) noexcept -> char { + if (c >= 'A' && c <= 'Z') { + return c + ('a' - 'A'); + } + return c; + }}}; + + auto rng{std::ranges::equal_range(HASH_ALGORITHMS, lowered, std::ranges::lexicographical_compare, &std::pair::first)}; + if (!rng.empty()) [[likely]] { + return rng.front().second; + } - return it != nullptr && it != HASH_ALGOS.end() ? std::optional{it->second} : std::nullopt; + return std::nullopt; } -kphp::coro::task send_and_get_string(tl::storer tls, bool raw_output) noexcept { - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } +auto calc_hashed_size(HashAlgorithm algorithm) noexcept -> size_t { + static constexpr std::array sizes{ + 20, // DSS1 + 20, // SHA1 + 28, // SHA224 + 32, // SHA256 + 48, // SHA384 + 64, // SHA512 + 20, // RMD160 + 16, // MD2 + 16, // MD4 + 16 //MD5 + }; - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; - } + static_assert(sizes.size() == HashAlgorithm::COUNT_HASH); - tl::fetcher tlf{response_bytes}; - tl::String response{}; - kphp::log::assertion(response.fetch(tlf)); + return sizes[algorithm]; +} - if (!raw_output) { - co_return kphp::strings::bin2hex(response.inner.value); +string hash_impl(HashAlgorithm algorithm, const string& data, bool binary) noexcept { + string hashed_data{static_cast(calc_hashed_size(algorithm)), false}; + auto res{k2::hash(algorithm, {data.c_str(), data.size()}, {hashed_data.buffer(), hashed_data.size()})}; + if (!res) [[unlikely]] { + return string{false}; } - // Important to pass size because response.inner.value is binary so - // it may contain zero in any position, not only in the end - co_return string{response.inner.value.data(), static_cast(response.inner.value.size())}; -} -kphp::coro::task hash_impl(tl::HashAlgorithm algo, string s, bool raw_output) noexcept { - tl::Hash hash{.algorithm = algo, .data = {.value = {s.c_str(), s.size()}}}; - tl::storer tls{hash.footprint()}; - hash.store(tls); - co_return co_await send_and_get_string(std::move(tls), raw_output); + return binary ? std::move(hashed_data) : kphp::strings::bin2hex({hashed_data.c_str(), hashed_data.size()}); } } // namespace array f$hash_algos() noexcept { - array response{array_size{HASH_ALGOS.size(), true}}; - for (auto [algo_name, _] : HASH_ALGOS) { - response.push_back(string{algo_name.data(), static_cast(algo_name.size())}); + array res{array_size{HASH_ALGORITHMS.size(), true}}; + for (auto [algo, _] : HASH_ALGORITHMS) { + res.push_back(string{algo.data(), static_cast(algo.size())}); } - return response; + + return res; } array f$hash_hmac_algos() noexcept { return f$hash_algos(); } -kphp::coro::task f$hash(string algo_str, string s, bool raw_output) noexcept { - const auto algo{parse_hash_algorithm({algo_str.c_str(), algo_str.size()})}; +string f$hash(const string& algorithm, const string& data, bool binary) noexcept { + const auto algo{parse_hash_algorithm({algorithm.c_str(), algorithm.size()})}; if (!algo) [[unlikely]] { - kphp::log::error("algo {} not supported in function hash", algo_str.c_str()); + kphp::log::error("algorithm {} not supported in function hash", algorithm.c_str()); } - co_return co_await hash_impl(*algo, s, raw_output); + + return hash_impl(*algo, data, binary); } -kphp::coro::task f$hash_hmac(string algo_str, string s, string key, bool raw_output) noexcept { - const auto algo{parse_hash_algorithm({algo_str.c_str(), algo_str.size()})}; +string f$hash_hmac(const string& algorithm, const string& data, const string& key, bool binary) noexcept { + const auto algo{parse_hash_algorithm({algorithm.c_str(), algorithm.size()})}; if (!algo) [[unlikely]] { - kphp::log::error("algo {} not supported in function hash", algo_str.c_str()); + kphp::log::error("algorithm {} not supported in function hash_hmac", algorithm.c_str()); + } + + string hashed_data{static_cast(calc_hashed_size(*algo)), false}; + auto res{k2::hash_hmac(*algo, {data.c_str(), data.size()}, {key.c_str(), key.size()}, {hashed_data.buffer(), hashed_data.size()})}; + if (!res) [[unlikely]] { + return string{false}; } - tl::HashHmac hash_hmac{.algorithm = *algo, .data = {.value = {s.c_str(), s.size()}}, .secret_key = {.value = {key.c_str(), key.size()}}}; - tl::storer tls{hash_hmac.footprint()}; - hash_hmac.store(tls); - co_return co_await send_and_get_string(std::move(tls), raw_output); + return binary ? std::move(hashed_data) : kphp::strings::bin2hex({hashed_data.c_str(), hashed_data.size()}); } -kphp::coro::task f$sha1(string s, bool raw_output) noexcept { - co_return co_await hash_impl(tl::HashAlgorithm::SHA1, s, raw_output); +string f$sha1(const string& data, bool binary) noexcept { + return hash_impl(HashAlgorithm::SHA1, data, binary); } int64_t f$crc32(const string& s) noexcept { diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index c418ca8af9..a6df5259f3 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -24,9 +24,9 @@ kphp::coro::task f$openssl_verify(string data, string signature, string array f$openssl_get_cipher_methods(bool aliases = false) noexcept; -Optional f$openssl_cipher_iv_length(const string& method) noexcept; +Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept; -Optional f$openssl_encrypt(const string& data, const string& method, const string& key, int64_t options = 0, const string& iv = string{}, +Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, std::optional> tag = {}, const string& aad = string{}, int64_t tag_length = 16) noexcept; kphp::coro::task> f$openssl_decrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, string tag = string{}, @@ -44,11 +44,11 @@ array f$hash_algos() noexcept; array f$hash_hmac_algos() noexcept; -kphp::coro::task f$hash(string algo_str, string s, bool raw_output = false) noexcept; +string f$hash(const string& algorithm, const string& data, bool binary = false) noexcept; -kphp::coro::task f$hash_hmac(string algo_str, string s, string key, bool raw_output = false) noexcept; +string f$hash_hmac(const string& algorithm, const string& data, const string& key, bool binary = false) noexcept; -kphp::coro::task f$sha1(string s, bool raw_output = false) noexcept; +string f$sha1(const string& data, bool binary = false) noexcept; inline string f$md5(const string& str, bool binary = false) noexcept { constexpr auto MD5_HASH_LEN = 16; From a12c2361b3aef55c7633af27843e8be8d7d74e0f Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 15 Oct 2026 17:36:18 +0300 Subject: [PATCH 18/38] remove redundant hash algos --- runtime-light/k2-platform/k2-header.h | 2 +- runtime-light/stdlib/crypto/crypto-functions.cpp | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 3b79c2ba90..9cadac408a 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -102,7 +102,7 @@ enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2 enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; -enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, RMD160 = 6, MD2 = 7, MD4 = 8, MD5 = 9, COUNT_HASH }; +enum HashAlgorithm : uint32_t { SHA1 = 0, SHA224 = 1, SHA256 = 2, SHA384 = 3, SHA512 = 4, MD5 = 5, COUNT_HASH }; enum RpcKind { TL_RPC = 0, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index b277c417c8..51b4b6ca3c 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -645,22 +645,16 @@ kphp::coro::task f$openssl_private_decrypt(string data, mixed& result, str namespace { -constexpr std::string_view DSS1 = "dss1"; constexpr std::string_view SHA1 = "sha1"; constexpr std::string_view SHA224 = "sha224"; constexpr std::string_view SHA256 = "sha256"; constexpr std::string_view SHA384 = "sha384"; constexpr std::string_view SHA512 = "sha512"; -constexpr std::string_view RMD160 = "rmd160"; -constexpr std::string_view MD2 = "md2"; -constexpr std::string_view MD4 = "md4"; constexpr std::string_view MD5 = "md5"; constexpr auto HASH_ALGORITHMS = []() noexcept { - auto arr{std::array{std::pair{DSS1, HashAlgorithm::DSS1}, std::pair{SHA1, HashAlgorithm::SHA1}, std::pair{SHA224, HashAlgorithm::SHA224}, - std::pair{SHA256, HashAlgorithm::SHA256}, std::pair{SHA384, HashAlgorithm::SHA384}, std::pair{SHA512, HashAlgorithm::SHA512}, - std::pair{RMD160, HashAlgorithm::RMD160}, std::pair{MD2, HashAlgorithm::MD2}, std::pair{MD4, HashAlgorithm::MD4}, - std::pair{MD5, HashAlgorithm::MD5}}}; + auto arr{std::array{std::pair{SHA1, HashAlgorithm::SHA1}, std::pair{SHA224, HashAlgorithm::SHA224}, std::pair{SHA256, HashAlgorithm::SHA256}, + std::pair{SHA384, HashAlgorithm::SHA384}, std::pair{SHA512, HashAlgorithm::SHA512}, std::pair{MD5, HashAlgorithm::MD5}}}; std::ranges::sort(arr, std::less{}, &std::pair::first); return arr; @@ -686,15 +680,11 @@ std::optional parse_hash_algorithm(std::string_view algorithm) no auto calc_hashed_size(HashAlgorithm algorithm) noexcept -> size_t { static constexpr std::array sizes{ - 20, // DSS1 20, // SHA1 28, // SHA224 32, // SHA256 48, // SHA384 64, // SHA512 - 20, // RMD160 - 16, // MD2 - 16, // MD4 16 //MD5 }; From 7e73a2ea276d87270d7a977522d87eea5c686e6f Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 15 Oct 2026 18:37:16 +0300 Subject: [PATCH 19/38] remove interurptible from php doc --- builtin-functions/kphp-light/stdlib/crypto-functions.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 1f71036d4c..ab0e7aecd5 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -54,12 +54,9 @@ function openssl_private_decrypt ($data ::: string, &$result ::: mixed, $key ::: function hash_algos () ::: string[]; function hash_hmac_algos () ::: string[]; -/** @kphp-extern-func-info interruptible */ function hash ($algo ::: string, $data ::: string, $raw_output ::: bool = false) ::: string; -/** @kphp-extern-func-info interruptible */ function hash_hmac ($algo ::: string, $data ::: string, $key ::: string, $raw_output ::: bool = false) ::: string; -/** @kphp-extern-func-info interruptible */ function sha1 ($s ::: string, $raw_output ::: bool = false) ::: string; function md5 ($s ::: string, $raw_output ::: bool = false): string; From cf48d462e5337ca11fe30622e7438c08b8bcbe60 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 20 Oct 2026 15:58:57 +0300 Subject: [PATCH 20/38] add hash traits --- .../stdlib/crypto/crypto-functions.cpp | 132 ++++++++---------- 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 51b4b6ca3c..e06a2045c7 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -187,16 +187,28 @@ std::optional parse_cipher_algorithm(const string& method) return std::nullopt; } +enum class encryption_mode { BLOCK, STREAM, AEAD }; + +struct cipher_algorithm_traits { + int64_t key_len; + int64_t iv_len; + int64_t block_size; + encryption_mode mode; + CipherAlgorithm algo; +}; + constexpr auto CIPHER_ALGORITHMS = [] { - auto arr{std::array{std::pair{AES_128_CBC, AES128_CBC}, std::pair{AES_256_CBC, AES256_CBC}, std::pair{AES_128_GCM, AES128_GCM}, - std::pair{AES_256_GCM, AES256_GCM}}}; - std::ranges::sort(arr, std::less{}, &std::pair::first); + auto arr{std::array{std::pair{AES_128_CBC, cipher_algorithm_traits{16, 16, 16, encryption_mode::BLOCK, AES128_CBC}}, + std::pair{AES_256_CBC, cipher_algorithm_traits{32, 16, 16, encryption_mode::BLOCK, AES256_CBC}}, + std::pair{AES_128_GCM, cipher_algorithm_traits{16, 12, 0, encryption_mode::AEAD, AES128_GCM}}, + std::pair{AES_256_GCM, cipher_algorithm_traits{32, 12, 0, encryption_mode::AEAD, AES256_GCM}}}}; + std::ranges::sort(arr, std::less{}, &std::pair::first); return arr; }(); static_assert(CIPHER_ALGORITHMS.size() == CipherAlgorithm::COUNT_CIPHER); -std::optional parse_cipher_algorithm_test(std::string_view algorithm) noexcept { +std::optional> parse_cipher_algorithm_test(std::string_view algorithm) noexcept { auto lowered{std::ranges::transform_view{algorithm, [](char c) noexcept -> char { if (c >= 'A' && c <= 'Z') { return c + ('a' - 'A'); @@ -204,7 +216,8 @@ std::optional parse_cipher_algorithm_test(std::string_view algo return c; }}}; - auto rng{std::ranges::equal_range(CIPHER_ALGORITHMS, lowered, std::ranges::lexicographical_compare, &std::pair::first)}; + auto rng{ + std::ranges::equal_range(CIPHER_ALGORITHMS, lowered, std::ranges::lexicographical_compare, &std::pair::first)}; if (!rng.empty()) [[likely]] { return rng.front().second; } @@ -212,28 +225,6 @@ std::optional parse_cipher_algorithm_test(std::string_view algo return std::nullopt; } -enum class encryption_mode { BLOCK, STREAM, AEAD }; - -struct cipher_algorithm_traits { - int64_t key_len; - int64_t iv_len; - int64_t block_size; - encryption_mode mode; -}; - -constexpr const cipher_algorithm_traits& get_cipher_algorithm_traits(CipherAlgorithm algorithm) noexcept { - static constexpr std::array traits{ - cipher_algorithm_traits{16, 12, 0, encryption_mode::AEAD}, // AES128_GCM - cipher_algorithm_traits{32, 12, 0, encryption_mode::AEAD}, // AES256_GCM - cipher_algorithm_traits{16, 16, 16, encryption_mode::BLOCK}, // AES128_CBC - cipher_algorithm_traits{32, 16, 16, encryption_mode::BLOCK} // AES256_CBC - }; - - static_assert(traits.size() == CipherAlgorithm::COUNT_CIPHER); - - return traits[algorithm]; -} - bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { return algorithm == tl::AES128_GCM || algorithm == tl::AES256_GCM; } @@ -242,15 +233,15 @@ bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { return is_gcm_algorithm(algorithm); } -auto calc_encrypted_size(const cipher_algorithm_traits& algorithm_traits, int64_t tag_size, int64_t data_len) noexcept { +auto calc_encrypted_size(const cipher_algorithm_traits& traits, int64_t tag_size, int64_t data_len) noexcept { struct encrypted_data_size_info { int64_t encrypted_size; int64_t tag_size; }; - switch (algorithm_traits.mode) { + switch (traits.mode) { case encryption_mode::BLOCK: - return encrypted_data_size_info{data_len + algorithm_traits.block_size, 0}; + return encrypted_data_size_info{data_len + traits.block_size, 0}; case encryption_mode::STREAM: return encrypted_data_size_info{data_len, 0}; case encryption_mode::AEAD: @@ -326,16 +317,16 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg return std::make_pair(key, iv); } -std::optional> algorithm_pad_key_iv_test(const cipher_algorithm_traits& algorithm_traits, const string& source_key, - const string& source_iv, int64_t options) noexcept { +std::optional> algorithm_pad_key_iv_test(const cipher_algorithm_traits& traits, const string& source_key, const string& source_iv, + int64_t options) noexcept { auto iv = source_iv; - if (algorithm_traits.mode == encryption_mode::AEAD) { + if (traits.mode == encryption_mode::AEAD) { if (source_iv.empty()) [[unlikely]] { kphp::log::warning("Setting of IV length for AEAD mode failed"); return std::nullopt; } } else { - int64_t iv_required_len{algorithm_traits.iv_len}; + int64_t iv_required_len{traits.iv_len}; if (iv.size() < iv_required_len) { kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len); iv.append(static_cast(iv_required_len - iv.size()), '\0'); @@ -346,7 +337,7 @@ std::optional> algorithm_pad_key_iv_test(const cipher_ } auto key = source_key; - int64_t key_required_len{algorithm_traits.key_len}; + int64_t key_required_len{traits.key_len}; if (key.size() < key_required_len) { if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) [[unlikely]] { kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); @@ -367,7 +358,7 @@ std::optional> algorithm_pad_key_iv_test(const cipher_ array f$openssl_get_cipher_methods([[maybe_unused]] bool aliases) noexcept { array res{array_size{CIPHER_ALGORITHMS.size(), true}}; - for (auto [algo, _] : CIPHER_ALGORITHMS) { + for (const auto& [algo, _] : CIPHER_ALGORITHMS) { res.push_back(string{algo.data(), static_cast(algo.size())}); } @@ -381,7 +372,7 @@ Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept { return false; } - return get_cipher_algorithm_traits(*algo).iv_len; + return algo->get().iv_len; } Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& source_key, int64_t options, const string& source_iv, @@ -392,9 +383,8 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, return false; } - const auto& algorithm_traits{get_cipher_algorithm_traits(*algo)}; - - bool is_aead{algorithm_traits.mode == encryption_mode::AEAD}; + const auto& traits{algo->get()}; + bool is_aead{traits.mode == encryption_mode::AEAD}; if (is_aead && (!tag || tag_length == 0)) [[unlikely]] { kphp::log::warning("A tag must be provided when using AEAD mode"); return false; @@ -414,7 +404,7 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); } - auto key_iv{algorithm_pad_key_iv_test(algorithm_traits, source_key, source_iv, options)}; + auto key_iv{algorithm_pad_key_iv_test(traits, source_key, source_iv, options)}; if (!key_iv) [[unlikely]] { return false; } @@ -424,10 +414,10 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, padding = NO_PADDING; } - auto encrypted_size{calc_encrypted_size(algorithm_traits, tag_length, data.size())}; + auto encrypted_size{calc_encrypted_size(traits, tag_length, data.size())}; string encrypted_data{static_cast(encrypted_size.encrypted_size), false}; string received_tag{static_cast(encrypted_size.tag_size), false}; - auto res{k2::openssl_encrypt(*algo, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, + auto res{k2::openssl_encrypt(traits.algo, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; if (!res) [[unlikely]] { @@ -652,17 +642,33 @@ constexpr std::string_view SHA384 = "sha384"; constexpr std::string_view SHA512 = "sha512"; constexpr std::string_view MD5 = "md5"; -constexpr auto HASH_ALGORITHMS = []() noexcept { - auto arr{std::array{std::pair{SHA1, HashAlgorithm::SHA1}, std::pair{SHA224, HashAlgorithm::SHA224}, std::pair{SHA256, HashAlgorithm::SHA256}, - std::pair{SHA384, HashAlgorithm::SHA384}, std::pair{SHA512, HashAlgorithm::SHA512}, std::pair{MD5, HashAlgorithm::MD5}}}; +constexpr size_t SHA1_HASHED_SIZE = 20; +constexpr size_t SHA224_HASHED_SIZE = 28; +constexpr size_t SHA256_HASHED_SIZE = 32; +constexpr size_t SHA384_HASHED_SIZE = 48; +constexpr size_t SHA512_HASHED_SIZE = 64; +constexpr size_t MD5_HASHED_SIZE = 16; - std::ranges::sort(arr, std::less{}, &std::pair::first); +struct hash_algorithm_traits { + int64_t hashed_size; + HashAlgorithm algo; +}; + +constexpr auto HASH_ALGORITHMS = []() noexcept { + auto arr{std::array{std::pair{SHA1, hash_algorithm_traits{SHA1_HASHED_SIZE, HashAlgorithm::SHA1}}, + std::pair{SHA224, hash_algorithm_traits{SHA224_HASHED_SIZE, HashAlgorithm::SHA224}}, + std::pair{SHA256, hash_algorithm_traits{SHA256_HASHED_SIZE, HashAlgorithm::SHA256}}, + std::pair{SHA384, hash_algorithm_traits{SHA384_HASHED_SIZE, HashAlgorithm::SHA384}}, + std::pair{SHA512, hash_algorithm_traits{SHA512_HASHED_SIZE, HashAlgorithm::SHA512}}, + std::pair{MD5, hash_algorithm_traits{MD5_HASHED_SIZE, HashAlgorithm::MD5}}}}; + + std::ranges::sort(arr, std::less{}, &std::pair::first); return arr; }(); static_assert(HASH_ALGORITHMS.size() == HashAlgorithm::COUNT_HASH); -std::optional parse_hash_algorithm(std::string_view algorithm) noexcept { +std::optional parse_hash_algorithm(std::string_view algorithm) noexcept { auto lowered{std::ranges::transform_view{algorithm, [](char c) noexcept -> char { if (c >= 'A' && c <= 'Z') { return c + ('a' - 'A'); @@ -670,7 +676,8 @@ std::optional parse_hash_algorithm(std::string_view algorithm) no return c; }}}; - auto rng{std::ranges::equal_range(HASH_ALGORITHMS, lowered, std::ranges::lexicographical_compare, &std::pair::first)}; + auto rng{ + std::ranges::equal_range(HASH_ALGORITHMS, lowered, std::ranges::lexicographical_compare, &std::pair::first)}; if (!rng.empty()) [[likely]] { return rng.front().second; } @@ -678,24 +685,9 @@ std::optional parse_hash_algorithm(std::string_view algorithm) no return std::nullopt; } -auto calc_hashed_size(HashAlgorithm algorithm) noexcept -> size_t { - static constexpr std::array sizes{ - 20, // SHA1 - 28, // SHA224 - 32, // SHA256 - 48, // SHA384 - 64, // SHA512 - 16 //MD5 - }; - - static_assert(sizes.size() == HashAlgorithm::COUNT_HASH); - - return sizes[algorithm]; -} - -string hash_impl(HashAlgorithm algorithm, const string& data, bool binary) noexcept { - string hashed_data{static_cast(calc_hashed_size(algorithm)), false}; - auto res{k2::hash(algorithm, {data.c_str(), data.size()}, {hashed_data.buffer(), hashed_data.size()})}; +string hash_impl(hash_algorithm_traits traits, const string& data, bool binary) noexcept { + string hashed_data{static_cast(traits.hashed_size), false}; + auto res{k2::hash(traits.algo, {data.c_str(), data.size()}, {hashed_data.buffer(), hashed_data.size()})}; if (!res) [[unlikely]] { return string{false}; } @@ -733,8 +725,8 @@ string f$hash_hmac(const string& algorithm, const string& data, const string& ke kphp::log::error("algorithm {} not supported in function hash_hmac", algorithm.c_str()); } - string hashed_data{static_cast(calc_hashed_size(*algo)), false}; - auto res{k2::hash_hmac(*algo, {data.c_str(), data.size()}, {key.c_str(), key.size()}, {hashed_data.buffer(), hashed_data.size()})}; + string hashed_data{static_cast(algo->hashed_size), false}; + auto res{k2::hash_hmac(algo->algo, {data.c_str(), data.size()}, {key.c_str(), key.size()}, {hashed_data.buffer(), hashed_data.size()})}; if (!res) [[unlikely]] { return string{false}; } @@ -743,7 +735,7 @@ string f$hash_hmac(const string& algorithm, const string& data, const string& ke } string f$sha1(const string& data, bool binary) noexcept { - return hash_impl(HashAlgorithm::SHA1, data, binary); + return hash_impl(hash_algorithm_traits{SHA1_HASHED_SIZE, HashAlgorithm::SHA1}, data, binary); } int64_t f$crc32(const string& s) noexcept { From e6776fe02ff1a40266d8e687d1ca0d2805c4fa6f Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 20 Oct 2026 17:14:30 +0300 Subject: [PATCH 21/38] make hash functions zero copy --- .../stdlib/string/string-functions.h | 19 +++++++++++---- .../stdlib/crypto/crypto-functions.cpp | 24 ++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/runtime-common/stdlib/string/string-functions.h b/runtime-common/stdlib/string/string-functions.h index ce0784e8b6..6639d13d7c 100644 --- a/runtime-common/stdlib/string/string-functions.h +++ b/runtime-common/stdlib/string/string-functions.h @@ -19,15 +19,24 @@ namespace kphp { namespace strings { +inline void bin2hex(const char* src, size_t src_len, char* dst, size_t dst_len) noexcept { + php_assert(dst_len >= 2 * src_len); + const auto& string_lib_constants{StringLibConstants::get()}; + for (size_t i = src_len; i-- > 0;) { + dst[2 * i + 1] = string_lib_constants.lhex_digits[src[i] & 15]; + dst[2 * i] = string_lib_constants.lhex_digits[(src[i] >> 4) & 15]; + } +} + +inline void bin2hex(char* buf, size_t src_len, size_t buf_len) noexcept { + bin2hex(buf, src_len, buf, buf_len); +} + inline string bin2hex(std::string_view str) noexcept { int len = str.size(); string result(2 * len, false); - const auto& string_lib_constants{StringLibConstants::get()}; - for (int i = 0; i < len; i++) { - result[2 * i] = string_lib_constants.lhex_digits[(str[i] >> 4) & 15]; - result[2 * i + 1] = string_lib_constants.lhex_digits[str[i] & 15]; - } + bin2hex(str.data(), str.size(), result.buffer(), result.size()); return result; } diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index e06a2045c7..6dae2adc46 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -686,13 +686,17 @@ std::optional parse_hash_algorithm(std::string_view algor } string hash_impl(hash_algorithm_traits traits, const string& data, bool binary) noexcept { - string hashed_data{static_cast(traits.hashed_size), false}; - auto res{k2::hash(traits.algo, {data.c_str(), data.size()}, {hashed_data.buffer(), hashed_data.size()})}; + string hashed_data{binary ? static_cast(traits.hashed_size) : static_cast(traits.hashed_size * 2), false}; + auto res{k2::hash(traits.algo, {data.c_str(), data.size()}, {hashed_data.buffer(), static_cast(traits.hashed_size)})}; if (!res) [[unlikely]] { - return string{false}; + return {}; } - return binary ? std::move(hashed_data) : kphp::strings::bin2hex({hashed_data.c_str(), hashed_data.size()}); + if (!binary) { + kphp::strings::bin2hex(hashed_data.buffer(), traits.hashed_size, hashed_data.size()); + } + + return hashed_data; } } // namespace @@ -725,13 +729,17 @@ string f$hash_hmac(const string& algorithm, const string& data, const string& ke kphp::log::error("algorithm {} not supported in function hash_hmac", algorithm.c_str()); } - string hashed_data{static_cast(algo->hashed_size), false}; - auto res{k2::hash_hmac(algo->algo, {data.c_str(), data.size()}, {key.c_str(), key.size()}, {hashed_data.buffer(), hashed_data.size()})}; + string hashed_data{binary ? static_cast(algo->hashed_size) : static_cast(algo->hashed_size * 2), false}; + auto res{k2::hash_hmac(algo->algo, {data.c_str(), data.size()}, {key.c_str(), key.size()}, {hashed_data.buffer(), static_cast(algo->hashed_size)})}; if (!res) [[unlikely]] { - return string{false}; + return {}; + } + + if (!binary) { + kphp::strings::bin2hex(hashed_data.buffer(), algo->hashed_size, hashed_data.size()); } - return binary ? std::move(hashed_data) : kphp::strings::bin2hex({hashed_data.c_str(), hashed_data.size()}); + return hashed_data; } string f$sha1(const string& data, bool binary) noexcept { From e8832d40fb5dea29c3a6efcadb33033f5d810555 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 20 Oct 2026 17:51:57 +0300 Subject: [PATCH 22/38] implement f via k2 header --- .../kphp-light/stdlib/crypto-functions.txt | 2 - runtime-light/k2-platform/k2-api.h | 13 ++ runtime-light/k2-platform/k2-header.h | 4 + .../stdlib/crypto/crypto-functions.cpp | 202 +++++------------- .../stdlib/crypto/crypto-functions.h | 4 +- 5 files changed, 76 insertions(+), 149 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index ab0e7aecd5..9b22a2becc 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -36,8 +36,6 @@ function openssl_cipher_iv_length($method ::: string) ::: int | false; function openssl_encrypt($data ::: string, $method ::: string, $key ::: string, $options ::: int = 0, $iv ::: string = '', string &$tag = TODO, $aad ::: string = '', $tag_length ::: int = 16) ::: string | false; - -/** @kphp-extern-func-info interruptible */ function openssl_decrypt($data ::: string, $method ::: string, $key ::: string, $options ::: int = 0, $iv ::: string = '', $tag ::: string = '', $aad ::: string = '') ::: string | false; diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index b7a29a3032..5f6e3233f5 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -276,6 +276,19 @@ inline std::expected openssl_encrypt(CipherAlgorithm algorithm, return {encrypted_data_written}; } +inline std::expected openssl_decrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view key, std::string_view iv, + std::string_view aad, std::string_view tag, std::string_view data, + std::span decrypted_data) noexcept { + size_t decrypted_data_written{0}; + if (auto error_code{k2_openssl_decrypt(algorithm, padding, key.data(), key.size(), iv.data(), iv.size(), aad.data(), aad.size(), tag.data(), tag.size(), + data.data(), data.size(), decrypted_data.data(), decrypted_data.size(), std::addressof(decrypted_data_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {decrypted_data_written}; +} + inline std::expected hash(HashAlgorithm algorithm, std::string_view data, std::span hashed_data) noexcept { if (auto error_code{k2_hash(algorithm, data.data(), data.size(), hashed_data.data(), hashed_data.size())}; error_code != k2::errno_ok) { return std::unexpected{error_code}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 9cadac408a..bbbeb6dd8e 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -524,6 +524,10 @@ int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding pad const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, size_t* encrypted_data_written, char* received_tag, size_t received_tag_len); +int32_t k2_openssl_decrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, + const char* aad, size_t aad_len, const char* tag, size_t tag_len, const char* data, size_t data_len, char* decrypted_data, + size_t decrypted_data_len, size_t* decrypted_data_written); + int32_t k2_hash(enum HashAlgorithm algorithm, const char* data, size_t data_len, char* hashed_data, size_t hashed_data_len); int32_t k2_hash_hmac(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* key, size_t key_len, char* hashed_data, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 6dae2adc46..eda4711c44 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -169,24 +169,6 @@ constexpr std::string_view AES_256_CBC = "aes-256-cbc"; constexpr std::string_view AES_128_GCM = "aes-128-gcm"; constexpr std::string_view AES_256_GCM = "aes-256-gcm"; -std::optional parse_cipher_algorithm(const string& method) noexcept { - using namespace std::string_view_literals; - std::string_view method_sv{method.c_str(), method.size()}; - - const auto ichar_equals = [](char a, char b) { return std::tolower(a) == std::tolower(b); }; - - if (std::ranges::equal(method_sv, AES_128_CBC, ichar_equals)) { - return tl::CipherAlgorithm::AES128_CBC; - } else if (std::ranges::equal(method_sv, AES_256_CBC, ichar_equals)) { - return tl::CipherAlgorithm::AES256_CBC; - } else if (std::ranges::equal(method_sv, AES_128_GCM, ichar_equals)) { - return tl::CipherAlgorithm::AES128_GCM; - } else if (std::ranges::equal(method_sv, AES_256_GCM, ichar_equals)) { - return tl::CipherAlgorithm::AES256_GCM; - } - return std::nullopt; -} - enum class encryption_mode { BLOCK, STREAM, AEAD }; struct cipher_algorithm_traits { @@ -208,7 +190,7 @@ constexpr auto CIPHER_ALGORITHMS = [] { static_assert(CIPHER_ALGORITHMS.size() == CipherAlgorithm::COUNT_CIPHER); -std::optional> parse_cipher_algorithm_test(std::string_view algorithm) noexcept { +std::optional> parse_cipher_algorithm(std::string_view algorithm) noexcept { auto lowered{std::ranges::transform_view{algorithm, [](char c) noexcept -> char { if (c >= 'A' && c <= 'Z') { return c + ('a' - 'A'); @@ -225,14 +207,6 @@ std::optional> parse_ciphe return std::nullopt; } -bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) { - return algorithm == tl::AES128_GCM || algorithm == tl::AES256_GCM; -} - -bool is_aead_algorithm(tl::CipherAlgorithm algorithm) { - return is_gcm_algorithm(algorithm); -} - auto calc_encrypted_size(const cipher_algorithm_traits& traits, int64_t tag_size, int64_t data_len) noexcept { struct encrypted_data_size_info { int64_t encrypted_size; @@ -249,76 +223,20 @@ auto calc_encrypted_size(const cipher_algorithm_traits& traits, int64_t tag_size } } -constexpr size_t GCM_NONCE_LEN = 12; -constexpr size_t AES_BLOCK_LEN = 16; -constexpr size_t AES_128_KEY_LEN = 16; -constexpr size_t AES_256_KEY_LEN = 32; - -int64_t algorithm_iv_len([[maybe_unused]] tl::CipherAlgorithm algorithm) noexcept { - if (is_gcm_algorithm(algorithm)) { - return GCM_NONCE_LEN; - } - return AES_BLOCK_LEN; -} - -int64_t algorithm_key_len(tl::CipherAlgorithm algorithm) noexcept { - switch (algorithm) { - case tl::CipherAlgorithm::AES128_GCM: - case tl::CipherAlgorithm::AES128_CBC: { - return AES_128_KEY_LEN; - } - case tl::CipherAlgorithm::AES256_GCM: - case tl::CipherAlgorithm::AES256_CBC: { - return AES_256_KEY_LEN; - } - default: { - kphp::log::warning("unexpected cipher algorithm"); - return 0; - } +auto calc_decrypted_size(const cipher_algorithm_traits& traits, int64_t data_len) noexcept { + switch (traits.mode) { + case encryption_mode::BLOCK: + return data_len + traits.block_size; + case encryption_mode::STREAM: + case encryption_mode::AEAD: + return data_len; } } enum class cipher_opts : int64_t { OPENSSL_RAW_DATA = 1, OPENSSL_ZERO_PADDING = 2, OPENSSL_DONT_ZERO_PAD_KEY = 4 }; -Optional> algorithm_pad_key_iv(tl::CipherAlgorithm algorithm, const string& source_key, const string& source_iv, - int64_t options) noexcept { - const size_t iv_required_len = algorithm_iv_len(algorithm); - const size_t key_required_len = algorithm_key_len(algorithm); - auto iv = source_iv; - if (is_aead_algorithm(algorithm)) { - if (source_iv.empty()) { - kphp::log::warning("Setting of IV length for AEAD mode failed"); - return false; - } - } else { - if (iv.size() < iv_required_len) { - kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len); - iv.append(static_cast(iv_required_len - iv.size()), '\0'); - } else if (iv.size() > iv_required_len) { - kphp::log::warning("IV passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", iv.size(), iv_required_len); - iv.shrink(static_cast(iv_required_len)); - } - } - - auto key = source_key; - if (key.size() < key_required_len) { - if (options & static_cast(cipher_opts::OPENSSL_DONT_ZERO_PAD_KEY)) { - kphp::log::warning("Key length should be {} bytes long:\n", key_required_len); - return false; - } - kphp::log::warning("passphrase passed is only {} bytes long, cipher expects an passphrase of precisely {} bytes, padding with \\0", key.size(), - key_required_len); - key.append(key_required_len - key.size(), '\0'); - } else if (key.size() > key_required_len) { - kphp::log::warning("passphrase passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", key.size(), key_required_len); - key.shrink(static_cast(key_required_len)); - } - - return std::make_pair(key, iv); -} - -std::optional> algorithm_pad_key_iv_test(const cipher_algorithm_traits& traits, const string& source_key, const string& source_iv, - int64_t options) noexcept { +std::optional> algorithm_pad_key_iv(const cipher_algorithm_traits& traits, const string& source_key, const string& source_iv, + int64_t options) noexcept { auto iv = source_iv; if (traits.mode == encryption_mode::AEAD) { if (source_iv.empty()) [[unlikely]] { @@ -366,7 +284,7 @@ array f$openssl_get_cipher_methods([[maybe_unused]] bool aliases) noexce } Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept { - auto algo{parse_cipher_algorithm_test({algorithm.c_str(), algorithm.size()})}; + auto algo{parse_cipher_algorithm({algorithm.c_str(), algorithm.size()})}; if (!algo) { kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); return false; @@ -375,9 +293,9 @@ Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept { return algo->get().iv_len; } -Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& source_key, int64_t options, const string& source_iv, +Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& key, int64_t options, const string& iv, std::optional> tag, const string& aad, [[maybe_unused]] int64_t tag_length) noexcept { - auto algo{parse_cipher_algorithm_test({algorithm.c_str(), algorithm.size()})}; + auto algo{parse_cipher_algorithm({algorithm.c_str(), algorithm.size()})}; if (!algo) [[unlikely]] { kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); return false; @@ -400,11 +318,11 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, } } - if (source_iv.empty()) { + if (iv.empty()) { kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); } - auto key_iv{algorithm_pad_key_iv_test(traits, source_key, source_iv, options)}; + auto key_iv{algorithm_pad_key_iv(traits, key, iv, options)}; if (!key_iv) [[unlikely]] { return false; } @@ -433,69 +351,63 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, return (options & static_cast(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(encrypted_data) : f$base64_encode(encrypted_data); } -kphp::coro::task> f$openssl_decrypt(string data, string method, string source_key, int64_t options, string source_iv, string tag, - string aad) noexcept { - if (!(options & static_cast(cipher_opts::OPENSSL_RAW_DATA))) { - Optional decoding_data{f$base64_decode(data, true)}; - if (!decoding_data.has_value()) { - kphp::log::warning("Failed to base64 decode the input"); - co_return false; - } - data = std::move(decoding_data.val()); +Optional f$openssl_decrypt(const string& data, const string& algorithm, const string& key, int64_t options, const string& iv, const string& tag, + const string& aad) noexcept { + auto algo{parse_cipher_algorithm({algorithm.c_str(), algorithm.size()})}; + if (!algo) [[unlikely]] { + kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); + return false; } - auto algorithm{parse_cipher_algorithm(method)}; - if (!algorithm.has_value()) { - kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); - co_return false; + const auto& traits{algo->get()}; + bool is_aead{traits.mode == encryption_mode::AEAD}; + if (is_aead && tag.empty()) [[unlikely]] { + kphp::log::warning("A tag must be provided when using AEAD mode"); + return false; } - bool aead{is_aead_algorithm(*algorithm)}; - if (!tag.empty() && !aead) { - kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); + if (!is_aead) { + if (!tag.empty()) { + kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); + } + + if (!aad.empty()) { + kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); + } } - if (!aad.empty() && !aead) { - kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); + + string input_data{data}; + if (!(options & static_cast(cipher_opts::OPENSSL_RAW_DATA))) { + Optional decoding_data{f$base64_decode(data, true)}; + if (!decoding_data.has_value()) [[unlikely]] { + kphp::log::warning("Failed to base64 decode the input"); + return false; + } + + input_data = std::move(decoding_data.val()); } - auto key_iv{algorithm_pad_key_iv(*algorithm, source_key, source_iv, options)}; - if (!key_iv.has_value()) { - co_return false; + auto key_iv{algorithm_pad_key_iv(traits, key, iv, options)}; + if (!key_iv) [[unlikely]] { + return false; } - tl::BlockPadding padding{tl::BlockPadding::PKCS7}; + BlockPadding padding{BlockPadding::PKCS7}; if (options & static_cast(cipher_opts::OPENSSL_ZERO_PADDING)) { - padding = tl::BlockPadding::NO_PADDING; - } - tl::Decrypt decrypt{.algorithm = *algorithm, - .padding = padding, - .passphrase = {.value = {key_iv.val().first.c_str(), key_iv.val().first.size()}}, - .iv = {.value = {key_iv.val().second.c_str(), key_iv.val().second.size()}}, - .tag = {.value = {tag.c_str(), tag.size()}}, - .aad = {.value = {aad.c_str(), aad.size()}}, - .data = {.value = {data.c_str(), data.size()}}}; - tl::storer tls{decrypt.footprint()}; - decrypt.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; + padding = BlockPadding::NO_PADDING; } - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; + string decrypted_data{static_cast(calc_decrypted_size(traits, input_data.size())), false}; + auto res{k2::openssl_decrypt(traits.algo, padding, {(*key_iv).first.c_str(), (*key_iv).first.size()}, {(*key_iv).second.c_str(), (*key_iv).second.size()}, + {aad.c_str(), aad.size()}, {tag.c_str(), tag.size()}, {input_data.c_str(), input_data.size()}, + {decrypted_data.buffer(), decrypted_data.size()})}; + if (!res) [[unlikely]] { + return false; } - tl::fetcher tlf{response_bytes}; - tl::Maybe response{}; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; - } + decrypted_data.shrink(static_cast(*res)); - co_return string{(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; + return std::move(decrypted_data); } kphp::coro::task> f$openssl_pkey_get_public(string key) noexcept { diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index a6df5259f3..b880991683 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -29,8 +29,8 @@ Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept; Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, std::optional> tag = {}, const string& aad = string{}, int64_t tag_length = 16) noexcept; -kphp::coro::task> f$openssl_decrypt(string data, string method, string key, int64_t options = 0, string iv = string{}, string tag = string{}, - string aad = string{}) noexcept; +Optional f$openssl_decrypt(string data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, + const string& tag = string{}, const string& aad = string{}) noexcept; kphp::coro::task> f$openssl_pkey_get_public(string key) noexcept; From e233febe002b5afd2bdd3169a02fe19a0b9b56a8 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 21 Oct 2026 14:59:14 +0300 Subject: [PATCH 23/38] implement f and f via k2 header --- .../kphp-light/stdlib/crypto-functions.txt | 2 - runtime-light/k2-platform/k2-api.h | 24 ++++ runtime-light/k2-platform/k2-header.h | 10 +- .../stdlib/crypto/crypto-functions.cpp | 112 +++++++----------- .../stdlib/crypto/crypto-functions.h | 6 +- 5 files changed, 82 insertions(+), 72 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 9b22a2becc..ab8d5e41c8 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -44,9 +44,7 @@ function openssl_pkey_get_private ($key ::: string, $passphrase ::: string = '') /** @kphp-extern-func-info interruptible */ function openssl_pkey_get_public ($key ::: string) ::: string | false; -/** @kphp-extern-func-info interruptible */ function openssl_public_encrypt ($data ::: string, &$result ::: mixed, $key ::: string) ::: bool; -/** @kphp-extern-func-info interruptible */ function openssl_private_decrypt ($data ::: string, &$result ::: mixed, $key ::: string) ::: bool; function hash_algos () ::: string[]; diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 5f6e3233f5..71da94ca39 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -289,6 +289,30 @@ inline std::expected openssl_decrypt(CipherAlgorithm algorithm, return {decrypted_data_written}; } +inline std::expected openssl_public_encrypt(std::string_view data, std::span encrypted_data, std::string_view public_key, + AsymmPadding padding) noexcept { + size_t encrypted_data_written{0}; + if (auto error_code{k2_openssl_public_encrypt(padding, public_key.data(), public_key.size(), data.data(), data.size(), encrypted_data.data(), + encrypted_data.size(), std::addressof(encrypted_data_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {encrypted_data_written}; +} + +inline std::expected openssl_private_decrypt(std::string_view data, std::span decrypted_data, std::string_view private_key, + AsymmPadding padding) noexcept { + size_t decrypted_data_written{0}; + if (auto error_code{k2_openssl_private_decrypt(padding, private_key.data(), private_key.size(), data.data(), data.size(), decrypted_data.data(), + decrypted_data.size(), std::addressof(decrypted_data_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {decrypted_data_written}; +} + inline std::expected hash(HashAlgorithm algorithm, std::string_view data, std::span hashed_data) noexcept { if (auto error_code{k2_hash(algorithm, data.data(), data.size(), hashed_data.data(), hashed_data.size())}; error_code != k2::errno_ok) { return std::unexpected{error_code}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index bbbeb6dd8e..56120c6a5d 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -100,7 +100,9 @@ enum UpdateStatus { enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT_CIPHER }; -enum BlockPadding : uint32_t { PKCS7 = 0, NO_PADDING = 1 }; +enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; + +enum AsymmPadding : uint32_t { PKCS1 = 1, SSLV23 = 3, ASYMM_NO_PADDING = 3, PKCS1_OAEP = 4, PKCS1_PSS = 5 }; enum HashAlgorithm : uint32_t { SHA1 = 0, SHA224 = 1, SHA256 = 2, SHA384 = 3, SHA512 = 4, MD5 = 5, COUNT_HASH }; @@ -528,6 +530,12 @@ int32_t k2_openssl_decrypt(enum CipherAlgorithm algorithm, enum BlockPadding pad const char* aad, size_t aad_len, const char* tag, size_t tag_len, const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, size_t* decrypted_data_written); +int32_t k2_openssl_public_encrypt(enum AsymmPadding padding, const char* public_key, size_t public_key_len, const char* data, size_t data_len, + char* encrypted_data, size_t encrypted_data_len, size_t* encrypted_data_written); + +int32_t k2_openssl_private_decrypt(enum AsymmPadding padding, const char* private_key, size_t private_key_len, const char* data, size_t data_len, + char* decrypted_data, size_t decrypted_data_len, size_t* decrypted_data_written); + int32_t k2_hash(enum HashAlgorithm algorithm, const char* data, size_t data_len, char* hashed_data, size_t hashed_data_len); int32_t k2_hash_hmac(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* key, size_t key_len, char* hashed_data, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index eda4711c44..edc705797e 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -164,6 +164,8 @@ kphp::coro::task f$openssl_verify(string data, string signature, string namespace { +constexpr size_t RSA_MAX_ENCRYPTED_SIZE = 1024; + constexpr std::string_view AES_128_CBC = "aes-128-cbc"; constexpr std::string_view AES_256_CBC = "aes-256-cbc"; constexpr std::string_view AES_128_GCM = "aes-128-gcm"; @@ -327,9 +329,9 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, return false; } - BlockPadding padding{PKCS7}; + BlockPadding padding{BlockPadding::PKCS7}; if (options & static_cast(cipher_opts::OPENSSL_ZERO_PADDING)) { - padding = NO_PADDING; + padding = BlockPadding::BLOCK_NO_PADDING; } auto encrypted_size{calc_encrypted_size(traits, tag_length, data.size())}; @@ -339,6 +341,7 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, {aad.c_str(), aad.size()}, {data.c_str(), data.size()}, {encrypted_data.buffer(), encrypted_data.size()}, {received_tag.buffer(), received_tag.size()})}; if (!res) [[unlikely]] { + kphp::log::warning("Failed to encrypt data"); return false; } @@ -394,7 +397,7 @@ Optional f$openssl_decrypt(const string& data, const string& algorithm, BlockPadding padding{BlockPadding::PKCS7}; if (options & static_cast(cipher_opts::OPENSSL_ZERO_PADDING)) { - padding = BlockPadding::NO_PADDING; + padding = BlockPadding::BLOCK_NO_PADDING; } string decrypted_data{static_cast(calc_decrypted_size(traits, input_data.size())), false}; @@ -402,6 +405,7 @@ Optional f$openssl_decrypt(const string& data, const string& algorithm, {aad.c_str(), aad.size()}, {tag.c_str(), tag.size()}, {input_data.c_str(), input_data.size()}, {decrypted_data.buffer(), decrypted_data.size()})}; if (!res) [[unlikely]] { + kphp::log::warning("Failed to decrypt data"); return false; } @@ -465,84 +469,58 @@ kphp::coro::task> f$openssl_pkey_get_private(string key, string co_return string{(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; } -kphp::coro::task f$openssl_public_encrypt(string data, string& encrypted_data, string public_key) noexcept { - tl::PublicEncrypt public_encrypt{ - .key = {.value = {public_key.c_str(), public_key.size()}}, - .data = {.value = {data.c_str(), data.size()}}, - }; - tl::storer tls{public_encrypt.footprint()}; - public_encrypt.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } - - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; +bool f$openssl_public_encrypt(const string& data, string& encrypted_data, const string& public_key) noexcept { + string buffer{RSA_MAX_ENCRYPTED_SIZE, false}; + auto res{ + k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1)}; + if (!res) [[unlikely]] { + kphp::log::warning("Failed to encrypt data with the given public key"); + return false; } - tl::fetcher tlf{response_bytes}; - tl::Maybe response{}; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; - } + buffer.shrink(static_cast(*res)); + encrypted_data = std::move(buffer); - encrypted_data = {(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; - co_return true; + return true; } -kphp::coro::task f$openssl_public_encrypt(string data, mixed& result, string key) noexcept { - string result_string; - if (co_await f$openssl_public_encrypt(data, result_string, key)) { - result = std::move(result_string); - co_return true; +bool f$openssl_public_encrypt(const string& data, mixed& encrypted_data, const string& public_key) noexcept { + string result; + if (f$openssl_public_encrypt(data, result, public_key)) { + encrypted_data = std::move(result); + return true; } - result = mixed{}; - co_return false; -} -kphp::coro::task f$openssl_private_decrypt(string data, string& decrypted_data, string private_key) noexcept { - tl::PrivateDecrypt private_decrypt{ - .key = {.value = {private_key.c_str(), private_key.size()}}, - .data = {.value = {data.c_str(), data.size()}}, - }; - tl::storer tls{private_decrypt.footprint()}; - private_decrypt.store(tls); + encrypted_data = mixed{}; - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } + return false; +} - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; +bool f$openssl_private_decrypt(const string& data, string& decrypted_data, const string& private_key) noexcept { + string buffer{static_cast(data.size()), false}; + auto res{k2::openssl_private_decrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {private_key.c_str(), private_key.size()}, + AsymmPadding::PKCS1)}; + if (!res) [[unlikely]] { + kphp::log::warning("Failed to decrypt data with the given private key"); + return false; } - tl::fetcher tlf{response_bytes}; - tl::Maybe response{}; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; - } + buffer.shrink(static_cast(*res)); + decrypted_data = std::move(buffer); - decrypted_data = {(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; - co_return true; + return true; } -kphp::coro::task f$openssl_private_decrypt(string data, mixed& result, string key) noexcept { - string result_string; - if (co_await f$openssl_private_decrypt(data, result_string, key)) { - result = std::move(result_string); - co_return true; +bool f$openssl_private_decrypt(const string& data, mixed& decrypted_data, const string& private_key) noexcept { + string result; + if (f$openssl_private_decrypt(data, result, private_key)) { + decrypted_data = std::move(result); + return true; } - result = mixed{}; - co_return false; + + decrypted_data = mixed{}; + + return false; } namespace { @@ -601,6 +579,7 @@ string hash_impl(hash_algorithm_traits traits, const string& data, bool binary) string hashed_data{binary ? static_cast(traits.hashed_size) : static_cast(traits.hashed_size * 2), false}; auto res{k2::hash(traits.algo, {data.c_str(), data.size()}, {hashed_data.buffer(), static_cast(traits.hashed_size)})}; if (!res) [[unlikely]] { + kphp::log::warning("Failed to generate hash"); return {}; } @@ -644,6 +623,7 @@ string f$hash_hmac(const string& algorithm, const string& data, const string& ke string hashed_data{binary ? static_cast(algo->hashed_size) : static_cast(algo->hashed_size * 2), false}; auto res{k2::hash_hmac(algo->algo, {data.c_str(), data.size()}, {key.c_str(), key.size()}, {hashed_data.buffer(), static_cast(algo->hashed_size)})}; if (!res) [[unlikely]] { + kphp::log::warning("Failed to generate HMAC hash"); return {}; } diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index b880991683..d53ea14d23 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -29,16 +29,16 @@ Optional f$openssl_cipher_iv_length(const string& algorithm) noexcept; Optional f$openssl_encrypt(const string& data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, std::optional> tag = {}, const string& aad = string{}, int64_t tag_length = 16) noexcept; -Optional f$openssl_decrypt(string data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, +Optional f$openssl_decrypt(const string& data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, const string& tag = string{}, const string& aad = string{}) noexcept; kphp::coro::task> f$openssl_pkey_get_public(string key) noexcept; kphp::coro::task> f$openssl_pkey_get_private(string key, string passphrase = string{}) noexcept; -kphp::coro::task f$openssl_public_encrypt(string data, mixed& result, string key) noexcept; +bool f$openssl_public_encrypt(const string& data, mixed& encrypted_data, const string& public_key) noexcept; -kphp::coro::task f$openssl_private_decrypt(string data, mixed& result, string key) noexcept; +bool f$openssl_private_decrypt(const string& data, mixed& decrypted_data, const string& private_key) noexcept; array f$hash_algos() noexcept; From 23ba28828c239c14e4b33b7ec1f3178509b0e273 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 21 Oct 2026 15:07:29 +0300 Subject: [PATCH 24/38] implement f and f via k2 header --- runtime-light/k2-platform/k2-header.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 56120c6a5d..7c40169828 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -102,7 +102,7 @@ enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2 enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; -enum AsymmPadding : uint32_t { PKCS1 = 1, SSLV23 = 3, ASYMM_NO_PADDING = 3, PKCS1_OAEP = 4, PKCS1_PSS = 5 }; +enum AsymmPadding : uint32_t { PKCS1 = 1, SSLV23 = 2, ASYMM_NO_PADDING = 3, PKCS1_OAEP = 4, PKCS1_PSS = 5 }; enum HashAlgorithm : uint32_t { SHA1 = 0, SHA224 = 1, SHA256 = 2, SHA384 = 3, SHA512 = 4, MD5 = 5, COUNT_HASH }; From 31cfe59ba1e5a1b09b3da8d8001ea5aba0583e13 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 21 Oct 2026 15:33:04 +0300 Subject: [PATCH 25/38] implement f and f via k2 header --- runtime-light/k2-platform/k2-api.h | 8 ++++---- runtime-light/k2-platform/k2-header.h | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 71da94ca39..223d3ee27c 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -292,8 +292,8 @@ inline std::expected openssl_decrypt(CipherAlgorithm algorithm, inline std::expected openssl_public_encrypt(std::string_view data, std::span encrypted_data, std::string_view public_key, AsymmPadding padding) noexcept { size_t encrypted_data_written{0}; - if (auto error_code{k2_openssl_public_encrypt(padding, public_key.data(), public_key.size(), data.data(), data.size(), encrypted_data.data(), - encrypted_data.size(), std::addressof(encrypted_data_written))}; + if (auto error_code{k2_openssl_public_encrypt(data.data(), data.size(), encrypted_data.data(), encrypted_data.size(), public_key.data(), public_key.size(), + padding, std::addressof(encrypted_data_written))}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } @@ -304,8 +304,8 @@ inline std::expected openssl_public_encrypt(std::string_view da inline std::expected openssl_private_decrypt(std::string_view data, std::span decrypted_data, std::string_view private_key, AsymmPadding padding) noexcept { size_t decrypted_data_written{0}; - if (auto error_code{k2_openssl_private_decrypt(padding, private_key.data(), private_key.size(), data.data(), data.size(), decrypted_data.data(), - decrypted_data.size(), std::addressof(decrypted_data_written))}; + if (auto error_code{k2_openssl_private_decrypt(data.data(), data.size(), decrypted_data.data(), decrypted_data.size(), private_key.data(), private_key.size(), + padding, std::addressof(decrypted_data_written))}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 7c40169828..d166b2c3dc 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -102,7 +102,7 @@ enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2 enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; -enum AsymmPadding : uint32_t { PKCS1 = 1, SSLV23 = 2, ASYMM_NO_PADDING = 3, PKCS1_OAEP = 4, PKCS1_PSS = 5 }; +enum AsymmPadding : uint32_t { PKCS1 = 1, ASYMM_NO_PADDING = 3, PKCS1_OAEP = 4 }; enum HashAlgorithm : uint32_t { SHA1 = 0, SHA224 = 1, SHA256 = 2, SHA384 = 3, SHA512 = 4, MD5 = 5, COUNT_HASH }; @@ -530,11 +530,11 @@ int32_t k2_openssl_decrypt(enum CipherAlgorithm algorithm, enum BlockPadding pad const char* aad, size_t aad_len, const char* tag, size_t tag_len, const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, size_t* decrypted_data_written); -int32_t k2_openssl_public_encrypt(enum AsymmPadding padding, const char* public_key, size_t public_key_len, const char* data, size_t data_len, - char* encrypted_data, size_t encrypted_data_len, size_t* encrypted_data_written); +int32_t k2_openssl_public_encrypt(const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, const char* public_key, + size_t public_key_len, enum AsymmPadding padding, size_t* encrypted_data_written); -int32_t k2_openssl_private_decrypt(enum AsymmPadding padding, const char* private_key, size_t private_key_len, const char* data, size_t data_len, - char* decrypted_data, size_t decrypted_data_len, size_t* decrypted_data_written); +int32_t k2_openssl_private_decrypt(const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, const char* private_key, + size_t private_key_len, enum AsymmPadding padding, size_t* decrypted_data_written); int32_t k2_hash(enum HashAlgorithm algorithm, const char* data, size_t data_len, char* hashed_data, size_t hashed_data_len); From 3e022df1fbdeeee3bd45754e41ed1f969a6e1937 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 21 Oct 2026 19:04:59 +0300 Subject: [PATCH 26/38] implement pkey_get_public and pkey_get_private functions via k2 header --- .../kphp-light/stdlib/crypto-functions.txt | 2 - runtime-light/k2-platform/k2-api.h | 21 +++++ runtime-light/k2-platform/k2-header.h | 5 ++ .../stdlib/crypto/crypto-functions.cpp | 80 +++++++------------ .../stdlib/crypto/crypto-functions.h | 5 +- 5 files changed, 59 insertions(+), 54 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index ab8d5e41c8..517b8fb767 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -39,9 +39,7 @@ function openssl_encrypt($data ::: string, $method ::: string, $key ::: string, function openssl_decrypt($data ::: string, $method ::: string, $key ::: string, $options ::: int = 0, $iv ::: string = '', $tag ::: string = '', $aad ::: string = '') ::: string | false; -/** @kphp-extern-func-info interruptible */ function openssl_pkey_get_private ($key ::: string, $passphrase ::: string = '') ::: string | false; -/** @kphp-extern-func-info interruptible */ function openssl_pkey_get_public ($key ::: string) ::: string | false; function openssl_public_encrypt ($data ::: string, &$result ::: mixed, $key ::: string) ::: bool; diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 223d3ee27c..b2b67628e9 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -262,6 +262,27 @@ inline int32_t component_access(std::string_view component_name) noexcept { return k2_component_access(component_name.size(), component_name.data()); } +inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_pkey_get_private(std::string_view private_key, std::string_view passphrase, std::span result) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_pkey_get_private(private_key.data(), private_key.size(), passphrase.data(), passphrase.size(), result.data(), result.size(), + std::addressof(result_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + inline std::expected openssl_encrypt(CipherAlgorithm algorithm, BlockPadding padding, std::string_view key, std::string_view iv, std::string_view aad, std::string_view data, std::span encrypted_data, std::span received_tag) noexcept { diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index d166b2c3dc..f9af6b5cf7 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -522,6 +522,11 @@ int32_t k2_unlink(const char* path, size_t path_len); */ int32_t k2_component_access(size_t name_len, const char* name); +int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written); + +int32_t k2_openssl_pkey_get_private(const char* private_key, size_t private_key_len, const char* passphrase, size_t passphrase_len, char* result, + size_t result_len, size_t* result_written); + int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, size_t* encrypted_data_written, char* received_tag, size_t received_tag_len); diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index edc705797e..2686ea1651 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -164,8 +164,6 @@ kphp::coro::task f$openssl_verify(string data, string signature, string namespace { -constexpr size_t RSA_MAX_ENCRYPTED_SIZE = 1024; - constexpr std::string_view AES_128_CBC = "aes-128-cbc"; constexpr std::string_view AES_256_CBC = "aes-256-cbc"; constexpr std::string_view AES_128_GCM = "aes-128-gcm"; @@ -414,71 +412,53 @@ Optional f$openssl_decrypt(const string& data, const string& algorithm, return std::move(decrypted_data); } -kphp::coro::task> f$openssl_pkey_get_public(string key) noexcept { - tl::GetPublicKey get_public_key{.key = {.value = {key.c_str(), key.size()}}}; - tl::storer tls{get_public_key.footprint()}; - get_public_key.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } - - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; +Optional f$openssl_pkey_get_public(const string& public_key) noexcept { + auto size{k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {})}; + if (!size) [[unlikely]] { + kphp::log::warning("Failed to get public key"); + return false; } - tl::fetcher tlf{response_bytes}; - tl::Maybe response; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; + string buffer{static_cast(*size), false}; + if (!k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {buffer.buffer(), buffer.size()})) [[unlikely]] { + kphp::log::warning("Failed to get public key"); + return false; } - co_return string{(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; + return buffer; } -kphp::coro::task> f$openssl_pkey_get_private(string key, string passphrase) noexcept { - tl::GetPrivateKey get_private_key{ - .key = {.value = {key.c_str(), key.size()}}, - .passphrase = {.value = {passphrase.c_str(), passphrase.size()}}, - }; - tl::storer tls{get_private_key.footprint()}; - get_private_key.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } - - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; +Optional f$openssl_pkey_get_private(const string& private_key, const string& passphrase) noexcept { + auto size{k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {})}; + if (!size) [[unlikely]] { + kphp::log::warning("Failed to get private key"); + return false; } - tl::fetcher tlf{response_bytes}; - tl::Maybe response; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; + string buffer{static_cast(*size), false}; + if (!k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()})) + [[unlikely]] { + kphp::log::warning("Failed to get private key"); + return false; } - co_return string{(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; + return buffer; } bool f$openssl_public_encrypt(const string& data, string& encrypted_data, const string& public_key) noexcept { - string buffer{RSA_MAX_ENCRYPTED_SIZE, false}; - auto res{ - k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1)}; - if (!res) [[unlikely]] { + auto size{k2::openssl_public_encrypt({data.c_str(), data.size()}, {}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1)}; + if (!size) [[unlikely]] { + kphp::log::warning("Failed to encrypt data with the given public key"); + return false; + } + + string buffer{static_cast(*size), false}; + if (!k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1)) + [[unlikely]] { kphp::log::warning("Failed to encrypt data with the given public key"); return false; } - buffer.shrink(static_cast(*res)); encrypted_data = std::move(buffer); return true; diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index d53ea14d23..635987f3a9 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -32,9 +32,9 @@ Optional f$openssl_encrypt(const string& data, const string& algorithm, Optional f$openssl_decrypt(const string& data, const string& algorithm, const string& key, int64_t options = 0, const string& iv = string{}, const string& tag = string{}, const string& aad = string{}) noexcept; -kphp::coro::task> f$openssl_pkey_get_public(string key) noexcept; +Optional f$openssl_pkey_get_public(const string& public_key) noexcept; -kphp::coro::task> f$openssl_pkey_get_private(string key, string passphrase = string{}) noexcept; +Optional f$openssl_pkey_get_private(const string& private_key, const string& passphrase = string{}) noexcept; bool f$openssl_public_encrypt(const string& data, mixed& encrypted_data, const string& public_key) noexcept; @@ -61,6 +61,7 @@ inline string f$md5(const string& str, bool binary = false) noexcept { output[2 * i] = lhex_digits[(output[i] >> 4) & 0x0F]; } } + return output; } From 0651d05ea9ec472442037d28921a15e014173b71 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 21 Oct 2026 19:22:09 +0300 Subject: [PATCH 27/38] implement random_pseudo_bytes function via k2 header --- .../kphp-light/stdlib/crypto-functions.txt | 1 - runtime-light/k2-platform/k2-api.h | 8 ++++++ runtime-light/k2-platform/k2-header.h | 2 ++ .../stdlib/crypto/crypto-functions.cpp | 27 +++++-------------- .../stdlib/crypto/crypto-functions.h | 2 +- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 517b8fb767..adab347eef 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -24,7 +24,6 @@ function openssl_sign ($data ::: string, &$signature ::: string, $priv_key_id :: /** @kphp-extern-func-info interruptible */ function openssl_verify ($data ::: string, $signature ::: string, $pub_key_id ::: string, $signature_alg ::: int = 1) ::: int; -/** @kphp-extern-func-info interruptible */ function openssl_random_pseudo_bytes ($length ::: int) ::: string | false; /** @kphp-extern-func-info interruptible */ diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index b2b67628e9..1710725ce7 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -262,6 +262,14 @@ inline int32_t component_access(std::string_view component_name) noexcept { return k2_component_access(component_name.size(), component_name.data()); } +inline std::expected openssl_random_pseudo_bytes(std::span bytes) noexcept { + if (auto ret{k2_openssl_random_pseudo_bytes(bytes.data(), bytes.size())}; ret != 0) { + return std::unexpected(ret); + } + + return {}; +} + inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result) noexcept { size_t result_written{0}; if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written))}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index f9af6b5cf7..1d7e6c8f75 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -522,6 +522,8 @@ int32_t k2_unlink(const char* path, size_t path_len); */ int32_t k2_component_access(size_t name_len, const char* name); +int32_t k2_openssl_random_pseudo_bytes(char* bytes, size_t length); + int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written); int32_t k2_openssl_pkey_get_private(const char* private_key, size_t private_key_len, const char* passphrase, size_t passphrase_len, char* result, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 2686ea1651..0356040dbf 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -36,31 +36,18 @@ constexpr std::string_view CRYPTO_COMPONENT_NAME = "crypto"; } // namespace -kphp::coro::task> f$openssl_random_pseudo_bytes(int64_t length) noexcept { +Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept { if (length <= 0 || length > string::max_size()) [[unlikely]] { - co_return false; - } - - tl::GetCryptosecurePseudorandomBytes get_pseudorandom_bytes{.size = {.value = static_cast(length)}}; - tl::storer tls{get_pseudorandom_bytes.footprint()}; - get_pseudorandom_bytes.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; + return false; } - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response)))) [[unlikely]] { - co_return false; + string buffer{static_cast(length), false}; + if (!k2::openssl_random_pseudo_bytes({buffer.buffer(), buffer.size()})) [[unlikely]] { + kphp::log::warning("Failed to generate pseudo-random bytes"); + return false; } - tl::fetcher tlf{response}; - tl::magic magic{}; - tl::string str{}; - kphp::log::assertion(magic.fetch(tlf) && magic.expect(TL_MAYBE_TRUE) && str.fetch(tlf)); - co_return string{str.value.data(), static_cast(str.value.size())}; + return buffer; } kphp::coro::task>> f$openssl_x509_parse(string data, bool shortnames) noexcept { diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index 635987f3a9..19d2d3f473 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -14,7 +14,7 @@ #include "runtime-light/stdlib/string/string-state.h" #include "runtime-light/tl/tl-types.h" -kphp::coro::task> f$openssl_random_pseudo_bytes(int64_t length) noexcept; +Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept; kphp::coro::task>> f$openssl_x509_parse(string data, bool shortnames = true) noexcept; From 09019c7b6c8ae52f7adc225dfb03af3f1a488f4e Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 22 Oct 2026 16:29:17 +0300 Subject: [PATCH 28/38] add crypto cache --- runtime-light/k2-platform/k2-api.h | 13 +++++++------ runtime-light/k2-platform/k2-header.h | 8 ++++---- runtime-light/stdlib/crypto/crypto-functions.cpp | 8 +++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 1710725ce7..4ec8bd7842 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -270,9 +270,9 @@ inline std::expected openssl_random_pseudo_bytes(std::span return {}; } -inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result) noexcept { +inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result, bool retry = false) noexcept { size_t result_written{0}; - if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written))}; + if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written), retry)}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } @@ -280,10 +280,11 @@ inline std::expected openssl_pkey_get_public(std::string_view p return {result_written}; } -inline std::expected openssl_pkey_get_private(std::string_view private_key, std::string_view passphrase, std::span result) noexcept { +inline std::expected openssl_pkey_get_private(std::string_view private_key, std::string_view passphrase, std::span result, + bool retry = false) noexcept { size_t result_written{0}; if (auto error_code{k2_openssl_pkey_get_private(private_key.data(), private_key.size(), passphrase.data(), passphrase.size(), result.data(), result.size(), - std::addressof(result_written))}; + std::addressof(result_written), retry)}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } @@ -319,10 +320,10 @@ inline std::expected openssl_decrypt(CipherAlgorithm algorithm, } inline std::expected openssl_public_encrypt(std::string_view data, std::span encrypted_data, std::string_view public_key, - AsymmPadding padding) noexcept { + AsymmPadding padding, bool retry = false) noexcept { size_t encrypted_data_written{0}; if (auto error_code{k2_openssl_public_encrypt(data.data(), data.size(), encrypted_data.data(), encrypted_data.size(), public_key.data(), public_key.size(), - padding, std::addressof(encrypted_data_written))}; + padding, std::addressof(encrypted_data_written), retry)}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 1d7e6c8f75..4de3582530 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -102,7 +102,7 @@ enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2 enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; -enum AsymmPadding : uint32_t { PKCS1 = 1, ASYMM_NO_PADDING = 3, PKCS1_OAEP = 4 }; +enum AsymmPadding : uint32_t { PKCS1 = 0, PKCS1_OAEP = 1, ASYMM_NO_PADDING = 2 }; enum HashAlgorithm : uint32_t { SHA1 = 0, SHA224 = 1, SHA256 = 2, SHA384 = 3, SHA512 = 4, MD5 = 5, COUNT_HASH }; @@ -524,10 +524,10 @@ int32_t k2_component_access(size_t name_len, const char* name); int32_t k2_openssl_random_pseudo_bytes(char* bytes, size_t length); -int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written); +int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written, bool retry); int32_t k2_openssl_pkey_get_private(const char* private_key, size_t private_key_len, const char* passphrase, size_t passphrase_len, char* result, - size_t result_len, size_t* result_written); + size_t result_len, size_t* result_written, bool retry); int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, @@ -538,7 +538,7 @@ int32_t k2_openssl_decrypt(enum CipherAlgorithm algorithm, enum BlockPadding pad size_t decrypted_data_len, size_t* decrypted_data_written); int32_t k2_openssl_public_encrypt(const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, const char* public_key, - size_t public_key_len, enum AsymmPadding padding, size_t* encrypted_data_written); + size_t public_key_len, enum AsymmPadding padding, size_t* encrypted_data_written, bool retry); int32_t k2_openssl_private_decrypt(const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, const char* private_key, size_t private_key_len, enum AsymmPadding padding, size_t* decrypted_data_written); diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 0356040dbf..5ba3412c9b 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -407,7 +407,7 @@ Optional f$openssl_pkey_get_public(const string& public_key) noexcept { } string buffer{static_cast(*size), false}; - if (!k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {buffer.buffer(), buffer.size()})) [[unlikely]] { + if (!k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {buffer.buffer(), buffer.size()}, true)) [[unlikely]] { kphp::log::warning("Failed to get public key"); return false; } @@ -423,7 +423,8 @@ Optional f$openssl_pkey_get_private(const string& private_key, const str } string buffer{static_cast(*size), false}; - if (!k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()})) + if (!k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()}, + true)) [[unlikely]] { kphp::log::warning("Failed to get private key"); return false; @@ -440,7 +441,8 @@ bool f$openssl_public_encrypt(const string& data, string& encrypted_data, const } string buffer{static_cast(*size), false}; - if (!k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1)) + if (!k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1, + true)) [[unlikely]] { kphp::log::warning("Failed to encrypt data with the given public key"); return false; From 3dee51fd26520dc53179796b06b9df7f69a1ae13 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 22 Oct 2026 17:47:43 +0300 Subject: [PATCH 29/38] extend hash algorithms support --- runtime-light/k2-platform/k2-header.h | 2 +- .../stdlib/crypto/crypto-functions.cpp | 32 +++++++++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 4de3582530..f1db4a7d30 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -104,7 +104,7 @@ enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; enum AsymmPadding : uint32_t { PKCS1 = 0, PKCS1_OAEP = 1, ASYMM_NO_PADDING = 2 }; -enum HashAlgorithm : uint32_t { SHA1 = 0, SHA224 = 1, SHA256 = 2, SHA384 = 3, SHA512 = 4, MD5 = 5, COUNT_HASH }; +enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, MD2 = 6, MD4 = 7, MD5 = 8, RMD160 = 9, COUNT_HASH }; enum RpcKind { TL_RPC = 0, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 5ba3412c9b..88a6c624a4 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -423,8 +423,7 @@ Optional f$openssl_pkey_get_private(const string& private_key, const str } string buffer{static_cast(*size), false}; - if (!k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()}, - true)) + if (!k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()}, true)) [[unlikely]] { kphp::log::warning("Failed to get private key"); return false; @@ -442,8 +441,7 @@ bool f$openssl_public_encrypt(const string& data, string& encrypted_data, const string buffer{static_cast(*size), false}; if (!k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1, - true)) - [[unlikely]] { + true)) [[unlikely]] { kphp::log::warning("Failed to encrypt data with the given public key"); return false; } @@ -494,19 +492,27 @@ bool f$openssl_private_decrypt(const string& data, mixed& decrypted_data, const namespace { +constexpr std::string_view DSS1 = "dss1"; constexpr std::string_view SHA1 = "sha1"; constexpr std::string_view SHA224 = "sha224"; constexpr std::string_view SHA256 = "sha256"; constexpr std::string_view SHA384 = "sha384"; constexpr std::string_view SHA512 = "sha512"; +constexpr std::string_view MD2 = "md2"; +constexpr std::string_view MD4 = "md4"; constexpr std::string_view MD5 = "md5"; +constexpr std::string_view RMD160 = "rmd160"; +constexpr size_t DSS1_HASHED_SIZE = 20; constexpr size_t SHA1_HASHED_SIZE = 20; constexpr size_t SHA224_HASHED_SIZE = 28; constexpr size_t SHA256_HASHED_SIZE = 32; constexpr size_t SHA384_HASHED_SIZE = 48; constexpr size_t SHA512_HASHED_SIZE = 64; +constexpr size_t MD2_HASHED_SIZE = 16; +constexpr size_t MD4_HASHED_SIZE = 16; constexpr size_t MD5_HASHED_SIZE = 16; +constexpr size_t RMD160_HASHED_SIZE = 20; struct hash_algorithm_traits { int64_t hashed_size; @@ -514,12 +520,18 @@ struct hash_algorithm_traits { }; constexpr auto HASH_ALGORITHMS = []() noexcept { - auto arr{std::array{std::pair{SHA1, hash_algorithm_traits{SHA1_HASHED_SIZE, HashAlgorithm::SHA1}}, - std::pair{SHA224, hash_algorithm_traits{SHA224_HASHED_SIZE, HashAlgorithm::SHA224}}, - std::pair{SHA256, hash_algorithm_traits{SHA256_HASHED_SIZE, HashAlgorithm::SHA256}}, - std::pair{SHA384, hash_algorithm_traits{SHA384_HASHED_SIZE, HashAlgorithm::SHA384}}, - std::pair{SHA512, hash_algorithm_traits{SHA512_HASHED_SIZE, HashAlgorithm::SHA512}}, - std::pair{MD5, hash_algorithm_traits{MD5_HASHED_SIZE, HashAlgorithm::MD5}}}}; + auto arr{std::array{ + std::pair{DSS1, hash_algorithm_traits{DSS1_HASHED_SIZE, HashAlgorithm::DSS1}}, + std::pair{SHA1, hash_algorithm_traits{SHA1_HASHED_SIZE, HashAlgorithm::SHA1}}, + std::pair{SHA224, hash_algorithm_traits{SHA224_HASHED_SIZE, HashAlgorithm::SHA224}}, + std::pair{SHA256, hash_algorithm_traits{SHA256_HASHED_SIZE, HashAlgorithm::SHA256}}, + std::pair{SHA384, hash_algorithm_traits{SHA384_HASHED_SIZE, HashAlgorithm::SHA384}}, + std::pair{SHA512, hash_algorithm_traits{SHA512_HASHED_SIZE, HashAlgorithm::SHA512}}, + std::pair{MD2, hash_algorithm_traits{MD2_HASHED_SIZE, HashAlgorithm::MD2}}, + std::pair{MD4, hash_algorithm_traits{MD4_HASHED_SIZE, HashAlgorithm::MD4}}, + std::pair{MD5, hash_algorithm_traits{MD5_HASHED_SIZE, HashAlgorithm::MD5}}, + std::pair{RMD160, hash_algorithm_traits{RMD160_HASHED_SIZE, HashAlgorithm::RMD160}}, + }}; std::ranges::sort(arr, std::less{}, &std::pair::first); return arr; From ee521288bd967f554c438a3d3143bead8ba7700a Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Fri, 23 Oct 2026 18:48:55 +0300 Subject: [PATCH 30/38] implement sign and verify functions via k2 header --- .../kphp-light/stdlib/crypto-functions.txt | 22 ++--- runtime-light/k2-platform/k2-api.h | 30 ++++++- runtime-light/k2-platform/k2-header.h | 14 +++- .../stdlib/crypto/crypto-functions.cpp | 82 ++++++------------- .../stdlib/crypto/crypto-functions.h | 5 +- 5 files changed, 73 insertions(+), 80 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index adab347eef..3df23cde1e 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -2,26 +2,21 @@ // ===== SUPPORTED ===== -define('OPENSSL_ALGO_SHA1' , 0x215fb97d); -define('OPENSSL_ALGO_MD5' , 0x257ddf13); -define('OPENSSL_ALGO_MD4' , 0x317fe3d1); -define('OPENSSL_ALGO_MD2' , 0x5aca6998); -define('OPENSSL_ALGO_DSS1' , 0xf572d6b6); -define('OPENSSL_ALGO_SHA224', 0x8bce55e9); -define('OPENSSL_ALGO_SHA256', 0x6c977f8c); -define('OPENSSL_ALGO_SHA384', 0xf54c2608); -define('OPENSSL_ALGO_SHA512', 0x225df2b6); -define('OPENSSL_ALGO_RMD160', 0x1887e6b4); +define('OPENSSL_ALGO_DSS1' , 0); +define('OPENSSL_ALGO_SHA1' , 1); +define('OPENSSL_ALGO_SHA224', 2); +define('OPENSSL_ALGO_SHA256', 3); +define('OPENSSL_ALGO_SHA384', 4); +define('OPENSSL_ALGO_SHA512', 5); +define('OPENSSL_ALGO_MD5' , 6); +define('OPENSSL_ALGO_RMD160', 7); define('OPENSSL_RAW_DATA', 1); define('OPENSSL_ZERO_PADDING', 2); define('OPENSSL_DONT_ZERO_PAD_KEY', 4); - -/** @kphp-extern-func-info interruptible */ function openssl_sign ($data ::: string, &$signature ::: string, $priv_key_id ::: string, $signature_alg ::: int = 1) ::: bool; -/** @kphp-extern-func-info interruptible */ function openssl_verify ($data ::: string, $signature ::: string, $pub_key_id ::: string, $signature_alg ::: int = 1) ::: int; function openssl_random_pseudo_bytes ($length ::: int) ::: string | false; @@ -82,7 +77,6 @@ define('PKCS7_NOATTR', 0x100); /** @kphp-extern-func-info stub generation-required */ function openssl_x509_verify ($x509cert ::: string, $public_key ::: string) ::: int; - /** @kphp-extern-func-info stub generation-required */ function openssl_pkcs7_sign($infile ::: string, $outfile ::: string, $sign_cert ::: string, $priv_key ::: string, diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 4ec8bd7842..20a7293a79 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -281,7 +281,7 @@ inline std::expected openssl_pkey_get_public(std::string_view p } inline std::expected openssl_pkey_get_private(std::string_view private_key, std::string_view passphrase, std::span result, - bool retry = false) noexcept { + bool retry = false) noexcept { size_t result_written{0}; if (auto error_code{k2_openssl_pkey_get_private(private_key.data(), private_key.size(), passphrase.data(), passphrase.size(), result.data(), result.size(), std::addressof(result_written), retry)}; @@ -320,7 +320,7 @@ inline std::expected openssl_decrypt(CipherAlgorithm algorithm, } inline std::expected openssl_public_encrypt(std::string_view data, std::span encrypted_data, std::string_view public_key, - AsymmPadding padding, bool retry = false) noexcept { + RsaPadding padding, bool retry = false) noexcept { size_t encrypted_data_written{0}; if (auto error_code{k2_openssl_public_encrypt(data.data(), data.size(), encrypted_data.data(), encrypted_data.size(), public_key.data(), public_key.size(), padding, std::addressof(encrypted_data_written), retry)}; @@ -332,7 +332,7 @@ inline std::expected openssl_public_encrypt(std::string_view da } inline std::expected openssl_private_decrypt(std::string_view data, std::span decrypted_data, std::string_view private_key, - AsymmPadding padding) noexcept { + RsaPadding padding) noexcept { size_t decrypted_data_written{0}; if (auto error_code{k2_openssl_private_decrypt(data.data(), data.size(), decrypted_data.data(), decrypted_data.size(), private_key.data(), private_key.size(), padding, std::addressof(decrypted_data_written))}; @@ -360,6 +360,30 @@ inline std::expected hash_hmac(HashAlgorithm algorithm, std::stri return {}; } +inline std::expected openssl_sign(HashAlgorithm algorithm, std::string_view data, std::string_view private_key, std::span result, + bool retry = false) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_sign(algorithm, data.data(), data.size(), private_key.data(), private_key.size(), result.data(), result.size(), + std::addressof(result_written), retry)}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_verify(HashAlgorithm algorithm, std::string_view data, std::string_view public_key, + std::string_view signature) noexcept { + int32_t is_valid{0}; + if (auto error_code{k2_openssl_verify(algorithm, data.data(), data.size(), public_key.data(), public_key.size(), signature.data(), signature.size(), + std::addressof(is_valid))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {is_valid != 0}; +} + inline std::expected rpc_send_request(std::string_view actor_name, std::span request_buffer, k2::RpcKind rpc_kind) noexcept { k2::descriptor descriptor{}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index f1db4a7d30..3331b3eceb 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -102,9 +102,9 @@ enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2 enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; -enum AsymmPadding : uint32_t { PKCS1 = 0, PKCS1_OAEP = 1, ASYMM_NO_PADDING = 2 }; +enum RsaPadding : uint32_t { PKCS1 = 0, PKCS1_OAEP = 1, ASYMM_NO_PADDING = 2 }; -enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, MD2 = 6, MD4 = 7, MD5 = 8, RMD160 = 9, COUNT_HASH }; +enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, MD5 = 6, RMD160 = 7, COUNT_HASH }; enum RpcKind { TL_RPC = 0, @@ -524,6 +524,12 @@ int32_t k2_component_access(size_t name_len, const char* name); int32_t k2_openssl_random_pseudo_bytes(char* bytes, size_t length); +int32_t k2_openssl_sign(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* private_key, size_t private_key_len, char* result, + size_t result_len, size_t* result_written, bool retry); + +int32_t k2_openssl_verify(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* public_key, size_t public_key_len, const char* signature, + size_t signature_len, int32_t* is_valid); + int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written, bool retry); int32_t k2_openssl_pkey_get_private(const char* private_key, size_t private_key_len, const char* passphrase, size_t passphrase_len, char* result, @@ -538,10 +544,10 @@ int32_t k2_openssl_decrypt(enum CipherAlgorithm algorithm, enum BlockPadding pad size_t decrypted_data_len, size_t* decrypted_data_written); int32_t k2_openssl_public_encrypt(const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, const char* public_key, - size_t public_key_len, enum AsymmPadding padding, size_t* encrypted_data_written, bool retry); + size_t public_key_len, enum RsaPadding padding, size_t* encrypted_data_written, bool retry); int32_t k2_openssl_private_decrypt(const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, const char* private_key, - size_t private_key_len, enum AsymmPadding padding, size_t* decrypted_data_written); + size_t private_key_len, enum RsaPadding padding, size_t* decrypted_data_written); int32_t k2_hash(enum HashAlgorithm algorithm, const char* data, size_t data_len, char* hashed_data, size_t hashed_data_len); diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 88a6c624a4..3e50148bc0 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -15,7 +15,6 @@ #include #include "common/crc32_generic.h" -#include "common/tl/constants/common.h" #include "runtime-common/core/allocator/script-allocator.h" #include "runtime-common/core/runtime-core.h" #include "runtime-common/core/std/containers.h" @@ -94,59 +93,36 @@ kphp::coro::task>> f$openssl_x509_parse(string data, bool co_return std::move(response); } -// FIXME it isn't safe to accept signature by reference -kphp::coro::task f$openssl_sign(string data, string& signature, string private_key, int64_t algo) noexcept { - tl::DigestSign digest_sign{.data = {.value = {data.c_str(), data.size()}}, - .private_key = {.value = {private_key.c_str(), private_key.size()}}, - .algorithm = static_cast(algo)}; - tl::storer tls{digest_sign.footprint()}; - digest_sign.store(tls); - - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; +bool f$openssl_sign(const string& data, string& signature, const string& private_key, int64_t algorithm) noexcept { + auto size{k2::openssl_sign(static_cast(algorithm), {data.c_str(), data.size()}, {private_key.c_str(), private_key.size()}, {})}; + if (!size) [[unlikely]] { + kphp::log::warning("Failed to sign data"); + return false; } - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; + string buffer{static_cast(*size), false}; + auto written{k2::openssl_sign(static_cast(algorithm), {data.c_str(), data.size()}, {private_key.c_str(), private_key.size()}, + {buffer.buffer(), buffer.size()}, true)}; + if (!written) [[unlikely]] { + kphp::log::warning("Failed to sign data"); + return false; } - tl::fetcher tlf{response_bytes}; - tl::magic magic{}; - tl::string str{}; - kphp::log::assertion(magic.fetch(tlf) && magic.expect(TL_MAYBE_TRUE) && str.fetch(tlf)); - signature = string{str.value.data(), static_cast(str.value.size())}; - co_return true; -} - -kphp::coro::task f$openssl_verify(string data, string signature, string pub_key, int64_t algo) noexcept { - tl::DigestVerify digest_verify{.data = {.value = {data.c_str(), data.size()}}, - .public_key = {.value = {pub_key.c_str(), pub_key.size()}}, - .algorithm = static_cast(algo), - .signature = {.value = {signature.c_str(), signature.size()}}}; - tl::storer tls{digest_verify.footprint()}; - digest_verify.store(tls); + buffer.shrink(static_cast(*written)); + signature = std::move(buffer); - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return 0; - } + return true; +} - auto stream{*std::move(expected_stream)}; - std::array response{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), response))) [[unlikely]] { - co_return 0; +int64_t f$openssl_verify(const string& data, const string& signature, const string& public_key, int64_t algorithm) noexcept { + auto res{k2::openssl_verify(static_cast(algorithm), {data.c_str(), data.size()}, {public_key.c_str(), public_key.size()}, + {signature.c_str(), signature.size()})}; + if (!res) [[unlikely]] { + kphp::log::warning("Failed to verify signature"); + return -1; } - tl::fetcher tlf{response}; - // For now returns only 1 or 0, -1 is never returned - // Because it's currently impossible to distiguish error from negative verification - if (tl::magic magic{}; !magic.fetch(tlf) || !magic.expect(TL_BOOL_TRUE)) { - co_return 0; - } - co_return 1; + return *res ? 1 : 0; } namespace { @@ -433,14 +409,14 @@ Optional f$openssl_pkey_get_private(const string& private_key, const str } bool f$openssl_public_encrypt(const string& data, string& encrypted_data, const string& public_key) noexcept { - auto size{k2::openssl_public_encrypt({data.c_str(), data.size()}, {}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1)}; + auto size{k2::openssl_public_encrypt({data.c_str(), data.size()}, {}, {public_key.c_str(), public_key.size()}, RsaPadding::PKCS1)}; if (!size) [[unlikely]] { kphp::log::warning("Failed to encrypt data with the given public key"); return false; } string buffer{static_cast(*size), false}; - if (!k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, AsymmPadding::PKCS1, + if (!k2::openssl_public_encrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {public_key.c_str(), public_key.size()}, RsaPadding::PKCS1, true)) [[unlikely]] { kphp::log::warning("Failed to encrypt data with the given public key"); return false; @@ -465,8 +441,8 @@ bool f$openssl_public_encrypt(const string& data, mixed& encrypted_data, const s bool f$openssl_private_decrypt(const string& data, string& decrypted_data, const string& private_key) noexcept { string buffer{static_cast(data.size()), false}; - auto res{k2::openssl_private_decrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {private_key.c_str(), private_key.size()}, - AsymmPadding::PKCS1)}; + auto res{ + k2::openssl_private_decrypt({data.c_str(), data.size()}, {buffer.buffer(), buffer.size()}, {private_key.c_str(), private_key.size()}, RsaPadding::PKCS1)}; if (!res) [[unlikely]] { kphp::log::warning("Failed to decrypt data with the given private key"); return false; @@ -498,8 +474,6 @@ constexpr std::string_view SHA224 = "sha224"; constexpr std::string_view SHA256 = "sha256"; constexpr std::string_view SHA384 = "sha384"; constexpr std::string_view SHA512 = "sha512"; -constexpr std::string_view MD2 = "md2"; -constexpr std::string_view MD4 = "md4"; constexpr std::string_view MD5 = "md5"; constexpr std::string_view RMD160 = "rmd160"; @@ -509,8 +483,6 @@ constexpr size_t SHA224_HASHED_SIZE = 28; constexpr size_t SHA256_HASHED_SIZE = 32; constexpr size_t SHA384_HASHED_SIZE = 48; constexpr size_t SHA512_HASHED_SIZE = 64; -constexpr size_t MD2_HASHED_SIZE = 16; -constexpr size_t MD4_HASHED_SIZE = 16; constexpr size_t MD5_HASHED_SIZE = 16; constexpr size_t RMD160_HASHED_SIZE = 20; @@ -527,8 +499,6 @@ constexpr auto HASH_ALGORITHMS = []() noexcept { std::pair{SHA256, hash_algorithm_traits{SHA256_HASHED_SIZE, HashAlgorithm::SHA256}}, std::pair{SHA384, hash_algorithm_traits{SHA384_HASHED_SIZE, HashAlgorithm::SHA384}}, std::pair{SHA512, hash_algorithm_traits{SHA512_HASHED_SIZE, HashAlgorithm::SHA512}}, - std::pair{MD2, hash_algorithm_traits{MD2_HASHED_SIZE, HashAlgorithm::MD2}}, - std::pair{MD4, hash_algorithm_traits{MD4_HASHED_SIZE, HashAlgorithm::MD4}}, std::pair{MD5, hash_algorithm_traits{MD5_HASHED_SIZE, HashAlgorithm::MD5}}, std::pair{RMD160, hash_algorithm_traits{RMD160_HASHED_SIZE, HashAlgorithm::RMD160}}, }}; diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index 19d2d3f473..e3d69932ab 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -12,15 +12,14 @@ #include "runtime-common/core/runtime-core.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/stdlib/string/string-state.h" -#include "runtime-light/tl/tl-types.h" Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept; kphp::coro::task>> f$openssl_x509_parse(string data, bool shortnames = true) noexcept; -kphp::coro::task f$openssl_sign(string data, string& signature, string private_key, int64_t algo = tl::HashAlgorithm::SHA1) noexcept; +bool f$openssl_sign(const string& data, string& signature, const string& private_key, int64_t algorithm = HashAlgorithm::SHA1) noexcept; -kphp::coro::task f$openssl_verify(string data, string signature, string pub_key_id, int64_t algo = tl::HashAlgorithm::SHA1) noexcept; +int64_t f$openssl_verify(const string& data, const string& signature, const string& public_key, int64_t algorithm = HashAlgorithm::SHA1) noexcept; array f$openssl_get_cipher_methods(bool aliases = false) noexcept; From 19f69097a1cb86e7c084a5337ae58de85e489e47 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 27 Oct 2026 14:45:51 +0300 Subject: [PATCH 31/38] implement openssl_x509_parse via k2 header --- .../kphp-light/stdlib/crypto-functions.txt | 1 - runtime-light/k2-platform/k2-api.h | 166 ++++++++++++--- runtime-light/k2-platform/k2-header.h | 26 +++ .../stdlib/crypto/crypto-functions.cpp | 201 ++++++++++++++---- .../stdlib/crypto/crypto-functions.h | 4 +- .../openssl/10_openssl_x509_parse_light.php | 8 +- 6 files changed, 326 insertions(+), 80 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 3df23cde1e..5500366312 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -21,7 +21,6 @@ function openssl_verify ($data ::: string, $signature ::: string, $pub_key_id :: function openssl_random_pseudo_bytes ($length ::: int) ::: string | false; -/** @kphp-extern-func-info interruptible */ function openssl_x509_parse ($x509cert ::: string, $shortnames ::: bool = true) ::: mixed[] | false; function openssl_get_cipher_methods($aliases ::: bool = false) ::: string[]; diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 20a7293a79..43303c42e8 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -270,6 +270,148 @@ inline std::expected openssl_random_pseudo_bytes(std::span return {}; } +inline std::expected openssl_x509_open(std::string_view data) noexcept { + if (auto error_code{k2_openssl_x509_open(data.data(), data.size())}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {}; +} + +inline std::expected openssl_x509_get_subject_name(std::span result, bool retry = false) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_x509_get_subject_name(result.data(), result.size(), std::addressof(result_written), retry)}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_x509_name_entry_count(bool is_issuer) noexcept { + size_t result{0}; + if (auto error_code{k2_openssl_x509_name_entry_count(is_issuer, std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result}; +} + +inline std::expected openssl_x509_name_entry_key(bool is_issuer, bool shortnames, size_t index, std::span result) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_x509_name_entry_key(is_issuer, shortnames, index, result.data(), result.size(), std::addressof(result_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_x509_name_entry_value(bool is_issuer, size_t index, std::span result) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_x509_name_entry_value(is_issuer, index, result.data(), result.size(), std::addressof(result_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_x509_get_hash(std::span result) noexcept { + if (auto error_code{k2_openssl_x509_get_hash(result.data(), result.size())}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {}; +} + +inline std::expected openssl_x509_get_version() noexcept { + int64_t result{0}; + if (auto error_code{k2_openssl_x509_get_version(std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result}; +} + +inline std::expected openssl_x509_get_valid_from() noexcept { + int64_t result{0}; + if (auto error_code{k2_openssl_x509_get_valid_from(std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result}; +} + +inline std::expected openssl_x509_get_valid_to() noexcept { + int64_t result{0}; + if (auto error_code{k2_openssl_x509_get_valid_to(std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result}; +} + +inline std::expected openssl_x509_purpose_count() noexcept { + size_t result{0}; + if (auto error_code{k2_openssl_x509_purpose_count(std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result}; +} + +inline std::expected openssl_x509_purpose_id(size_t index) noexcept { + int64_t result{0}; + if (auto error_code{k2_openssl_x509_purpose_id(index, std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result}; +} + +inline std::expected openssl_x509_purpose_check(size_t index, bool ca) noexcept { + bool result{false}; + if (auto error_code{k2_openssl_x509_purpose_check(index, ca, std::addressof(result))}; error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return result; +} + +inline std::expected openssl_x509_purpose_name(size_t index, bool shortnames, std::span result) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_x509_purpose_name(index, shortnames, result.data(), result.size(), std::addressof(result_written))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_sign(HashAlgorithm algorithm, std::string_view data, std::string_view private_key, std::span result, + bool retry = false) noexcept { + size_t result_written{0}; + if (auto error_code{k2_openssl_sign(algorithm, data.data(), data.size(), private_key.data(), private_key.size(), result.data(), result.size(), + std::addressof(result_written), retry)}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {result_written}; +} + +inline std::expected openssl_verify(HashAlgorithm algorithm, std::string_view data, std::string_view public_key, + std::string_view signature) noexcept { + int32_t is_valid{0}; + if (auto error_code{k2_openssl_verify(algorithm, data.data(), data.size(), public_key.data(), public_key.size(), signature.data(), signature.size(), + std::addressof(is_valid))}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + return {is_valid != 0}; +} + inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result, bool retry = false) noexcept { size_t result_written{0}; if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written), retry)}; @@ -360,30 +502,6 @@ inline std::expected hash_hmac(HashAlgorithm algorithm, std::stri return {}; } -inline std::expected openssl_sign(HashAlgorithm algorithm, std::string_view data, std::string_view private_key, std::span result, - bool retry = false) noexcept { - size_t result_written{0}; - if (auto error_code{k2_openssl_sign(algorithm, data.data(), data.size(), private_key.data(), private_key.size(), result.data(), result.size(), - std::addressof(result_written), retry)}; - error_code != k2::errno_ok) { - return std::unexpected{error_code}; - } - - return {result_written}; -} - -inline std::expected openssl_verify(HashAlgorithm algorithm, std::string_view data, std::string_view public_key, - std::string_view signature) noexcept { - int32_t is_valid{0}; - if (auto error_code{k2_openssl_verify(algorithm, data.data(), data.size(), public_key.data(), public_key.size(), signature.data(), signature.size(), - std::addressof(is_valid))}; - error_code != k2::errno_ok) { - return std::unexpected{error_code}; - } - - return {is_valid != 0}; -} - inline std::expected rpc_send_request(std::string_view actor_name, std::span request_buffer, k2::RpcKind rpc_kind) noexcept { k2::descriptor descriptor{}; diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 3331b3eceb..4c67729fce 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -524,6 +524,32 @@ int32_t k2_component_access(size_t name_len, const char* name); int32_t k2_openssl_random_pseudo_bytes(char* bytes, size_t length); +int32_t k2_openssl_x509_open(const char* data, size_t data_len); + +int32_t k2_openssl_x509_get_subject_name(char* result, size_t result_len, size_t* result_written, bool retry); + +int32_t k2_openssl_x509_name_entry_count(bool is_issuer, size_t* result); + +int32_t k2_openssl_x509_name_entry_key(bool is_issuer, bool shortnames, size_t index, char* result, size_t result_len, size_t* result_written); + +int32_t k2_openssl_x509_name_entry_value(bool is_issuer, size_t index, char* result, size_t result_len, size_t* result_written); + +int32_t k2_openssl_x509_get_hash(char* result, size_t result_len); + +int32_t k2_openssl_x509_get_version(int64_t* result); + +int32_t k2_openssl_x509_get_valid_from(int64_t* result); + +int32_t k2_openssl_x509_get_valid_to(int64_t* result); + +int32_t k2_openssl_x509_purpose_count(size_t* result); + +int32_t k2_openssl_x509_purpose_id(size_t index, int64_t* result); + +int32_t k2_openssl_x509_purpose_check(size_t index, bool ca, bool* result); + +int32_t k2_openssl_x509_purpose_name(size_t index, bool shortnames, char* result, size_t result_len, size_t* result_written); + int32_t k2_openssl_sign(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* private_key, size_t private_key_len, char* result, size_t result_len, size_t* result_written, bool retry); diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index 3e50148bc0..e611a8b1d5 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -9,31 +9,18 @@ #include #include #include +#include #include #include #include #include #include "common/crc32_generic.h" -#include "runtime-common/core/allocator/script-allocator.h" #include "runtime-common/core/runtime-core.h" -#include "runtime-common/core/std/containers.h" #include "runtime-common/stdlib/server/url-functions.h" #include "runtime-common/stdlib/string/string-functions.h" #include "runtime-light/k2-platform/k2-api.h" -#include "runtime-light/stdlib/component/component-api.h" #include "runtime-light/stdlib/diagnostics/logs.h" -#include "runtime-light/streams/read-ext.h" -#include "runtime-light/streams/stream.h" -#include "runtime-light/tl/tl-core.h" -#include "runtime-light/tl/tl-functions.h" -#include "runtime-light/tl/tl-types.h" - -namespace { - -constexpr std::string_view CRYPTO_COMPONENT_NAME = "crypto"; - -} // namespace Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept { if (length <= 0 || length > string::max_size()) [[unlikely]] { @@ -49,48 +36,168 @@ Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept { return buffer; } -kphp::coro::task>> f$openssl_x509_parse(string data, bool shortnames) noexcept { - tl::GetPemCertInfo get_perm_cert_info{.is_short = shortnames, .bytes = {.value = {data.c_str(), data.size()}}}; - tl::storer tls{get_perm_cert_info.footprint()}; - get_perm_cert_info.store(tls); +namespace { - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; +Optional> x509_get_name_entries(bool is_issuer, bool shortnames) noexcept { + auto count{k2::openssl_x509_name_entry_count(is_issuer)}; + if (!count) [[unlikely]] { + kphp::log::warning("Failed to get X509 name entry count"); + return false; } - auto stream{*std::move(expected_stream)}; - kphp::stl::vector response_bytes{}; - if (!co_await kphp::forks::id_managed(kphp::component::query(stream, tls.view(), kphp::component::read_ext::append(response_bytes)))) [[unlikely]] { - co_return false; + array res; + for (size_t i = 0; i < *count; ++i) { + auto key_size{k2::openssl_x509_name_entry_key(is_issuer, shortnames, i, {})}; + if (!key_size) [[unlikely]] { + kphp::log::warning("Failed to get X509 name entry key"); + return false; + } + + string key{static_cast(*key_size), false}; + auto key_written{k2::openssl_x509_name_entry_key(is_issuer, shortnames, i, {key.buffer(), key.size()})}; + if (!key_written) [[unlikely]] { + kphp::log::warning("Failed to get X509 name entry key"); + return false; + } + + key.shrink(static_cast(*key_written)); + + auto value_size{k2::openssl_x509_name_entry_value(is_issuer, i, {})}; + if (!value_size) [[unlikely]] { + kphp::log::warning("Failed to get X509 name entry value"); + return false; + } + + string value{static_cast(*value_size), false}; + auto value_written{k2::openssl_x509_name_entry_value(is_issuer, i, {value.buffer(), value.size()})}; + if (!value_written) [[unlikely]] { + kphp::log::warning("Failed to get X509 name entry value"); + return false; + } + + value.shrink(static_cast(*value_written)); + + res.set_value(key, value); } - tl::fetcher tlf{response_bytes}; - tl::Maybe> cert_items{}; - kphp::log::assertion(cert_items.fetch(tlf)); - if (!cert_items.opt_value) { - co_return false; + return res; +} + +Optional> x509_get_purposes(bool shortnames) noexcept { + auto count{k2::openssl_x509_purpose_count()}; + if (!count) [[unlikely]] { + kphp::log::warning("Failed to get X509 purpose count"); + return false; } - array response{array_size{static_cast((*cert_items.opt_value).size()), false}}; - auto item_to_mixed{tl::CertInfoItem::MakeVisitor{ - [](tl::i64 val) noexcept -> mixed { return val.value; }, [](tl::string val) noexcept -> mixed { return string(val.value.data(), val.value.size()); }, - [](const tl::dictionary& sub_dict) noexcept -> mixed { - array resp{array_size{static_cast(sub_dict.size()), false}}; - for (auto sub_item : sub_dict) { - auto key{string{sub_item.key.value.data(), static_cast(sub_item.key.value.size())}}; - auto value{string{sub_item.value.value.data(), static_cast(sub_item.value.value.size())}}; - resp[key] = std::move(value); - } - return resp; - }}}; + array res; + for (size_t i = 0; i < *count; ++i) { + auto id{k2::openssl_x509_purpose_id(i)}; + if (!id) [[unlikely]] { + kphp::log::warning("Failed to get X509 purpose id"); + return false; + } - for (auto cert_kv : std::move(*cert_items.opt_value)) { - auto key{string{cert_kv.key.value.data(), static_cast(cert_kv.key.value.size())}}; - tl::CertInfoItem val{std::move(cert_kv.value)}; - response[string{cert_kv.key.value.data(), static_cast(cert_kv.key.value.size())}] = std::visit(item_to_mixed, val.data); + auto purpset_0{k2::openssl_x509_purpose_check(i, false)}; + auto purpset_1{k2::openssl_x509_purpose_check(i, true)}; + if (!purpset_0 || !purpset_1) [[unlikely]] { + kphp::log::warning("Failed to check X509 purpose"); + return false; + } + + auto name_size{k2::openssl_x509_purpose_name(i, shortnames, {})}; + if (!name_size) [[unlikely]] { + kphp::log::warning("Failed to get X509 purpose name"); + return false; + } + + string name{static_cast(*name_size), false}; + auto name_written{k2::openssl_x509_purpose_name(i, shortnames, {name.buffer(), name.size()})}; + if (!name_written) [[unlikely]] { + kphp::log::warning("Failed to get X509 purpose name"); + return false; + } + + name.shrink(static_cast(*name_written)); + + res.set_value(*id, array::create(*purpset_0, *purpset_1, name)); } - co_return std::move(response); + + return res; +} + +} // namespace + +Optional> f$openssl_x509_parse(const string& data, bool shortnames) noexcept { + static constexpr size_t SUBJECT_NAME_HASH_SIZE{8}; + + if (!k2::openssl_x509_open({data.c_str(), data.size()})) [[unlikely]] { + kphp::log::warning("Failed to parse X509 certificate"); + return false; + } + + auto name_size{k2::openssl_x509_get_subject_name({})}; + if (!name_size) [[unlikely]] { + kphp::log::warning("Failed to get X509 subject name"); + return false; + } + + string name{static_cast(*name_size), false}; + auto name_written{k2::openssl_x509_get_subject_name({name.buffer(), name.size()}, true)}; + if (!name_written) [[unlikely]] { + kphp::log::warning("Failed to get X509 subject name"); + return false; + } + + name.shrink(static_cast(*name_written)); + + auto subject{x509_get_name_entries(false, shortnames)}; + if (!subject.has_value()) [[unlikely]] { + return false; + } + + string hash{SUBJECT_NAME_HASH_SIZE, false}; + if (!k2::openssl_x509_get_hash({hash.buffer(), hash.size()})) [[unlikely]] { + kphp::log::warning("Failed to get X509 hash"); + return false; + } + + auto issuer{x509_get_name_entries(true, shortnames)}; + if (!issuer.has_value()) [[unlikely]] { + return false; + } + + auto version{k2::openssl_x509_get_version()}; + if (!version) [[unlikely]] { + kphp::log::warning("Failed to get X509 version"); + return false; + } + + auto valid_from{k2::openssl_x509_get_valid_from()}; + if (!valid_from) [[unlikely]] { + kphp::log::warning("Failed to get X509 validFrom time"); + return false; + } + + auto valid_to{k2::openssl_x509_get_valid_to()}; + if (!valid_to) [[unlikely]] { + kphp::log::warning("Failed to get X509 validTo time"); + return false; + } + + auto purposes{x509_get_purposes(shortnames)}; + if (!purposes.has_value()) [[unlikely]] { + return false; + } + + return std::initializer_list>{{string{"name", 4}, name}, + {string{"subject", 7}, subject.val()}, + {string{"hash", 4}, hash}, + {string{"issuer", 6}, issuer.val()}, + {string{"version", 7}, *version}, + {string{"validFrom_time_t", 16}, *valid_from}, + {string{"validTo_time_t", 14}, *valid_to}, + {string{"purposes", 8}, purposes.val()}}; } bool f$openssl_sign(const string& data, string& signature, const string& private_key, int64_t algorithm) noexcept { diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h index e3d69932ab..5387cd01fe 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -10,12 +10,12 @@ #include "common/md5.h" #include "runtime-common/core/runtime-core.h" -#include "runtime-light/coroutine/task.h" +#include "runtime-light/k2-platform/k2-api.h" #include "runtime-light/stdlib/string/string-state.h" Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept; -kphp::coro::task>> f$openssl_x509_parse(string data, bool shortnames = true) noexcept; +Optional> f$openssl_x509_parse(const string& data, bool shortnames = true) noexcept; bool f$openssl_sign(const string& data, string& signature, const string& private_key, int64_t algorithm = HashAlgorithm::SHA1) noexcept; diff --git a/tests/phpt/openssl/10_openssl_x509_parse_light.php b/tests/phpt/openssl/10_openssl_x509_parse_light.php index a311f97a39..cbc1686da5 100644 --- a/tests/phpt/openssl/10_openssl_x509_parse_light.php +++ b/tests/phpt/openssl/10_openssl_x509_parse_light.php @@ -15,12 +15,8 @@ function print_cert_info(string $certificate) { var_dump($res["version"]); var_dump($res["validFrom_time_t"]); var_dump($res["validTo_time_t"]); - - // Currently not supported in K2 - // var_dump($res["hash"]); - - // Currently not supported in K2 - // var_dump($res["purposes"]); + var_dump($res["hash"]); + var_dump($res["purposes"]); } $certificate = << Date: Tue, 27 Oct 2026 15:16:25 +0300 Subject: [PATCH 32/38] define unsupported constants for md2 and md4 --- builtin-functions/kphp-light/stdlib/crypto-functions.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 5500366312..d3ebbb78f5 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -53,6 +53,9 @@ function crc32 ($s ::: string) ::: int; // ===== UNSUPPORTED ===== +define('OPENSSL_ALGO_MD2', 8); +define('OPENSSL_ALGO_MD4', 9); + define('X509_PURPOSE_SSL_CLIENT' , 1); define('X509_PURPOSE_SSL_SERVER' , 2); define('X509_PURPOSE_NS_SSL_SERVER' , 3); @@ -80,4 +83,3 @@ function openssl_x509_verify ($x509cert ::: string, $public_key ::: string) ::: function openssl_pkcs7_sign($infile ::: string, $outfile ::: string, $sign_cert ::: string, $priv_key ::: string, string[] $headers, $flags ::: int = PKCS7_DETACHED, $extra_certs ::: string = "") ::: bool; - From d76452e31ab13f0613941afa7d41e7248d50207c Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Tue, 27 Oct 2026 15:18:30 +0300 Subject: [PATCH 33/38] define unsupported constants for md2 and md4 --- runtime-light/k2-platform/k2-api.h | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 43303c42e8..b9171dac39 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -4,7 +4,6 @@ #pragma once -#include <__expected/expected.h> #include #include #include From 1752cae285d2bc15e378434bb7a7119dd0e3835c Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Wed, 28 Oct 2026 15:33:56 +0300 Subject: [PATCH 34/38] remove constants for md2 and md4 --- builtin-functions/kphp-light/stdlib/crypto-functions.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index d3ebbb78f5..06b4d70af9 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -53,9 +53,6 @@ function crc32 ($s ::: string) ::: int; // ===== UNSUPPORTED ===== -define('OPENSSL_ALGO_MD2', 8); -define('OPENSSL_ALGO_MD4', 9); - define('X509_PURPOSE_SSL_CLIENT' , 1); define('X509_PURPOSE_SSL_SERVER' , 2); define('X509_PURPOSE_NS_SSL_SERVER' , 3); From 9659332a2e3846ae481637f0f9b869fab333e8d1 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 29 Oct 2026 20:46:10 +0300 Subject: [PATCH 35/38] add constants for md2 and md4 --- builtin-functions/kphp-light/stdlib/crypto-functions.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 06b4d70af9..8401ee85d5 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -10,6 +10,8 @@ define('OPENSSL_ALGO_SHA384', 4); define('OPENSSL_ALGO_SHA512', 5); define('OPENSSL_ALGO_MD5' , 6); define('OPENSSL_ALGO_RMD160', 7); +define('OPENSSL_ALGO_MD2', 8); +define('OPENSSL_ALGO_MD4', 9); define('OPENSSL_RAW_DATA', 1); define('OPENSSL_ZERO_PADDING', 2); From ad379209ee7d17f84491eabd2a3213f0a4ef2032 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Mon, 2 Nov 2026 17:41:31 +0300 Subject: [PATCH 36/38] add support of md2 and md4 --- .../kphp-light/stdlib/crypto-functions.txt | 4 ++-- runtime-light/k2-platform/k2-header.h | 2 +- .../stdlib/crypto/crypto-functions.cpp | 24 +++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index 8401ee85d5..38cf2792a6 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -10,8 +10,8 @@ define('OPENSSL_ALGO_SHA384', 4); define('OPENSSL_ALGO_SHA512', 5); define('OPENSSL_ALGO_MD5' , 6); define('OPENSSL_ALGO_RMD160', 7); -define('OPENSSL_ALGO_MD2', 8); -define('OPENSSL_ALGO_MD4', 9); +define('OPENSSL_ALGO_MD2' , 8); +define('OPENSSL_ALGO_MD4' , 9); define('OPENSSL_RAW_DATA', 1); define('OPENSSL_ZERO_PADDING', 2); diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 4c67729fce..49de38b7f2 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -104,7 +104,7 @@ enum BlockPadding : uint32_t { PKCS7 = 0, BLOCK_NO_PADDING = 1 }; enum RsaPadding : uint32_t { PKCS1 = 0, PKCS1_OAEP = 1, ASYMM_NO_PADDING = 2 }; -enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, MD5 = 6, RMD160 = 7, COUNT_HASH }; +enum HashAlgorithm : uint32_t { DSS1 = 0, SHA1 = 1, SHA224 = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, MD5 = 6, RMD160 = 7, MD2 = 8, MD4 = 9, COUNT_HASH }; enum RpcKind { TL_RPC = 0, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index e611a8b1d5..fd58d1f1a0 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -583,6 +583,8 @@ constexpr std::string_view SHA384 = "sha384"; constexpr std::string_view SHA512 = "sha512"; constexpr std::string_view MD5 = "md5"; constexpr std::string_view RMD160 = "rmd160"; +constexpr std::string_view MD2 = "md2"; +constexpr std::string_view MD4 = "md4"; constexpr size_t DSS1_HASHED_SIZE = 20; constexpr size_t SHA1_HASHED_SIZE = 20; @@ -592,6 +594,8 @@ constexpr size_t SHA384_HASHED_SIZE = 48; constexpr size_t SHA512_HASHED_SIZE = 64; constexpr size_t MD5_HASHED_SIZE = 16; constexpr size_t RMD160_HASHED_SIZE = 20; +constexpr size_t MD2_HASHED_SIZE = 16; +constexpr size_t MD4_HASHED_SIZE = 16; struct hash_algorithm_traits { int64_t hashed_size; @@ -599,16 +603,16 @@ struct hash_algorithm_traits { }; constexpr auto HASH_ALGORITHMS = []() noexcept { - auto arr{std::array{ - std::pair{DSS1, hash_algorithm_traits{DSS1_HASHED_SIZE, HashAlgorithm::DSS1}}, - std::pair{SHA1, hash_algorithm_traits{SHA1_HASHED_SIZE, HashAlgorithm::SHA1}}, - std::pair{SHA224, hash_algorithm_traits{SHA224_HASHED_SIZE, HashAlgorithm::SHA224}}, - std::pair{SHA256, hash_algorithm_traits{SHA256_HASHED_SIZE, HashAlgorithm::SHA256}}, - std::pair{SHA384, hash_algorithm_traits{SHA384_HASHED_SIZE, HashAlgorithm::SHA384}}, - std::pair{SHA512, hash_algorithm_traits{SHA512_HASHED_SIZE, HashAlgorithm::SHA512}}, - std::pair{MD5, hash_algorithm_traits{MD5_HASHED_SIZE, HashAlgorithm::MD5}}, - std::pair{RMD160, hash_algorithm_traits{RMD160_HASHED_SIZE, HashAlgorithm::RMD160}}, - }}; + auto arr{std::array{std::pair{DSS1, hash_algorithm_traits{DSS1_HASHED_SIZE, HashAlgorithm::DSS1}}, + std::pair{SHA1, hash_algorithm_traits{SHA1_HASHED_SIZE, HashAlgorithm::SHA1}}, + std::pair{SHA224, hash_algorithm_traits{SHA224_HASHED_SIZE, HashAlgorithm::SHA224}}, + std::pair{SHA256, hash_algorithm_traits{SHA256_HASHED_SIZE, HashAlgorithm::SHA256}}, + std::pair{SHA384, hash_algorithm_traits{SHA384_HASHED_SIZE, HashAlgorithm::SHA384}}, + std::pair{SHA512, hash_algorithm_traits{SHA512_HASHED_SIZE, HashAlgorithm::SHA512}}, + std::pair{MD5, hash_algorithm_traits{MD5_HASHED_SIZE, HashAlgorithm::MD5}}, + std::pair{RMD160, hash_algorithm_traits{RMD160_HASHED_SIZE, HashAlgorithm::RMD160}}, + std::pair{MD2, hash_algorithm_traits{MD2_HASHED_SIZE, HashAlgorithm::MD2}}, + std::pair{MD4, hash_algorithm_traits{MD4_HASHED_SIZE, HashAlgorithm::MD4}}}}; std::ranges::sort(arr, std::less{}, &std::pair::first); return arr; From ca2725d5c3a4ce5051d018c2cf269d151b338d78 Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 5 Nov 2026 13:03:25 +0300 Subject: [PATCH 37/38] add cache for pkeys --- runtime-light/k2-platform/k2-api.h | 9 +++--- runtime-light/k2-platform/k2-header.h | 4 +-- .../stdlib/crypto/crypto-functions.cpp | 30 +++++++++---------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/runtime-light/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index b9171dac39..7b984e5943 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -411,9 +411,9 @@ inline std::expected openssl_verify(HashAlgorithm algorithm, std: return {is_valid != 0}; } -inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result, bool retry = false) noexcept { +inline std::expected openssl_pkey_get_public(std::string_view public_key, std::span result) noexcept { size_t result_written{0}; - if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written), retry)}; + if (auto error_code{k2_openssl_pkey_get_public(public_key.data(), public_key.size(), result.data(), result.size(), std::addressof(result_written))}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } @@ -421,11 +421,10 @@ inline std::expected openssl_pkey_get_public(std::string_view p return {result_written}; } -inline std::expected openssl_pkey_get_private(std::string_view private_key, std::string_view passphrase, std::span result, - bool retry = false) noexcept { +inline std::expected openssl_pkey_get_private(std::string_view private_key, std::string_view passphrase, std::span result) noexcept { size_t result_written{0}; if (auto error_code{k2_openssl_pkey_get_private(private_key.data(), private_key.size(), passphrase.data(), passphrase.size(), result.data(), result.size(), - std::addressof(result_written), retry)}; + std::addressof(result_written))}; error_code != k2::errno_ok) { return std::unexpected{error_code}; } diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 49de38b7f2..827925426c 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -556,10 +556,10 @@ int32_t k2_openssl_sign(enum HashAlgorithm algorithm, const char* data, size_t d int32_t k2_openssl_verify(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* public_key, size_t public_key_len, const char* signature, size_t signature_len, int32_t* is_valid); -int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written, bool retry); +int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written); int32_t k2_openssl_pkey_get_private(const char* private_key, size_t private_key_len, const char* passphrase, size_t passphrase_len, char* result, - size_t result_len, size_t* result_written, bool retry); + size_t result_len, size_t* result_written); int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp index fd58d1f1a0..ed70217bba 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -482,35 +482,35 @@ Optional f$openssl_decrypt(const string& data, const string& algorithm, return std::move(decrypted_data); } +namespace { + +constexpr size_t PKEY_HANDLE_MAX_SIZE = 32; + +} // namespace + Optional f$openssl_pkey_get_public(const string& public_key) noexcept { - auto size{k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {})}; - if (!size) [[unlikely]] { + string buffer{static_cast(PKEY_HANDLE_MAX_SIZE), false}; + auto written{k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {buffer.buffer(), buffer.size()})}; + if (!written) [[unlikely]] { kphp::log::warning("Failed to get public key"); return false; } - string buffer{static_cast(*size), false}; - if (!k2::openssl_pkey_get_public({public_key.c_str(), public_key.size()}, {buffer.buffer(), buffer.size()}, true)) [[unlikely]] { - kphp::log::warning("Failed to get public key"); - return false; - } + buffer.shrink(static_cast(*written)); return buffer; } Optional f$openssl_pkey_get_private(const string& private_key, const string& passphrase) noexcept { - auto size{k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {})}; - if (!size) [[unlikely]] { + string buffer{static_cast(PKEY_HANDLE_MAX_SIZE), false}; + auto written{ + k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()})}; + if (!written) [[unlikely]] { kphp::log::warning("Failed to get private key"); return false; } - string buffer{static_cast(*size), false}; - if (!k2::openssl_pkey_get_private({private_key.c_str(), private_key.size()}, {passphrase.c_str(), passphrase.size()}, {buffer.buffer(), buffer.size()}, true)) - [[unlikely]] { - kphp::log::warning("Failed to get private key"); - return false; - } + buffer.shrink(static_cast(*written)); return buffer; } From 4ab9859eef35584f3dc95efc06998c6af6a1845b Mon Sep 17 00:00:00 2001 From: Kirill Kotliar Date: Thu, 5 Nov 2026 13:18:01 +0300 Subject: [PATCH 38/38] add comments for k2-api --- runtime-light/k2-platform/k2-header.h | 415 ++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) diff --git a/runtime-light/k2-platform/k2-header.h b/runtime-light/k2-platform/k2-header.h index 827925426c..440e887d94 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -522,61 +522,476 @@ int32_t k2_unlink(const char* path, size_t path_len); */ int32_t k2_component_access(size_t name_len, const char* name); +/** + * Fills `bytes` with `length` cryptographically secure random bytes. + * + * @param `bytes` A pointer to the buffer to fill. + * @param `length` The number of bytes to generate. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `bytes` is `NULL`, or OpenSSL failed to generate random bytes. + */ int32_t k2_openssl_random_pseudo_bytes(char* bytes, size_t length); +/** + * Parses a PEM-encoded X509 certificate and makes it the current certificate of the instance. + * All other `k2_openssl_x509_*` functions operate on the current certificate. A previously opened certificate is replaced. + * + * @param `data` A pointer to the PEM-encoded certificate. + * @param `data_len` The length of `data`. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `data` is `NULL`, or `data` is not a valid PEM certificate. + */ int32_t k2_openssl_x509_open(const char* data, size_t data_len); +/** + * Writes the one-line representation of the subject name of the current certificate (like OpenSSL's `X509_NAME_oneline`). + * The result is not null-terminated. + * + * Two-phase protocol: if `result_len` is `0`, nothing is written to `result`, the required size is stored in `result_written`. + * Then call again with `retry` set to `true` and a buffer of at least that size. + * + * @param `result` A pointer to the output buffer (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * @param `retry` `true` for the second call of the two-phase protocol. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result_written` is `NULL`, or OpenSSL failed. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`, or `retry` is `true` but there is no saved state from the first call. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_x509_get_subject_name(char* result, size_t result_len, size_t* result_written, bool retry); +/** + * Writes the number of entries in the subject or issuer name of the current certificate. + * + * @param `is_issuer` `true` for the issuer name, `false` for the subject name. + * @param `result` A pointer where the entries count will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + */ int32_t k2_openssl_x509_name_entry_count(bool is_issuer, size_t* result); +/** + * Writes the key of the `index`-th entry of the subject or issuer name. + * The result is not null-terminated. If `result_len` is `0`, nothing is written and the required size is stored in `result_written`. + * + * @param `is_issuer` `true` for the issuer name, `false` for the subject name. + * @param `shortnames` `true` for the short name (`CN`), `false` for the long name (`commonName`). + * @param `index` The index of the entry, must be less than the count from `k2_openssl_x509_name_entry_count`. + * @param `result` A pointer to the output buffer (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result_written` is `NULL`, `index` is out of range, or OpenSSL failed. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_x509_name_entry_key(bool is_issuer, bool shortnames, size_t index, char* result, size_t result_len, size_t* result_written); +/** + * Writes the value (raw bytes of the ASN.1 string) of the `index`-th entry of the subject or issuer name. + * The result is not null-terminated. If `result_len` is `0`, nothing is written and the required size is stored in `result_written`. + * + * @param `is_issuer` `true` for the issuer name, `false` for the subject name. + * @param `index` The index of the entry, must be less than the count from `k2_openssl_x509_name_entry_count`. + * @param `result` A pointer to the output buffer (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result_written` is `NULL` or `index` is out of range. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_x509_name_entry_value(bool is_issuer, size_t index, char* result, size_t result_len, size_t* result_written); +/** + * Writes the hash of the subject name of the current certificate as exactly 8 lowercase hex characters (not null-terminated). + * + * @param `result` A pointer to the output buffer. + * @param `result_len` The size of the output buffer, must be at least 8. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + * `ERANGE` => `result_len` is less than 8. + */ int32_t k2_openssl_x509_get_hash(char* result, size_t result_len); +/** + * Writes the version of the current certificate as returned by OpenSSL's `X509_get_version`. + * + * @param `result` A pointer where the version will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + */ int32_t k2_openssl_x509_get_version(int64_t* result); +/** + * Writes the beginning of the validity period of the current certificate as a Unix timestamp in seconds. + * + * @param `result` A pointer where the timestamp will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`, or OpenSSL failed to convert the time. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + */ int32_t k2_openssl_x509_get_valid_from(int64_t* result); +/** + * Writes the end of the validity period of the current certificate as a Unix timestamp in seconds. + * + * @param `result` A pointer where the timestamp will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`, or OpenSSL failed to convert the time. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + */ int32_t k2_openssl_x509_get_valid_to(int64_t* result); +/** + * Writes the number of X509 purposes known to OpenSSL. Valid `index` values for other `k2_openssl_x509_purpose_*` functions are `[0, count)`. + * + * @param `result` A pointer where the count will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`, or OpenSSL failed. + */ int32_t k2_openssl_x509_purpose_count(size_t* result); +/** + * Writes the OpenSSL purpose identifier (`X509_PURPOSE_*`) of the purpose with the given index. + * + * @param `index` The index of the purpose, must be less than the count from `k2_openssl_x509_purpose_count`. + * @param `result` A pointer where the identifier will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`, `index` is out of range, or OpenSSL failed. + */ int32_t k2_openssl_x509_purpose_id(size_t index, int64_t* result); +/** + * Checks whether the current certificate can be used for the purpose with the given index (like OpenSSL's `X509_check_purpose`). + * + * @param `index` The index of the purpose, must be less than the count from `k2_openssl_x509_purpose_count`. + * @param `ca` `true` to check the purpose for a CA certificate. + * @param `result` A pointer where `true` will be stored if the certificate is suitable for the purpose, `false` otherwise. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result` is `NULL`, `index` is out of range, or OpenSSL failed. + * `ENOENT` => no certificate was opened by `k2_openssl_x509_open`. + */ int32_t k2_openssl_x509_purpose_check(size_t index, bool ca, bool* result); +/** + * Writes the name of the purpose with the given index. The result is not null-terminated. + * If `result_len` is `0`, nothing is written and the required size is stored in `result_written`. + * + * @param `index` The index of the purpose, must be less than the count from `k2_openssl_x509_purpose_count`. + * @param `shortnames` `true` for the short name (e.g. `sslclient`), `false` for the long name (e.g. `SSL client`). + * @param `result` A pointer to the output buffer (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => `result_written` is `NULL`, `index` is out of range, or OpenSSL failed. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_x509_purpose_name(size_t index, bool shortnames, char* result, size_t result_len, size_t* result_written); +/** + * Signs `data` with a private key using the given hash algorithm. + * + * `private_key` is either a PEM-encoded private key or a key handle returned by `k2_openssl_pkey_get_private`. + * + * Two-phase protocol (only for PEM keys, a key handle needs no state between calls): if `result_len` is `0`, nothing is signed, + * the required size is stored in `result_written` and the parsed key is kept in the instance. Then call again with the same arguments, + * `retry` set to `true` and a buffer of at least that size. For key handles `retry` is ignored and the size query only returns the size. + * + * @param `algorithm` The hash algorithm. + * @param `data` A pointer to the data to sign. + * @param `data_len` The length of `data`. + * @param `private_key` A pointer to a PEM-encoded private key or to a key handle. + * @param `private_key_len` The length of `private_key`. + * @param `result` A pointer to the buffer for the signature (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * @param `retry` `true` for the second call of the two-phase protocol. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `algorithm` is unknown, `private_key` is not a valid key or handle, or OpenSSL failed. + * `ENOENT` => `retry` is `true` but there is no saved state from the first call. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_sign(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* private_key, size_t private_key_len, char* result, size_t result_len, size_t* result_written, bool retry); +/** + * Verifies the `signature` of `data` with a public key using the given hash algorithm. + * + * `public_key` is either a PEM-encoded public key (or certificate) or a key handle returned by `k2_openssl_pkey_get_public`. + * + * @param `algorithm` The hash algorithm. + * @param `data` A pointer to the signed data. + * @param `data_len` The length of `data`. + * @param `public_key` A pointer to a PEM-encoded public key (or certificate) or to a key handle. + * @param `public_key_len` The length of `public_key`. + * @param `signature` A pointer to the signature. + * @param `signature_len` The length of `signature`. + * @param `is_valid` A pointer where `1` will be stored if the signature is valid, `0` otherwise. + * + * @return Returns `0` on success (the check itself was performed). On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `algorithm` is unknown, `public_key` is not a valid key or handle, or OpenSSL failed. + */ int32_t k2_openssl_verify(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* public_key, size_t public_key_len, const char* signature, size_t signature_len, int32_t* is_valid); +/** + * Parses a public key from a PEM-encoded public key or certificate, stores it in the instance and writes its handle. + * A handle is a short ASCII string (`;;N`, where `N` is a decimal number) that can be passed instead of the PEM + * to `k2_openssl_sign`, `k2_openssl_verify`, `k2_openssl_public_encrypt` and `k2_openssl_private_decrypt`. + * Handles stay valid until the end of the instance. The handle is not null-terminated. + * + * If `public_key` is already a valid handle, it is written to `result` unchanged. + * + * If `result_len` is `0`, nothing is parsed or stored: only the handle size is stored in `result_written`. + * + * @param `public_key` A pointer to a PEM-encoded public key (or certificate) or to a key handle. + * @param `public_key_len` The length of `public_key`. + * @param `result` A pointer to the output buffer (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `public_key` is not a valid key, or an unknown handle. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_pkey_get_public(const char* public_key, size_t public_key_len, char* result, size_t result_len, size_t* result_written); +/** + * Parses a private key from a PEM (decrypting it with `passphrase` if needed), stores it in the instance and writes its handle. + * A handle is a short ASCII string (`::N`, where `N` is a decimal number) that can be passed instead of the PEM + * to `k2_openssl_sign`, `k2_openssl_verify`, `k2_openssl_public_encrypt` and `k2_openssl_private_decrypt`. + * Handles stay valid until the end of the instance. The handle is not null-terminated. + * + * If `private_key` is already a valid handle, it is written to `result` unchanged. + * + * If `result_len` is `0`, nothing is parsed or stored: only the handle size is stored in `result_written`. + * + * @param `private_key` A pointer to a PEM-encoded private key or to a key handle. + * @param `private_key_len` The length of `private_key`. + * @param `passphrase` A pointer to the passphrase of the key (may be empty). + * @param `passphrase_len` The length of `passphrase`. + * @param `result` A pointer to the output buffer (may be `NULL` when `result_len` is `0`). + * @param `result_len` The size of the output buffer. + * @param `result_written` A pointer where the number of bytes (required or written) will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `private_key` is not a valid key (or the passphrase is wrong), or an unknown handle. + * `ERANGE` => `result_len` is not enough. + */ int32_t k2_openssl_pkey_get_private(const char* private_key, size_t private_key_len, const char* passphrase, size_t passphrase_len, char* result, size_t result_len, size_t* result_written); +/** + * Encrypts `data` with a symmetric cipher. + * For AEAD ciphers (GCM) the authentication tag is written to `received_tag` (its size is `received_tag_len`) and `aad` is authenticated. + * For block ciphers (CBC) `padding` is applied; `aad` and `received_tag` are ignored. + * + * @param `algorithm` The cipher algorithm. + * @param `padding` The block padding (CBC only). + * @param `key` A pointer to the key. + * @param `key_len` The length of `key`. + * @param `iv` A pointer to the initialization vector. + * @param `iv_len` The length of `iv`. + * @param `aad` A pointer to additional authenticated data (GCM only, may be empty). + * @param `aad_len` The length of `aad`. + * @param `data` A pointer to the data to encrypt. + * @param `data_len` The length of `data`. + * @param `encrypted_data` A pointer to the output buffer. Must have at least `data_len` bytes for GCM + * and `data_len` + cipher block size for CBC. + * @param `encrypted_data_len` The size of the output buffer. + * @param `encrypted_data_written` A pointer where the number of written bytes will be stored. + * @param `received_tag` A pointer to the buffer for the authentication tag (GCM only). + * @param `received_tag_len` The size of the tag buffer. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a required pointer argument is `NULL`, `algorithm` is unknown, `key`/`iv` have invalid sizes, or OpenSSL failed. + * `ERANGE` => `encrypted_data_len` is not enough. + */ int32_t k2_openssl_encrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, const char* aad, size_t aad_len, const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, size_t* encrypted_data_written, char* received_tag, size_t received_tag_len); +/** + * Decrypts `data` with a symmetric cipher. + * For AEAD ciphers (GCM) `tag` is verified together with `aad`; if the check fails the call fails. + * For block ciphers (CBC) `padding` is applied; `aad` and `tag` are ignored. + * + * @param `algorithm` The cipher algorithm. + * @param `padding` The block padding (CBC only). + * @param `key` A pointer to the key. + * @param `key_len` The length of `key`. + * @param `iv` A pointer to the initialization vector. + * @param `iv_len` The length of `iv`. + * @param `aad` A pointer to additional authenticated data (GCM only, may be empty). + * @param `aad_len` The length of `aad`. + * @param `tag` A pointer to the authentication tag (GCM only). + * @param `tag_len` The length of `tag`. + * @param `data` A pointer to the data to decrypt. + * @param `data_len` The length of `data`. + * @param `decrypted_data` A pointer to the output buffer. Must have at least `data_len` bytes for GCM + * and `data_len` + cipher block size for CBC. + * @param `decrypted_data_len` The size of the output buffer. + * @param `decrypted_data_written` A pointer where the number of written bytes will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a required pointer argument is `NULL`, `algorithm` is unknown, `key`/`iv` have invalid sizes, + * the tag check or the padding check failed, or OpenSSL failed. + * `ERANGE` => `decrypted_data_len` is not enough. + */ int32_t k2_openssl_decrypt(enum CipherAlgorithm algorithm, enum BlockPadding padding, const char* key, size_t key_len, const char* iv, size_t iv_len, const char* aad, size_t aad_len, const char* tag, size_t tag_len, const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, size_t* decrypted_data_written); +/** + * Encrypts `data` with an RSA public key. + * + * `public_key` is either a PEM-encoded RSA public key or a key handle returned by `k2_openssl_pkey_get_public`. + * + * Two-phase protocol (only for PEM keys, a key handle needs no state between calls): if `encrypted_data_len` is `0`, + * nothing is encrypted, the required size is stored in `encrypted_data_written` and the parsed key is kept in the instance. + * Then call again with the same arguments, `retry` set to `true` and a buffer of at least that size. + * For key handles `retry` is ignored and the size query only returns the size. + * + * @param `data` A pointer to the data to encrypt. + * @param `data_len` The length of `data`. + * @param `encrypted_data` A pointer to the output buffer (may be `NULL` when `encrypted_data_len` is `0`). + * @param `encrypted_data_len` The size of the output buffer. + * @param `public_key` A pointer to a PEM-encoded RSA public key or to a key handle. + * @param `public_key_len` The length of `public_key`. + * @param `padding` The RSA padding. + * @param `encrypted_data_written` A pointer where the number of bytes (required or written) will be stored. + * @param `retry` `true` for the second call of the two-phase protocol. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `padding` is unknown, `public_key` is not a valid key or handle, or OpenSSL failed. + * `ENOENT` => `retry` is `true` but there is no saved state from the first call. + * `ERANGE` => `encrypted_data_len` is not enough. + */ int32_t k2_openssl_public_encrypt(const char* data, size_t data_len, char* encrypted_data, size_t encrypted_data_len, const char* public_key, size_t public_key_len, enum RsaPadding padding, size_t* encrypted_data_written, bool retry); +/** + * Decrypts `data` with an RSA private key. + * + * `private_key` is either a PEM-encoded RSA private key or a key handle returned by `k2_openssl_pkey_get_private`. + * + * @param `data` A pointer to the data to decrypt. + * @param `data_len` The length of `data`. + * @param `decrypted_data` A pointer to the output buffer. Must have at least the RSA key size in bytes. + * @param `decrypted_data_len` The size of the output buffer. + * @param `private_key` A pointer to a PEM-encoded RSA private key or to a key handle. + * @param `private_key_len` The length of `private_key`. + * @param `padding` The RSA padding. + * @param `decrypted_data_written` A pointer where the number of written bytes will be stored. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `padding` is unknown, `private_key` is not a valid key or handle, + * the data does not decrypt with this key and padding, or OpenSSL failed. + * `ERANGE` => `decrypted_data_len` is less than the RSA key size. + */ int32_t k2_openssl_private_decrypt(const char* data, size_t data_len, char* decrypted_data, size_t decrypted_data_len, const char* private_key, size_t private_key_len, enum RsaPadding padding, size_t* decrypted_data_written); +/** + * Computes the digest of `data` with the given hash algorithm and writes the raw (binary) digest to `hashed_data`. + * Exactly the digest size bytes are written. + * + * @param `algorithm` The hash algorithm. + * @param `data` A pointer to the data to hash. + * @param `data_len` The length of `data`. + * @param `hashed_data` A pointer to the output buffer. + * @param `hashed_data_len` The size of the output buffer, must be at least the digest size. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `algorithm` is unknown, or OpenSSL failed. + * `ERANGE` => `hashed_data_len` is less than the digest size. + */ int32_t k2_hash(enum HashAlgorithm algorithm, const char* data, size_t data_len, char* hashed_data, size_t hashed_data_len); +/** + * Computes the HMAC of `data` with `key` and the given hash algorithm and writes the raw (binary) result to `hashed_data`. + * Exactly the digest size bytes are written. + * + * @param `algorithm` The hash algorithm. + * @param `data` A pointer to the data to authenticate. + * @param `data_len` The length of `data`. + * @param `key` A pointer to the HMAC key. + * @param `key_len` The length of `key`. + * @param `hashed_data` A pointer to the output buffer. + * @param `hashed_data_len` The size of the output buffer, must be at least the digest size. + * + * @return Returns `0` on success. On failure, returns a non-zero value corresponding to a libc-like `errno`. + * + * Possible `errno` values: + * `EINVAL` => a pointer argument is `NULL`, `algorithm` is unknown, `key_len` is too big, or OpenSSL failed. + * `ERANGE` => `hashed_data_len` is less than the digest size. + */ int32_t k2_hash_hmac(enum HashAlgorithm algorithm, const char* data, size_t data_len, const char* key, size_t key_len, char* hashed_data, size_t hashed_data_len);