diff --git a/builtin-functions/kphp-light/stdlib/crypto-functions.txt b/builtin-functions/kphp-light/stdlib/crypto-functions.txt index e91af71891..38cf2792a6 100644 --- a/builtin-functions/kphp-light/stdlib/crypto-functions.txt +++ b/builtin-functions/kphp-light/stdlib/crypto-functions.txt @@ -2,65 +2,50 @@ // ===== 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_ALGO_MD2' , 8); +define('OPENSSL_ALGO_MD4' , 9); 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; -/** @kphp-extern-func-info interruptible */ 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[]; 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; - -/** @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; -/** @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; -/** @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[]; 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; @@ -93,9 +78,7 @@ 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, string[] $headers, $flags ::: int = PKCS7_DETACHED, $extra_certs ::: string = "") ::: bool; - 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/k2-platform/k2-api.h b/runtime-light/k2-platform/k2-api.h index 768c2512f5..7b984e5943 100644 --- a/runtime-light/k2-platform/k2-api.h +++ b/runtime-light/k2-platform/k2-api.h @@ -261,6 +261,245 @@ 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_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) 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 { + 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(), std::addressof(encrypted_data_written), received_tag.data(), + received_tag.size())}; + error_code != k2::errno_ok) { + return std::unexpected{error_code}; + } + + 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 openssl_public_encrypt(std::string_view data, std::span encrypted_data, std::string_view public_key, + 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)}; + 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, + 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))}; + 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}; + } + + 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 4a5b9a49b4..440e887d94 100644 --- a/runtime-light/k2-platform/k2-header.h +++ b/runtime-light/k2-platform/k2-header.h @@ -98,6 +98,14 @@ enum UpdateStatus { NewDescriptor = 2, }; +enum CipherAlgorithm : uint32_t { AES128_GCM = 0, AES256_GCM = 1, AES128_CBC = 2, AES256_CBC = 3, COUNT_CIPHER }; + +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, MD2 = 8, MD4 = 9, COUNT_HASH }; + enum RpcKind { TL_RPC = 0, }; @@ -514,6 +522,479 @@ 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); + /** * 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..ed70217bba 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.cpp +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -9,157 +9,227 @@ #include #include #include +#include #include #include #include #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" #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" + +Optional f$openssl_random_pseudo_bytes(int64_t length) noexcept { + if (length <= 0 || length > string::max_size()) [[unlikely]] { + 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; + } + + return buffer; +} namespace { -constexpr std::string_view CRYPTO_COMPONENT_NAME = "crypto"; +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; + } + + 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; + } -} // namespace + 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; + } -kphp::coro::task> f$openssl_random_pseudo_bytes(int64_t length) noexcept { - if (length <= 0 || length > string::max_size()) [[unlikely]] { - co_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::GetCryptosecurePseudorandomBytes get_pseudorandom_bytes{.size = {.value = static_cast(length)}}; - tl::storer tls{get_pseudorandom_bytes.footprint()}; - get_pseudorandom_bytes.store(tls); + return res; +} - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; +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; } - 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; + 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; + } + + 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)); } - 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 res; } -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> 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 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; + auto name_size{k2::openssl_x509_get_subject_name({})}; + if (!name_size) [[unlikely]] { + kphp::log::warning("Failed to get X509 subject name"); + return false; } - tl::fetcher tlf{response_bytes}; - tl::Maybe> cert_items{}; - kphp::log::assertion(cert_items.fetch(tlf)); - if (!cert_items.opt_value) { - co_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; } - 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; - }}}; + name.shrink(static_cast(*name_written)); - 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 subject{x509_get_name_entries(false, shortnames)}; + if (!subject.has_value()) [[unlikely]] { + return false; } - 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); + 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 expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; + auto issuer{x509_get_name_entries(true, shortnames)}; + if (!issuer.has_value()) [[unlikely]] { + 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; + auto version{k2::openssl_x509_get_version()}; + if (!version) [[unlikely]] { + kphp::log::warning("Failed to get X509 version"); + 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; -} + auto valid_from{k2::openssl_x509_get_valid_from()}; + if (!valid_from) [[unlikely]] { + kphp::log::warning("Failed to get X509 validFrom time"); + return false; + } -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); + 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 expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return 0; + auto purposes{x509_get_purposes(shortnames)}; + if (!purposes.has_value()) [[unlikely]] { + return false; } - 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; + 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 { + 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; + } + + 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}; - // 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; + buffer.shrink(static_cast(*written)); + signature = std::move(buffer); + + return true; +} + +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; } - co_return 1; + + return *res ? 1 : 0; } namespace { @@ -169,74 +239,82 @@ 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; +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, 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(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; } - 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); + return std::nullopt; } -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; +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; + }; -int64_t algorithm_iv_len([[maybe_unused]] tl::CipherAlgorithm algorithm) noexcept { - if (is_gcm_algorithm(algorithm)) { - return GCM_NONCE_LEN; + switch (traits.mode) { + case encryption_mode::BLOCK: + 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: + return encrypted_data_size_info{data_len, tag_size}; } - 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); +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 (is_aead_algorithm(algorithm)) { - if (source_iv.empty()) { + if (traits.mode == encryption_mode::AEAD) { + if (source_iv.empty()) [[unlikely]] { kphp::log::warning("Setting of IV length for AEAD mode failed"); - return false; + return std::nullopt; } } else { + 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'); @@ -247,10 +325,11 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg } auto key = source_key; + 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)) { + 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 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); @@ -260,384 +339,365 @@ Optional> algorithm_pad_key_iv(tl::CipherAlgorithm alg key.shrink(static_cast(key_required_len)); } - return std::make_pair(key, iv); + return std::make_pair(std::move(key), std::move(iv)); } } // 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 (const 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({algorithm.c_str(), algorithm.size()})}; + if (!algo) { + kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); return false; } - return algorithm_iv_len(*algorithm); + + return algo->get().iv_len; } -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)}; - if (!algorithm) { - kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); - co_return false; +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({algorithm.c_str(), algorithm.size()})}; + if (!algo) [[unlikely]] { + kphp::log::warning("Unknown cipher algorithm {}", algorithm.c_str()); + return false; } - bool aead{is_aead_algorithm(*algorithm)}; - if (aead && (!tag.has_value() || tag_length == 0)) { + 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"); - co_return false; - } - if (tag.has_value() && !aead) { - kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); + return false; } - if (!aad.empty() && !aead) { - kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); + + if (!is_aead) { + if (tag) { + 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 (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(*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; + padding = BlockPadding::BLOCK_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; + 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(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]] { + kphp::log::warning("Failed to encrypt 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; + encrypted_data.shrink(static_cast(*res)); + + if (tag) { + (*tag).get() = std::move(received_tag); } - tl::fetcher tlf{response_bytes}; - tl::Maybe> response{}; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; + return (options & static_cast(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(encrypted_data) : f$base64_encode(encrypted_data); +} + +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; + } + + 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; } - string result{(*response.opt_value).value[0].value.data(), static_cast((*response.opt_value).value[0].value.size())}; + if (!is_aead) { + if (!tag.empty()) { + kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD"); + } - 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); + if (!aad.empty()) { + kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); + } } - co_return (options & static_cast(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(result) : f$base64_encode(result); -} -kphp::coro::task> f$openssl_decrypt(string data, string method, string source_key, int64_t options, string source_iv, string tag, - string aad) noexcept { + 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()) { + if (!decoding_data.has_value()) [[unlikely]] { kphp::log::warning("Failed to base64 decode the input"); - co_return false; + return false; } - data = std::move(decoding_data.val()); - } - auto algorithm{parse_cipher_algorithm(method)}; - if (!algorithm.has_value()) { - kphp::log::warning("Unknown cipher algorithm {}", method.c_str()); - co_return false; + input_data = std::move(decoding_data.val()); } - 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 (!aad.empty() && !aead) { - kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD"); - } - - 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; + padding = BlockPadding::BLOCK_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; - } - - 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]] { + kphp::log::warning("Failed to decrypt data"); + 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 { - tl::GetPublicKey get_public_key{.key = {.value = {key.c_str(), key.size()}}}; - tl::storer tls{get_public_key.footprint()}; - get_public_key.store(tls); +namespace { - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } +constexpr size_t PKEY_HANDLE_MAX_SIZE = 32; - 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; - } +} // namespace - tl::fetcher tlf{response_bytes}; - tl::Maybe response; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; +Optional f$openssl_pkey_get_public(const string& public_key) noexcept { + 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; } - co_return string{(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; -} - -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); + buffer.shrink(static_cast(*written)); - auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)}; - if (!expected_stream) [[unlikely]] { - co_return false; - } + return buffer; +} - 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 { + 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; } - 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(*written)); - co_return string{(*response.opt_value).value.data(), static_cast((*response.opt_value).value.size())}; + return buffer; } -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; +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()}, RsaPadding::PKCS1)}; + if (!size) [[unlikely]] { + kphp::log::warning("Failed to encrypt data with the given public key"); + 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}; + 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; } - tl::fetcher tlf{response_bytes}; - tl::Maybe response{}; - kphp::log::assertion(response.fetch(tlf)); - if (!response.opt_value) { - co_return false; - } + 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()}, RsaPadding::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; -} -namespace { + decrypted_data = mixed{}; -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; })}; - - return it != nullptr && it != HASH_ALGOS.end() ? std::optional{it->second} : std::nullopt; + return false; } -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; - } +namespace { - 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; +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 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; +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; +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; + HashAlgorithm algo; +}; + +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}}, + 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; +}(); + +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; } - tl::fetcher tlf{response_bytes}; - tl::String response{}; - kphp::log::assertion(response.fetch(tlf)); + return std::nullopt; +} - if (!raw_output) { - co_return kphp::strings::bin2hex(response.inner.value); +string hash_impl(hash_algorithm_traits traits, const string& data, bool binary) noexcept { + 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 {}; + } + + if (!binary) { + kphp::strings::bin2hex(hashed_data.buffer(), traits.hashed_size, hashed_data.size()); } - // 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 hashed_data; } } // 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{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 {}; + } + + if (!binary) { + kphp::strings::bin2hex(hashed_data.buffer(), algo->hashed_size, hashed_data.size()); } - 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 hashed_data; } -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(hash_algorithm_traits{SHA1_HASHED_SIZE, 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 d17f920338..5387cd01fe 100644 --- a/runtime-light/stdlib/crypto/crypto-functions.h +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -10,45 +10,44 @@ #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" -#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; +Optional> f$openssl_x509_parse(const 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; -Optional f$openssl_cipher_iv_length(const string& method) noexcept; +Optional f$openssl_cipher_iv_length(const string& algorithm) 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; -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_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_pkey_get_public(string key) noexcept; +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_private(string key, string passphrase = string{}) noexcept; +Optional f$openssl_pkey_get_public(const string& public_key) noexcept; -kphp::coro::task f$openssl_public_encrypt(string data, mixed& result, string key) noexcept; +Optional f$openssl_pkey_get_private(const string& private_key, const string& passphrase = string{}) noexcept; -kphp::coro::task f$openssl_private_decrypt(string data, mixed& result, string key) noexcept; +bool f$openssl_public_encrypt(const string& data, mixed& encrypted_data, const string& public_key) noexcept; + +bool f$openssl_private_decrypt(const string& data, mixed& decrypted_data, const string& private_key) noexcept; 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; @@ -61,6 +60,7 @@ inline string f$md5(const string& str, bool binary = false) noexcept { output[2 * i] = lhex_digits[(output[i] >> 4) & 0x0F]; } } + return output; } 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 = <<