diff options
Diffstat (limited to 'src/lib/crypto/ecdh_ossl.cpp')
-rw-r--r-- | src/lib/crypto/ecdh_ossl.cpp | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/src/lib/crypto/ecdh_ossl.cpp b/src/lib/crypto/ecdh_ossl.cpp new file mode 100644 index 0000000..60b7260 --- /dev/null +++ b/src/lib/crypto/ecdh_ossl.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2021-2022, [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 <string> +#include <cassert> +#include "ecdh.h" +#include "ecdh_utils.h" +#include "ec_ossl.h" +#include "hash.hpp" +#include "symmetric.h" +#include "types.h" +#include "utils.h" +#include "logging.h" +#include "mem.h" +#include <openssl/evp.h> +#include <openssl/err.h> + +static const struct ecdh_wrap_alg_map_t { + pgp_symm_alg_t alg; + const char * name; +} ecdh_wrap_alg_map[] = {{PGP_SA_AES_128, "aes128-wrap"}, + {PGP_SA_AES_192, "aes192-wrap"}, + {PGP_SA_AES_256, "aes256-wrap"}}; + +rnp_result_t +ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return ec_validate_key(*key, secret); +} + +static rnp_result_t +ecdh_derive_kek(uint8_t * x, + size_t xlen, + const pgp_ec_key_t & key, + const pgp_fingerprint_t &fingerprint, + uint8_t * kek, + const size_t kek_len) +{ + const ec_curve_desc_t *curve_desc = get_curve_desc(key.curve); + if (!curve_desc) { + RNP_LOG("unsupported curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // Serialize other info, see 13.5 of RFC 4880 bis + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + const size_t hash_len = rnp::Hash::size(key.kdf_hash_alg); + if (!hash_len) { + // must not assert here as kdf/hash algs are not checked during key parsing + RNP_LOG("Unsupported key wrap hash algorithm."); + return RNP_ERROR_NOT_SUPPORTED; + } + size_t other_len = kdf_other_info_serialize( + other_info, curve_desc, fingerprint, key.kdf_hash_alg, key.key_wrap_alg); + // Self-check + assert(other_len == curve_desc->OIDhex_len + 46); + // Derive KEK, using the KDF from SP800-56A + rnp::secure_array<uint8_t, PGP_MAX_HASH_SIZE> dgst; + assert(hash_len <= PGP_MAX_HASH_SIZE); + size_t reps = (kek_len + hash_len - 1) / hash_len; + // As we use AES & SHA2 we should not get more then 2 iterations + if (reps > 2) { + RNP_LOG("Invalid key wrap/hash alg combination."); + return RNP_ERROR_NOT_SUPPORTED; + } + size_t have = 0; + try { + for (size_t i = 1; i <= reps; i++) { + auto hash = rnp::Hash::create(key.kdf_hash_alg); + hash->add(i); + hash->add(x, xlen); + hash->add(other_info, other_len); + hash->finish(dgst.data()); + size_t bytes = std::min(hash_len, kek_len - have); + memcpy(kek + have, dgst.data(), bytes); + have += bytes; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to derive kek: %s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static rnp_result_t +ecdh_rfc3394_wrap_ctx(EVP_CIPHER_CTX **ctx, + pgp_symm_alg_t wrap_alg, + const uint8_t * key, + bool decrypt) +{ + /* get OpenSSL EVP cipher for key wrap */ + const char *cipher_name = NULL; + ARRAY_LOOKUP_BY_ID(ecdh_wrap_alg_map, alg, name, wrap_alg, cipher_name); + if (!cipher_name) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name); + if (!cipher) { + RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name); + return RNP_ERROR_NOT_SUPPORTED; + } + *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Context allocation failed : %lu", ERR_peek_last_error()); + return RNP_ERROR_OUT_OF_MEMORY; + } + EVP_CIPHER_CTX_set_flags(*ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + int res = decrypt ? EVP_DecryptInit_ex(*ctx, cipher, NULL, key, NULL) : + EVP_EncryptInit_ex(*ctx, cipher, NULL, key, NULL); + if (res <= 0) { + RNP_LOG("Failed to initialize cipher : %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(*ctx); + *ctx = NULL; + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} + +static rnp_result_t +ecdh_rfc3394_wrap(uint8_t * out, + size_t * out_len, + const uint8_t *const in, + size_t in_len, + const uint8_t * key, + pgp_symm_alg_t wrap_alg) +{ + EVP_CIPHER_CTX *ctx = NULL; + rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, false); + if (ret) { + RNP_LOG("Wrap context initialization failed."); + return ret; + } + int intlen = *out_len; + /* encrypts in one pass, no final is needed */ + int res = EVP_EncryptUpdate(ctx, out, &intlen, in, in_len); + if (res <= 0) { + RNP_LOG("Failed to encrypt data : %lu", ERR_peek_last_error()); + } else { + *out_len = intlen; + } + EVP_CIPHER_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} + +static rnp_result_t +ecdh_rfc3394_unwrap(uint8_t * out, + size_t * out_len, + const uint8_t *const in, + size_t in_len, + const uint8_t * key, + pgp_symm_alg_t wrap_alg) +{ + if ((in_len < 16) || (in_len % 8)) { + RNP_LOG("Invalid wrapped key size."); + return RNP_ERROR_GENERIC; + } + EVP_CIPHER_CTX *ctx = NULL; + rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, true); + if (ret) { + RNP_LOG("Unwrap context initialization failed."); + return ret; + } + int intlen = *out_len; + /* decrypts in one pass, no final is needed */ + int res = EVP_DecryptUpdate(ctx, out, &intlen, in, in_len); + if (res <= 0) { + RNP_LOG("Failed to decrypt data : %lu", ERR_peek_last_error()); + } else { + *out_len = intlen; + } + EVP_CIPHER_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} + +static bool +ecdh_derive_secret(EVP_PKEY *sec, EVP_PKEY *peer, uint8_t *x, size_t *xlen) +{ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(sec, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + return false; + } + bool res = false; + if (EVP_PKEY_derive_init(ctx) <= 0) { + RNP_LOG("Key derivation init failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0) { + RNP_LOG("Peer setting failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_derive(ctx, x, xlen) <= 0) { + RNP_LOG("Failed to obtain shared secret size: %lu", ERR_peek_last_error()); + goto done; + } + res = true; +done: + EVP_PKEY_CTX_free(ctx); + return res; +} + +static size_t +ecdh_kek_len(pgp_symm_alg_t wrap_alg) +{ + switch (wrap_alg) { + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + return pgp_key_size(wrap_alg); + default: + return 0; + } +} + +rnp_result_t +ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint) +{ + if (!key || !out || !in || (in_len > MAX_SESSION_KEY_SIZE)) { + return RNP_ERROR_BAD_PARAMETERS; + } +#if !defined(ENABLE_SM2) + if (key->curve == PGP_CURVE_SM2_P_256) { + RNP_LOG("SM2 curve support is disabled."); + return RNP_ERROR_NOT_IMPLEMENTED; + } +#endif + /* check whether we have valid wrap_alg before doing heavy operations */ + size_t keklen = ecdh_kek_len(key->key_wrap_alg); + if (!keklen) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + /* load our public key */ + EVP_PKEY *pkey = ec_load_key(key->p, NULL, key->curve); + if (!pkey) { + RNP_LOG("Failed to load public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec; + rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad; + + size_t seclen = sec.size(); + rnp_result_t ret = RNP_ERROR_GENERIC; + /* generate ephemeral key */ + EVP_PKEY *ephkey = ec_generate_pkey(PGP_PKA_ECDH, key->curve); + if (!ephkey) { + RNP_LOG("Failed to generate ephemeral key."); + ret = RNP_ERROR_KEY_GENERATION; + goto done; + } + /* do ECDH derivation */ + if (!ecdh_derive_secret(ephkey, pkey, sec.data(), &seclen)) { + RNP_LOG("ECDH derivation failed."); + goto done; + } + /* here we got x value in sec, deriving kek */ + ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen); + if (ret) { + RNP_LOG("Failed to derive KEK."); + goto done; + } + /* add PKCS#7 padding */ + size_t m_padded_len; + m_padded_len = ((in_len / 8) + 1) * 8; + memcpy(mpad.data(), in, in_len); + if (!pad_pkcs7(mpad.data(), m_padded_len, in_len)) { + RNP_LOG("Failed to add PKCS #7 padding."); + goto done; + } + /* do RFC 3394 AES key wrap */ + static_assert(sizeof(out->m) == ECDH_WRAPPED_KEY_SIZE, "Wrong ECDH wrapped key size."); + out->mlen = ECDH_WRAPPED_KEY_SIZE; + ret = ecdh_rfc3394_wrap( + out->m, &out->mlen, mpad.data(), m_padded_len, kek.data(), key->key_wrap_alg); + if (ret) { + RNP_LOG("Failed to wrap key."); + goto done; + } + /* write ephemeral public key */ + if (!ec_write_pubkey(ephkey, out->p, key->curve)) { + RNP_LOG("Failed to write ec key."); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_free(ephkey); + EVP_PKEY_free(pkey); + return ret; +} + +rnp_result_t +ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint) +{ + if (!out || !out_len || !in || !key || !mpi_bytes(&key->x)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* check whether we have valid wrap_alg before doing heavy operations */ + size_t keklen = ecdh_kek_len(key->key_wrap_alg); + if (!keklen) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + /* load ephemeral public key */ + EVP_PKEY *ephkey = ec_load_key(in->p, NULL, key->curve); + if (!ephkey) { + RNP_LOG("Failed to load ephemeral public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + /* load our secret key */ + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec; + rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad; + + size_t seclen = sec.size(); + size_t mpadlen = mpad.size(); + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY * pkey = ec_load_key(key->p, &key->x, key->curve); + if (!pkey) { + RNP_LOG("Failed to load secret key."); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + /* do ECDH derivation */ + if (!ecdh_derive_secret(pkey, ephkey, sec.data(), &seclen)) { + RNP_LOG("ECDH derivation failed."); + goto done; + } + /* here we got x value in sec, deriving kek */ + ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen); + if (ret) { + RNP_LOG("Failed to derive KEK."); + goto done; + } + /* do RFC 3394 AES key unwrap */ + ret = ecdh_rfc3394_unwrap( + mpad.data(), &mpadlen, in->m, in->mlen, kek.data(), key->key_wrap_alg); + if (ret) { + RNP_LOG("Failed to unwrap key."); + goto done; + } + /* remove PKCS#7 padding */ + if (!unpad_pkcs7(mpad.data(), mpadlen, &mpadlen)) { + RNP_LOG("Failed to unpad key."); + goto done; + } + assert(mpadlen <= *out_len); + *out_len = mpadlen; + memcpy(out, mpad.data(), mpadlen); + ret = RNP_SUCCESS; +done: + EVP_PKEY_free(ephkey); + EVP_PKEY_free(pkey); + return ret; +} |