Add a range check (from SP800-56Ar3) to DH key derivation.

Fixes #14401

Note that this moves the public key check out of DH compute_key() since
key validation does not belong inside this primitive..
The check has been moved to the EVP_PKEY_derive_set_peer() function so that
it generally applies to all exchange operations.. Use EVP_PKEY_derive_set_peer_ex()
to disable this behaviour.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/14717)
This commit is contained in:
Shane Lontis 2021-03-29 13:38:00 +10:00
parent 9e6f30e683
commit e454a3934c
13 changed files with 135 additions and 85 deletions

View File

@ -23,6 +23,14 @@ OpenSSL 3.0
### Changes between 1.1.1 and 3.0 [xx XXX xxxx]
* A public key check is now performed during EVP_PKEY_derive_set_peer().
Previously DH was internally doing this during EVP_PKEY_derive().
To disable this check use EVP_PKEY_derive_set_peer_ex(dh, peer, 0). This
may mean that an error can occur in EVP_PKEY_derive_set_peer() rather than
during EVP_PKEY_derive().
*Shane Lontis*
* The EVP_PKEY_public_check() and EVP_PKEY_param_check() functions now work for
more key types including RSA, DSA, ED25519, X25519, ED448 and X448.
Previously (in 1.1.1) they would return -2. For key types that do not have

View File

@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@ -41,6 +41,7 @@ static const ERR_STRING_DATA DH_str_reasons[] = {
{ERR_PACK(ERR_LIB_DH, 0, DH_R_INVALID_PARAMETER_NID),
"invalid parameter nid"},
{ERR_PACK(ERR_LIB_DH, 0, DH_R_INVALID_PUBKEY), "invalid public key"},
{ERR_PACK(ERR_LIB_DH, 0, DH_R_INVALID_SECRET), "invalid secret"},
{ERR_PACK(ERR_LIB_DH, 0, DH_R_KDF_PARAMETER_ERROR), "kdf parameter error"},
{ERR_PACK(ERR_LIB_DH, 0, DH_R_KEYS_NOT_SET), "keys not set"},
{ERR_PACK(ERR_LIB_DH, 0, DH_R_MISSING_PUBKEY), "missing pubkey"},

View File

@ -33,15 +33,16 @@ static int dh_bn_mod_exp(const DH *dh, BIGNUM *r,
static int dh_init(DH *dh);
static int dh_finish(DH *dh);
static int compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh)
/*
* See SP800-56Ar3 Section 5.7.1.1
* Finite Field Cryptography Diffie-Hellman (FFC DH) Primitive
*/
int ossl_dh_compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh)
{
BN_CTX *ctx = NULL;
BN_MONT_CTX *mont = NULL;
BIGNUM *tmp;
BIGNUM *z = NULL, *pminus1;
int ret = -1;
#ifndef FIPS_MODULE
int check_result;
#endif
if (BN_num_bits(dh->params.p) > OPENSSL_DH_MAX_MODULUS_BITS) {
ERR_raise(ERR_LIB_DH, DH_R_MODULUS_TOO_LARGE);
@ -57,8 +58,9 @@ static int compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh)
if (ctx == NULL)
goto err;
BN_CTX_start(ctx);
tmp = BN_CTX_get(ctx);
if (tmp == NULL)
pminus1 = BN_CTX_get(ctx);
z = BN_CTX_get(ctx);
if (z == NULL)
goto err;
if (dh->priv_key == NULL) {
@ -73,22 +75,27 @@ static int compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh)
if (!mont)
goto err;
}
/* TODO(3.0) : Solve in a PR related to Key validation for DH */
#ifndef FIPS_MODULE
if (!DH_check_pub_key(dh, pub_key, &check_result) || check_result) {
ERR_raise(ERR_LIB_DH, DH_R_INVALID_PUBKEY);
goto err;
}
#endif
if (!dh->meth->bn_mod_exp(dh, tmp, pub_key, dh->priv_key, dh->params.p, ctx,
/* (Step 1) Z = pub_key^priv_key mod p */
if (!dh->meth->bn_mod_exp(dh, z, pub_key, dh->priv_key, dh->params.p, ctx,
mont)) {
ERR_raise(ERR_LIB_DH, ERR_R_BN_LIB);
goto err;
}
/* (Step 2) Error if z <= 1 or z = p - 1 */
if (BN_copy(pminus1, dh->params.p) == NULL
|| !BN_sub_word(pminus1, 1)
|| BN_cmp(z, BN_value_one()) <= 0
|| BN_cmp(z, pminus1) == 0) {
ERR_raise(ERR_LIB_DH, DH_R_INVALID_SECRET);
goto err;
}
/* return the padded key, i.e. same number of bytes as the modulus */
ret = BN_bn2binpad(tmp, key, BN_num_bytes(dh->params.p));
ret = BN_bn2binpad(z, key, BN_num_bytes(dh->params.p));
err:
BN_clear(z); /* (Step 2) destroy intermediate values */
BN_CTX_end(ctx);
BN_CTX_free(ctx);
return ret;
@ -105,7 +112,7 @@ int DH_compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh)
/* compute the key; ret is constant unless compute_key is external */
#ifdef FIPS_MODULE
ret = compute_key(key, pub_key, dh);
ret = ossl_dh_compute_key(key, pub_key, dh);
#else
ret = dh->meth->compute_key(key, pub_key, dh);
#endif
@ -134,7 +141,7 @@ int DH_compute_key_padded(unsigned char *key, const BIGNUM *pub_key, DH *dh)
/* rv is constant unless compute_key is external */
#ifdef FIPS_MODULE
rv = compute_key(key, pub_key, dh);
rv = ossl_dh_compute_key(key, pub_key, dh);
#else
rv = dh->meth->compute_key(key, pub_key, dh);
#endif
@ -152,7 +159,7 @@ int DH_compute_key_padded(unsigned char *key, const BIGNUM *pub_key, DH *dh)
static DH_METHOD dh_ossl = {
"OpenSSL DH Method",
generate_key,
compute_key,
ossl_dh_compute_key,
dh_bn_mod_exp,
dh_init,
dh_finish,

View File

@ -477,6 +477,7 @@ DH_R_DECODE_ERROR:104:decode error
DH_R_INVALID_PARAMETER_NAME:110:invalid parameter name
DH_R_INVALID_PARAMETER_NID:114:invalid parameter nid
DH_R_INVALID_PUBKEY:102:invalid public key
DH_R_INVALID_SECRET:128:invalid secret
DH_R_KDF_PARAMETER_ERROR:112:kdf parameter error
DH_R_KEYS_NOT_SET:108:keys not set
DH_R_MISSING_PUBKEY:125:missing pubkey

View File

@ -317,10 +317,12 @@ int EVP_PKEY_derive_init_ex(EVP_PKEY_CTX *ctx, const OSSL_PARAM params[])
#endif
}
int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer)
int EVP_PKEY_derive_set_peer_ex(EVP_PKEY_CTX *ctx, EVP_PKEY *peer,
int validate_peer)
{
int ret = 0;
int ret = 0, check;
void *provkey = NULL;
EVP_PKEY_CTX *check_ctx = NULL;
if (ctx == NULL) {
ERR_raise(ERR_LIB_EVP, ERR_R_PASSED_NULL_PARAMETER);
@ -335,6 +337,16 @@ int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer)
return -2;
}
if (validate_peer) {
check_ctx = EVP_PKEY_CTX_new_from_pkey(ctx->libctx, peer, ctx->propquery);
if (check_ctx == NULL)
return -1;
check = EVP_PKEY_public_check(check_ctx);
EVP_PKEY_CTX_free(check_ctx);
if (check <= 0)
return -1;
}
provkey = evp_pkey_export_to_provider(peer, ctx->libctx, &ctx->keymgmt,
ctx->propquery);
/*
@ -410,6 +422,11 @@ int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer)
#endif
}
int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer)
{
return EVP_PKEY_derive_set_peer_ex(ctx, peer, 1);
}
int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *pkeylen)
{
int ret;

View File

@ -3,7 +3,7 @@
=head1 NAME
EVP_PKEY_derive_init, EVP_PKEY_derive_init_ex,
EVP_PKEY_derive_set_peer, EVP_PKEY_derive
EVP_PKEY_derive_set_peer_ex, EVP_PKEY_derive_set_peer, EVP_PKEY_derive
- derive public key algorithm shared secret
=head1 SYNOPSIS
@ -12,6 +12,8 @@ EVP_PKEY_derive_set_peer, EVP_PKEY_derive
int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_derive_init_ex(EVP_PKEY_CTX *ctx, const OSSL_PARAM params[]);
int EVP_PKEY_derive_set_peer_ex(EVP_PKEY_CTX *ctx, EVP_PKEY *peer,
int validate_peer);
int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer);
int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen);
@ -26,8 +28,12 @@ more information about implicit fetches.
EVP_PKEY_derive_init_ex() is the same as EVP_PKEY_derive_init() but additionally
sets the passed parameters I<params> on the context before returning.
EVP_PKEY_derive_set_peer() sets the peer key: this will normally
be a public key.
EVP_PKEY_derive_set_peer_ex() sets the peer key: this will normally
be a public key. The I<validate_peer> will validate the public key if this value
is non zero.
EVP_PKEY_derive_set_peer() is similiar to EVP_PKEY_derive_set_peer_ex() with
I<validate_peer> set to 1.
EVP_PKEY_derive() derives a shared secret using I<ctx>.
If I<key> is NULL then the maximum size of the output buffer is written to the
@ -103,11 +109,12 @@ L<EVP_KEYEXCH_fetch(3)>
The EVP_PKEY_derive_init(), EVP_PKEY_derive_set_peer() and EVP_PKEY_derive()
functions were originally added in OpenSSL 1.0.0.
The EVP_PKEY_derive_init_ex() function was added in OpenSSL 3.0.
The EVP_PKEY_derive_init_ex() and EVP_PKEY_derive_set_peer_ex() functions were
added in OpenSSL 3.0.
=head1 COPYRIGHT
Copyright 2006-2020 The OpenSSL Project Authors. All Rights Reserved.
Copyright 2006-2021 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use
this file except in compliance with the License. You can obtain a copy

View File

@ -38,6 +38,7 @@ int ossl_dh_params_todata(DH *dh, OSSL_PARAM_BLD *bld, OSSL_PARAM params[]);
int ossl_dh_key_todata(DH *dh, OSSL_PARAM_BLD *bld, OSSL_PARAM params[]);
DH *ossl_dh_key_from_pkcs8(const PKCS8_PRIV_KEY_INFO *p8inf,
OSSL_LIB_CTX *libctx, const char *propq);
int ossl_dh_compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh);
int ossl_dh_check_pub_key_partial(const DH *dh, const BIGNUM *pub_key, int *ret);
int ossl_dh_check_priv_key(const DH *dh, const BIGNUM *priv_key, int *ret);

View File

@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 2020 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 2020-2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy

View File

@ -39,6 +39,7 @@
# define DH_R_INVALID_PARAMETER_NAME 110
# define DH_R_INVALID_PARAMETER_NID 114
# define DH_R_INVALID_PUBKEY 102
# define DH_R_INVALID_SECRET 128
# define DH_R_KDF_PARAMETER_ERROR 112
# define DH_R_KEYS_NOT_SET 108
# define DH_R_MISSING_PUBKEY 125

View File

@ -1825,6 +1825,8 @@ int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx,
int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_derive_init_ex(EVP_PKEY_CTX *ctx, const OSSL_PARAM params[]);
int EVP_PKEY_derive_set_peer_ex(EVP_PKEY_CTX *ctx, EVP_PKEY *peer,
int validate_peer);
int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer);
int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen);

View File

@ -1,5 +1,5 @@
/*
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@ -28,6 +28,8 @@
#ifndef OPENSSL_NO_DH
# include <openssl/dh.h>
# include "crypto/bn_dh.h"
# include "crypto/dh.h"
static int cb(int p, int n, BN_GENCB *arg);
@ -233,6 +235,61 @@ static int cb(int p, int n, BN_GENCB *arg)
return 1;
}
static int dh_computekey_range_test(void)
{
int ret = 0, sz;
DH *dh = NULL;
BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub = NULL, *priv = NULL;
unsigned char *buf = NULL;
if (!TEST_ptr(p = BN_dup(&ossl_bignum_ffdhe2048_p))
|| !TEST_ptr(q = BN_dup(&ossl_bignum_ffdhe2048_q))
|| !TEST_ptr(g = BN_dup(&ossl_bignum_const_2))
|| !TEST_ptr(dh = DH_new())
|| !TEST_true(DH_set0_pqg(dh, p, q, g)))
goto err;
p = q = g = NULL;
sz = DH_size(dh);
if (!TEST_ptr(buf = OPENSSL_malloc(sz))
|| !TEST_ptr(pub = BN_new())
|| !TEST_ptr(priv = BN_new()))
goto err;
if (!TEST_true(BN_set_word(priv, 1))
|| !TEST_true(DH_set0_key(dh, NULL, priv))
|| !TEST_true(BN_set_word(pub, 1)))
goto err;
/* Given z = pub ^ priv mod p */
/* Test that z == 1 fails */
if (!TEST_int_le(ossl_dh_compute_key(buf, pub, dh), 0))
goto err;
/* Test that z == 0 fails */
if (!TEST_ptr(BN_copy(pub, DH_get0_p(dh)))
|| !TEST_int_le(ossl_dh_compute_key(buf, pub, dh), 0))
goto err;
/* Test that z == p - 1 fails */
if (!TEST_true(BN_sub_word(pub, 1))
|| !TEST_int_le(ossl_dh_compute_key(buf, pub, dh), 0))
goto err;
/* Test that z == p - 2 passes */
if (!TEST_true(BN_sub_word(pub, 1))
|| !TEST_int_eq(ossl_dh_compute_key(buf, pub, dh), sz))
goto err;
ret = 1;
err:
OPENSSL_free(buf);
BN_free(pub);
BN_free(g);
BN_free(q);
BN_free(p);
DH_free(dh);
return ret;
}
/* Test data from RFC 5114 */
static const unsigned char dhtest_1024_160_xA[] = {
@ -461,31 +518,6 @@ static const unsigned char dhtest_2048_256_Z[] = {
0xC2, 0x6C, 0x5D, 0x7C
};
static const unsigned char dhtest_rfc5114_2048_224_bad_y[] = {
0x45, 0x32, 0x5F, 0x51, 0x07, 0xE5, 0xDF, 0x1C, 0xD6, 0x02, 0x82, 0xB3,
0x32, 0x8F, 0xA4, 0x0F, 0x87, 0xB8, 0x41, 0xFE, 0xB9, 0x35, 0xDE, 0xAD,
0xC6, 0x26, 0x85, 0xB4, 0xFF, 0x94, 0x8C, 0x12, 0x4C, 0xBF, 0x5B, 0x20,
0xC4, 0x46, 0xA3, 0x26, 0xEB, 0xA4, 0x25, 0xB7, 0x68, 0x8E, 0xCC, 0x67,
0xBA, 0xEA, 0x58, 0xD0, 0xF2, 0xE9, 0xD2, 0x24, 0x72, 0x60, 0xDA, 0x88,
0x18, 0x9C, 0xE0, 0x31, 0x6A, 0xAD, 0x50, 0x6D, 0x94, 0x35, 0x8B, 0x83,
0x4A, 0x6E, 0xFA, 0x48, 0x73, 0x0F, 0x83, 0x87, 0xFF, 0x6B, 0x66, 0x1F,
0xA8, 0x82, 0xC6, 0x01, 0xE5, 0x80, 0xB5, 0xB0, 0x52, 0xD0, 0xE9, 0xD8,
0x72, 0xF9, 0x7D, 0x5B, 0x8B, 0xA5, 0x4C, 0xA5, 0x25, 0x95, 0x74, 0xE2,
0x7A, 0x61, 0x4E, 0xA7, 0x8F, 0x12, 0xE2, 0xD2, 0x9D, 0x8C, 0x02, 0x70,
0x34, 0x44, 0x32, 0xC7, 0xB2, 0xF3, 0xB9, 0xFE, 0x17, 0x2B, 0xD6, 0x1F,
0x8B, 0x7E, 0x4A, 0xFA, 0xA3, 0xB5, 0x3E, 0x7A, 0x81, 0x9A, 0x33, 0x66,
0x62, 0xA4, 0x50, 0x18, 0x3E, 0xA2, 0x5F, 0x00, 0x07, 0xD8, 0x9B, 0x22,
0xE4, 0xEC, 0x84, 0xD5, 0xEB, 0x5A, 0xF3, 0x2A, 0x31, 0x23, 0xD8, 0x44,
0x22, 0x2A, 0x8B, 0x37, 0x44, 0xCC, 0xC6, 0x87, 0x4B, 0xBE, 0x50, 0x9D,
0x4A, 0xC4, 0x8E, 0x45, 0xCF, 0x72, 0x4D, 0xC0, 0x89, 0xB3, 0x72, 0xED,
0x33, 0x2C, 0xBC, 0x7F, 0x16, 0x39, 0x3B, 0xEB, 0xD2, 0xDD, 0xA8, 0x01,
0x73, 0x84, 0x62, 0xB9, 0x29, 0xD2, 0xC9, 0x51, 0x32, 0x9E, 0x7A, 0x6A,
0xCF, 0xC1, 0x0A, 0xDB, 0x0E, 0xE0, 0x62, 0x77, 0x6F, 0x59, 0x62, 0x72,
0x5A, 0x69, 0xA6, 0x5B, 0x70, 0xCA, 0x65, 0xC4, 0x95, 0x6F, 0x9A, 0xC2,
0xDF, 0x72, 0x6D, 0xB1, 0x1E, 0x54, 0x7B, 0x51, 0xB4, 0xEF, 0x7F, 0x89,
0x93, 0x74, 0x89, 0x59
};
typedef struct {
DH *(*get_param) (void);
const unsigned char *xA;
@ -523,7 +555,7 @@ static int rfc5114_test(void)
unsigned char *Z1 = NULL;
unsigned char *Z2 = NULL;
const rfc5114_td *td = NULL;
BIGNUM *bady = NULL, *priv_key = NULL, *pub_key = NULL;
BIGNUM *priv_key = NULL, *pub_key = NULL;
const BIGNUM *pub_key_tmp;
for (i = 0; i < (int)OSSL_NELEM(rfctd); i++) {
@ -576,37 +608,9 @@ static int rfc5114_test(void)
OPENSSL_free(Z2);
Z2 = NULL;
}
/* Now i == OSSL_NELEM(rfctd) */
/* RFC5114 uses unsafe primes, so now test an invalid y value */
if (!TEST_ptr(dhA = DH_get_2048_224())
|| !TEST_ptr(Z1 = OPENSSL_malloc(DH_size(dhA))))
goto bad_err;
if (!TEST_ptr(bady = BN_bin2bn(dhtest_rfc5114_2048_224_bad_y,
sizeof(dhtest_rfc5114_2048_224_bad_y),
NULL)))
goto bad_err;
if (!DH_generate_key(dhA))
goto bad_err;
if (DH_compute_key(Z1, bady, dhA) != -1) {
/*
* DH_compute_key should fail with -1. If we get here we unexpectedly
* allowed an invalid y value
*/
goto err;
}
/* We'll have a stale error on the queue from the above test so clear it */
ERR_clear_error();
BN_free(bady);
DH_free(dhA);
OPENSSL_free(Z1);
return 1;
bad_err:
BN_free(bady);
DH_free(dhA);
DH_free(dhB);
BN_free(pub_key);
@ -617,7 +621,6 @@ static int rfc5114_test(void)
return 0;
err:
BN_free(bady);
DH_free(dhA);
DH_free(dhB);
OPENSSL_free(Z1);
@ -793,6 +796,7 @@ int setup_tests(void)
TEST_note("No DH support");
#else
ADD_TEST(dh_test);
ADD_TEST(dh_computekey_range_test);
ADD_TEST(rfc5114_test);
ADD_TEST(rfc7919_test);
ADD_ALL_TESTS(dh_test_prime_groups, OSSL_NELEM(prime_groups));

View File

@ -1632,7 +1632,7 @@ static int pderive_test_parse(EVP_TEST *t,
EVP_PKEY *peer;
if (find_key(&peer, value, public_keys) == 0)
return -1;
if (EVP_PKEY_derive_set_peer(kdata->ctx, peer) <= 0) {
if (EVP_PKEY_derive_set_peer_ex(kdata->ctx, peer, 0) <= 0) {
t->err = "DERIVE_SET_PEER_ERROR";
return 1;
}

View File

@ -5332,3 +5332,4 @@ TS_RESP_CTX_new_ex ? 3_0_0 EXIST::FUNCTION:TS
X509_REQ_new_ex ? 3_0_0 EXIST::FUNCTION:
EVP_PKEY_dup ? 3_0_0 EXIST::FUNCTION:
RSA_PSS_PARAMS_dup ? 3_0_0 EXIST::FUNCTION:
EVP_PKEY_derive_set_peer_ex ? 3_0_0 EXIST::FUNCTION: