Repository: randombit/botan
Pull request #3770 (2023-10-23 15:10:41)

Feature: `AsymmetricKey::generate_another()`
As suggested in #3706, this adds `::generate_another(RNG&)`. Given some (abstract) asymmetric key object, one can simply generate another equivalent key pair that uses the same algorithm and parameters. This comes in handy if one needs to generate an ephemeral key pair for a public key received from a peer.

Currently, the new method is used in the `KEX_to_KEM_Adapter` class, only. Though, the DLIES and ECIES implementation might benefit from them as well. At first glance, their APIs appeared to require a bit of a make-over to benefit from `::generate_another()`. I've left this for future work.
Similarly, the PKCS#11 key variants are left for future work -- currently throwing `Not_Implemented`. It remains to-be-discussed whether `::generate_another()` should generate a software instance of the given asymmetric algorithm or whether they should attempt to generate an equivalent keypair via PKCS#11. 


Patch:
diff --git a/src/lib/prov/pkcs11/p11_ecdh.h b/src/lib/prov/pkcs11/p11_ecdh.h
index 500fd955d..19fc603ed 100644
--- a/src/lib/prov/pkcs11/p11_ecdh.h
+++ b/src/lib/prov/pkcs11/p11_ecdh.h
@@ -43,6 +43,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDH_PublicKey : public PKCS11_EC_PublicKey
 
       inline std::string algo_name() const override { return "ECDH"; }
 
+      /**
+       * @throws Not_Implemented
+       */
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const final {
+         throw Not_Implemented("Cannot generate a new PKCS#11 ECDH keypair from this public key");
+      }
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
 
       /// @return the exported ECDH public key
@@ -97,6 +104,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDH_PrivateKey final : public virtual PKCS1
 
       secure_vector<uint8_t> private_key_bits() const override;
 
+      /**
+       * @throws Not_Implemented
+       */
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const override {
+         throw Not_Implemented("Cannot generate a new PKCS#11 ECDH keypair from this private key");
+      }
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
 
       std::unique_ptr<PK_Ops::Key_Agreement> create_key_agreement_op(RandomNumberGenerator& rng,
diff --git a/src/lib/prov/pkcs11/p11_ecdsa.h b/src/lib/prov/pkcs11/p11_ecdsa.h
index 865765198..e9e5a2701 100644
--- a/src/lib/prov/pkcs11/p11_ecdsa.h
+++ b/src/lib/prov/pkcs11/p11_ecdsa.h
@@ -51,6 +51,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDSA_PublicKey final : public PKCS11_EC_Pub
       /// @return the exported ECDSA public key
       ECDSA_PublicKey export_key() const;
 
+      /**
+       * @throws Not_Implemented
+       */
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const final {
+         throw Not_Implemented("Cannot generate a new PKCS#11 ECDSA keypair from this public key");
+      }
+
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
                                                                    std::string_view provider) const override;
 };
@@ -89,6 +96,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDSA_PrivateKey final : public PKCS11_EC_Pr
 
       inline std::string algo_name() const override { return "ECDSA"; }
 
+      /**
+       * @throws Not_Implemented
+       */
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const override {
+         throw Not_Implemented("Cannot generate a new PKCS#11 ECDSA keypair from this private key");
+      }
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       size_t message_parts() const override { return 2; }
diff --git a/src/lib/prov/pkcs11/p11_rsa.h b/src/lib/prov/pkcs11/p11_rsa.h
index b0b446603..2bfc5f4f6 100644
--- a/src/lib/prov/pkcs11/p11_rsa.h
+++ b/src/lib/prov/pkcs11/p11_rsa.h
@@ -74,6 +74,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_RSA_PublicKey : public Object,
       */
       PKCS11_RSA_PublicKey(Session& session, const RSA_PublicKeyImportProperties& pubkey_props);
 
+      /**
+       * @throws Not_Implemented
+       */
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const final {
+         throw Not_Implemented("Cannot generate a new PKCS#11 RSA keypair from this public key");
+      }
+
       std::unique_ptr<PK_Ops::Encryption> create_encryption_op(RandomNumberGenerator& rng,
                                                                std::string_view params,
                                                                std::string_view provider) const override;
diff --git a/src/lib/prov/tpm/tpm.h b/src/lib/prov/tpm/tpm.h
index e74283e10..00088c9f0 100644
--- a/src/lib/prov/tpm/tpm.h
+++ b/src/lib/prov/tpm/tpm.h
@@ -162,6 +162,10 @@ class BOTAN_PUBLIC_API(2, 0) TPM_PrivateKey final : public Private_Key {
 
       std::string algo_name() const override { return "RSA"; }  // ???
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const override {
+         throw Not_Implemented("Cannot generate a new TPM-based keypair from this asymmetric key");
+      }
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       std::unique_ptr<PK_Ops::Signature> create_signature_op(RandomNumberGenerator& rng,
diff --git a/src/lib/pubkey/curve25519/curve25519.cpp b/src/lib/pubkey/curve25519/curve25519.cpp
index d7cf44ab5..715dda180 100644
--- a/src/lib/pubkey/curve25519/curve25519.cpp
+++ b/src/lib/pubkey/curve25519/curve25519.cpp
@@ -54,6 +54,10 @@ std::vector<uint8_t> Curve25519_PublicKey::public_key_bits() const {
    return m_public;
 }
 
+std::unique_ptr<Private_Key> Curve25519_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<Curve25519_PrivateKey>(rng);
+};
+
 Curve25519_PrivateKey::Curve25519_PrivateKey(const secure_vector<uint8_t>& secret_key) {
    if(secret_key.size() != 32) {
       throw Decoding_Error("Invalid size for Curve25519 private key");
diff --git a/src/lib/pubkey/curve25519/curve25519.h b/src/lib/pubkey/curve25519/curve25519.h
index a08e1a76d..422479657 100644
--- a/src/lib/pubkey/curve25519/curve25519.h
+++ b/src/lib/pubkey/curve25519/curve25519.h
@@ -30,6 +30,8 @@ class BOTAN_PUBLIC_API(2, 0) Curve25519_PublicKey : public virtual Public_Key {
 
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       /**
       * Create a Curve25519 Public Key.
       * @param alg_id the X.509 algorithm identifier
diff --git a/src/lib/pubkey/dh/dh.cpp b/src/lib/pubkey/dh/dh.cpp
index 5cbceeee0..6485e369f 100644
--- a/src/lib/pubkey/dh/dh.cpp
+++ b/src/lib/pubkey/dh/dh.cpp
@@ -53,6 +53,10 @@ bool DH_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
    return m_public_key->check_key(rng, strong);
 }
 
+std::unique_ptr<Private_Key> DH_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<DH_PrivateKey>(rng, group());
+}
+
 DH_PrivateKey::DH_PrivateKey(RandomNumberGenerator& rng, const DL_Group& group) {
    m_private_key = std::make_shared<DL_PrivateKey>(group, rng);
    m_public_key = m_private_key->public_key();
diff --git a/src/lib/pubkey/dh/dh.h b/src/lib/pubkey/dh/dh.h
index 734795599..28df4b599 100644
--- a/src/lib/pubkey/dh/dh.h
+++ b/src/lib/pubkey/dh/dh.h
@@ -53,6 +53,8 @@ class BOTAN_PUBLIC_API(2, 0) DH_PublicKey : public virtual Public_Key {
 
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       const DL_Group& group() const;
 
    private:
diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
index f2668970a..d81b31e5b 100644
--- a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
+++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
@@ -540,6 +540,10 @@ bool Dilithium_PublicKey::check_key(RandomNumberGenerator&, bool) const {
    return true;  // ???
 }
 
+std::unique_ptr<Private_Key> Dilithium_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<Dilithium_PrivateKey>(rng, m_public->mode().mode());
+}
+
 std::unique_ptr<PK_Ops::Verification> Dilithium_PublicKey::create_verification_op(std::string_view params,
                                                                                   std::string_view provider) const {
    BOTAN_ARG_CHECK(params.empty() || params == "Pure", "Unexpected parameters for verifying with Dilithium");
diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium.h
index 19869089a..12af07797 100644
--- a/src/lib/pubkey/dilithium/dilithium_common/dilithium.h
+++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium.h
@@ -74,6 +74,8 @@ class BOTAN_PUBLIC_API(3, 0) Dilithium_PublicKey : public virtual Public_Key {
 
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       Dilithium_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> pk);
 
       Dilithium_PublicKey(std::span<const uint8_t> pk, DilithiumMode mode);
diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h
index 6d9a12676..c752843aa 100644
--- a/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h
+++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h
@@ -146,6 +146,8 @@ class DilithiumModeConstants {
 
       OID oid() const { return m_mode.object_identifier(); }
 
+      DilithiumMode mode() const { return m_mode; }
+
       size_t private_key_bytes() const { return m_private_key_bytes; }
 
       size_t nist_security_strength() const { return m_nist_security_strength; }
diff --git a/src/lib/pubkey/dsa/dsa.cpp b/src/lib/pubkey/dsa/dsa.cpp
index cd498405e..4970e9ba8 100644
--- a/src/lib/pubkey/dsa/dsa.cpp
+++ b/src/lib/pubkey/dsa/dsa.cpp
@@ -48,6 +48,10 @@ bool DSA_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
    return m_public_key->check_key(rng, strong);
 }
 
+std::unique_ptr<Private_Key> DSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<DSA_PrivateKey>(rng, m_public_key->group());
+}
+
 DSA_PublicKey::DSA_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits) {
    m_public_key = std::make_shared<DL_PublicKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_57);
 
diff --git a/src/lib/pubkey/dsa/dsa.h b/src/lib/pubkey/dsa/dsa.h
index fe31f5cd3..504d4be55 100644
--- a/src/lib/pubkey/dsa/dsa.h
+++ b/src/lib/pubkey/dsa/dsa.h
@@ -50,6 +50,8 @@ class BOTAN_PUBLIC_API(2, 0) DSA_PublicKey : public virtual Public_Key {
 
       bool check_key(RandomNumberGenerator& rng, bool strong) const override;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       size_t estimated_strength() const override;
       size_t key_length() const override;
 
diff --git a/src/lib/pubkey/ecdh/ecdh.cpp b/src/lib/pubkey/ecdh/ecdh.cpp
index faff168de..8c99c02e2 100644
--- a/src/lib/pubkey/ecdh/ecdh.cpp
+++ b/src/lib/pubkey/ecdh/ecdh.cpp
@@ -53,6 +53,10 @@ class ECDH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF {
 
 }  // namespace
 
+std::unique_ptr<Private_Key> ECDH_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<ECDH_PrivateKey>(rng, domain());
+}
+
 std::unique_ptr<PK_Ops::Key_Agreement> ECDH_PrivateKey::create_key_agreement_op(RandomNumberGenerator& rng,
                                                                                 std::string_view params,
                                                                                 std::string_view provider) const {
diff --git a/src/lib/pubkey/ecdh/ecdh.h b/src/lib/pubkey/ecdh/ecdh.h
index 0cd082f22..2c43cc756 100644
--- a/src/lib/pubkey/ecdh/ecdh.h
+++ b/src/lib/pubkey/ecdh/ecdh.h
@@ -52,6 +52,8 @@ class BOTAN_PUBLIC_API(2, 0) ECDH_PublicKey : public virtual EC_PublicKey {
 
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
    protected:
       ECDH_PublicKey() = default;
 };
diff --git a/src/lib/pubkey/ecdsa/ecdsa.cpp b/src/lib/pubkey/ecdsa/ecdsa.cpp
index be1f10d23..578a597cb 100644
--- a/src/lib/pubkey/ecdsa/ecdsa.cpp
+++ b/src/lib/pubkey/ecdsa/ecdsa.cpp
@@ -77,6 +77,10 @@ ECDSA_PublicKey::ECDSA_PublicKey(
    const EC_Group& group, const std::vector<uint8_t>& msg, const BigInt& r, const BigInt& s, uint8_t v) :
       EC_PublicKey(group, recover_ecdsa_public_key(group, msg, r, s, v)) {}
 
+std::unique_ptr<Private_Key> ECDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<ECDSA_PrivateKey>(rng, domain());
+}
+
 uint8_t ECDSA_PublicKey::recovery_param(const std::vector<uint8_t>& msg, const BigInt& r, const BigInt& s) const {
    for(uint8_t v = 0; v != 4; ++v) {
       try {
diff --git a/src/lib/pubkey/ecdsa/ecdsa.h b/src/lib/pubkey/ecdsa/ecdsa.h
index a63c8a4f8..34282d8e2 100644
--- a/src/lib/pubkey/ecdsa/ecdsa.h
+++ b/src/lib/pubkey/ecdsa/ecdsa.h
@@ -58,6 +58,8 @@ class BOTAN_PUBLIC_API(2, 0) ECDSA_PublicKey : public virtual EC_PublicKey {
 
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const override;
+
       uint8_t recovery_param(const std::vector<uint8_t>& msg, const BigInt& r, const BigInt& s) const;
 
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
diff --git a/src/lib/pubkey/ecgdsa/ecgdsa.cpp b/src/lib/pubkey/ecgdsa/ecgdsa.cpp
index 73aca08f3..acf43e95a 100644
--- a/src/lib/pubkey/ecgdsa/ecgdsa.cpp
+++ b/src/lib/pubkey/ecgdsa/ecgdsa.cpp
@@ -132,6 +132,10 @@ bool ECGDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,
 
 }  // namespace
 
+std::unique_ptr<Private_Key> ECGDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<ECGDSA_PrivateKey>(rng, domain());
+}
+
 std::unique_ptr<PK_Ops::Verification> ECGDSA_PublicKey::create_verification_op(std::string_view params,
                                                                                std::string_view provider) const {
    if(provider == "base" || provider.empty()) {
diff --git a/src/lib/pubkey/ecgdsa/ecgdsa.h b/src/lib/pubkey/ecgdsa/ecgdsa.h
index 5735a483e..8225fd5e5 100644
--- a/src/lib/pubkey/ecgdsa/ecgdsa.h
+++ b/src/lib/pubkey/ecgdsa/ecgdsa.h
@@ -42,6 +42,8 @@ class BOTAN_PUBLIC_API(2, 0) ECGDSA_PublicKey : public virtual EC_PublicKey {
 
       size_t message_part_size() const override { return domain().get_order().bytes(); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
diff --git a/src/lib/pubkey/ecies/ecies.cpp b/src/lib/pubkey/ecies/ecies.cpp
index 4ab7ebc10..db29df22b 100644
--- a/src/lib/pubkey/ecies/ecies.cpp
+++ b/src/lib/pubkey/ecies/ecies.cpp
@@ -40,6 +40,10 @@ class ECIES_PrivateKey final : public EC_PrivateKey,
 
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const override {
+         return m_key.generate_another(rng);
+      }
+
       std::unique_ptr<PK_Ops::Key_Agreement> create_key_agreement_op(RandomNumberGenerator& rng,
                                                                      std::string_view params,
                                                                      std::string_view provider) const override;
diff --git a/src/lib/pubkey/eckcdsa/eckcdsa.cpp b/src/lib/pubkey/eckcdsa/eckcdsa.cpp
index d1443d4c0..62644cbb4 100644
--- a/src/lib/pubkey/eckcdsa/eckcdsa.cpp
+++ b/src/lib/pubkey/eckcdsa/eckcdsa.cpp
@@ -292,6 +292,10 @@ bool ECKCDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,
 
 }  // namespace
 
+std::unique_ptr<Private_Key> ECKCDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<ECKCDSA_PrivateKey>(rng, domain());
+}
+
 std::unique_ptr<PK_Ops::Verification> ECKCDSA_PublicKey::create_verification_op(std::string_view params,
                                                                                 std::string_view provider) const {
    if(provider == "base" || provider.empty()) {
diff --git a/src/lib/pubkey/eckcdsa/eckcdsa.h b/src/lib/pubkey/eckcdsa/eckcdsa.h
index 045bd078b..5a9d2950e 100644
--- a/src/lib/pubkey/eckcdsa/eckcdsa.h
+++ b/src/lib/pubkey/eckcdsa/eckcdsa.h
@@ -41,6 +41,8 @@ class BOTAN_PUBLIC_API(2, 0) ECKCDSA_PublicKey : public virtual EC_PublicKey {
 
       size_t message_part_size() const override { return domain().get_order().bytes(); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
diff --git a/src/lib/pubkey/ed25519/ed25519.h b/src/lib/pubkey/ed25519/ed25519.h
index d2e9ce4bf..d136c035a 100644
--- a/src/lib/pubkey/ed25519/ed25519.h
+++ b/src/lib/pubkey/ed25519/ed25519.h
@@ -29,6 +29,8 @@ class BOTAN_PUBLIC_API(2, 2) Ed25519_PublicKey : public virtual Public_Key {
 
       std::vector<uint8_t> public_key_bits() const override;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       const std::vector<uint8_t>& get_public_key() const { return m_public; }
diff --git a/src/lib/pubkey/ed25519/ed25519_key.cpp b/src/lib/pubkey/ed25519/ed25519_key.cpp
index 89e76c0fc..10a76ab7d 100644
--- a/src/lib/pubkey/ed25519/ed25519_key.cpp
+++ b/src/lib/pubkey/ed25519/ed25519_key.cpp
@@ -83,6 +83,10 @@ std::vector<uint8_t> Ed25519_PublicKey::public_key_bits() const {
    return m_public;
 }
 
+std::unique_ptr<Private_Key> Ed25519_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<Ed25519_PrivateKey>(rng);
+}
+
 Ed25519_PrivateKey::Ed25519_PrivateKey(const secure_vector<uint8_t>& secret_key) {
    if(secret_key.size() == 64) {
       m_private = secret_key;
diff --git a/src/lib/pubkey/elgamal/elgamal.cpp b/src/lib/pubkey/elgamal/elgamal.cpp
index 8dda6da24..afd0a9e9d 100644
--- a/src/lib/pubkey/elgamal/elgamal.cpp
+++ b/src/lib/pubkey/elgamal/elgamal.cpp
@@ -43,6 +43,10 @@ const BigInt& ElGamal_PublicKey::get_int_field(std::string_view field) const {
    return m_public_key->get_int_field(algo_name(), field);
 }
 
+std::unique_ptr<Private_Key> ElGamal_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<ElGamal_PrivateKey>(rng, m_public_key->group());
+}
+
 bool ElGamal_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
    return m_public_key->check_key(rng, strong);
 }
diff --git a/src/lib/pubkey/elgamal/elgamal.h b/src/lib/pubkey/elgamal/elgamal.h
index 2f8ed456f..0924bdfac 100644
--- a/src/lib/pubkey/elgamal/elgamal.h
+++ b/src/lib/pubkey/elgamal/elgamal.h
@@ -51,6 +51,8 @@ class BOTAN_PUBLIC_API(2, 0) ElGamal_PublicKey : public virtual Public_Key {
 
       const BigInt& get_int_field(std::string_view field) const override;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       std::unique_ptr<PK_Ops::Encryption> create_encryption_op(RandomNumberGenerator& rng,
                                                                std::string_view params,
                                                                std::string_view provider) const override;
diff --git a/src/lib/pubkey/gost_3410/gost_3410.cpp b/src/lib/pubkey/gost_3410/gost_3410.cpp
index a17e5a1e9..20f91375d 100644
--- a/src/lib/pubkey/gost_3410/gost_3410.cpp
+++ b/src/lib/pubkey/gost_3410/gost_3410.cpp
@@ -270,6 +270,10 @@ bool GOST_3410_Verification_Operation::verify(const uint8_t msg[],
 
 }  // namespace
 
+std::unique_ptr<Private_Key> GOST_3410_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<GOST_3410_PrivateKey>(rng, domain());
+}
+
 std::unique_ptr<PK_Ops::Verification> GOST_3410_PublicKey::create_verification_op(std::string_view params,
                                                                                   std::string_view provider) const {
    if(provider == "base" || provider.empty()) {
diff --git a/src/lib/pubkey/gost_3410/gost_3410.h b/src/lib/pubkey/gost_3410/gost_3410.h
index 4b51eb282..fa2c362c8 100644
--- a/src/lib/pubkey/gost_3410/gost_3410.h
+++ b/src/lib/pubkey/gost_3410/gost_3410.h
@@ -50,6 +50,8 @@ class BOTAN_PUBLIC_API(2, 0) GOST_3410_PublicKey : public virtual EC_PublicKey {
 
       Signature_Format default_x509_signature_format() const override { return Signature_Format::Standard; }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.cpp b/src/lib/pubkey/kyber/kyber_common/kyber.cpp
index 8fd8bed5a..edd299bf5 100644
--- a/src/lib/pubkey/kyber/kyber_common/kyber.cpp
+++ b/src/lib/pubkey/kyber/kyber_common/kyber.cpp
@@ -1247,6 +1247,10 @@ bool Kyber_PublicKey::check_key(RandomNumberGenerator&, bool) const {
    return true;  // ??
 }
 
+std::unique_ptr<Private_Key> Kyber_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<Kyber_PrivateKey>(rng, mode());
+}
+
 Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) {
    KyberConstants mode(m);
 
diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.h b/src/lib/pubkey/kyber/kyber_common/kyber.h
index d39fde9bc..a6113b5ce 100644
--- a/src/lib/pubkey/kyber/kyber_common/kyber.h
+++ b/src/lib/pubkey/kyber/kyber_common/kyber.h
@@ -96,6 +96,8 @@ class BOTAN_PUBLIC_API(3, 0) Kyber_PublicKey : public virtual Public_Key {
 
       bool check_key(RandomNumberGenerator&, bool) const override;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override {
          return (op == PublicKeyOperation::KeyEncapsulation);
       }
diff --git a/src/lib/pubkey/mce/mceliece.h b/src/lib/pubkey/mce/mceliece.h
index 3d798b67b..5b7e9c0bd 100644
--- a/src/lib/pubkey/mce/mceliece.h
+++ b/src/lib/pubkey/mce/mceliece.h
@@ -56,6 +56,8 @@ class BOTAN_PUBLIC_API(2, 0) McEliece_PublicKey : public virtual Public_Key {
 
       bool operator!=(const McEliece_PublicKey& other) const { return !(*this == other); }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override {
          return (op == PublicKeyOperation::KeyEncapsulation);
       }
diff --git a/src/lib/pubkey/mce/mceliece_key.cpp b/src/lib/pubkey/mce/mceliece_key.cpp
index 8e54fd562..4e4be6724 100644
--- a/src/lib/pubkey/mce/mceliece_key.cpp
+++ b/src/lib/pubkey/mce/mceliece_key.cpp
@@ -341,6 +341,10 @@ class MCE_KEM_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF {
 
 }  // namespace
 
+std::unique_ptr<Private_Key> McEliece_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<McEliece_PrivateKey>(rng, get_code_length(), get_t());
+}
+
 std::unique_ptr<PK_Ops::KEM_Encryption> McEliece_PublicKey::create_kem_encryption_op(std::string_view params,
                                                                                      std::string_view provider) const {
    if(provider == "base" || provider.empty()) {
diff --git a/src/lib/pubkey/pk_keys.h b/src/lib/pubkey/pk_keys.h
index 618b48cff..b490e48fe 100644
--- a/src/lib/pubkey/pk_keys.h
+++ b/src/lib/pubkey/pk_keys.h
@@ -47,6 +47,8 @@ enum class PublicKeyOperation {
    KeyAgreement,
 };
 
+class Private_Key;
+
 /**
 * An interface for objects that are keys in public key algorithms
 *
@@ -99,6 +101,12 @@ class BOTAN_PUBLIC_API(3, 0) Asymmetric_Key {
       * of operation.
       */
       virtual bool supports_operation(PublicKeyOperation op) const = 0;
+
+      /**
+       * Generate another (cryptographically independent) key pair using the
+       * same algorithm parameters as this key.
+       */
+      virtual std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const = 0;
 };
 
 /*
diff --git a/src/lib/pubkey/rsa/rsa.cpp b/src/lib/pubkey/rsa/rsa.cpp
index b8ad1b255..2657af6f0 100644
--- a/src/lib/pubkey/rsa/rsa.cpp
+++ b/src/lib/pubkey/rsa/rsa.cpp
@@ -129,6 +129,10 @@ const BigInt& RSA_PublicKey::get_int_field(std::string_view field) const {
    }
 }
 
+std::unique_ptr<Private_Key> RSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<RSA_PrivateKey>(rng, m_public->public_modulus_bits(), m_public->get_e().to_u32bit());
+}
+
 const BigInt& RSA_PublicKey::get_n() const {
    return m_public->get_n();
 }
diff --git a/src/lib/pubkey/rsa/rsa.h b/src/lib/pubkey/rsa/rsa.h
index 25efbd19f..89759da39 100644
--- a/src/lib/pubkey/rsa/rsa.h
+++ b/src/lib/pubkey/rsa/rsa.h
@@ -61,6 +61,8 @@ class BOTAN_PUBLIC_API(2, 0) RSA_PublicKey : public virtual Public_Key {
 
       const BigInt& get_int_field(std::string_view field) const override;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const override;
+
       bool supports_operation(PublicKeyOperation op) const override;
 
       // internal functions:
diff --git a/src/lib/pubkey/sm2/sm2.cpp b/src/lib/pubkey/sm2/sm2.cpp
index 8d4c47b52..0734ca57b 100644
--- a/src/lib/pubkey/sm2/sm2.cpp
+++ b/src/lib/pubkey/sm2/sm2.cpp
@@ -240,6 +240,10 @@ void parse_sm2_param_string(std::string_view params, std::string& userid, std::s
 
 }  // namespace
 
+std::unique_ptr<Private_Key> SM2_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<SM2_PrivateKey>(rng, domain());
+}
+
 std::unique_ptr<PK_Ops::Verification> SM2_PublicKey::create_verification_op(std::string_view params,
                                                                             std::string_view provider) const {
    if(provider == "base" || provider.empty()) {
diff --git a/src/lib/pubkey/sm2/sm2.h b/src/lib/pubkey/sm2/sm2.h
index 5aa90473d..92d82d096 100644
--- a/src/lib/pubkey/sm2/sm2.h
+++ b/src/lib/pubkey/sm2/sm2.h
@@ -40,6 +40,8 @@ class BOTAN_PUBLIC_API(2, 2) SM2_PublicKey : public virtual EC_PublicKey {
 
       size_t message_parts() const override { return 2; }
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override {
          return (op == PublicKeyOperation::Signature || op == PublicKeyOperation::Encryption);
       }
diff --git a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp
index b876718c2..c7e3fad58 100644
--- a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp
+++ b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp
@@ -126,6 +126,10 @@ std::vector<uint8_t> SphincsPlus_PublicKey::public_key_bits() const {
    return m_public->key_bits();
 }
 
+std::unique_ptr<Private_Key> SphincsPlus_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<SphincsPlus_PrivateKey>(rng, m_public->parameters());
+}
+
 class SphincsPlus_Verification_Operation final : public PK_Ops::Verification {
    public:
       SphincsPlus_Verification_Operation(std::shared_ptr<SphincsPlus_PublicKeyInternal> pub_key) :
diff --git a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h
index e2664d368..f2f41e725 100644
--- a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h
+++ b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h
@@ -48,6 +48,8 @@ class BOTAN_PUBLIC_API(3, 1) SphincsPlus_PublicKey : public virtual Public_Key {
       bool check_key(RandomNumberGenerator& rng, bool strong) const override;
       std::vector<uint8_t> public_key_bits() const override;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
                                                                    std::string_view provider) const override;
 
diff --git a/src/lib/pubkey/xmss/xmss.h b/src/lib/pubkey/xmss/xmss.h
index ae8de668c..09df37db2 100644
--- a/src/lib/pubkey/xmss/xmss.h
+++ b/src/lib/pubkey/xmss/xmss.h
@@ -98,6 +98,8 @@ class BOTAN_PUBLIC_API(2, 0) XMSS_PublicKey : public virtual Public_Key {
        **/
       std::vector<uint8_t> raw_public_key() const;
 
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
+
       bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }
 
       std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
diff --git a/src/lib/pubkey/xmss/xmss_publickey.cpp b/src/lib/pubkey/xmss/xmss_publickey.cpp
index bb27c8488..a1c29732a 100644
--- a/src/lib/pubkey/xmss/xmss_publickey.cpp
+++ b/src/lib/pubkey/xmss/xmss_publickey.cpp
@@ -137,4 +137,11 @@ std::vector<uint8_t> XMSS_PublicKey::public_key_bits() const {
    return output;
 }
 
+std::unique_ptr<Private_Key> XMSS_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   // Note: Given only an XMSS public key we cannot know which WOTS key
+   //       derivation method was used to build the XMSS tree. Hence, we have to
+   //       use the default here.
+   return std::make_unique<XMSS_PrivateKey>(m_xmss_params.oid(), rng);
+}
+
 }  // namespace Botan
diff --git a/src/lib/tls/tls13_pqc/hybrid_public_key.cpp b/src/lib/tls/tls13_pqc/hybrid_public_key.cpp
index 7fc531bc5..e9e46b1c3 100644
--- a/src/lib/tls/tls13_pqc/hybrid_public_key.cpp
+++ b/src/lib/tls/tls13_pqc/hybrid_public_key.cpp
@@ -202,6 +202,15 @@ std::vector<uint8_t> Hybrid_KEM_PublicKey::public_value() const {
    });
 }
 
+std::unique_ptr<Private_Key> Hybrid_KEM_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   std::vector<std::unique_ptr<Private_Key>> new_private_keys;
+   std::transform(
+      m_public_keys.begin(), m_public_keys.end(), std::back_inserter(new_private_keys), [&](const auto& public_key) {
+         return public_key->generate_another(rng);
+      });
+   return std::make_unique<Hybrid_KEM_PrivateKey>(std::move(new_private_keys));
+}
+
 bool Hybrid_KEM_PublicKey::supports_operation(PublicKeyOperation op) const {
    return PublicKeyOperation::KeyEncapsulation == op;
 }
diff --git a/src/lib/tls/tls13_pqc/hybrid_public_key.h b/src/lib/tls/tls13_pqc/hybrid_public_key.h
index 9dc00b156..fe38133c4 100644
--- a/src/lib/tls/tls13_pqc/hybrid_public_key.h
+++ b/src/lib/tls/tls13_pqc/hybrid_public_key.h
@@ -57,8 +57,8 @@ class BOTAN_TEST_API Hybrid_KEM_PublicKey : public virtual Public_Key {
       bool check_key(RandomNumberGenerator& rng, bool strong) const override;
       AlgorithmIdentifier algorithm_identifier() const override;
       std::vector<uint8_t> public_key_bits() const override;
-
       std::vector<uint8_t> public_value() const;
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
 
       bool supports_operation(PublicKeyOperation op) const override;
 
diff --git a/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp b/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp
index 922261057..b43b581b6 100644
--- a/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp
+++ b/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp
@@ -104,44 +104,23 @@ size_t kex_shared_key_length(const Public_Key& kex_public_key) {
 
 /**
  * This helper generates an ephemeral key agreement private key given a
- * public key instance of a certain key agreement algorithm. Currently,
- * there is no generic way to generate a new key from an abstract public
- * key object.
- *
- * See also: https://github.com/randombit/botan/discussions/3581 where a
- *           new Public_Key::generate_another_keypair() method is proposed
- *           to fill this gap.
- *
- * TODO: Implement the suggestion above or find a better way to
- *       generically handle the generation of a matching ephemeral key
- *       agreement private key.
+ * public key instance of a certain key agreement algorithm.
  */
 std::unique_ptr<PK_Key_Agreement_Key> generate_key_agreement_private_key(const Public_Key& kex_public_key,
                                                                          RandomNumberGenerator& rng) {
    BOTAN_ASSERT_NOMSG(kex_public_key.supports_operation(PublicKeyOperation::KeyAgreement));
 
-#if defined(BOTAN_HAS_ECDH)
-   if(const auto* ecdh = dynamic_cast<const ECDH_PublicKey*>(&kex_public_key)) {
-      return std::make_unique<ECDH_PrivateKey>(rng, ecdh->domain());
-   }
-#endif
-
-#if defined(BOTAN_HAS_DIFFIE_HELLMAN)
-   if(const auto* dh = dynamic_cast<const DH_PublicKey*>(&kex_public_key)) {
-      return std::make_unique<DH_PrivateKey>(rng, dh->group());
-   }
-#endif
-
-#if defined(BOTAN_HAS_CURVE_25519)
-   if(const auto* curve = dynamic_cast<const Curve25519_PublicKey*>(&kex_public_key)) {
-      BOTAN_UNUSED(curve);
-      return std::make_unique<Curve25519_PrivateKey>(rng);
-   }
-#endif
+   auto new_kex_key = [&] {
+      auto new_private_key = kex_public_key.generate_another(rng);
+      const auto kex_key = dynamic_cast<PK_Key_Agreement_Key*>(new_private_key.get());
+      if(kex_key) [[likely]] {
+         (void)new_private_key.release();
+      }
+      return std::unique_ptr<PK_Key_Agreement_Key>(kex_key);
+   }();
 
-   throw Not_Implemented(fmt(
-      "Cannot generate a private key matching an unknown key agreement public key of type '{}' in the hybrid KEM key",
-      kex_public_key.algo_name()));
+   BOTAN_ASSERT(new_kex_key, "Keys wrapped in this adapter are always key-agreement keys");
+   return new_kex_key;
 }
 
 std::unique_ptr<Public_Key> maybe_get_public_key(const std::unique_ptr<PK_Key_Agreement_Key>& private_key) {
@@ -250,6 +229,10 @@ std::vector<uint8_t> KEX_to_KEM_Adapter_PublicKey::public_key_bits() const {
    return kex_public_value(*m_public_key);
 }
 
+std::unique_ptr<Private_Key> KEX_to_KEM_Adapter_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+   return std::make_unique<KEX_to_KEM_Adapter_PrivateKey>(generate_key_agreement_private_key(*m_public_key, rng));
+}
+
 bool KEX_to_KEM_Adapter_PublicKey::supports_operation(PublicKeyOperation op) const {
    return op == PublicKeyOperation::KeyEncapsulation;
 }
diff --git a/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h b/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h
index e6b4fbeb2..e6ab52dee 100644
--- a/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h
+++ b/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h
@@ -31,6 +31,7 @@ class BOTAN_TEST_API KEX_to_KEM_Adapter_PublicKey : public virtual Public_Key {
       bool check_key(RandomNumberGenerator& rng, bool strong) const override;
       AlgorithmIdentifier algorithm_identifier() const override;
       std::vector<uint8_t> public_key_bits() const override;
+      std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;
 
       bool supports_operation(PublicKeyOperation op) const override;
 


Test patch:
diff --git a/src/tests/test_pubkey.cpp b/src/tests/test_pubkey.cpp
index d499c1bb0..864f836ca 100644
--- a/src/tests/test_pubkey.cpp
+++ b/src/tests/test_pubkey.cpp
@@ -601,6 +601,18 @@ std::vector<Test::Result> PK_Key_Generation_Test::run() {
          result.test_eq(
             "public_key has same encoding", Botan::X509::PEM_encode(key), Botan::X509::PEM_encode(*public_key));
 
+         // Test generation of another key pair from a given (abstract) asymmetric key
+         auto sk2 = public_key->generate_another(Test::rng());
+         auto pk2 = sk2->public_key();
+
+         result.test_eq("new private key has the same name", sk2->algo_name(), key.algo_name());
+         result.test_eq("new public key has the same name", pk2->algo_name(), public_key->algo_name());
+         result.test_eq(
+            "new private key has the same est. strength", sk2->estimated_strength(), key.estimated_strength());
+         result.test_eq(
+            "new public key has the same est. strength", pk2->estimated_strength(), public_key->estimated_strength());
+         result.test_ne("new private keys are different keys", sk2->private_key_bits(), key.private_key_bits());
+
          // Test PEM public key round trips OK
          try {
             Botan::DataSource_Memory data_src(Botan::X509::PEM_encode(key));