410 lines
7.9 KiB
C
410 lines
7.9 KiB
C
/* $OpenBSD: csi_dh.c,v 1.4 2022/10/04 20:46:13 tb Exp $ */
|
|
/*
|
|
* Copyright (c) 2000, 2001, 2015 Markus Friedl <markus@openbsd.org>
|
|
* Copyright (c) 2006, 2016 Damien Miller <djm@openbsd.org>
|
|
* Copyright (c) 2018 Joel Sing <jsing@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
|
|
#include <openssl/ec.h>
|
|
#include <openssl/ecdh.h>
|
|
|
|
#include <csi.h>
|
|
|
|
#include "csi_internal.h"
|
|
|
|
struct csi_dh *
|
|
csi_dh_new()
|
|
{
|
|
return calloc(1, sizeof(struct csi_dh));
|
|
}
|
|
|
|
static void
|
|
csi_dh_reset(struct csi_dh *cdh)
|
|
{
|
|
DH_free(cdh->dh);
|
|
cdh->dh = NULL;
|
|
|
|
BN_free(cdh->peer_pubkey);
|
|
cdh->peer_pubkey = NULL;
|
|
|
|
csi_err_clear(&cdh->err);
|
|
}
|
|
|
|
void
|
|
csi_dh_free(struct csi_dh *cdh)
|
|
{
|
|
if (cdh == NULL)
|
|
return;
|
|
|
|
csi_dh_reset(cdh);
|
|
|
|
freezero(cdh, sizeof(*cdh));
|
|
}
|
|
|
|
const char *
|
|
csi_dh_error(struct csi_dh *cdh)
|
|
{
|
|
return cdh->err.msg;
|
|
}
|
|
|
|
int
|
|
csi_dh_error_code(struct csi_dh *cdh)
|
|
{
|
|
return cdh->err.code;
|
|
}
|
|
|
|
static int
|
|
csi_dh_init(struct csi_dh *cdh)
|
|
{
|
|
csi_dh_reset(cdh);
|
|
|
|
if ((cdh->dh = DH_new()) == NULL) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_MEM, "out of memory");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct csi_dh_params *
|
|
csi_dh_params_dup(struct csi_dh_params *cdhp)
|
|
{
|
|
struct csi_dh_params *ncdhp = NULL;
|
|
|
|
if ((ncdhp = calloc(1, sizeof(*ncdhp))) == NULL)
|
|
goto err;
|
|
|
|
if ((ncdhp->p.data = malloc(cdhp->p.len)) == NULL)
|
|
goto err;
|
|
ncdhp->p.len = cdhp->p.len;
|
|
memcpy((uint8_t *)ncdhp->p.data, cdhp->p.data, cdhp->p.len);
|
|
|
|
if ((ncdhp->g.data = malloc(cdhp->g.len)) == NULL)
|
|
goto err;
|
|
ncdhp->g.len = cdhp->g.len;
|
|
memcpy((uint8_t *)ncdhp->g.data, cdhp->g.data, cdhp->g.len);
|
|
|
|
return ncdhp;
|
|
|
|
err:
|
|
csi_dh_params_free(ncdhp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
csi_dh_params_free(struct csi_dh_params *cdhp)
|
|
{
|
|
if (cdhp == NULL)
|
|
return;
|
|
|
|
free((uint8_t *)cdhp->p.data);
|
|
free((uint8_t *)cdhp->g.data);
|
|
free(cdhp);
|
|
}
|
|
|
|
void
|
|
csi_dh_public_free(struct csi_dh_public *cdhp)
|
|
{
|
|
if (cdhp == NULL)
|
|
return;
|
|
|
|
free((uint8_t *)cdhp->key.data);
|
|
free(cdhp);
|
|
}
|
|
|
|
void
|
|
csi_dh_shared_free(struct csi_dh_shared *cdhs)
|
|
{
|
|
if (cdhs == NULL)
|
|
return;
|
|
|
|
freezero((uint8_t *)cdhs->key.data, cdhs->key.len);
|
|
freezero(cdhs, sizeof(*cdhs));
|
|
}
|
|
|
|
int
|
|
csi_dh_set_params(struct csi_dh *cdh, struct csi_dh_params *params)
|
|
{
|
|
BIGNUM *p = NULL, *g = NULL;
|
|
|
|
if (csi_dh_init(cdh) == -1)
|
|
goto err;
|
|
|
|
if (csi_integer_to_bn(&cdh->err, "p", ¶ms->p, &p) == -1)
|
|
goto err;
|
|
if (csi_integer_to_bn(&cdh->err, "g", ¶ms->g, &g) == -1)
|
|
goto err;
|
|
if (!DH_set0_pqg(cdh->dh, p, NULL, g))
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
BN_free(p);
|
|
BN_free(g);
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
csi_dh_public_is_valid(struct csi_dh *cdh, const BIGNUM *pubkey)
|
|
{
|
|
BIGNUM *tmp = NULL;
|
|
int bits_set = 0;
|
|
int rv = 0;
|
|
int i;
|
|
|
|
if ((tmp = BN_new()) == NULL) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_MEM, "out of memory");
|
|
goto bad;
|
|
}
|
|
|
|
if (BN_is_negative(pubkey)) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL,
|
|
"invalid DH public key value (negative)");
|
|
goto bad;
|
|
}
|
|
|
|
if (BN_cmp(pubkey, BN_value_one()) != 1) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL,
|
|
"invalid DH public key value (<= 1)");
|
|
goto bad;
|
|
}
|
|
|
|
if (!BN_sub(tmp, DH_get0_p(cdh->dh), BN_value_one()) ||
|
|
BN_cmp(pubkey, tmp) != -1) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL,
|
|
"invalid DH public key value (>= p-1)");
|
|
goto bad;
|
|
}
|
|
|
|
/*
|
|
* If g == 2 and bits_set == 1, then computing log_g(pubkey) is trivial.
|
|
*/
|
|
for (i = 0; i <= BN_num_bits(pubkey); i++) {
|
|
if (BN_is_bit_set(pubkey, i))
|
|
bits_set++;
|
|
}
|
|
if (bits_set < 4) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL,
|
|
"invalid DH public key value (%d/%d bits)",
|
|
bits_set, BN_num_bits(DH_get0_p(cdh->dh)));
|
|
goto bad;
|
|
}
|
|
|
|
rv = 1;
|
|
|
|
bad:
|
|
BN_clear_free(tmp);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
csi_dh_set_peer_public(struct csi_dh *cdh, struct csi_dh_public *peer)
|
|
{
|
|
BIGNUM *ppk = NULL;
|
|
|
|
if (cdh->dh == NULL) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL, "no params set");
|
|
goto err;
|
|
}
|
|
|
|
if (csi_integer_to_bn(&cdh->err, "key", &peer->key, &ppk) == -1)
|
|
goto err;
|
|
if (!csi_dh_public_is_valid(cdh, ppk))
|
|
goto err;
|
|
|
|
cdh->peer_pubkey = ppk;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
BN_clear_free(ppk);
|
|
|
|
return -1;
|
|
}
|
|
|
|
struct csi_dh_params *
|
|
csi_dh_params(struct csi_dh *cdh)
|
|
{
|
|
struct csi_dh_params *cdhp;
|
|
|
|
if ((cdhp = calloc(1, sizeof(*cdhp))) == NULL)
|
|
goto errmem;
|
|
if (csi_bn_to_integer(&cdh->err, DH_get0_p(cdh->dh), &cdhp->p) != 0)
|
|
goto err;
|
|
if (csi_bn_to_integer(&cdh->err, DH_get0_g(cdh->dh), &cdhp->g) != 0)
|
|
goto err;
|
|
|
|
return cdhp;
|
|
|
|
errmem:
|
|
csi_err_setx(&cdh->err, CSI_ERR_MEM, "out of memory");
|
|
err:
|
|
csi_dh_params_free(cdhp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct csi_dh_public *
|
|
csi_dh_public_key(struct csi_dh *cdh)
|
|
{
|
|
struct csi_dh_public *cdhp;
|
|
|
|
if ((cdhp = calloc(1, sizeof(*cdhp))) == NULL)
|
|
goto errmem;
|
|
if (csi_bn_to_integer(&cdh->err, DH_get0_pub_key(cdh->dh),
|
|
&cdhp->key) != 0)
|
|
goto err;
|
|
|
|
return cdhp;
|
|
|
|
errmem:
|
|
csi_err_setx(&cdh->err, CSI_ERR_MEM, "out of memory");
|
|
err:
|
|
csi_dh_public_free(cdhp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct csi_dh_public *
|
|
csi_dh_peer_public_key(struct csi_dh *cdh)
|
|
{
|
|
struct csi_dh_public *cdhp;
|
|
|
|
if ((cdhp = calloc(1, sizeof(*cdhp))) == NULL)
|
|
goto errmem;
|
|
if (csi_bn_to_integer(&cdh->err, cdh->peer_pubkey, &cdhp->key) != 0)
|
|
goto err;
|
|
|
|
return cdhp;
|
|
|
|
errmem:
|
|
csi_err_setx(&cdh->err, CSI_ERR_MEM, "out of memory");
|
|
err:
|
|
csi_dh_public_free(cdhp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
csi_dh_generate_keys(struct csi_dh *cdh, size_t length,
|
|
struct csi_dh_public **public)
|
|
{
|
|
int pbits;
|
|
|
|
if (cdh->dh == NULL) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL, "no params set");
|
|
goto err;
|
|
}
|
|
|
|
if (length > 0) {
|
|
if (length > INT_MAX / 2) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL,
|
|
"length too large");
|
|
goto err;
|
|
}
|
|
if (length < CSI_MIN_DH_LENGTH)
|
|
length = CSI_MIN_DH_LENGTH;
|
|
|
|
/*
|
|
* Pollard Rho, Big Step/Little Step attacks are O(sqrt(n)),
|
|
* so double requested length.
|
|
*/
|
|
length *= 2;
|
|
|
|
if ((pbits = BN_num_bits(DH_get0_p(cdh->dh))) <= 0) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_CRYPTO,
|
|
"invalid p bignum");
|
|
goto err;
|
|
}
|
|
if ((int)length > pbits) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL,
|
|
"length too large");
|
|
goto err;
|
|
}
|
|
|
|
if (!DH_set_length(cdh->dh, MINIMUM((int)length, pbits - 1)))
|
|
goto err;
|
|
}
|
|
|
|
if (!DH_generate_key(cdh->dh)) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_CRYPTO, "dh generation failed");
|
|
goto err;
|
|
}
|
|
|
|
if (!csi_dh_public_is_valid(cdh, DH_get0_pub_key(cdh->dh)))
|
|
goto err;
|
|
|
|
if (public != NULL) {
|
|
csi_dh_public_free(*public);
|
|
if ((*public = csi_dh_public_key(cdh)) == NULL)
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
csi_dh_derive_shared_key(struct csi_dh *cdh, struct csi_dh_shared **cdhs)
|
|
{
|
|
struct csi_dh_shared *dhs = NULL;
|
|
uint8_t *key = NULL;
|
|
size_t key_len = 0;
|
|
int len;
|
|
|
|
csi_dh_shared_free(*cdhs);
|
|
*cdhs = NULL;
|
|
|
|
if (cdh->dh == NULL) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL, "no params set");
|
|
goto err;
|
|
}
|
|
|
|
if ((len = DH_size(cdh->dh)) <= 0) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_INVAL, "invalid dh size %i", len);
|
|
goto err;
|
|
}
|
|
key_len = (size_t)len;
|
|
if ((key = calloc(1, key_len)) == NULL)
|
|
goto errmem;
|
|
if (DH_compute_key(key, cdh->peer_pubkey, cdh->dh) < 0) {
|
|
csi_err_setx(&cdh->err, CSI_ERR_CRYPTO, "failed to derive key");
|
|
goto err;
|
|
}
|
|
|
|
if ((dhs = calloc(1, sizeof(*dhs))) == NULL)
|
|
goto errmem;
|
|
dhs->key.data = key;
|
|
dhs->key.len = key_len;
|
|
|
|
*cdhs = dhs;
|
|
|
|
return 0;
|
|
|
|
errmem:
|
|
csi_err_setx(&cdh->err, CSI_ERR_MEM, "out of memory");
|
|
err:
|
|
return -1;
|
|
}
|