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