Add some barebones support for DH in EVP
OpenSSH needs this. Features that have been intentionally omitted for
now:
- X9.42-style Diffie-Hellman ("DHX"). We continue not to support this.
Use ECDH or X25519 instead.
- SPKI and PKCS#8 serialization. Use ECDH or X25519 instead. The format
is a bit ill-defined. Moreover, until we solve the serialization
aspects of https://crbug.com/boringssl/497, adding them would put this
legacy algorithm on path for every caller.
- Most of the random options like stapling a KDF, etc. Though I did add
EVP_PKEY_CTX_set_dh_pad because it's the only way to undo OpenSSL's
bug where they chop off leading zeros by default.
- Parameter generation. Diffie-Hellman parameters should not be
generated at runtime.
This means you need to bootstrap with a DH object and then wrap it in an
EVP_PKEY. This matches the limitations of the EVP API in OpenSSL 1.1.x.
Unfortunately the OpenSSL 3.x APIs are unsuitable for many, many
reasons, so I expect when we get further along in
https://crbug.com/boringssl/535, we'll have established some patterns
here that we can apply to EVP_PKEY_DH too.
Change-Id: I34b4e8799afb266ea5602a70115cc2146f19c6a7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/67207
Reviewed-by: Theo Buehler <theorbuehler@gmail.com>
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
index 26c3507..e064938 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -158,6 +158,8 @@
evp/evp.c
evp/evp_asn1.c
evp/evp_ctx.c
+ evp/p_dh.c
+ evp/p_dh_asn1.c
evp/p_dsa_asn1.c
evp/p_ec.c
evp/p_ec_asn1.c
diff --git a/crypto/err/evp.errordata b/crypto/err/evp.errordata
index a581b1b..f65b7b0 100644
--- a/crypto/err/evp.errordata
+++ b/crypto/err/evp.errordata
@@ -7,6 +7,7 @@
EVP,105,ENCODE_ERROR
EVP,106,EXPECTING_AN_EC_KEY_KEY
EVP,107,EXPECTING_AN_RSA_KEY
+EVP,138,EXPECTING_A_DH_KEY
EVP,108,EXPECTING_A_DSA_KEY
EVP,109,ILLEGAL_OR_UNSUPPORTED_PADDING_MODE
EVP,137,INVALID_BUFFER_SIZE
diff --git a/crypto/evp/evp.c b/crypto/evp/evp.c
index 3eefb52..d81abe9 100644
--- a/crypto/evp/evp.c
+++ b/crypto/evp/evp.c
@@ -232,12 +232,9 @@
return nid;
}
-DH *EVP_PKEY_get0_DH(const EVP_PKEY *pkey) { return NULL; }
-DH *EVP_PKEY_get1_DH(const EVP_PKEY *pkey) { return NULL; }
-
int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key) {
- // This function can only be used to assign RSA, DSA, and EC keys. Other key
- // types have internal representations which are not exposed through the
+ // This function can only be used to assign RSA, DSA, EC, and DH keys. Other
+ // key types have internal representations which are not exposed through the
// public API.
switch (type) {
case EVP_PKEY_RSA:
@@ -246,6 +243,8 @@
return EVP_PKEY_assign_DSA(pkey, key);
case EVP_PKEY_EC:
return EVP_PKEY_assign_EC_KEY(pkey, key);
+ case EVP_PKEY_DH:
+ return EVP_PKEY_assign_DH(pkey, key);
}
OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
diff --git a/crypto/evp/evp_asn1.c b/crypto/evp/evp_asn1.c
index 8f341e9..11a0b89 100644
--- a/crypto/evp/evp_asn1.c
+++ b/crypto/evp/evp_asn1.c
@@ -69,6 +69,7 @@
#include "../internal.h"
+// We intentionally omit |dh_asn1_meth| from this list. It is not serializable.
static const EVP_PKEY_ASN1_METHOD *const kASN1Methods[] = {
&rsa_asn1_meth,
&ec_asn1_meth,
diff --git a/crypto/evp/evp_extra_test.cc b/crypto/evp/evp_extra_test.cc
index 0b57dc9..2a77165 100644
--- a/crypto/evp/evp_extra_test.cc
+++ b/crypto/evp/evp_extra_test.cc
@@ -24,6 +24,7 @@
#include <openssl/bytestring.h>
#include <openssl/crypto.h>
+#include <openssl/dh.h>
#include <openssl/digest.h>
#include <openssl/err.h>
#include <openssl/pkcs8.h>
@@ -1074,6 +1075,68 @@
}
}
+TEST(EVPExtraTest, DHKeygen) {
+ // Set up some DH params in an |EVP_PKEY|. There is currently no API to do
+ // this from EVP directly.
+ bssl::UniquePtr<BIGNUM> p(BN_get_rfc3526_prime_1536(nullptr));
+ ASSERT_TRUE(p);
+ bssl::UniquePtr<BIGNUM> g(BN_new());
+ ASSERT_TRUE(g);
+ ASSERT_TRUE(BN_set_u64(g.get(), 2));
+ bssl::UniquePtr<DH> params_dh(DH_new());
+ ASSERT_TRUE(params_dh);
+ ASSERT_TRUE(
+ DH_set0_pqg(params_dh.get(), p.release(), /*q=*/nullptr, g.release()));
+ bssl::UniquePtr<EVP_PKEY> params(EVP_PKEY_new());
+ ASSERT_TRUE(params);
+ ASSERT_TRUE(EVP_PKEY_set1_DH(params.get(), params_dh.get()));
+
+ for (bool copy : {false, true}) {
+ SCOPED_TRACE(copy);
+
+ auto maybe_copy = [&](bssl::UniquePtr<EVP_PKEY_CTX> *ctx) -> bool {
+ if (copy) {
+ ctx->reset(EVP_PKEY_CTX_dup(ctx->get()));
+ }
+ return *ctx != nullptr;
+ };
+
+ // |params| may be used as a template for key generation.
+ bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(params.get(), nullptr));
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(maybe_copy(&ctx));
+ ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get()));
+ ASSERT_TRUE(maybe_copy(&ctx));
+ EVP_PKEY *raw = nullptr;
+ ASSERT_TRUE(EVP_PKEY_keygen(ctx.get(), &raw));
+ bssl::UniquePtr<EVP_PKEY> pkey(raw);
+
+ EXPECT_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_DH);
+ const DH *dh = EVP_PKEY_get0_DH(pkey.get());
+ EXPECT_EQ(0, BN_cmp(DH_get0_p(dh), DH_get0_p(params_dh.get())));
+ EXPECT_EQ(0, BN_cmp(DH_get0_g(dh), DH_get0_g(params_dh.get())));
+ EXPECT_FALSE(DH_get0_q(dh));
+ EXPECT_TRUE(DH_get0_pub_key(dh));
+ EXPECT_TRUE(DH_get0_priv_key(dh));
+ EXPECT_EQ(1, EVP_PKEY_cmp_parameters(params.get(), pkey.get()));
+ EXPECT_EQ(0, EVP_PKEY_cmp(params.get(), pkey.get()));
+
+ // Generate a second key.
+ ctx.reset(EVP_PKEY_CTX_new(params.get(), nullptr));
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(maybe_copy(&ctx));
+ ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get()));
+ ASSERT_TRUE(maybe_copy(&ctx));
+ raw = nullptr;
+ ASSERT_TRUE(EVP_PKEY_keygen(ctx.get(), &raw));
+ bssl::UniquePtr<EVP_PKEY> pkey2(raw);
+
+ EXPECT_EQ(1, EVP_PKEY_cmp_parameters(params.get(), pkey2.get()));
+ EXPECT_EQ(1, EVP_PKEY_cmp_parameters(pkey.get(), pkey2.get()));
+ EXPECT_EQ(0, EVP_PKEY_cmp(pkey.get(), pkey2.get()));
+ }
+}
+
// Test that |EVP_PKEY_keygen| works for Ed25519.
TEST(EVPExtraTest, Ed25519Keygen) {
bssl::UniquePtr<EVP_PKEY_CTX> pctx(
diff --git a/crypto/evp/evp_test.cc b/crypto/evp/evp_test.cc
index fafd50b..9189d25 100644
--- a/crypto/evp/evp_test.cc
+++ b/crypto/evp/evp_test.cc
@@ -70,9 +70,11 @@
#include <gtest/gtest.h>
+#include <openssl/bn.h>
#include <openssl/bytestring.h>
#include <openssl/crypto.h>
#include <openssl/digest.h>
+#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
@@ -249,6 +251,60 @@
return true;
}
+static bool GetOptionalBignum(FileTest *t, bssl::UniquePtr<BIGNUM> *out,
+ const std::string &key) {
+ if (!t->HasAttribute(key)) {
+ *out = nullptr;
+ return true;
+ }
+
+ std::vector<uint8_t> bytes;
+ if (!t->GetBytes(&bytes, key)) {
+ return false;
+ }
+
+ out->reset(BN_bin2bn(bytes.data(), bytes.size(), nullptr));
+ return *out != nullptr;
+}
+
+static bool ImportDHKey(FileTest *t, KeyMap *key_map) {
+ bssl::UniquePtr<BIGNUM> p, q, g, pub_key, priv_key;
+ if (!GetOptionalBignum(t, &p, "P") || //
+ !GetOptionalBignum(t, &q, "Q") || //
+ !GetOptionalBignum(t, &g, "G") ||
+ !GetOptionalBignum(t, &pub_key, "Public") ||
+ !GetOptionalBignum(t, &priv_key, "Private")) {
+ return false;
+ }
+
+ bssl::UniquePtr<DH> dh(DH_new());
+ if (dh == nullptr || !DH_set0_pqg(dh.get(), p.get(), q.get(), g.get())) {
+ return false;
+ }
+ // |DH_set0_pqg| takes ownership on success.
+ p.release();
+ q.release();
+ g.release();
+
+ if (!DH_set0_key(dh.get(), pub_key.get(), priv_key.get())) {
+ return false;
+ }
+ // |DH_set0_key| takes ownership on success.
+ pub_key.release();
+ priv_key.release();
+
+ bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
+ if (pkey == nullptr || !EVP_PKEY_set1_DH(pkey.get(), dh.get())) {
+ return false;
+ }
+
+ // Save the key for future tests.
+ const std::string &key_name = t->GetParameter();
+ EXPECT_EQ(0u, key_map->count(key_name)) << "Duplicate key: " << key_name;
+ (*key_map)[key_name] = std::move(pkey);
+ return true;
+}
+
// SetupContext configures |ctx| based on attributes in |t|, with the exception
// of the signing digest which must be configured externally.
static bool SetupContext(FileTest *t, KeyMap *key_map, EVP_PKEY_CTX *ctx) {
@@ -302,6 +358,9 @@
return false;
}
}
+ if (t->HasAttribute("DiffieHellmanPad") && !EVP_PKEY_CTX_set_dh_pad(ctx, 1)) {
+ return false;
+ }
return true;
}
@@ -372,6 +431,10 @@
return ImportKey(t, key_map, EVP_parse_public_key, EVP_marshal_public_key);
}
+ if (t->GetType() == "DHKey") {
+ return ImportDHKey(t, key_map);
+ }
+
// Load the key.
const std::string &key_name = t->GetParameter();
if (key_map->count(key_name) == 0) {
diff --git a/crypto/evp/evp_tests.txt b/crypto/evp/evp_tests.txt
index b9c8f9e..e8e9f27 100644
--- a/crypto/evp/evp_tests.txt
+++ b/crypto/evp/evp_tests.txt
@@ -1717,3 +1717,83 @@
Derive = X25519-Private
DerivePeer = X25519-SmallOrderPeer
Error = INVALID_PEER_KEY
+
+DHKey = DH-Public1
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
+G = 02
+Public = c680b01bb49e303893a5c339c9448c63dc3d0ffdcb8024a784292ebccfe95bdfb97a456f51cb1decf904704d0dbab69689bd87cde04e4fcb793f66024a43dacd0830ac155f8149aeb42f3d1ec03b05c70a1349492d24e20b58b0283155816465c0efa3b01e78fe935f1633745826a0e5c8d87ca60418d816721503ccaf7540e7e3c13093a8fb72c34c452ab35cb07ec867f58f2c1d0ad6629b6359b2b990a50d29aedaca2efbf0b1b904005859f348797b9be660f438cc763dd2180ec7dd81c9
+
+DHKey = DH-Private1
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
+G = 02
+Public = c680b01bb49e303893a5c339c9448c63dc3d0ffdcb8024a784292ebccfe95bdfb97a456f51cb1decf904704d0dbab69689bd87cde04e4fcb793f66024a43dacd0830ac155f8149aeb42f3d1ec03b05c70a1349492d24e20b58b0283155816465c0efa3b01e78fe935f1633745826a0e5c8d87ca60418d816721503ccaf7540e7e3c13093a8fb72c34c452ab35cb07ec867f58f2c1d0ad6629b6359b2b990a50d29aedaca2efbf0b1b904005859f348797b9be660f438cc763dd2180ec7dd81c9
+Private = 05953ba55a5ff41a700744e06cebcd30f6fd76a6b1f7efb6bdc05028e7db2e50ef56385c65bad4a1cfff232c5d83179559e59a8901a88119ababdcc0c4e4fd75cdf6161fb07a72fb3d4c6c0fb140a2eb3e93627d4f2e93e086ba672149a4fb25594b2c6cb74a97a8e68d45097cc937cf30dd9141dbd3abdd4fb9fec45a240d528efa4a5b5690f40250a96ff54b0b90a3a0540e5cc54754579d4e65db233edcc9e55c26dd2a6f7fd8ee440b3f5bce547e0bb9197894f1728c2060b0597cbee547
+
+DHKey = DH-Public2
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
+G = 02
+Public = 98f7472e4950b3bfcb3f37bf02b77323f7919e434e11c6e4b76b9a55132e50c3dca3c7ac55b69126cd06d7c337be1552b81ac011d6f5e56a688b89e4fa10d31a5305b78b6591354b5a22678675bdd248b82a4b267eac643cfbec1cebce93fa8aa59558e5121e9f76fb119cf90e587661aba85b2a617304c9492e5565f21af693caceea9c7fb0f68909f3279ccfe347d7132e9e76a058f99b805f9b2275a082353f5be00258670d640cc9c7926984ebba4cffb3902961a6951373ac4915a70aeb
+
+DHKey = DH-Private2
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
+G = 02
+Public = 98f7472e4950b3bfcb3f37bf02b77323f7919e434e11c6e4b76b9a55132e50c3dca3c7ac55b69126cd06d7c337be1552b81ac011d6f5e56a688b89e4fa10d31a5305b78b6591354b5a22678675bdd248b82a4b267eac643cfbec1cebce93fa8aa59558e5121e9f76fb119cf90e587661aba85b2a617304c9492e5565f21af693caceea9c7fb0f68909f3279ccfe347d7132e9e76a058f99b805f9b2275a082353f5be00258670d640cc9c7926984ebba4cffb3902961a6951373ac4915a70aeb
+Private = 984de7473d1186e97b3dc4797f14ec8ab97df321192bf40e8fb575a2ab93210f6c32cc4d915cff27d2d4f9bbc661bc809243d116db8b844377993ae8399b4fa089c9404c7515003c71a2bfdd0361cc192dcf2e56a555105e2ef25b0c7545a6a30ba62607b0563ad46714ac8b6720446ad0e33af2c183cdf045b01ff0415fbdd8e2bd506729a84731fb68dd54a4caecfe028a09d157f94f48e90c3d5cb63f0db39e05d556a4dc85594c9c7f2f07c6dd27878512748fc8eba2652f2bd7a6395586
+
+# By default, the leading zero is removed for OpenSSL compatibility (insecure).
+Derive = DH-Private1
+DerivePeer = DH-Public2
+Output = 5d21ea6f2a141f62e77f3943a2fac88dae9bc6baf3030f467c6dd34582432c80ae0a16655e75f35dea69943503ab8a25b7bbc9cca8e82a85e14c52293635792fbc27d5089c60e528f519c054f4d89b9ef673a4167e8734e226c5bc1b88016ed8534e65e19574da4ccc5197f8cd681ea86794a294385cc7bac913f30bca359c142a7989663793fc173aa029cdd269dd29649e225bd5d7863bc084555e53ca3485fd813b6cf8f36b06b22fb42d57e19c5e00d01a8bbe7dcc6eea965178851495
+
+Derive = DH-Private2
+DerivePeer = DH-Public1
+Output = 5d21ea6f2a141f62e77f3943a2fac88dae9bc6baf3030f467c6dd34582432c80ae0a16655e75f35dea69943503ab8a25b7bbc9cca8e82a85e14c52293635792fbc27d5089c60e528f519c054f4d89b9ef673a4167e8734e226c5bc1b88016ed8534e65e19574da4ccc5197f8cd681ea86794a294385cc7bac913f30bca359c142a7989663793fc173aa029cdd269dd29649e225bd5d7863bc084555e53ca3485fd813b6cf8f36b06b22fb42d57e19c5e00d01a8bbe7dcc6eea965178851495
+
+# Setting EVP_PKEY_CTX_set_dh_pad fixes this.
+Derive = DH-Private1
+DerivePeer = DH-Public2
+DiffieHellmanPad
+Output = 005d21ea6f2a141f62e77f3943a2fac88dae9bc6baf3030f467c6dd34582432c80ae0a16655e75f35dea69943503ab8a25b7bbc9cca8e82a85e14c52293635792fbc27d5089c60e528f519c054f4d89b9ef673a4167e8734e226c5bc1b88016ed8534e65e19574da4ccc5197f8cd681ea86794a294385cc7bac913f30bca359c142a7989663793fc173aa029cdd269dd29649e225bd5d7863bc084555e53ca3485fd813b6cf8f36b06b22fb42d57e19c5e00d01a8bbe7dcc6eea965178851495
+
+Derive = DH-Private2
+DerivePeer = DH-Public1
+DiffieHellmanPad
+Output = 005d21ea6f2a141f62e77f3943a2fac88dae9bc6baf3030f467c6dd34582432c80ae0a16655e75f35dea69943503ab8a25b7bbc9cca8e82a85e14c52293635792fbc27d5089c60e528f519c054f4d89b9ef673a4167e8734e226c5bc1b88016ed8534e65e19574da4ccc5197f8cd681ea86794a294385cc7bac913f30bca359c142a7989663793fc173aa029cdd269dd29649e225bd5d7863bc084555e53ca3485fd813b6cf8f36b06b22fb42d57e19c5e00d01a8bbe7dcc6eea965178851495
+
+Derive = DH-Public1
+DerivePeer = DH-Public2
+Error = NO_PRIVATE_VALUE
+
+DHKey = DH-WrongGroup
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327fffffffffffffffe
+G = 02
+Public = 98f7472e4950b3bfcb3f37bf02b77323f7919e434e11c6e4b76b9a55132e50c3dca3c7ac55b69126cd06d7c337be1552b81ac011d6f5e56a688b89e4fa10d31a5305b78b6591354b5a22678675bdd248b82a4b267eac643cfbec1cebce93fa8aa59558e5121e9f76fb119cf90e587661aba85b2a617304c9492e5565f21af693caceea9c7fb0f68909f3279ccfe347d7132e9e76a058f99b805f9b2275a082353f5be00258670d640cc9c7926984ebba4cffb3902961a6951373ac4915a70aeb
+Private = 984de7473d1186e97b3dc4797f14ec8ab97df321192bf40e8fb575a2ab93210f6c32cc4d915cff27d2d4f9bbc661bc809243d116db8b844377993ae8399b4fa089c9404c7515003c71a2bfdd0361cc192dcf2e56a555105e2ef25b0c7545a6a30ba62607b0563ad46714ac8b6720446ad0e33af2c183cdf045b01ff0415fbdd8e2bd506729a84731fb68dd54a4caecfe028a09d157f94f48e90c3d5cb63f0db39e05d556a4dc85594c9c7f2f07c6dd27878512748fc8eba2652f2bd7a6395586
+
+Derive = DH-WrongGroup
+DerivePeer = DH-Public2
+Error = DIFFERENT_PARAMETERS
+
+Derive = DH-Private1
+DerivePeer = DH-WrongGroup
+Error = DIFFERENT_PARAMETERS
+
+DHKey = DH-Params
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
+G = 02
+
+Derive = DH-Private1
+DerivePeer = DH-Params
+Error = KEYS_NOT_SET
+
+DHKey = DH-Private1-With-Q
+P = ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
+Q = 7fffffffffffffffe487ed5110b4611a62633145c06e0e68948127044533e63a0105df531d89cd9128a5043cc71a026ef7ca8cd9e69d218d98158536f92f8a1ba7f09ab6b6a8e122f242dabb312f3f637a262174d31bf6b585ffae5b7a035bf6f71c35fdad44cfd2d74f9208be258ff324943328f6722d9ee1003e5c50b1df82cc6d241b0e2ae9cd348b1fd47e9267afc1b2ae91ee51d6cb0e3179ab1042a95dcf6a9483b84b4b36b3861aa7255e4c0278ba36046511b993ffffffffffffffff
+G = 02
+Public = c680b01bb49e303893a5c339c9448c63dc3d0ffdcb8024a784292ebccfe95bdfb97a456f51cb1decf904704d0dbab69689bd87cde04e4fcb793f66024a43dacd0830ac155f8149aeb42f3d1ec03b05c70a1349492d24e20b58b0283155816465c0efa3b01e78fe935f1633745826a0e5c8d87ca60418d816721503ccaf7540e7e3c13093a8fb72c34c452ab35cb07ec867f58f2c1d0ad6629b6359b2b990a50d29aedaca2efbf0b1b904005859f348797b9be660f438cc763dd2180ec7dd81c9
+Private = 05953ba55a5ff41a700744e06cebcd30f6fd76a6b1f7efb6bdc05028e7db2e50ef56385c65bad4a1cfff232c5d83179559e59a8901a88119ababdcc0c4e4fd75cdf6161fb07a72fb3d4c6c0fb140a2eb3e93627d4f2e93e086ba672149a4fb25594b2c6cb74a97a8e68d45097cc937cf30dd9141dbd3abdd4fb9fec45a240d528efa4a5b5690f40250a96ff54b0b90a3a0540e5cc54754579d4e65db233edcc9e55c26dd2a6f7fd8ee440b3f5bce547e0bb9197894f1728c2060b0597cbee547
+
+Derive = DH-Private1-With-Q
+DerivePeer = DH-Public2
+DiffieHellmanPad
+Output = 005d21ea6f2a141f62e77f3943a2fac88dae9bc6baf3030f467c6dd34582432c80ae0a16655e75f35dea69943503ab8a25b7bbc9cca8e82a85e14c52293635792fbc27d5089c60e528f519c054f4d89b9ef673a4167e8734e226c5bc1b88016ed8534e65e19574da4ccc5197f8cd681ea86794a294385cc7bac913f30bca359c142a7989663793fc173aa029cdd269dd29649e225bd5d7863bc084555e53ca3485fd813b6cf8f36b06b22fb42d57e19c5e00d01a8bbe7dcc6eea965178851495
diff --git a/crypto/evp/internal.h b/crypto/evp/internal.h
index cf287c8..1d47427 100644
--- a/crypto/evp/internal.h
+++ b/crypto/evp/internal.h
@@ -213,6 +213,7 @@
#define EVP_PKEY_CTRL_HKDF_KEY (EVP_PKEY_ALG_CTRL + 16)
#define EVP_PKEY_CTRL_HKDF_SALT (EVP_PKEY_ALG_CTRL + 17)
#define EVP_PKEY_CTRL_HKDF_INFO (EVP_PKEY_ALG_CTRL + 18)
+#define EVP_PKEY_CTRL_DH_PAD (EVP_PKEY_ALG_CTRL + 19)
struct evp_pkey_ctx_st {
// Method associated with this operation
@@ -288,12 +289,14 @@
extern const EVP_PKEY_ASN1_METHOD rsa_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth;
extern const EVP_PKEY_ASN1_METHOD x25519_asn1_meth;
+extern const EVP_PKEY_ASN1_METHOD dh_asn1_meth;
extern const EVP_PKEY_METHOD rsa_pkey_meth;
extern const EVP_PKEY_METHOD ec_pkey_meth;
extern const EVP_PKEY_METHOD ed25519_pkey_meth;
extern const EVP_PKEY_METHOD x25519_pkey_meth;
extern const EVP_PKEY_METHOD hkdf_pkey_meth;
+extern const EVP_PKEY_METHOD dh_pkey_meth;
// evp_pkey_set_method behaves like |EVP_PKEY_set_type|, but takes a pointer to
// a method table. This avoids depending on every |EVP_PKEY_ASN1_METHOD|.
diff --git a/crypto/evp/p_dh.c b/crypto/evp/p_dh.c
new file mode 100644
index 0000000..9953f82
--- /dev/null
+++ b/crypto/evp/p_dh.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2006-2019 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/evp.h>
+
+#include <assert.h>
+
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+
+#include "internal.h"
+
+
+typedef struct dh_pkey_ctx_st {
+ int pad;
+} DH_PKEY_CTX;
+
+static int pkey_dh_init(EVP_PKEY_CTX *ctx) {
+ DH_PKEY_CTX *dctx = OPENSSL_zalloc(sizeof(DH_PKEY_CTX));
+ if (dctx == NULL) {
+ return 0;
+ }
+
+ ctx->data = dctx;
+ return 1;
+}
+
+static int pkey_dh_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) {
+ if (!pkey_dh_init(dst)) {
+ return 0;
+ }
+
+ const DH_PKEY_CTX *sctx = src->data;
+ DH_PKEY_CTX *dctx = dst->data;
+ dctx->pad = sctx->pad;
+ return 1;
+}
+
+static void pkey_dh_cleanup(EVP_PKEY_CTX *ctx) {
+ OPENSSL_free(ctx->data);
+ ctx->data = NULL;
+}
+
+static int pkey_dh_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
+ DH *dh = DH_new();
+ if (dh == NULL || !EVP_PKEY_assign_DH(pkey, dh)) {
+ DH_free(dh);
+ return 0;
+ }
+
+ if (ctx->pkey != NULL && !EVP_PKEY_copy_parameters(pkey, ctx->pkey)) {
+ return 0;
+ }
+
+ return DH_generate_key(dh);
+}
+
+static int pkey_dh_derive(EVP_PKEY_CTX *ctx, uint8_t *out, size_t *out_len) {
+ DH_PKEY_CTX *dctx = ctx->data;
+ if (ctx->pkey == NULL || ctx->peerkey == NULL) {
+ OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET);
+ return 0;
+ }
+
+ DH *our_key = ctx->pkey->pkey;
+ DH *peer_key = ctx->peerkey->pkey;
+ if (our_key == NULL || peer_key == NULL) {
+ OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET);
+ return 0;
+ }
+
+ const BIGNUM *pub_key = DH_get0_pub_key(peer_key);
+ if (pub_key == NULL) {
+ OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET);
+ return 0;
+ }
+
+ if (out == NULL) {
+ *out_len = DH_size(our_key);
+ return 1;
+ }
+
+ if (*out_len < (size_t)DH_size(our_key)) {
+ OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+ return 0;
+ }
+
+ int ret = dctx->pad ? DH_compute_key_padded(out, pub_key, our_key)
+ : DH_compute_key(out, pub_key, our_key);
+ if (ret < 0) {
+ return 0;
+ }
+
+ assert(ret <= DH_size(our_key));
+ *out_len = (size_t)ret;
+ return 1;
+}
+
+static int pkey_dh_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) {
+ DH_PKEY_CTX *dctx = ctx->data;
+ switch (type) {
+ case EVP_PKEY_CTRL_PEER_KEY:
+ // |EVP_PKEY_derive_set_peer| requires the key implement this command,
+ // even if it is a no-op.
+ return 1;
+
+ case EVP_PKEY_CTRL_DH_PAD:
+ dctx->pad = p1;
+ return 1;
+
+ default:
+ OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED);
+ return 0;
+ }
+}
+
+const EVP_PKEY_METHOD dh_pkey_meth = {
+ .pkey_id = EVP_PKEY_DH,
+ .init = pkey_dh_init,
+ .copy = pkey_dh_copy,
+ .cleanup = pkey_dh_cleanup,
+ .keygen = pkey_dh_keygen,
+ .derive = pkey_dh_derive,
+ .ctrl = pkey_dh_ctrl,
+};
+
+int EVP_PKEY_CTX_set_dh_pad(EVP_PKEY_CTX *ctx, int pad) {
+ return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_DERIVE,
+ EVP_PKEY_CTRL_DH_PAD, pad, NULL);
+}
diff --git a/crypto/evp/p_dh_asn1.c b/crypto/evp/p_dh_asn1.c
new file mode 100644
index 0000000..b1038d1
--- /dev/null
+++ b/crypto/evp/p_dh_asn1.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2006-2021 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/evp.h>
+
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/err.h>
+
+#include "internal.h"
+#include "../internal.h"
+
+
+static void dh_free(EVP_PKEY *pkey) {
+ DH_free(pkey->pkey);
+ pkey->pkey = NULL;
+}
+
+static int dh_size(const EVP_PKEY *pkey) { return DH_size(pkey->pkey); }
+
+static int dh_bits(const EVP_PKEY *pkey) { return DH_bits(pkey->pkey); }
+
+static int dh_param_missing(const EVP_PKEY *pkey) {
+ const DH *dh = pkey->pkey;
+ return dh == NULL || DH_get0_p(dh) == NULL || DH_get0_g(dh) == NULL;
+}
+
+static int dh_param_copy(EVP_PKEY *to, const EVP_PKEY *from) {
+ if (dh_param_missing(from)) {
+ OPENSSL_PUT_ERROR(EVP, EVP_R_MISSING_PARAMETERS);
+ return 0;
+ }
+
+ const DH *dh = from->pkey;
+ const BIGNUM *q_old = DH_get0_q(dh);
+ BIGNUM *p = BN_dup(DH_get0_p(dh));
+ BIGNUM *q = q_old == NULL ? NULL : BN_dup(q_old);
+ BIGNUM *g = BN_dup(DH_get0_g(dh));
+ if (p == NULL || (q_old != NULL && q == NULL) || g == NULL ||
+ !DH_set0_pqg(to->pkey, p, q, g)) {
+ BN_free(p);
+ BN_free(q);
+ BN_free(g);
+ return 0;
+ }
+
+ // |DH_set0_pqg| took ownership of |p|, |q|, and |g|.
+ return 1;
+}
+
+static int dh_param_cmp(const EVP_PKEY *a, const EVP_PKEY *b) {
+ if (dh_param_missing(a) || dh_param_missing(b)) {
+ return -2;
+ }
+
+ // Matching OpenSSL, only compare p and g for PKCS#3-style Diffie-Hellman.
+ // OpenSSL only checks q in X9.42-style Diffie-Hellman ("DHX").
+ const DH *a_dh = a->pkey;
+ const DH *b_dh = b->pkey;
+ return BN_cmp(DH_get0_p(a_dh), DH_get0_p(b_dh)) == 0 &&
+ BN_cmp(DH_get0_g(a_dh), DH_get0_g(b_dh)) == 0;
+}
+
+static int dh_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) {
+ if (dh_param_cmp(a, b) <= 0) {
+ return 0;
+ }
+
+ const DH *a_dh = a->pkey;
+ const DH *b_dh = b->pkey;
+ return BN_cmp(DH_get0_pub_key(a_dh), DH_get0_pub_key(b_dh)) == 0;
+}
+
+const EVP_PKEY_ASN1_METHOD dh_asn1_meth = {
+ .pkey_id = EVP_PKEY_DH,
+ .pkey_method = &dh_pkey_meth,
+ .pub_cmp = dh_pub_cmp,
+ .pkey_size = dh_size,
+ .pkey_bits = dh_bits,
+ .param_missing = dh_param_missing,
+ .param_copy = dh_param_copy,
+ .param_cmp = dh_param_cmp,
+ .pkey_free = dh_free,
+};
+
+int EVP_PKEY_set1_DH(EVP_PKEY *pkey, DH *key) {
+ if (EVP_PKEY_assign_DH(pkey, key)) {
+ DH_up_ref(key);
+ return 1;
+ }
+ return 0;
+}
+
+int EVP_PKEY_assign_DH(EVP_PKEY *pkey, DH *key) {
+ evp_pkey_set_method(pkey, &dh_asn1_meth);
+ pkey->pkey = key;
+ return key != NULL;
+}
+
+DH *EVP_PKEY_get0_DH(const EVP_PKEY *pkey) {
+ if (pkey->type != EVP_PKEY_DH) {
+ OPENSSL_PUT_ERROR(EVP, EVP_R_EXPECTING_A_DH_KEY);
+ return NULL;
+ }
+ return pkey->pkey;
+}
+
+DH *EVP_PKEY_get1_DH(const EVP_PKEY *pkey) {
+ DH *dh = EVP_PKEY_get0_DH(pkey);
+ if (dh != NULL) {
+ DH_up_ref(dh);
+ }
+ return dh;
+}
diff --git a/include/openssl/evp.h b/include/openssl/evp.h
index 93b2eb3..43180f2 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h
@@ -167,6 +167,11 @@
OPENSSL_EXPORT EC_KEY *EVP_PKEY_get0_EC_KEY(const EVP_PKEY *pkey);
OPENSSL_EXPORT EC_KEY *EVP_PKEY_get1_EC_KEY(const EVP_PKEY *pkey);
+OPENSSL_EXPORT int EVP_PKEY_set1_DH(EVP_PKEY *pkey, DH *key);
+OPENSSL_EXPORT int EVP_PKEY_assign_DH(EVP_PKEY *pkey, DH *key);
+OPENSSL_EXPORT DH *EVP_PKEY_get0_DH(const EVP_PKEY *pkey);
+OPENSSL_EXPORT DH *EVP_PKEY_get1_DH(const EVP_PKEY *pkey);
+
#define EVP_PKEY_NONE NID_undef
#define EVP_PKEY_RSA NID_rsaEncryption
#define EVP_PKEY_RSA_PSS NID_rsassaPss
@@ -175,6 +180,7 @@
#define EVP_PKEY_ED25519 NID_ED25519
#define EVP_PKEY_X25519 NID_X25519
#define EVP_PKEY_HKDF NID_hkdf
+#define EVP_PKEY_DH NID_dhKeyAgreement
// EVP_PKEY_set_type sets the type of |pkey| to |type|. It returns one if
// successful or zero if the |type| argument is not one of the |EVP_PKEY_*|
@@ -810,11 +816,23 @@
int nid);
-// Deprecated functions.
+// Diffie-Hellman-specific control functions.
-// EVP_PKEY_DH is defined for compatibility, but it is impossible to create an
-// |EVP_PKEY| of that type.
-#define EVP_PKEY_DH NID_dhKeyAgreement
+// EVP_PKEY_CTX_set_dh_pad configures configures whether |ctx|, which must be an
+// |EVP_PKEY_derive| operation, configures the handling of leading zeros in the
+// Diffie-Hellman shared secret. If |pad| is zero, leading zeros are removed
+// from the secret. If |pad| is non-zero, the fixed-width shared secret is used
+// unmodified, as in PKCS #3. If this function is not called, the default is to
+// remove leading zeros.
+//
+// WARNING: The behavior when |pad| is zero leaks information about the shared
+// secret. This may result in side channel attacks such as
+// https://raccoon-attack.com/, particularly when the same private key is used
+// for multiple operations.
+OPENSSL_EXPORT int EVP_PKEY_CTX_set_dh_pad(EVP_PKEY_CTX *ctx, int pad);
+
+
+// Deprecated functions.
// EVP_PKEY_RSA2 was historically an alternate form for RSA public keys (OID
// 2.5.8.1.1), but is no longer accepted.
@@ -913,12 +931,6 @@
OPENSSL_EXPORT EVP_PKEY *d2i_PublicKey(int type, EVP_PKEY **out,
const uint8_t **inp, long len);
-// EVP_PKEY_get0_DH returns NULL.
-OPENSSL_EXPORT DH *EVP_PKEY_get0_DH(const EVP_PKEY *pkey);
-
-// EVP_PKEY_get1_DH returns NULL.
-OPENSSL_EXPORT DH *EVP_PKEY_get1_DH(const EVP_PKEY *pkey);
-
// EVP_PKEY_CTX_set_ec_param_enc returns one if |encoding| is
// |OPENSSL_EC_NAMED_CURVE| or zero with an error otherwise.
OPENSSL_EXPORT int EVP_PKEY_CTX_set_ec_param_enc(EVP_PKEY_CTX *ctx,
diff --git a/include/openssl/evp_errors.h b/include/openssl/evp_errors.h
index 8583f52..163f17e 100644
--- a/include/openssl/evp_errors.h
+++ b/include/openssl/evp_errors.h
@@ -95,5 +95,6 @@
#define EVP_R_NOT_XOF_OR_INVALID_LENGTH 135
#define EVP_R_EMPTY_PSK 136
#define EVP_R_INVALID_BUFFER_SIZE 137
+#define EVP_R_EXPECTING_A_DH_KEY 138
#endif // OPENSSL_HEADER_EVP_ERRORS_H