/* * Copyright (c) 2026 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "x25519_x448.h" #include "exdsa_ecdhkem.h" #include "hkdf.hpp" #include "utils.h" #include "logging.h" #include "ossl_utils.hpp" #include #include #include static const std::vector hkdf_x25519_info_str = { 'O', 'p', 'e', 'n', 'P', 'G', 'P', ' ', 'X', '2', '5', '5', '1', '9'}; #if defined(ENABLE_CRYPTO_REFRESH) static const std::vector hkdf_x448_info_str = { 'O', 'p', 'e', 'n', 'P', 'G', 'P', ' ', 'X', '4', '4', '8'}; #endif static void x_hkdf(std::vector & derived_key, const std::vector &ephemeral_pubkey_material, const std::vector &recipient_pubkey_material, const std::vector &shared_key, const std::vector &info_str) { auto kdf = rnp::Hkdf::create(PGP_HASH_SHA256); derived_key.resize(pgp_key_size(PGP_SA_AES_128)); // 128-bit AES key wrap std::vector kdf_input; kdf_input.insert(kdf_input.end(), std::begin(ephemeral_pubkey_material), std::end(ephemeral_pubkey_material)); kdf_input.insert(kdf_input.end(), std::begin(recipient_pubkey_material), std::end(recipient_pubkey_material)); kdf_input.insert(kdf_input.end(), std::begin(shared_key), std::end(shared_key)); kdf->extract_expand(NULL, 0, // no salt kdf_input.data(), kdf_input.size(), info_str.data(), info_str.size(), derived_key.data(), derived_key.size()); } static rnp_result_t rfc3394_wrap(std::vector & out, const uint8_t * in, size_t in_len, const std::vector &kek) { const EVP_CIPHER *cipher = EVP_get_cipherbyname("aes128-wrap"); if (!cipher) { RNP_LOG("aes128-wrap cipher not available"); return RNP_ERROR_NOT_SUPPORTED; } rnp::ossl::evp::CipherCtx ctx(EVP_CIPHER_CTX_new()); if (!ctx) { return RNP_ERROR_OUT_OF_MEMORY; } EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); if (EVP_EncryptInit_ex(ctx.get(), cipher, NULL, kek.data(), NULL) <= 0) { RNP_LOG("Failed to init wrap: %lu", ERR_peek_last_error()); return RNP_ERROR_GENERIC; } out.resize(in_len + 8); int outl = (int) out.size(); if (EVP_EncryptUpdate(ctx.get(), out.data(), &outl, in, (int) in_len) <= 0) { RNP_LOG("Failed to wrap: %lu", ERR_peek_last_error()); return RNP_ERROR_GENERIC; } out.resize(outl); return RNP_SUCCESS; } static rnp_result_t rfc3394_unwrap(std::vector & out, const std::vector &in, const std::vector &kek) { if ((in.size() < 16) || (in.size() % 8)) { RNP_LOG("Invalid wrapped key size."); return RNP_ERROR_GENERIC; } const EVP_CIPHER *cipher = EVP_get_cipherbyname("aes128-wrap"); if (!cipher) { RNP_LOG("aes128-wrap cipher not available"); return RNP_ERROR_NOT_SUPPORTED; } rnp::ossl::evp::CipherCtx ctx(EVP_CIPHER_CTX_new()); if (!ctx) { return RNP_ERROR_OUT_OF_MEMORY; } EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); if (EVP_DecryptInit_ex(ctx.get(), cipher, NULL, kek.data(), NULL) <= 0) { RNP_LOG("Failed to init unwrap: %lu", ERR_peek_last_error()); return RNP_ERROR_GENERIC; } out.resize(in.size() - 8); int outl = (int) out.size(); if (EVP_DecryptUpdate(ctx.get(), out.data(), &outl, in.data(), (int) in.size()) <= 0) { RNP_LOG("Failed to unwrap: %lu", ERR_peek_last_error()); return RNP_ERROR_GENERIC; } out.resize(outl); return RNP_SUCCESS; } rnp_result_t generate_x25519_native(rnp::RNG * rng, std::vector &privkey, std::vector &pubkey) { rnp::ossl::evp::PKeyCtx ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL)); if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) { RNP_LOG("Failed to init X25519 keygen: %lu", ERR_peek_last_error()); return RNP_ERROR_KEY_GENERATION; } EVP_PKEY *rawkey = NULL; if (EVP_PKEY_keygen(ctx.get(), &rawkey) <= 0) { RNP_LOG("X25519 keygen failed: %lu", ERR_peek_last_error()); return RNP_ERROR_KEY_GENERATION; } rnp::ossl::evp::PKey pkey(rawkey); privkey.resize(32); size_t privlen = 32; pubkey.resize(32); size_t publen = 32; if (EVP_PKEY_get_raw_private_key(pkey.get(), privkey.data(), &privlen) <= 0 || EVP_PKEY_get_raw_public_key(pkey.get(), pubkey.data(), &publen) <= 0) { RNP_LOG("Failed to extract X25519 key: %lu", ERR_peek_last_error()); return RNP_ERROR_KEY_GENERATION; } return RNP_SUCCESS; } rnp_result_t x25519_native_encrypt(rnp::RNG * rng, const std::vector &pubkey, const uint8_t * in, size_t in_len, pgp_x25519_encrypted_t * encrypted) { rnp_result_t ret; std::vector shared_key; std::vector derived_key; if (!in_len || (in_len % 8) != 0) { RNP_LOG("incorrect size of in, AES key wrap requires a multiple of 8 bytes"); return RNP_ERROR_BAD_FORMAT; } /* encapsulation */ ecdh_kem_public_key_t ecdhkem_pubkey(pubkey, PGP_CURVE_25519); ret = ecdhkem_pubkey.encapsulate(rng, encrypted->eph_key, shared_key); if (ret != RNP_SUCCESS) { RNP_LOG("encapsulation failed"); return ret; } x_hkdf(derived_key, encrypted->eph_key, pubkey, shared_key, hkdf_x25519_info_str); ret = rfc3394_wrap(encrypted->enc_sess_key, in, in_len, derived_key); if (ret != RNP_SUCCESS) { RNP_LOG("Keywrap failed"); } return ret; } rnp_result_t x25519_native_decrypt(rnp::RNG * rng, const pgp_x25519_key_t & keypair, const pgp_x25519_encrypted_t *encrypted, uint8_t * decbuf, size_t * decbuf_len) { rnp_result_t ret; std::vector shared_key; std::vector derived_key; static const size_t x25519_pubkey_size = 32; if (encrypted->eph_key.size() != x25519_pubkey_size) { RNP_LOG("Wrong ephemeral public key size"); return RNP_ERROR_BAD_FORMAT; } if (!encrypted->enc_sess_key.size()) { RNP_LOG("No encrypted session key provided"); return RNP_ERROR_BAD_FORMAT; } /* decapsulate */ ecdh_kem_private_key_t ecdhkem_privkey(keypair.priv, PGP_CURVE_25519); ret = ecdhkem_privkey.decapsulate(rng, encrypted->eph_key, shared_key); if (ret != RNP_SUCCESS) { RNP_LOG("decapsulation failed"); return ret; } x_hkdf(derived_key, encrypted->eph_key, keypair.pub, shared_key, hkdf_x25519_info_str); std::vector tmp_out; ret = rfc3394_unwrap(tmp_out, encrypted->enc_sess_key, derived_key); if (ret != RNP_SUCCESS) { RNP_LOG("Keyunwrap failed"); return ret; } if (*decbuf_len < tmp_out.size()) { RNP_LOG("buffer for decryption result too small"); return RNP_ERROR_DECRYPT_FAILED; } *decbuf_len = tmp_out.size(); memcpy(decbuf, tmp_out.data(), tmp_out.size()); return RNP_SUCCESS; } rnp_result_t x25519_validate_key_native(rnp::RNG *rng, const pgp_x25519_key_t *key, bool secret) { /* mirror Botan version: use priv field for the public key check */ rnp::ossl::evp::PKey pub( EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, key->priv.data(), key->priv.size())); if (!pub) { return RNP_ERROR_BAD_PARAMETERS; } if (secret) { rnp::ossl::evp::PKey priv(EVP_PKEY_new_raw_private_key( EVP_PKEY_X25519, NULL, key->priv.data(), key->priv.size())); if (!priv) { return RNP_ERROR_BAD_PARAMETERS; } } return RNP_SUCCESS; } #if defined(ENABLE_CRYPTO_REFRESH) rnp_result_t generate_x448_native(rnp::RNG * rng, std::vector &privkey, std::vector &pubkey) { rnp::ossl::evp::PKeyCtx ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_X448, NULL)); if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) { RNP_LOG("Failed to init X448 keygen: %lu", ERR_peek_last_error()); return RNP_ERROR_KEY_GENERATION; } EVP_PKEY *rawkey = NULL; if (EVP_PKEY_keygen(ctx.get(), &rawkey) <= 0) { RNP_LOG("X448 keygen failed: %lu", ERR_peek_last_error()); return RNP_ERROR_KEY_GENERATION; } rnp::ossl::evp::PKey pkey(rawkey); privkey.resize(56); size_t privlen = 56; pubkey.resize(56); size_t publen = 56; if (EVP_PKEY_get_raw_private_key(pkey.get(), privkey.data(), &privlen) <= 0 || EVP_PKEY_get_raw_public_key(pkey.get(), pubkey.data(), &publen) <= 0) { RNP_LOG("Failed to extract X448 key: %lu", ERR_peek_last_error()); return RNP_ERROR_KEY_GENERATION; } return RNP_SUCCESS; } rnp_result_t x448_native_encrypt(rnp::RNG * rng, const std::vector &pubkey, const uint8_t * in, size_t in_len, pgp_x448_encrypted_t * encrypted) { rnp_result_t ret; std::vector shared_key; std::vector derived_key; if (!in_len || (in_len % 8) != 0) { RNP_LOG("incorrect size of in, AES key wrap requires a multiple of 8 bytes"); return RNP_ERROR_BAD_FORMAT; } /* encapsulation */ ecdh_kem_public_key_t ecdhkem_pubkey(pubkey, PGP_CURVE_448); ret = ecdhkem_pubkey.encapsulate(rng, encrypted->eph_key, shared_key); if (ret != RNP_SUCCESS) { RNP_LOG("encapsulation failed"); return ret; } x_hkdf(derived_key, encrypted->eph_key, pubkey, shared_key, hkdf_x448_info_str); ret = rfc3394_wrap(encrypted->enc_sess_key, in, in_len, derived_key); if (ret != RNP_SUCCESS) { RNP_LOG("Keywrap failed"); } return ret; } rnp_result_t x448_native_decrypt(rnp::RNG * rng, const pgp_x448_key_t & keypair, const pgp_x448_encrypted_t *encrypted, uint8_t * decbuf, size_t * decbuf_len) { rnp_result_t ret; std::vector shared_key; std::vector derived_key; static const size_t x448_pubkey_size = 56; if (encrypted->eph_key.size() != x448_pubkey_size) { RNP_LOG("Wrong ephemeral public key size"); return RNP_ERROR_BAD_FORMAT; } if (!encrypted->enc_sess_key.size()) { RNP_LOG("No encrypted session key provided"); return RNP_ERROR_BAD_FORMAT; } /* decapsulate */ ecdh_kem_private_key_t ecdhkem_privkey(keypair.priv, PGP_CURVE_448); ret = ecdhkem_privkey.decapsulate(rng, encrypted->eph_key, shared_key); if (ret != RNP_SUCCESS) { RNP_LOG("decapsulation failed"); return ret; } x_hkdf(derived_key, encrypted->eph_key, keypair.pub, shared_key, hkdf_x448_info_str); std::vector tmp_out; ret = rfc3394_unwrap(tmp_out, encrypted->enc_sess_key, derived_key); if (ret != RNP_SUCCESS) { RNP_LOG("Keyunwrap failed"); return ret; } if (*decbuf_len < tmp_out.size()) { RNP_LOG("buffer for decryption result too small"); return RNP_ERROR_DECRYPT_FAILED; } *decbuf_len = tmp_out.size(); memcpy(decbuf, tmp_out.data(), tmp_out.size()); return RNP_SUCCESS; } rnp_result_t x448_validate_key_native(rnp::RNG *rng, const pgp_x448_key_t *key, bool secret) { /* mirror Botan version: use priv field for the public key check */ rnp::ossl::evp::PKey pub( EVP_PKEY_new_raw_public_key(EVP_PKEY_X448, NULL, key->priv.data(), key->priv.size())); if (!pub) { return RNP_ERROR_BAD_PARAMETERS; } if (secret) { rnp::ossl::evp::PKey priv(EVP_PKEY_new_raw_private_key( EVP_PKEY_X448, NULL, key->priv.data(), key->priv.size())); if (!priv) { return RNP_ERROR_BAD_PARAMETERS; } } return RNP_SUCCESS; } #endif