Source code

Revision control

Copy as Markdown

Other Tools

/*-
* 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;
}