diff options
Diffstat (limited to 'src/lib/crypto/elgamal.cpp')
-rw-r--r-- | src/lib/crypto/elgamal.cpp | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/src/lib/crypto/elgamal.cpp b/src/lib/crypto/elgamal.cpp new file mode 100644 index 0000000..acebf4d --- /dev/null +++ b/src/lib/crypto/elgamal.cpp @@ -0,0 +1,302 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * 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 HOLDERS 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 <stdlib.h> +#include <string.h> +#include <botan/ffi.h> +#include <botan/bigint.h> +#include <botan/numthry.h> +#include <botan/reducer.h> +#include <rnp/rnp_def.h> +#include "elgamal.h" +#include "utils.h" +#include "bn.h" + +// Max supported key byte size +#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS) + +static bool +elgamal_load_public_key(botan_pubkey_t *pubkey, const pgp_eg_key_t *keydata) +{ + bignum_t *p = NULL; + bignum_t *g = NULL; + bignum_t *y = NULL; + bool res = false; + + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) { + goto done; + } + + if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) || + !(y = mpi2bn(&keydata->y))) { + goto done; + } + + res = + !botan_pubkey_load_elgamal(pubkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y)); +done: + bn_free(p); + bn_free(g); + bn_free(y); + return res; +} + +static bool +elgamal_load_secret_key(botan_privkey_t *seckey, const pgp_eg_key_t *keydata) +{ + bignum_t *p = NULL; + bignum_t *g = NULL; + bignum_t *x = NULL; + bool res = false; + + // Check if provided secret key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) { + goto done; + } + + if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) || + !(x = mpi2bn(&keydata->x))) { + goto done; + } + + res = !botan_privkey_load_elgamal( + seckey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x)); +done: + bn_free(p); + bn_free(g); + bn_free(x); + return res; +} + +bool +elgamal_validate_key(const pgp_eg_key_t *key, bool secret) +{ + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&key->p) > ELGAMAL_MAX_P_BYTELEN) { + return false; + } + + /* Use custom validation since we added some custom validation, and Botan has slow test for + * prime for p */ + try { + Botan::BigInt p(key->p.mpi, key->p.len); + Botan::BigInt g(key->g.mpi, key->g.len); + + /* 1 < g < p */ + if ((g.cmp_word(1) != 1) || (g.cmp(p) != -1)) { + return false; + } + /* g ^ (p - 1) = 1 mod p */ + if (Botan::power_mod(g, p - 1, p).cmp_word(1)) { + return false; + } + /* check for small order subgroups */ + Botan::Modular_Reducer reducer(p); + Botan::BigInt v = g; + for (size_t i = 2; i < (1 << 17); i++) { + v = reducer.multiply(v, g); + if (!v.cmp_word(1)) { + RNP_LOG("Small subgroup detected. Order %zu", i); + return false; + } + } + if (!secret) { + return true; + } + /* check that g ^ x = y (mod p) */ + Botan::BigInt y(key->y.mpi, key->y.len); + Botan::BigInt x(key->x.mpi, key->x.len); + return Botan::power_mod(g, x, p) == y; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +rnp_result_t +elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key) +{ + botan_pubkey_t b_key = NULL; + botan_pk_op_encrypt_t op_ctx = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + /* Max size of an output len is twice an order of underlying group (p length) */ + uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0}; + size_t p_len; + + if (!elgamal_load_public_key(&b_key, key)) { + RNP_LOG("Failed to load public key"); + goto end; + } + + /* Size of output buffer must be equal to twice the size of key byte len. + * as ElGamal encryption outputs concatenation of two components, both + * of size equal to size of public key byte len. + * Successful call to botan's ElGamal encryption will return output that's + * always 2*pubkey size. + */ + p_len = mpi_bytes(&key->p) * 2; + + if (botan_pk_op_encrypt_create(&op_ctx, b_key, "PKCS1v15", 0) || + botan_pk_op_encrypt(op_ctx, rng->handle(), enc_buf, &p_len, in, in_len)) { + RNP_LOG("Failed to create operation context"); + goto end; + } + + /* + * Botan's ElGamal formats the g^k and msg*(y^k) together into a single byte string. + * We have to parse out the two values after encryption, as rnp stores those values + * separatelly. + * + * We don't trim zeros from octet string as it is done before final marshalling + * (add_packet_body_mpi) + * + * We must assume that botan copies even number of bytes to output buffer (to avoid + * memory corruption) + */ + p_len /= 2; + if (mem2mpi(&out->g, enc_buf, p_len) && mem2mpi(&out->m, enc_buf + p_len, p_len)) { + ret = RNP_SUCCESS; + } +end: + botan_pk_op_encrypt_destroy(op_ctx); + botan_pubkey_destroy(b_key); + return ret; +} + +rnp_result_t +elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key) +{ + botan_privkey_t b_key = NULL; + botan_pk_op_decrypt_t op_ctx = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0}; + size_t p_len; + size_t g_len; + size_t m_len; + + if (!mpi_bytes(&key->x)) { + RNP_LOG("empty secret key"); + goto end; + } + + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + p_len = mpi_bytes(&key->p); + g_len = mpi_bytes(&in->g); + m_len = mpi_bytes(&in->m); + + if ((2 * p_len > sizeof(enc_buf)) || (g_len > p_len) || (m_len > p_len)) { + RNP_LOG("Unsupported/wrong public key or encrypted data"); + goto end; + } + + if (!elgamal_load_secret_key(&b_key, key)) { + RNP_LOG("Failed to load private key"); + goto end; + } + + /* Botan expects ciphertext to be concatenated (g^k | encrypted m). Size must + * be equal to twice the byte size of public key, potentially prepended with zeros. + */ + memcpy(&enc_buf[p_len - g_len], in->g.mpi, g_len); + memcpy(&enc_buf[2 * p_len - m_len], in->m.mpi, m_len); + + *out_len = p_len; + if (botan_pk_op_decrypt_create(&op_ctx, b_key, "PKCS1v15", 0) || + botan_pk_op_decrypt(op_ctx, out, out_len, enc_buf, 2 * p_len)) { + RNP_LOG("Decryption failed"); + goto end; + } + ret = RNP_SUCCESS; +end: + botan_pk_op_decrypt_destroy(op_ctx); + botan_privkey_destroy(b_key); + return ret; +} + +rnp_result_t +elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits) +{ + if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t key_priv = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * p = bn_new(); + bignum_t * g = bn_new(); + bignum_t * y = bn_new(); + bignum_t * x = bn_new(); + + if (!p || !g || !y || !x) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + +start: + if (botan_privkey_create_elgamal(&key_priv, rng->handle(), keybits, keybits - 1)) { + RNP_LOG("Wrong parameters"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y")) { + RNP_LOG("Failed to obtain public key"); + goto end; + } + if (bn_num_bytes(*y) < BITS_TO_BYTES(keybits)) { + botan_privkey_destroy(key_priv); + goto start; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(p), key_priv, "p") || + botan_privkey_get_field(BN_HANDLE_PTR(g), key_priv, "g") || + botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y") || + botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) { + RNP_LOG("Botan FFI call failed"); + ret = RNP_ERROR_GENERIC; + goto end; + } + + if (bn2mpi(p, &key->p) && bn2mpi(g, &key->g) && bn2mpi(y, &key->y) && bn2mpi(x, &key->x)) { + ret = RNP_SUCCESS; + } +end: + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(key_priv); + return ret; +} |