]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
HPKE base mode with single-shot API
authorJouni Malinen <quic_jouni@quicinc.com>
Sun, 17 Jul 2022 20:33:11 +0000 (23:33 +0300)
committerJouni Malinen <j@w1.fi>
Mon, 18 Jul 2022 13:49:45 +0000 (16:49 +0300)
Add support for HPKE base mode with single-shot API (see RFC 9180) using
OpenSSL. This is needed for DPP private introduction protocol.

Signed-off-by: Jouni Malinen <quic_jouni@quicinc.com>
src/crypto/crypto.h
src/crypto/crypto_module_tests.c
src/crypto/crypto_openssl.c

index 67210a04758c87318c54b916975370ea01a5f45d..91df6079d4ba8ba61320cf75756f1147a1db64b9 100644 (file)
@@ -1010,6 +1010,16 @@ size_t crypto_ecdh_prime_len(struct crypto_ecdh *ecdh);
  */
 struct crypto_ec_key * crypto_ec_key_parse_priv(const u8 *der, size_t der_len);
 
+/**
+ * crypto_ec_key_set_priv - Initialize EC key pair from raw key data
+ * @group: Identifying number for the ECC group
+ * @raw: Raw key data
+ * @raw_len: Length of @raw buffer
+ * Returns: EC key or %NULL on failure
+ */
+struct crypto_ec_key * crypto_ec_key_set_priv(int group,
+                                             const u8 *raw, size_t raw_len);
+
 /**
  * crypto_ec_key_parse_pub - Initialize EC key pair from SubjectPublicKeyInfo ASN.1
  * @der: DER encoding of ASN.1 SubjectPublicKeyInfo
@@ -1313,6 +1323,58 @@ struct wpabuf * crypto_rsa_oaep_sha256_decrypt(struct crypto_rsa_key *key,
  */
 void crypto_rsa_key_free(struct crypto_rsa_key *key);
 
+enum hpke_mode {
+       HPKE_MODE_BASE = 0x00,
+       HPKE_MODE_PSK = 0x01,
+       HPKE_MODE_AUTH = 0x02,
+       HPKE_MODE_AUTH_PSK = 0x03,
+};
+
+enum hpke_kem_id {
+       HPKE_DHKEM_P256_HKDF_SHA256 = 0x0010,
+       HPKE_DHKEM_P384_HKDF_SHA384 = 0x0011,
+       HPKE_DHKEM_P521_HKDF_SHA512 = 0x0012,
+       HPKE_DHKEM_X5519_HKDF_SHA256 = 0x0020,
+       HPKE_DHKEM_X448_HKDF_SHA512 = 0x0021,
+};
+
+enum hpke_kdf_id {
+       HPKE_KDF_HKDF_SHA256 = 0x0001,
+       HPKE_KDF_HKDF_SHA384 = 0x0002,
+       HPKE_KDF_HKDF_SHA512 = 0x0003,
+};
+
+enum hpke_aead_id {
+       HPKE_AEAD_AES_128_GCM = 0x0001,
+       HPKE_AEAD_AES_256_GCM = 0x0002,
+       HPKE_AEAD_CHACHA20POLY1305 = 0x0003,
+};
+
+/**
+ * hpke_base_seal - HPKE base mode single-shot encrypt
+ * Returns: enc | ct; or %NULL on failure
+ */
+struct wpabuf * hpke_base_seal(enum hpke_kem_id kem_id,
+                              enum hpke_kdf_id kdf_id,
+                              enum hpke_aead_id aead_id,
+                              struct crypto_ec_key *peer_pub,
+                              const u8 *info, size_t info_len,
+                              const u8 *aad, size_t aad_len,
+                              const u8 *pt, size_t pt_len);
+
+/**
+ * hpke_base_open - HPKE base mode single-shot decrypt
+ * @enc_ct: enc | ct
+ * Returns: pt; or %NULL on failure
+ */
+struct wpabuf * hpke_base_open(enum hpke_kem_id kem_id,
+                              enum hpke_kdf_id kdf_id,
+                              enum hpke_aead_id aead_id,
+                              struct crypto_ec_key *own_priv,
+                              const u8 *info, size_t info_len,
+                              const u8 *aad, size_t aad_len,
+                              const u8 *enc_ct, size_t enc_ct_len);
+
 /**
  * crypto_unload - Unload crypto resources
  *
index fafb688b45f6105edb12a339406fa1d64e8139d9..4147f414e5a953724d324822d467481226dc8d9b 100644 (file)
@@ -2190,6 +2190,285 @@ static int test_extract_expand_hkdf(void)
 }
 
 
+#ifdef CONFIG_DPP3
+
+static const struct hpke_test {
+       const char *name;
+       enum hpke_mode mode;
+       enum hpke_kem_id kem_id;
+       enum hpke_kdf_id kdf_id;
+       enum hpke_aead_id aead_id;
+       const char *info;
+       int sk_r_group;
+       const char *pk_r;
+       const char *sk_r;
+       const char *enc;
+       const char *pt;
+       const char *aad;
+       const char *ct;
+} hpke_tests[] = {
+       {
+               .name = "A.3. DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P256_HKDF_SHA256,
+               .kdf_id = HPKE_KDF_HKDF_SHA256,
+               .aead_id = HPKE_AEAD_AES_128_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 19,
+               .pk_r = "04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0",
+               .sk_r = "f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2",
+               .enc = "04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "5ad590bb8baa577f8619db35a36311226a896e7342a6d836d8b7bcd2f20b6c7f9076ac232e3ab2523f39513434",
+       },
+       {
+               .name = "A.4. DHKEM(P-256, HKDF-SHA256), HKDF-SHA512, AES-128-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P256_HKDF_SHA256,
+               .kdf_id = HPKE_KDF_HKDF_SHA512,
+               .aead_id = HPKE_AEAD_AES_128_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 19,
+               .pk_r = "04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610edd",
+               .sk_r = "3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38",
+               .enc = "0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "d3cf4984931484a080f74c1bb2a6782700dc1fef9abe8442e44a6f09044c88907200b332003543754eb51917ba",
+       },
+       {
+               .name = "A.6. DHKEM(P-521, HKDF-SHA512), HKDF-SHA512, AES-256-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P521_HKDF_SHA512,
+               .kdf_id = HPKE_KDF_HKDF_SHA512,
+               .aead_id = HPKE_AEAD_AES_256_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 21,
+               .pk_r = "0401b45498c1714e2dce167d3caf162e45e0642afc7ed435df7902ccae0e84ba0f7d373f646b7738bbbdca11ed91bdeae3cdcba3301f2457be452f271fa6837580e661012af49583a62e48d44bed350c7118c0d8dc861c238c72a2bda17f64704f464b57338e7f40b60959480c0e58e6559b190d81663ed816e523b6b6a418f66d2451ec64",
+               .sk_r = "01462680369ae375e4b3791070a7458ed527842f6a98a79ff5e0d4cbde83c27196a3916956655523a6a2556a7af62c5cadabe2ef9da3760bb21e005202f7b2462847",
+               .enc = "040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "170f8beddfe949b75ef9c387e201baf4132fa7374593dfafa90768788b7b2b200aafcc6d80ea4c795a7c5b841a",
+       },
+       { /* self-generated test vector for P-384 */
+               .name = "custom DHKEM(P-384, HKDF-SHA384), HKDF-SHA384, AES-256-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P384_HKDF_SHA384,
+               .kdf_id = HPKE_KDF_HKDF_SHA384,
+               .aead_id = HPKE_AEAD_AES_256_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 20,
+               .pk_r = "049c0e4dcbbb3c80715cafaa1839d0bc3c3adcc95eb8062f84175f9c3cec115e6b799061c65a0605907785c25b3571564706a8ba6a204452b38c7c205db17d328f2353df05d5f1c568e7503331178c36c2d37bbed48401295407face3f8dae5ed8",
+               .sk_r = "cabffb07d20ffcfdaa043e1de49e1654659e0f0aba5de56523e8b73dc80c579a9e5c89ed3810ec21c4bafcf74ad2a245",
+               .enc = "04b30bea96d0e51582033b02a4d676d0464a5eb2d858be86cda1c4e6f8b2aa9fb80f5365483f781b1b3a8b3b8efd50b0f7bca16f06d0435fa3da1d671ea0a318b40fe170a074923c651e5dc824966b7b98d0e36bdf932875dae7130369a793cecc",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "ae7feccfea0f8fcd620d15369a28db8701cdc90d55c20efff6296bd441697b0da34671d1f3c4864183e86d27fc",
+       },
+       { /* self-generated test vector for BP-256 */
+               .name = "custom PB-256 using DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P256_HKDF_SHA256,
+               .kdf_id = HPKE_KDF_HKDF_SHA256,
+               .aead_id = HPKE_AEAD_AES_128_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 28,
+               .pk_r = "04a2cb9c4cae90cdc1c27516e9f84b6b166e4b1dcc517286268239ddb0bf74cca6390fed092ac4423ab2192b8bb41a4824d908d2053b93fc813830bebac5ce19b9",
+               .sk_r = "11d9db41c4341166ca52f5a1775595c0bdb4934350daeb7bce659c4b7a40e314",
+               .enc = "047a25e309c7ee50ec27f13d44734a3ccd8c703e3affcc728513df416511ef9bf02f5e7750e7415de8b5f306ebd3fc88ea9b9368523eb1733a8d82c1a877e5a0f4",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "17c84b3f07f6ffe08ff2be45c709ea782229504aa5b2253876725c6c39f8d8c992304fc5877994f79d6c10d462",
+       },
+       { /* self-generated test vector for BP-384 */
+               .name = "custom PB-384 using DHKEM(P-384, HKDF-SHA384), HKDF-SHA384, AES-256-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P384_HKDF_SHA384,
+               .kdf_id = HPKE_KDF_HKDF_SHA384,
+               .aead_id = HPKE_AEAD_AES_256_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 29,
+               .pk_r = "041f4199ad28835908079c45d165d55630098be53eb4beede9921f5b2204fa396111f99ac54c56411f7cb2c43ec18d8e604d895027228cf975f5a4b598f189d8fb03e3fefe020258c40d4d1b15fd7587d209925d67a41f9659a8ed6f662fb441e4",
+               .sk_r = "7017cf8a5a9a81ad4e0d755ccbea27a378b787561f8d5662639850805fefcbaab6b9a15729872abb7dc53d19a6cf77e4",
+               .enc = "0415d49dedc5bc1ffe9f8de9022c266bb605ec6cd7b77b6ce68974095398856f8aefa4b7abbfbd496b99a2dda3a9c65f1a71b9d40255aa1c7c4205a8b4ef611b96ed29fd2d7b0cde4c0e82058805e6276025cc4fc606f6e5771c31bd9704e9ba0b",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "5f5e9f82bedadec0e9b01a1b304cb48b05c0d6d397b1c8a95ed541218ec54f634a41cbc4066910a409e47b254e",
+       },
+       { /* self-generated test vector for BP-512 */
+               .name = "custom PB-512 using DHKEM(P-521, HKDF-SHA512), HKDF-SHA512, AES-256-GCM",
+               .mode = HPKE_MODE_BASE,
+               .kem_id = HPKE_DHKEM_P521_HKDF_SHA512,
+               .kdf_id = HPKE_KDF_HKDF_SHA512,
+               .aead_id = HPKE_AEAD_AES_256_GCM,
+               .info = "4f6465206f6e2061204772656369616e2055726e",
+               .sk_r_group = 30,
+               .pk_r = "049e81046a531365a3b5215ac37e7b38f5fa34f86c4eb2e03113b197390a26c555bb007596e131c2541f336eb24a45f44283b5b53fedddfa5642675602fdec17d34120a35efffb44952e32dee7732f2f3245c3314269996b610703a63fb8555a75ca5092690a1125ae8712c1e31fd77aee42bd052e71f9f9459814d6f4065bcea0",
+               .sk_r = "483b6882608182b296843fa7dfffbdd61ed0372574d4aa32a035c8e33a493927aaf00d42bd9124ebe4df26010b38124668c02b35a749e74845d565734310cfe9",
+               .enc = "04158d18473aeb3b283d3345b1a87d3de2b192ff9e41b5a98f91daacfb24be72e698cbc04c33078681e507bf346c0ea70c927083a22ca9ea027f420067ee42285b798d95fea51002d097ce28371883202bfd300fb64943669e32c6f1a348087368bb480b757892ebd199a9389978c92cbc44076626d705a771fbbd90c030a6767e",
+               .pt = "4265617574792069732074727574682c20747275746820626561757479",
+               .aad = "436f756e742d30",
+               .ct = "033d91c4514857da5b833635180c1acc09f175cbf44777a7b71e177705cfd17437b1c85d671dd767bb4fe20e2e",
+       },
+};
+
+
+static int run_hpke_test(const struct hpke_test *test)
+{
+       struct wpabuf *info, *pk_r, *sk_r, *enc, *pt, *aad, *ct;
+       struct wpabuf *res_pt = NULL, *enc_ct = NULL, *res_ct = NULL;
+       struct crypto_ec_key *own_priv = NULL, *peer_pub = NULL;
+       int res = -1;
+       size_t coord_len;
+
+       wpa_printf(MSG_INFO, "- %s", test->name);
+
+       info = wpabuf_parse_bin(test->info);
+       pk_r = wpabuf_parse_bin(test->pk_r);
+       sk_r = wpabuf_parse_bin(test->sk_r);
+       enc = wpabuf_parse_bin(test->enc);
+       pt = wpabuf_parse_bin(test->pt);
+       aad = wpabuf_parse_bin(test->aad);
+       ct = wpabuf_parse_bin(test->ct);
+       if (!info || !pk_r || !sk_r || !enc || !pt || !aad || !ct) {
+               wpa_printf(MSG_ERROR, "Could not parse test data");
+               goto fail;
+       }
+
+       /* Receiver - decryption against the test vector */
+
+       enc_ct = wpabuf_concat(enc, ct);
+       enc = NULL;
+       ct = NULL;
+       if (!enc_ct)
+               goto fail;
+
+       own_priv = crypto_ec_key_set_priv(test->sk_r_group, wpabuf_head(sk_r),
+                                         wpabuf_len(sk_r));
+       if (!own_priv) {
+               wpa_printf(MSG_ERROR,
+                          "HPKE base open - failed to set private key");
+               goto fail;
+       }
+
+       res_pt = hpke_base_open(test->kem_id, test->kdf_id, test->aead_id,
+                               own_priv,
+                               wpabuf_head(info), wpabuf_len(info),
+                               wpabuf_head(aad), wpabuf_len(aad),
+                               wpabuf_head(enc_ct), wpabuf_len(enc_ct));
+       if (!res_pt) {
+               wpa_printf(MSG_ERROR, "HPKE base open - failed to decrypt");
+               wpa_hexdump_buf(MSG_INFO, "pt", res_pt);
+               goto fail;
+       }
+       if (wpabuf_len(res_pt) != wpabuf_len(pt) ||
+           os_memcmp(wpabuf_head(res_pt), wpabuf_head(pt),
+                     wpabuf_len(pt)) != 0) {
+               wpa_printf(MSG_ERROR,
+                          "HPKE base open - failed - decryption mismatch");
+               goto fail;
+       }
+
+       /* Sender - encryption (randomized algorithm) */
+
+       if (test->sk_r_group == 19)
+               coord_len = 32;
+       else if (test->sk_r_group == 20)
+               coord_len = 48;
+       else if (test->sk_r_group == 21)
+               coord_len = 66;
+       else if (test->sk_r_group == 28)
+               coord_len = 32;
+       else if (test->sk_r_group == 29)
+               coord_len = 48;
+       else if (test->sk_r_group == 30)
+               coord_len = 64;
+       else
+               goto fail;
+       if (wpabuf_len(pk_r) != 1 + 2 * coord_len) {
+               wpa_printf(MSG_ERROR, "Unexpected pkR length (%zu != %zu)",
+                          wpabuf_len(pk_r), 1 + 2 * coord_len);
+               goto fail;
+       }
+       peer_pub = crypto_ec_key_set_pub(test->sk_r_group,
+                                        wpabuf_head_u8(pk_r) + 1,
+                                        wpabuf_head_u8(pk_r) + 1 + coord_len,
+                                        coord_len);
+       if (!peer_pub) {
+               wpa_printf(MSG_ERROR,
+                          "HPKE base open - failed to set public key");
+               goto fail;
+       }
+
+       res_ct = hpke_base_seal(test->kem_id, test->kdf_id, test->aead_id,
+                               peer_pub,
+                               wpabuf_head(info), wpabuf_len(info),
+                               wpabuf_head(aad), wpabuf_len(aad),
+                               wpabuf_head(pt), wpabuf_len(pt));
+       if (!res_ct) {
+               wpa_printf(MSG_ERROR, "HPKE base open - failed to encrypt");
+               goto fail;
+       }
+
+       /* Receiver - decryption (to verify own encryption) */
+
+       wpabuf_free(res_pt);
+       res_pt = hpke_base_open(test->kem_id, test->kdf_id, test->aead_id,
+                               own_priv,
+                               wpabuf_head(info), wpabuf_len(info),
+                               wpabuf_head(aad), wpabuf_len(aad),
+                               wpabuf_head(res_ct), wpabuf_len(res_ct));
+       if (!res_pt) {
+               wpa_printf(MSG_ERROR, "HPKE base open - failed to decrypt own encrypted version");
+               goto fail;
+       }
+       if (wpabuf_len(res_pt) != wpabuf_len(pt) ||
+           os_memcmp(wpabuf_head(res_pt), wpabuf_head(pt),
+                     wpabuf_len(pt)) != 0) {
+               wpa_printf(MSG_ERROR,
+                          "HPKE base open - failed - decryption mismatch for own encrypted version");
+               wpa_hexdump_buf(MSG_INFO, "pt", res_pt);
+               goto fail;
+       }
+
+       res = 0;
+fail:
+       wpabuf_free(info);
+       wpabuf_free(pk_r);
+       wpabuf_free(sk_r);
+       wpabuf_free(enc);
+       wpabuf_free(pt);
+       wpabuf_free(aad);
+       wpabuf_free(ct);
+       wpabuf_free(enc_ct);
+       wpabuf_free(res_pt);
+       wpabuf_free(res_ct);
+       crypto_ec_key_deinit(own_priv);
+       return res;
+}
+
+#endif /* CONFIG_DPP3 */
+
+
+static int test_hpke(void)
+{
+#ifdef CONFIG_DPP3
+       unsigned int i;
+
+       wpa_printf(MSG_INFO, "RFC 9180 - HPKE");
+       for (i = 0; i < ARRAY_SIZE(hpke_tests); i++) {
+               if (run_hpke_test(&hpke_tests[i]) < 0)
+                       return -1;
+       }
+
+       wpa_printf(MSG_INFO, "HPKE base open test cases passed");
+#endif /* CONFIG_DPP3 */
+       return 0;
+}
+
+
 static int test_ms_funcs(void)
 {
 #ifndef CONFIG_FIPS
@@ -2310,6 +2589,7 @@ int crypto_module_tests(void)
            test_sha384() ||
            test_fips186_2_prf() ||
            test_extract_expand_hkdf() ||
+           test_hpke() ||
            test_ms_funcs())
                ret = -1;
 
index 153a1f7aa4cc3914a1028dd47864577d87b95c1d..b9fc1106357f4ca12c3d4cf41560898df915e200 100644 (file)
@@ -3039,6 +3039,141 @@ fail:
 }
 
 
+struct crypto_ec_key * crypto_ec_key_set_priv(int group,
+                                             const u8 *raw, size_t raw_len)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+       const char *group_name;
+       OSSL_PARAM params[4];
+       EVP_PKEY_CTX *ctx = NULL;
+       EVP_PKEY *pkey = NULL;
+       BIGNUM *priv;
+       EC_POINT *pub = NULL;
+       EC_GROUP *ec_group = NULL;
+       size_t len;
+       u8 *pub_bin = NULL;
+       u8 *priv_bin = NULL;
+       int priv_bin_len;
+
+       group_name = crypto_ec_group_2_name(group);
+       if (!group_name)
+               return NULL;
+
+       priv = BN_bin2bn(raw, raw_len, NULL);
+       if (!priv)
+               return NULL;
+       priv_bin = os_malloc(raw_len);
+       if (!priv_bin)
+               goto fail;
+       priv_bin_len = BN_bn2lebinpad(priv, priv_bin, raw_len);
+       if (priv_bin_len < 0)
+               goto fail;
+
+       ec_group = EC_GROUP_new_by_curve_name(crypto_ec_group_2_nid(group));
+       if (!ec_group)
+               goto fail;
+       pub = EC_POINT_new(ec_group);
+       if (!pub ||
+           EC_POINT_mul(ec_group, pub, priv, NULL, NULL, NULL) != 1)
+               goto fail;
+       len = EC_POINT_point2oct(ec_group, pub, POINT_CONVERSION_UNCOMPRESSED,
+                                NULL, 0, NULL);
+       if (len == 0)
+               goto fail;
+       pub_bin = os_malloc(len);
+       if (!pub_bin)
+               goto fail;
+       len = EC_POINT_point2oct(ec_group, pub, POINT_CONVERSION_UNCOMPRESSED,
+                                pub_bin, len, NULL);
+       if (len == 0)
+               goto fail;
+
+       params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+                                                    (char *) group_name, 0);
+       params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_PRIV_KEY,
+                                           priv_bin, priv_bin_len);
+       params[2] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY,
+                                                     pub_bin, len);
+       params[3] = OSSL_PARAM_construct_end();
+
+       ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+       if (!ctx ||
+           EVP_PKEY_fromdata_init(ctx) <= 0 ||
+           EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0)
+               goto fail;
+
+out:
+       bin_clear_free(priv_bin, raw_len);
+       os_free(pub_bin);
+       BN_clear_free(priv);
+       EVP_PKEY_CTX_free(ctx);
+       EC_POINT_free(pub);
+       EC_GROUP_free(ec_group);
+       return (struct crypto_ec_key *) pkey;
+
+fail:
+       EVP_PKEY_free(pkey);
+       pkey = NULL;
+       goto out;
+#else /* OpenSSL version >= 3.0 */
+       EC_KEY *eckey = NULL;
+       EVP_PKEY *pkey = NULL;
+       BIGNUM *priv = NULL;
+       int nid;
+       const EC_GROUP *ec_group;
+       EC_POINT *pub = NULL;
+
+       nid = crypto_ec_group_2_nid(group);
+       if (nid < 0) {
+               wpa_printf(MSG_ERROR, "OpenSSL: Unsupported group %d", group);
+               return NULL;
+       }
+
+       eckey = EC_KEY_new_by_curve_name(nid);
+       priv = BN_bin2bn(raw, raw_len, NULL);
+       if (!eckey || !priv ||
+           EC_KEY_set_private_key(eckey, priv) != 1) {
+               wpa_printf(MSG_ERROR,
+                          "OpenSSL: Failed to set EC_KEY: %s",
+                          ERR_error_string(ERR_get_error(), NULL));
+               goto fail;
+       }
+
+       ec_group = EC_KEY_get0_group(eckey);
+       if (!ec_group)
+               goto fail;
+       pub = EC_POINT_new(ec_group);
+       if (!pub ||
+           EC_POINT_mul(ec_group, pub, priv, NULL, NULL, NULL) != 1 ||
+           EC_KEY_set_public_key(eckey, pub) != 1) {
+               wpa_printf(MSG_ERROR,
+                          "OpenSSL: Failed to set EC_KEY(pub): %s",
+                          ERR_error_string(ERR_get_error(), NULL));
+               goto fail;
+       }
+
+       EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+
+       pkey = EVP_PKEY_new();
+       if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, eckey) != 1) {
+               wpa_printf(MSG_ERROR, "OpenSSL: Could not create EVP_PKEY");
+               goto fail;
+       }
+
+out:
+       BN_clear_free(priv);
+       EC_POINT_free(pub);
+       return (struct crypto_ec_key *) pkey;
+
+fail:
+       EC_KEY_free(eckey);
+       EVP_PKEY_free(pkey);
+       pkey = NULL;
+       goto out;
+#endif /* OpenSSL version >= 3.0 */
+}
+
+
 struct crypto_ec_key * crypto_ec_key_parse_pub(const u8 *der, size_t der_len)
 {
        EVP_PKEY *pkey;
@@ -4431,6 +4566,769 @@ void crypto_rsa_key_free(struct crypto_rsa_key *key)
 }
 
 
+#ifdef CONFIG_DPP3
+
+#define HPKE_MAX_SHARED_SECRET_LEN 66
+#define HPKE_MAX_HASH_LEN 64
+#define HPKE_MAX_KEY_LEN 32
+#define HPKE_MAX_NONCE_LEN 12
+#define HPKE_MAX_PUB_LEN (1 + 2 * 66)
+
+struct hpke_context {
+       /* KEM */
+       enum hpke_kem_id kem_id;
+       int kem_nid;
+       int iana_group;
+       size_t n_pk;
+       size_t n_secret;
+       const EVP_MD *kem_h;
+       size_t kem_n_h;
+
+       /* KDF */
+       enum hpke_kdf_id kdf_id;
+       const EVP_MD *kdf_h;
+       size_t n_h;
+
+       /* AEAD */
+       enum hpke_aead_id aead_id;
+       const EVP_CIPHER *cipher;
+       size_t n_k;
+       size_t n_n;
+       size_t n_t;
+       u8 key[HPKE_MAX_KEY_LEN];
+       u8 base_nonce[HPKE_MAX_NONCE_LEN];
+};
+
+
+static void hpke_free_context(struct hpke_context *ctx)
+{
+       bin_clear_free(ctx, sizeof(*ctx));
+}
+
+
+static struct hpke_context * hpke_get_context(enum hpke_kem_id kem_id,
+                                             enum hpke_kdf_id kdf_id,
+                                             enum hpke_aead_id aead_id,
+                                             struct crypto_ec_key *key)
+{
+       struct hpke_context *ctx;
+       int group;
+
+       ctx = os_zalloc(sizeof(*ctx));
+       if (!ctx)
+               return NULL;
+
+       ctx->kem_id = kem_id;
+       switch (kem_id) {
+       case HPKE_DHKEM_P256_HKDF_SHA256:
+               ctx->kem_nid = NID_X9_62_prime256v1;
+               ctx->iana_group = 19;
+               ctx->n_pk = 65;
+               ctx->n_secret = 32;
+               ctx->kem_h = EVP_sha256();
+               ctx->kem_n_h = 32;
+               break;
+       case HPKE_DHKEM_P384_HKDF_SHA384:
+               ctx->kem_nid = NID_secp384r1;
+               ctx->iana_group = 20;
+               ctx->n_pk = 97;
+               ctx->n_secret = 48;
+               ctx->kem_h = EVP_sha384();
+               ctx->kem_n_h = 48;
+               break;
+       case HPKE_DHKEM_P521_HKDF_SHA512:
+               ctx->kem_nid = NID_secp521r1;
+               ctx->iana_group = 21;
+               ctx->n_pk = 133;
+               ctx->n_secret = 64;
+               ctx->kem_h = EVP_sha512();
+               ctx->kem_n_h = 64;
+               break;
+       default:
+               goto fail;
+       }
+
+       ctx->kdf_id = kdf_id;
+       switch (kdf_id) {
+       case HPKE_KDF_HKDF_SHA256:
+               ctx->kdf_h = EVP_sha256();
+               ctx->n_h = 32;
+               break;
+       case HPKE_KDF_HKDF_SHA384:
+               ctx->kdf_h = EVP_sha384();
+               ctx->n_h = 48;
+               break;
+       case HPKE_KDF_HKDF_SHA512:
+               ctx->kdf_h = EVP_sha512();
+               ctx->n_h = 64;
+               break;
+       default:
+               goto fail;
+       }
+
+       ctx->aead_id = aead_id;
+       switch (aead_id) {
+       case HPKE_AEAD_AES_128_GCM:
+               ctx->cipher = EVP_aes_128_gcm();
+               ctx->n_k = 16;
+               ctx->n_n = 12;
+               ctx->n_t = 16;
+               break;
+       case HPKE_AEAD_AES_256_GCM:
+               ctx->cipher = EVP_aes_256_gcm();
+               ctx->n_k = 32;
+               ctx->n_n = 12;
+               ctx->n_t = 16;
+               break;
+       default:
+               goto fail;
+       }
+
+       /* Convert BP-256/384/512 to P-256/384/521 for DPP */
+       group = crypto_ec_key_group(key);
+       if (group == 28 && ctx->iana_group == 19) {
+               ctx->iana_group = 28;
+       } else if (group == 29 && ctx->iana_group == 20) {
+               ctx->iana_group = 29;
+       } else if (group == 30 && ctx->iana_group == 21) {
+               ctx->iana_group = 30;
+               ctx->n_pk = 129;
+       }
+       if (group != ctx->iana_group) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:group mismatch (%d != %d)",
+                          __func__, group, ctx->iana_group);
+               goto fail;
+       }
+
+       return ctx;
+fail:
+       hpke_free_context(ctx);
+       return NULL;
+}
+
+
+static size_t hpke_suite_id(struct hpke_context *ctx, bool kem, u8 *suite_id)
+{
+       size_t suite_id_len;
+
+       if (kem) {
+               os_memcpy(suite_id, "KEM", 3);
+               WPA_PUT_BE16(&suite_id[3], ctx->kem_id);
+               suite_id_len = 5;
+       } else {
+               os_memcpy(suite_id, "HPKE", 4);
+               WPA_PUT_BE16(&suite_id[4], ctx->kem_id);
+               WPA_PUT_BE16(&suite_id[6], ctx->kdf_id);
+               WPA_PUT_BE16(&suite_id[8], ctx->aead_id);
+               suite_id_len = 10;
+       }
+       return suite_id_len;
+}
+
+
+static int hpke_labeled_extract(struct hpke_context *ctx, bool kem,
+                               const u8 *salt, size_t salt_len,
+                               const char *label,
+                               const u8 *ikm, size_t ikm_len, u8 *prk)
+{
+       u8 zero[HPKE_MAX_HASH_LEN];
+       u8 suite_id[10];
+       size_t suite_id_len;
+       unsigned int mdlen = kem ? ctx->kem_n_h : ctx->n_h;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+       EVP_MAC *hmac;
+       OSSL_PARAM params[2];
+       EVP_MAC_CTX *hctx;
+       size_t mlen;
+       int res;
+#else /* OpenSSL version >= 3.0 */
+       HMAC_CTX *hctx;
+       int res;
+#endif /* OpenSSL version >= 3.0 */
+
+       if (!salt || !salt_len) {
+               salt_len = mdlen;
+               os_memset(zero, 0, salt_len);
+               salt = zero;
+       }
+
+       suite_id_len = hpke_suite_id(ctx, kem, suite_id);
+
+       /* labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
+        * return Extract(salt, labeled_ikm) */
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+       hmac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+       if (!hmac)
+               return -1;
+
+       params[0] = OSSL_PARAM_construct_utf8_string(
+               "digest",
+               (char *) EVP_MD_get0_name(kem ? ctx->kem_h : ctx->kdf_h), 0);
+       params[1] = OSSL_PARAM_construct_end();
+
+       hctx = EVP_MAC_CTX_new(hmac);
+       EVP_MAC_free(hmac);
+       if (!hctx)
+               return -1;
+
+       if (EVP_MAC_init(hctx, salt, salt_len, params) != 1)
+               goto fail;
+
+       if (EVP_MAC_update(hctx, (const unsigned char *) "HPKE-v1", 7) != 1 ||
+           EVP_MAC_update(hctx, suite_id, suite_id_len) != 1 ||
+           EVP_MAC_update(hctx, (const unsigned char *) label,
+                          os_strlen(label)) != 1 ||
+           EVP_MAC_update(hctx, ikm, ikm_len) != 1)
+               goto fail;
+
+       res = EVP_MAC_final(hctx, prk, &mlen, mdlen);
+       EVP_MAC_CTX_free(hctx);
+
+       return res == 1 ? 0 : -1;
+fail:
+       EVP_MAC_CTX_free(hctx);
+       return -1;
+#else /* OpenSSL version >= 3.0 */
+       hctx = HMAC_CTX_new();
+       if (!hctx)
+               return -1;
+       res = HMAC_Init_ex(hctx, salt, salt_len, kem ? ctx->kem_h : ctx->kdf_h,
+                          NULL);
+       if (res != 1)
+               goto done;
+
+       HMAC_Update(hctx, (const unsigned char *) "HPKE-v1", 7);
+       HMAC_Update(hctx, suite_id, suite_id_len);
+       HMAC_Update(hctx, (const unsigned char *) label, os_strlen(label));
+       HMAC_Update(hctx, ikm, ikm_len);
+
+       res = HMAC_Final(hctx, prk, &mdlen);
+done:
+       HMAC_CTX_free(hctx);
+
+       return res == 1 ? 0 : -1;
+#endif /* OpenSSL version >= 3.0 */
+}
+
+
+static int
+hpke_labeled_expand(struct hpke_context *ctx, bool kem, const u8 *prk,
+                   const char *label, const u8 *info, size_t info_len,
+                   u8 *out, size_t out_len)
+{
+       u8 suite_id[10];
+       size_t suite_id_len;
+       u8 hash[HPKE_MAX_HASH_LEN];
+       u8 iter = 0;
+       size_t label_len = os_strlen(label);
+       u8 *pos;
+       size_t left = out_len, clen;
+       int res = -1;
+       u8 *labeled_info;
+       size_t labeled_info_len;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+       EVP_MAC *hmac;
+       OSSL_PARAM params[2];
+       EVP_MAC_CTX *hctx = NULL;
+       size_t mdlen;
+#else /* OpenSSL version >= 3.0 */
+       HMAC_CTX *hctx;
+       unsigned int mdlen;
+#endif /* OpenSSL version >= 3.0 */
+
+       /* labeled_info = concat(I2OSP(L, 2), "HPKE-v1", suite_id,
+        *                       label, info)
+        * return Expand(prk, labeled_info, L) */
+       suite_id_len = hpke_suite_id(ctx, kem, suite_id);
+       labeled_info_len = 2 + 7 + suite_id_len + label_len + info_len;
+       labeled_info = os_malloc(labeled_info_len);
+       if (!labeled_info)
+               return -1;
+       pos = labeled_info;
+       WPA_PUT_BE16(pos, out_len);
+       pos += 2;
+       os_memcpy(pos, "HPKE-v1", 7);
+       pos += 7;
+       os_memcpy(pos, suite_id, suite_id_len);
+       pos += suite_id_len;
+       os_memcpy(pos, label, label_len);
+       pos += label_len;
+       if (info && info_len) {
+               os_memcpy(pos, info, info_len);
+               pos += info_len;
+       }
+
+       pos = out;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+       hmac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+       if (!hmac)
+               return -1;
+
+       params[0] = OSSL_PARAM_construct_utf8_string(
+               "digest",
+               (char *) EVP_MD_get0_name(kem ? ctx->kem_h : ctx->kdf_h), 0);
+       params[1] = OSSL_PARAM_construct_end();
+#else /* OpenSSL version >= 3.0 */
+       hctx = HMAC_CTX_new();
+       if (!hctx)
+               return -1;
+#endif /* OpenSSL version >= 3.0 */
+
+       while (left > 0) {
+               mdlen = kem ? ctx->kem_n_h : ctx->n_h;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+               EVP_MAC_CTX_free(hctx);
+               hctx = EVP_MAC_CTX_new(hmac);
+               if (!hctx)
+                       return -1;
+
+               if (EVP_MAC_init(hctx, prk, mdlen, params) != 1)
+                       goto fail;
+
+               if (iter > 0 && EVP_MAC_update(hctx, hash, mdlen) != 1)
+                       goto fail;
+               if (iter == 255)
+                       goto fail;
+               iter++;
+
+               if (EVP_MAC_update(hctx, labeled_info, labeled_info_len) != 1 ||
+                   EVP_MAC_update(hctx, &iter, sizeof(iter)) != 1)
+                       goto fail;
+
+               if (EVP_MAC_final(hctx, hash, &mdlen, mdlen) != 1)
+                       goto fail;
+#else /* OpenSSL version >= 3.0 */
+               if (HMAC_Init_ex(hctx, prk, mdlen,
+                                kem ? ctx->kem_h : ctx->kdf_h,
+                                NULL) != 1)
+                       goto fail;
+
+               if (iter > 0)
+                       HMAC_Update(hctx, hash, mdlen);
+               if (iter == 255)
+                       goto fail;
+               iter++;
+               HMAC_Update(hctx, labeled_info, labeled_info_len);
+               HMAC_Update(hctx, &iter, sizeof(iter));
+
+               if (HMAC_Final(hctx, hash, &mdlen) != 1)
+                       goto fail;
+               HMAC_CTX_reset(hctx);
+#endif /* OpenSSL version >= 3.0 */
+
+               clen = left > mdlen ? mdlen : left;
+               os_memcpy(pos, hash, clen);
+               pos += clen;
+               left -= clen;
+       }
+       res = 0;
+fail:
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+       EVP_MAC_free(hmac);
+       EVP_MAC_CTX_free(hctx);
+#else /* OpenSSL version >= 3.0 */
+       HMAC_CTX_free(hctx);
+#endif /* OpenSSL version >= 3.0 */
+       os_free(labeled_info);
+
+       return res;
+}
+
+
+static int hpke_extract_and_expand(struct hpke_context *ctx,
+                                  const u8 *dhss, size_t dhss_len,
+                                  const u8 *enc, size_t enc_len,
+                                  const u8 *pk_rm, size_t pk_rm_len,
+                                  u8 *shared_secret)
+{
+       u8 kem_context[2 * HPKE_MAX_PUB_LEN];
+       u8 eae_prk[HPKE_MAX_HASH_LEN];
+
+       /* eae_prk = LabeledExtract("", "eae_prk", dh) */
+       if (hpke_labeled_extract(ctx, true, NULL, 0, "eae_prk", dhss, dhss_len,
+                                eae_prk) < 0)
+               return -1;
+
+       if (enc_len > HPKE_MAX_PUB_LEN || pk_rm_len > HPKE_MAX_PUB_LEN)
+               return -1;
+       /* kem_context = concat(enc, pkRm) */
+       os_memcpy(kem_context, enc, enc_len);
+       os_memcpy(&kem_context[enc_len], pk_rm, pk_rm_len);
+
+       /* shared_secret = LabeledExpand(eae_prk, "shared_secret",
+        *                               kem_context, Nsecret) */
+       if (hpke_labeled_expand(ctx, true, eae_prk, "shared_secret",
+                               kem_context, enc_len + pk_rm_len,
+                               shared_secret, ctx->n_secret) < 0)
+               return -1;
+
+       forced_memzero(eae_prk, sizeof(eae_prk));
+       return 0;
+}
+
+
+static int hpke_key_schedule(struct hpke_context *ctx, const u8 *shared_secret,
+                            const u8 *info, size_t info_len)
+{
+       u8 key_schedule_context[1 + 2 * HPKE_MAX_HASH_LEN];
+       u8 secret[HPKE_MAX_HASH_LEN];
+       int res = -1;
+
+       /* key_schedule_context = concat(mode, psk_id_hash, info_hash) */
+       key_schedule_context[0] = HPKE_MODE_BASE;
+
+       /* psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id) */
+       if (hpke_labeled_extract(ctx, false, NULL, 0, "psk_id_hash",
+                                NULL, 0, &key_schedule_context[1]) < 0)
+               goto fail;
+
+       /* info_hash = LabeledExtract("", "info_hash", info) */
+       if (hpke_labeled_extract(ctx, false, NULL, 0, "info_hash",
+                                info, info_len,
+                                &key_schedule_context[1 + ctx->n_h]) < 0)
+               goto fail;
+
+       /* secret = LabeledExtract(shared_secret, "secret", psk) */
+       if (hpke_labeled_extract(ctx, false, shared_secret, ctx->n_secret,
+                                "secret", NULL, 0, secret) < 0)
+               goto fail;
+
+       /* key = LabeledExpand(secret, "key", key_schedule_context, Nk) */
+       if (hpke_labeled_expand(ctx, false, secret, "key",
+                               key_schedule_context, 1 + 2 * ctx->n_h,
+                               ctx->key, ctx->n_k) < 0)
+               goto fail;
+
+       /* base_nonce = LabeledExpand(secret, "base_nonce",
+        *                            key_schedule_context, Nn) */
+       if (hpke_labeled_expand(ctx, false, secret, "base_nonce",
+                               key_schedule_context, 1 + 2 * ctx->n_h,
+                               ctx->base_nonce, ctx->n_n) < 0)
+               goto fail;
+       res = 0;
+fail:
+       forced_memzero(key_schedule_context, sizeof(key_schedule_context));
+       forced_memzero(secret, sizeof(secret));
+       return res;
+}
+
+
+static int hpke_encap(struct hpke_context *ctx, struct crypto_ec_key *pk_r,
+                     u8 *shared_secret, u8 *enc)
+{
+       EVP_PKEY_CTX *pctx = NULL;
+       struct crypto_ec_key *sk_e;
+       int res = -1;
+       u8 dhss[HPKE_MAX_SHARED_SECRET_LEN];
+       size_t dhss_len;
+       struct wpabuf *enc_buf = NULL, *pk_rm = NULL;
+
+       /* skE, pkE = GenerateKeyPair() */
+       sk_e = crypto_ec_key_gen(ctx->iana_group);
+       if (!sk_e) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:Could not generate key pair",
+                          __func__);
+               goto fail;
+       }
+
+       /* dh = DH(skE, pkR) */
+       pctx = EVP_PKEY_CTX_new((EVP_PKEY *) sk_e, NULL);
+       if (!pctx ||
+           EVP_PKEY_derive_init(pctx) != 1 ||
+           EVP_PKEY_derive_set_peer(pctx, (EVP_PKEY *) pk_r) != 1 ||
+           EVP_PKEY_derive(pctx, NULL, &dhss_len) != 1 ||
+           dhss_len > HPKE_MAX_SHARED_SECRET_LEN ||
+           EVP_PKEY_derive(pctx, dhss, &dhss_len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL: EVP_PKEY_derive failed: %s",
+                          ERR_error_string(ERR_get_error(), NULL));
+               goto fail;
+       }
+
+       /* enc = SerializePublicKey(pkE) */
+       enc_buf = crypto_ec_key_get_pubkey_point(sk_e, 1);
+       if (!enc_buf)
+               goto fail;
+       os_memcpy(enc, wpabuf_head(enc_buf), wpabuf_len(enc_buf));
+
+       /* pkRm = SerializePublicKey(pkR) */
+       pk_rm = crypto_ec_key_get_pubkey_point(pk_r, 1);
+       if (!pk_rm)
+               goto fail;
+
+       /* kem_context = concat(enc, pkRm) */
+       /* shared_secret = ExtractAndExpand(dh, kem_context) */
+       /* return shared_secret, enc */
+       res = hpke_extract_and_expand(ctx, dhss, dhss_len, enc, ctx->n_pk,
+                                     wpabuf_head(pk_rm),
+                                     wpabuf_len(pk_rm), shared_secret);
+fail:
+       forced_memzero(dhss, sizeof(dhss));
+       crypto_ec_key_deinit(sk_e);
+       EVP_PKEY_CTX_free(pctx);
+       wpabuf_free(enc_buf);
+       wpabuf_free(pk_rm);
+       return res;
+}
+
+
+static struct wpabuf *
+hpke_aead_seal(struct hpke_context *ctx, const u8 *aad, size_t aad_len,
+              const u8 *pt, size_t pt_len)
+{
+       EVP_CIPHER_CTX *cctx;
+       int len = 0;
+       struct wpabuf *ct = NULL;
+
+       /* No need to xor in sequence number since we support only the
+        * single-shot API, i.e., base_nonce can be used as-is. */
+
+       cctx = EVP_CIPHER_CTX_new();
+       if (!cctx ||
+           EVP_EncryptInit_ex(cctx, ctx->cipher, NULL, ctx->key,
+                              ctx->base_nonce) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_DecryptInit_ex failed",
+                          __func__);
+               goto fail;
+       }
+       if (aad && aad_len &&
+           EVP_EncryptUpdate(cctx, NULL, &len, aad, aad_len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_EncryptUpdate(AAD) failed",
+                          __func__);
+               goto fail;
+       }
+       ct = wpabuf_alloc(pt_len + AES_BLOCK_SIZE + ctx->n_t);
+       if (!ct)
+               goto fail;
+       if (EVP_EncryptUpdate(cctx, wpabuf_put(ct, 0), &len, pt, pt_len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_EncryptUpdate failed",
+                          __func__);
+               goto fail;
+       }
+       wpabuf_put(ct, len);
+
+       if (EVP_EncryptFinal(cctx, wpabuf_put(ct, 0), &len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_DecryptFinal failed",
+                          __func__);
+               wpabuf_free(ct);
+               ct = NULL;
+               goto fail;
+       }
+
+       if (EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_GET_TAG, ctx->n_t,
+                               wpabuf_put(ct, ctx->n_t)) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:Could not get tag",
+                          __func__);
+               wpabuf_free(ct);
+               ct = NULL;
+               goto fail;
+       }
+fail:
+       EVP_CIPHER_CTX_free(cctx);
+       return ct;
+}
+
+
+struct wpabuf * hpke_base_seal(enum hpke_kem_id kem_id,
+                              enum hpke_kdf_id kdf_id,
+                              enum hpke_aead_id aead_id,
+                              struct crypto_ec_key *peer_pub,
+                              const u8 *info, size_t info_len,
+                              const u8 *aad, size_t aad_len,
+                              const u8 *pt, size_t pt_len)
+{
+       struct hpke_context *ctx;
+       u8 shared_secret[HPKE_MAX_SHARED_SECRET_LEN];
+       u8 enc[1 + 2 * HPKE_MAX_PUB_LEN];
+       struct wpabuf *ct = NULL, *enc_ct = NULL;
+
+       ctx = hpke_get_context(kem_id, kdf_id, aead_id, peer_pub);
+       if (!ctx)
+               return NULL;
+
+       /* shared_secret, enc = Encap(pkR) */
+       if (hpke_encap(ctx, peer_pub, shared_secret, enc) < 0)
+               goto fail;
+
+       /* KeyScheduleS(mode_base, shared_secret, info,
+        *              default_psk, default_psk_id) */
+       if (hpke_key_schedule(ctx, shared_secret, info, info_len) < 0)
+               goto fail;
+
+       /* ct = ctx.Seal(aad, pt) */
+       ct = hpke_aead_seal(ctx, aad, aad_len, pt, pt_len);
+       if (!ct)
+               goto fail;
+
+       /* return enc, ct */
+       enc_ct = wpabuf_alloc(ctx->n_pk + wpabuf_len(ct));
+       if (!enc_ct)
+               goto fail;
+       wpabuf_put_data(enc_ct, enc, ctx->n_pk);
+       wpabuf_put_buf(enc_ct, ct);
+
+fail:
+       forced_memzero(shared_secret, sizeof(shared_secret));
+       hpke_free_context(ctx);
+       wpabuf_free(ct);
+       return enc_ct;
+}
+
+
+static int hpke_decap(struct hpke_context *ctx, const u8 *enc,
+                     size_t enc_ct_len, struct crypto_ec_key *sk_r,
+                     u8 *shared_secret)
+{
+       EVP_PKEY_CTX *pctx = NULL;
+       struct wpabuf *pk_rm = NULL;
+       size_t len;
+       int res = -1;
+       struct crypto_ec_key *pk_e = NULL;
+       u8 dhss[HPKE_MAX_SHARED_SECRET_LEN];
+       size_t dhss_len;
+
+       /* pkE = DeserializePublicKey(enc) */
+       if (enc_ct_len < ctx->n_pk)
+               return -1; /* not enough room for enc */
+       if (enc[0] != 0x04)
+               return -1; /* not in uncompressed form */
+       len = (ctx->n_pk - 1) / 2;
+       pk_e = crypto_ec_key_set_pub(ctx->iana_group, &enc[1],
+                                    &enc[1 + len], len);
+       if (!pk_e)
+               return -1; /* invalid public key point */
+       /* dh = DH(skR, pkE) */
+       pctx = EVP_PKEY_CTX_new((EVP_PKEY *) sk_r, NULL);
+       if (!pctx ||
+           EVP_PKEY_derive_init(pctx) != 1 ||
+           EVP_PKEY_derive_set_peer(pctx, (EVP_PKEY *) pk_e) != 1 ||
+           EVP_PKEY_derive(pctx, NULL, &dhss_len) != 1 ||
+           dhss_len > HPKE_MAX_SHARED_SECRET_LEN ||
+           EVP_PKEY_derive(pctx, dhss, &dhss_len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL: EVP_PKEY_derive failed: %s",
+                          ERR_error_string(ERR_get_error(), NULL));
+               goto fail;
+       }
+
+       /* pkRm = SerializePublicKey(pk(skR)) */
+       pk_rm = crypto_ec_key_get_pubkey_point(sk_r, 1);
+       if (!pk_rm)
+               goto fail;
+
+       /* kem_context = concat(enc, pkRm) */
+       /* shared_secret = ExtractAndExpand(dh, kem_context) */
+       res = hpke_extract_and_expand(ctx, dhss, dhss_len, enc, ctx->n_pk,
+                                     wpabuf_head(pk_rm),
+                                     wpabuf_len(pk_rm), shared_secret);
+fail:
+       forced_memzero(dhss, sizeof(dhss));
+       crypto_ec_key_deinit(pk_e);
+       EVP_PKEY_CTX_free(pctx);
+       wpabuf_free(pk_rm);
+       return res;
+}
+
+
+static struct wpabuf *
+hpke_aead_open(struct hpke_context *ctx, const u8 *aad, size_t aad_len,
+              const u8 *ct, size_t ct_len)
+{
+       EVP_CIPHER_CTX *cctx;
+       int len = 0;
+       const u8 *tag;
+       struct wpabuf *pt = NULL;
+
+       if (ct_len < ctx->n_t)
+               return NULL;
+       tag = ct + ct_len - ctx->n_t;
+       ct_len -= ctx->n_t;
+
+       /* No need to xor in sequence number since we support only the
+        * single-shot API, i.e., base_nonce can be used as-is. */
+
+       cctx = EVP_CIPHER_CTX_new();
+       if (!cctx ||
+           EVP_DecryptInit_ex(cctx, ctx->cipher, NULL, ctx->key,
+                              ctx->base_nonce) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_DecryptInit_ex failed",
+                          __func__);
+               goto fail;
+       }
+       if (aad && aad_len &&
+           EVP_DecryptUpdate(cctx, NULL, &len, aad, aad_len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_DecryptUpdate(AAD) failed",
+                          __func__);
+               goto fail;
+       }
+       pt = wpabuf_alloc(ct_len + AES_BLOCK_SIZE);
+       if (!pt)
+               goto fail;
+       if (EVP_DecryptUpdate(cctx, wpabuf_put(pt, 0), &len, ct, ct_len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_DecryptUpdate failed",
+                          __func__);
+               goto fail;
+       }
+       wpabuf_put(pt, len);
+
+       if (EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_SET_TAG, ctx->n_t,
+                               (void *) tag) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:Could not set tag",
+                          __func__);
+               wpabuf_free(pt);
+               pt = NULL;
+               goto fail;
+       }
+
+       if (EVP_DecryptFinal(cctx, wpabuf_put(pt, 0), &len) != 1) {
+               wpa_printf(MSG_INFO, "OpenSSL:%s:EVP_DecryptFinal failed",
+                          __func__);
+               wpabuf_free(pt);
+               pt = NULL;
+       }
+fail:
+       EVP_CIPHER_CTX_free(cctx);
+       return pt;
+}
+
+
+struct wpabuf * hpke_base_open(enum hpke_kem_id kem_id,
+                              enum hpke_kdf_id kdf_id,
+                              enum hpke_aead_id aead_id,
+                              struct crypto_ec_key *own_priv,
+                              const u8 *info, size_t info_len,
+                              const u8 *aad, size_t aad_len,
+                              const u8 *enc_ct, size_t enc_ct_len)
+{
+       struct hpke_context *ctx;
+       u8 shared_secret[HPKE_MAX_SHARED_SECRET_LEN];
+       struct wpabuf *pt = NULL;
+
+       ctx = hpke_get_context(kem_id, kdf_id, aead_id, own_priv);
+       if (!ctx)
+               return NULL;
+
+       /* shared_secret = Decap(enc, skR) */
+       if (hpke_decap(ctx, enc_ct, enc_ct_len, own_priv, shared_secret) < 0)
+               goto fail;
+
+       /* KeyScheduleR(mode_base, shared_secret, info,
+        *              default_psk, default_psk_id) */
+       if (hpke_key_schedule(ctx, shared_secret, info, info_len) < 0)
+               goto fail;
+
+       /* return ctx.Open(aad, ct) */
+       pt = hpke_aead_open(ctx, aad, aad_len,
+                           &enc_ct[ctx->n_pk], enc_ct_len - ctx->n_pk);
+
+fail:
+       forced_memzero(shared_secret, sizeof(shared_secret));
+       hpke_free_context(ctx);
+       return pt;
+}
+
+#endif /* CONFIG_DPP3 */
+
+
 void crypto_unload(void)
 {
        openssl_unload_legacy_provider();