Repository: randombit/botan
Pull request #3125 (2022-12-15 17:14:31)

[TLS 1.3] Support signature_algorithm_cert Extension
## Pull Request Dependencies

* ~~#3053~~
* ~~#3081~~
* ~~#3186~~

## Description

This introduces support for [the `signature_algorithm_cert` extension](https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3) introduced with TLS 1.3. With that, one can create a finer-grained signature algorithm policy; independently specifying acceptable algorithms for signatures in X.509 certificates and in the TLS protocol itself (i.e. in the `CertificateVerify` message).

To communicate the peer's restrictions to the application, `CredentialsManager::find_cert_chain()` (and friends) will now additionally get a vector of `AlgorithmIdentifier`s specifying which algorithms are acceptable.

Note that also the TLS 1.2 implementation can process this new extension (as suggested by RFC 8446).

## New policy: `tls_acceptable_certificate_signature_schemes()`

By default, this returns `std::nullopt` meaning that the restrictions from the existing `::tls_acceptable_signature_schemes()` should also apply for signatures in certificates. If anything other than `std::nullopt` is returned, clients will add an explicit `signature_algorithm_cert` extension to the Client Hello; servers likewise in the `CertificateRequest` message.

reneme: Whoops, that almost broke the code examples. @lieser thanks for the CI job checking their successful compilation. 😅 
codecov-commenter: # [Codecov](https://codecov.io/gh/randombit/botan/pull/3125?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) Report
Base: **88.02**% // Head: **87.97**% // Decreases project coverage by **`-0.04%`** :warning:
> Coverage data is based on head [(`3f5ff8f`)](https://codecov.io/gh/randombit/botan/pull/3125?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) compared to base [(`affd315`)](https://codecov.io/gh/randombit/botan/commit/affd3151cda96ff7e5ca21ce758a719e47716436?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).
> Patch coverage: 85.24% of modified lines in pull request are covered.

> :exclamation: Current head 3f5ff8f differs from pull request most recent head e9397c0. Consider uploading reports for the commit e9397c0 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3125      +/-   ##
==========================================
- Coverage   88.02%   87.97%   -0.05%     
==========================================
  Files         600      600              
  Lines       67093    67064      -29     
  Branches     6703     6687      -16     
==========================================
- Hits        59057    58999      -58     
- Misses       5206     5244      +38     
+ Partials     2830     2821       -9     
```


| [Impacted Files](https://codecov.io/gh/randombit/botan/pull/3125?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | Coverage Δ | |
|---|---|---|
| [src/lib/tls/tls12/tls\_client\_impl\_12.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTIvdGxzX2NsaWVudF9pbXBsXzEyLmNwcA==) | `83.38% <ø> (ø)` | |
| [src/lib/tls/tls\_client.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX2NsaWVudC5jcHA=) | `83.92% <ø> (ø)` | |
| [src/lib/tls/tls\_session.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX3Nlc3Npb24uY3Bw) | `85.71% <0.00%> (ø)` | |
| [src/lib/tls/tls\_extensions.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX2V4dGVuc2lvbnMuY3Bw) | `82.01% <40.00%> (+0.02%)` | :arrow_up: |
| [src/cli/tls\_client.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2NsaS90bHNfY2xpZW50LmNwcA==) | `60.24% <42.85%> (ø)` | |
| [src/lib/tls/msg\_cert\_verify.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX2NlcnRfdmVyaWZ5LmNwcA==) | `90.36% <50.00%> (ø)` | |
| [src/lib/tls/msg\_client\_hello.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX2NsaWVudF9oZWxsby5jcHA=) | `79.93% <56.00%> (+1.40%)` | :arrow_up: |
| [src/lib/tls/tls13/msg\_certificate\_req\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvbXNnX2NlcnRpZmljYXRlX3JlcV8xMy5jcHA=) | `67.39% <62.50%> (-5.78%)` | :arrow_down: |
| [src/lib/tls/tls13/msg\_encrypted\_extensions.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvbXNnX2VuY3J5cHRlZF9leHRlbnNpb25zLmNwcA==) | `76.92% <66.66%> (+6.92%)` | :arrow_up: |
| [src/lib/tls/tls13/tls\_server\_impl\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvdGxzX3NlcnZlcl9pbXBsXzEzLmNwcA==) | `76.42% <76.42%> (-4.93%)` | :arrow_down: |
| ... and [70 more](https://codecov.io/gh/randombit/botan/pull/3125/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/randombit/botan/pull/3125?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).


Patch:
diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp
index cb4c80046..3f3097289 100644
--- a/src/bogo_shim/bogo_shim.cpp
+++ b/src/bogo_shim/bogo_shim.cpp
@@ -1289,6 +1289,7 @@ class Shim_Credentials final : public Botan::Credentials_Manager
 
       std::vector<Botan::X509_Certificate> cert_chain(
          const std::vector<std::string>& cert_key_types,
+         const std::vector<Botan::AlgorithmIdentifier>& /*cert_signature_schemes*/,
          const std::string& /*type*/,
          const std::string& /*context*/) override
          {
diff --git a/src/cli/tls_helpers.h b/src/cli/tls_helpers.h
index 72afc1e66..f29ed1ae2 100644
--- a/src/cli/tls_helpers.h
+++ b/src/cli/tls_helpers.h
@@ -117,10 +117,13 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager
 
       std::vector<Botan::X509_Certificate> find_cert_chain(
          const std::vector<std::string>& algos,
+         const std::vector<Botan::AlgorithmIdentifier>& cert_signature_schemes,
          const std::vector<Botan::X509_DN>& acceptable_cas,
          const std::string& type,
          const std::string& hostname) override
          {
+         BOTAN_UNUSED(cert_signature_schemes);
+
          if(type == "tls-client")
             {
             for(const auto& dn : acceptable_cas)
diff --git a/src/examples/tls_client.cpp b/src/examples/tls_client.cpp
index 93ed072bb..82e0534d8 100644
--- a/src/examples/tls_client.cpp
+++ b/src/examples/tls_client.cpp
@@ -53,6 +53,7 @@ public:
   }
 
   std::vector<Botan::X509_Certificate> cert_chain(const std::vector<std::string> &cert_key_types,
+                                                  const std::vector<Botan::AlgorithmIdentifier> &cert_signature_schemes,
                                                   const std::string &type,
                                                   const std::string &context) override {
     // when using tls client authentication (optional), return
diff --git a/src/examples/tls_custom_curves_client.cpp b/src/examples/tls_custom_curves_client.cpp
index 133b8e5d3..0e3bd4eaa 100644
--- a/src/examples/tls_custom_curves_client.cpp
+++ b/src/examples/tls_custom_curves_client.cpp
@@ -66,6 +66,7 @@ public:
   }
 
   std::vector<Botan::X509_Certificate> cert_chain(const std::vector<std::string> &cert_key_types,
+                                                  const std::vector<Botan::AlgorithmIdentifier> &cert_signature_schemes,
                                                   const std::string &type,
                                                   const std::string &context) override {
     // when using tls client authentication (optional), return
diff --git a/src/examples/tls_custom_curves_server.cpp b/src/examples/tls_custom_curves_server.cpp
index 47ad52d6e..e233d9d63 100644
--- a/src/examples/tls_custom_curves_server.cpp
+++ b/src/examples/tls_custom_curves_server.cpp
@@ -79,6 +79,7 @@ public:
   }
 
   std::vector<Botan::X509_Certificate> cert_chain(const std::vector<std::string> &cert_key_types,
+                                                  const std::vector<Botan::AlgorithmIdentifier> &cert_signature_schemes,
                                                   const std::string &type,
                                                   const std::string &context) override {
     // return the certificate chain being sent to the tls client
diff --git a/src/examples/tls_proxy.cpp b/src/examples/tls_proxy.cpp
index 48f43a20a..62666ab96 100644
--- a/src/examples/tls_proxy.cpp
+++ b/src/examples/tls_proxy.cpp
@@ -64,6 +64,7 @@ public:
   }
 
   std::vector<Botan::X509_Certificate> cert_chain(const std::vector<std::string> &cert_key_types,
+                                                  const std::vector<Botan::AlgorithmIdentifier> &cert_signature_schemes,
                                                   const std::string &type,
                                                   const std::string &context) override {
     // return the certificate chain being sent to the tls client
diff --git a/src/fuzzer/tls_server.cpp b/src/fuzzer/tls_server.cpp
index 202db822d..b9e0cee74 100644
--- a/src/fuzzer/tls_server.cpp
+++ b/src/fuzzer/tls_server.cpp
@@ -74,6 +74,7 @@ class Fuzzer_TLS_Server_Creds : public Botan::Credentials_Manager
 
       std::vector<Botan::X509_Certificate> cert_chain(
          const std::vector<std::string>& algos,
+         const std::vector<Botan::AlgorithmIdentifier>& /*signature_schemes*/,
          const std::string& /*type*/,
          const std::string& /*hostname*/) override
          {
diff --git a/src/lib/tls/credentials_manager.cpp b/src/lib/tls/credentials_manager.cpp
index dc349d160..cadd237db 100644
--- a/src/lib/tls/credentials_manager.cpp
+++ b/src/lib/tls/credentials_manager.cpp
@@ -32,15 +32,17 @@ SymmetricKey Credentials_Manager::psk(const std::string& /*unused*/,
 
 std::vector<X509_Certificate> Credentials_Manager::find_cert_chain(
    const std::vector<std::string>& key_types,
+   const std::vector<AlgorithmIdentifier>& cert_signature_schemes,
    const std::vector<X509_DN>& /*unused*/,
    const std::string& type,
    const std::string& context)
    {
-   return cert_chain(key_types, type, context);
+   return cert_chain(key_types, cert_signature_schemes, type, context);
    }
 
 std::vector<X509_Certificate> Credentials_Manager::cert_chain(
    const std::vector<std::string>& /*unused*/,
+   const std::vector<AlgorithmIdentifier>& /*unused*/,
    const std::string& /*unused*/,
    const std::string& /*unused*/)
    {
@@ -49,12 +51,11 @@ std::vector<X509_Certificate> Credentials_Manager::cert_chain(
 
 std::vector<X509_Certificate> Credentials_Manager::cert_chain_single_type(
    const std::string& cert_key_type,
+   const std::vector<AlgorithmIdentifier>& cert_signature_schemes,
    const std::string& type,
    const std::string& context)
    {
-   std::vector<std::string> cert_types;
-   cert_types.push_back(cert_key_type);
-   return find_cert_chain(cert_types, std::vector<X509_DN>(), type, context);
+   return find_cert_chain({cert_key_type}, cert_signature_schemes, std::vector<X509_DN>(), type, context);
    }
 
 Private_Key* Credentials_Manager::private_key_for(const X509_Certificate& /*unused*/,
diff --git a/src/lib/tls/credentials_manager.h b/src/lib/tls/credentials_manager.h
index d036a1524..f03a67a57 100644
--- a/src/lib/tls/credentials_manager.h
+++ b/src/lib/tls/credentials_manager.h
@@ -10,6 +10,7 @@
 
 #include <botan/pk_keys.h>
 #include <botan/x509cert.h>
+#include <botan/asn1_obj.h>
 #include <botan/certstor.h>
 #include <botan/symkey.h>
 #include <string>
@@ -52,9 +53,15 @@ class BOTAN_PUBLIC_API(2,0) Credentials_Manager
       * It is assumed that the caller can get the private key of the
       * leaf with private_key_for
       *
+      * For a comprehensive write-up of how to select certificates for TLS
+      * CertificateVerify messages, see RFC 8446 Sections 4.4.2.2 and 4.4.2.3.
+      *
       * @param cert_key_types specifies the key types desired ("RSA",
       *                       "DSA", "ECDSA", etc), or empty if there
       *                       is no preference by the caller.
+      * @param cert_signature_schemes specifies the signature types desired
+      *                               as signatures in the certificate(s) itself,
+      *                               or empty for no preference by the caller.
       *
       * @param acceptable_CAs the CAs the requestor will accept (possibly empty)
       * @param type specifies the type of operation occurring
@@ -62,6 +69,7 @@ class BOTAN_PUBLIC_API(2,0) Credentials_Manager
       */
       virtual std::vector<X509_Certificate> find_cert_chain(
          const std::vector<std::string>& cert_key_types,
+         const std::vector<AlgorithmIdentifier>& cert_signature_schemes,
          const std::vector<X509_DN>& acceptable_CAs,
          const std::string& type,
          const std::string& context);
@@ -79,6 +87,9 @@ class BOTAN_PUBLIC_API(2,0) Credentials_Manager
       * @param cert_key_types specifies the key types desired ("RSA",
       *                       "DSA", "ECDSA", etc), or empty if there
       *                       is no preference by the caller.
+      * @param cert_signature_schemes specifies the signature types desired
+      *                               as signatures in the certificate(s) itself,
+      *                               or empty for no preference by the caller.
       *
       * @param type specifies the type of operation occurring
       *
@@ -86,6 +97,7 @@ class BOTAN_PUBLIC_API(2,0) Credentials_Manager
       */
       virtual std::vector<X509_Certificate> cert_chain(
          const std::vector<std::string>& cert_key_types,
+         const std::vector<AlgorithmIdentifier>& cert_signature_schemes,
          const std::string& type,
          const std::string& context);
 
@@ -98,6 +110,9 @@ class BOTAN_PUBLIC_API(2,0) Credentials_Manager
       *
       * @param cert_key_type specifies the type of key requested
       *                      ("RSA", "DSA", "ECDSA", etc)
+      * @param cert_signature_schemes specifies the signature types desired
+      *                               as signatures in the certificate(s) itself,
+      *                               or empty for no preference by the caller.
       *
       * @param type specifies the type of operation occurring
       *
@@ -105,6 +120,7 @@ class BOTAN_PUBLIC_API(2,0) Credentials_Manager
       */
       std::vector<X509_Certificate> cert_chain_single_type(
          const std::string& cert_key_type,
+         const std::vector<AlgorithmIdentifier>& cert_signature_schemes,
          const std::string& type,
          const std::string& context);
 
diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp
index b808beb2e..a095a333b 100644
--- a/src/lib/tls/msg_cert_verify.cpp
+++ b/src/lib/tls/msg_cert_verify.cpp
@@ -208,7 +208,7 @@ bool Certificate_Verify_13::verify(const X509_Certificate& cert,
    // RFC 8446 4.2.3
    //    The keys found in certificates MUST [...] be of appropriate type for
    //    the signature algorithms they are used with.
-   if(m_scheme.algorithm_identifier() != cert.subject_public_key_algo())
+   if(m_scheme.key_algorithm_identifier() != cert.subject_public_key_algo())
       { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Signature algorithm does not match certificate's public key"); }
 
    const auto key = cert.load_subject_public_key();
diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp
index 0d729b27b..8cb5b06a6 100644
--- a/src/lib/tls/msg_client_hello.cpp
+++ b/src/lib/tls/msg_client_hello.cpp
@@ -279,6 +279,18 @@ std::vector<Signature_Scheme> Client_Hello::signature_schemes() const
    return {};
    }
 
+std::vector<Signature_Scheme> Client_Hello::certificate_signature_schemes() const
+   {
+   // RFC 8446 4.2.3
+   //   If no "signature_algorithms_cert" extension is present, then the
+   //   "signature_algorithms" extension also applies to signatures appearing
+   //   in certificates.
+   if(Signature_Algorithms_Cert* sigs = m_data->extensions.get<Signature_Algorithms_Cert>())
+      { return sigs->supported_schemes(); }
+   else
+      { return signature_schemes(); }
+   }
+
 std::vector<Group_Params> Client_Hello::supported_ecc_curves() const
    {
    if(Supported_Groups* groups = m_data->extensions.get<Supported_Groups>())
@@ -483,6 +495,14 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
    m_data->extensions.add(supported_groups.release());
 
    m_data->extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
+   if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes())
+      {
+      // RFC 8446 4.2.3
+      //    TLS 1.2 implementations SHOULD also process this extension.
+      //    Implementations which have the same policy in both cases MAY omit
+      //    the "signature_algorithms_cert" extension.
+      m_data->extensions.add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value())));
+      }
 
    if(reneg_info.empty() && !next_protocols.empty())
       { m_data->extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); }
@@ -549,6 +569,14 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
    m_data->extensions.add(supported_groups.release());
 
    m_data->extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
+   if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes())
+      {
+      // RFC 8446 4.2.3
+      //    TLS 1.2 implementations SHOULD also process this extension.
+      //    Implementations which have the same policy in both cases MAY omit
+      //    the "signature_algorithms_cert" extension.
+      m_data->extensions.add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value())));
+      }
 
    if(reneg_info.empty() && !next_protocols.empty())
       { m_data->extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); }
@@ -735,15 +763,19 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
    m_data->extensions.add(new Supported_Versions(Protocol_Version::TLS_V13, policy));
 
    m_data->extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
+   if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes())
+      {
+      // RFC 8446 4.2.3
+      //    Implementations which have the same policy in both cases MAY omit
+      //    the "signature_algorithms_cert" extension.
+      m_data->extensions.add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value())));
+      }
 
    // TODO: Support for PSK-only mode without a key exchange.
    //       This should be configurable in TLS::Policy and should allow no PSK
    //       support at all (e.g. to disable support for session resumption).
    m_data->extensions.add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE}));
 
-   // TODO: Add a signature_algorithms_cert extension negotiating the acceptable
-   //       signature algorithms in a server certificate chain's certificates.
-
    if(policy.support_cert_status_message())
       m_data->extensions.add(new Certificate_Status_Request({}, {}));
 
diff --git a/src/lib/tls/tls12/tls_client_impl_12.cpp b/src/lib/tls/tls12/tls_client_impl_12.cpp
index 5da8f88d0..456d658de 100644
--- a/src/lib/tls/tls12/tls_client_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_client_impl_12.cpp
@@ -647,6 +647,7 @@ void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state,
 
          std::vector<X509_Certificate> client_certs =
             m_creds.find_cert_chain(types,
+                                    {},
                                     state.cert_req()->acceptable_CAs(),
                                     "tls-client",
                                     m_info.hostname());
diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp
index a3e2e3bd6..866ebe173 100644
--- a/src/lib/tls/tls12/tls_server_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_server_impl_12.cpp
@@ -242,6 +242,7 @@ uint16_t choose_ciphersuite(
 
 std::map<std::string, std::vector<X509_Certificate>>
 get_server_certs(const std::string& hostname,
+                 const std::vector<Signature_Scheme>& cert_sig_schemes,
                  Credentials_Manager& creds)
    {
    const char* cert_types[] = { "RSA", "ECDSA", "DSA", nullptr };
@@ -251,7 +252,7 @@ get_server_certs(const std::string& hostname,
    for(size_t i = 0; cert_types[i]; ++i)
       {
       const std::vector<X509_Certificate> certs =
-         creds.cert_chain_single_type(cert_types[i], "tls-server", hostname);
+         creds.cert_chain_single_type(cert_types[i], to_algorithm_identifiers(cert_sig_schemes), "tls-server", hostname);
 
       if(!certs.empty())
          cert_chains[cert_types[i]] = certs;
@@ -812,11 +813,18 @@ void Server_Impl_12::session_create(Server_Handshake_State& pending_state,
 
    const std::string sni_hostname = pending_state.client_hello()->sni_hostname();
 
-   cert_chains = get_server_certs(sni_hostname, m_creds);
+   // RFC 8446 1.3
+   //    The "signature_algorithms_cert" extension allows a client to indicate
+   //    which signature algorithms it can validate in X.509 certificates.
+   //
+   // RFC 8446 4.2.3
+   //     TLS 1.2 implementations SHOULD also process this extension.
+   const auto cert_signature_schemes = pending_state.client_hello()->certificate_signature_schemes();
+   cert_chains = get_server_certs(sni_hostname, cert_signature_schemes, m_creds);
 
    if(!sni_hostname.empty() && cert_chains.empty())
       {
-      cert_chains = get_server_certs("", m_creds);
+      cert_chains = get_server_certs("", cert_signature_schemes, m_creds);
 
       /*
       * Only send the unrecognized_name alert if we couldn't
diff --git a/src/lib/tls/tls13/msg_certificate_13.cpp b/src/lib/tls/tls13/msg_certificate_13.cpp
index ae793d2e5..562318e7c 100644
--- a/src/lib/tls/tls13/msg_certificate_13.cpp
+++ b/src/lib/tls/tls13/msg_certificate_13.cpp
@@ -179,10 +179,9 @@ Certificate_13::Certificate_13(const Certificate_Request_13& cert_request,
    m_request_context(cert_request.context()),
    m_side(Connection_Side::CLIENT)
    {
-   // TODO: implement "signature_algorithms_cert"
-   //       -> see c'tor for server certificates
    setup_entries(credentials_manager.find_cert_chain(
                     filter_signature_schemes(cert_request.signature_schemes()),
+                    to_algorithm_identifiers(cert_request.certificate_signature_schemes()),
                     cert_request.acceptable_CAs(),
                     "tls-client",
                     hostname),
@@ -202,20 +201,11 @@ Certificate_13::Certificate_13(const Client_Hello_13& client_hello,
    m_request_context(),
    m_side(Connection_Side::SERVER)
    {
-   // TODO: implement "signature_algorithms_cert"
    BOTAN_ASSERT_NOMSG(client_hello.extensions().has<Signature_Algorithms>());
 
-   // TODO: To fully support "signature_algorithm_cert", this would need to
-   //       receive two algorithm names. One for the signature algorithm used
-   //       to sign the certificate and one for the public key contained in
-   //       the certificate. Both must be supported to comply with the
-   //       client's asymmetric key requirements.
-   // Note: This requires a change in the public API of CredentialsManager.
-   //
-   // see: https://github.com/randombit/botan/issues/2714#issuecomment-1057175631
-   // see: RFC 8446 4.4.2.2
    setup_entries(credentials_manager.find_cert_chain(
                     filter_signature_schemes(client_hello.signature_schemes()),
+                    to_algorithm_identifiers(client_hello.certificate_signature_schemes()),
                     {}, "tls-server", client_hello.sni_hostname()),
                  client_hello.extensions().get<Certificate_Status_Request>(),
                  callbacks);
diff --git a/src/lib/tls/tls13/msg_certificate_req_13.cpp b/src/lib/tls/tls13/msg_certificate_req_13.cpp
index a3981bed4..fa08489e2 100644
--- a/src/lib/tls/tls13/msg_certificate_req_13.cpp
+++ b/src/lib/tls/tls13/msg_certificate_req_13.cpp
@@ -70,8 +70,8 @@ Certificate_Request_13::Certificate_Request_13(const std::vector<uint8_t>& buf,
       }
    }
 
-Certificate_Request_13::Certificate_Request_13(std::vector<Signature_Scheme> signature_schemes,
-                                               std::vector<X509_DN> acceptable_CAs,
+Certificate_Request_13::Certificate_Request_13(std::vector<X509_DN> acceptable_CAs,
+                                               const Policy& policy,
                                                Callbacks&)
    {
    // RFC 8446 4.3.2
@@ -91,9 +91,14 @@ Certificate_Request_13::Certificate_Request_13(std::vector<Signature_Scheme> sig
    //
    //    The "signature_algorithms" extension MUST be specified, and other
    //    extensions may optionally be included if defined for this message.
-   //
-   // TODO: fully support 'signature_algorithms_cert'
-   m_extensions.add(std::make_unique<Signature_Algorithms>(std::move(signature_schemes)));
+   m_extensions.add(std::make_unique<Signature_Algorithms>(policy.acceptable_signature_schemes()));
+   if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes())
+      {
+      // RFC 8446 4.2.3
+      //    Implementations which have the same policy in both cases MAY omit
+      //    the "signature_algorithms_cert" extension.
+      m_extensions.add(std::make_unique<Signature_Algorithms_Cert>(std::move(cert_signing_prefs.value())));
+      }
 
    if(!acceptable_CAs.empty())
       {
@@ -127,8 +132,8 @@ Certificate_Request_13::maybe_create(const Client_Hello_13& client_hello,
       return std::nullopt;
       }
 
-   return Certificate_Request_13(policy.acceptable_signature_schemes(),
-                                 std::move(client_auth_CAs),
+   return Certificate_Request_13(std::move(client_auth_CAs),
+                                 policy,
                                  callbacks);
    }
 
@@ -148,6 +153,18 @@ const std::vector<Signature_Scheme>& Certificate_Request_13::signature_schemes()
    return m_extensions.get<Signature_Algorithms>()->supported_schemes();
    }
 
+const std::vector<Signature_Scheme>& Certificate_Request_13::certificate_signature_schemes() const
+   {
+   // RFC 8446 4.2.3
+   //   If no "signature_algorithms_cert" extension is present, then the
+   //   "signature_algorithms" extension also applies to signatures appearing
+   //   in certificates.
+   if(auto sig_schemes_cert = m_extensions.get<Signature_Algorithms_Cert>())
+      { return sig_schemes_cert->supported_schemes(); }
+   else
+      { return signature_schemes(); }
+   }
+
 std::vector<uint8_t> Certificate_Request_13::serialize() const
    {
    std::vector<uint8_t> buf;
diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp
index d3db80a4d..ee5b57971 100644
--- a/src/lib/tls/tls_extensions.cpp
+++ b/src/lib/tls/tls_extensions.cpp
@@ -49,6 +49,9 @@ std::unique_ptr<Extension> make_extension(TLS_Data_Reader& reader,
       case Extension_Code::SignatureAlgorithms:
          return std::make_unique<Signature_Algorithms>(reader, size);
 
+      case Extension_Code::CertSignatureAlgorithms:
+         return std::make_unique<Signature_Algorithms_Cert>(reader, size);
+
       case Extension_Code::UseSrtp:
          return std::make_unique<SRTP_Protection_Profiles>(reader, size);
 
@@ -86,9 +89,6 @@ std::unique_ptr<Extension> make_extension(TLS_Data_Reader& reader,
       case Extension_Code::CertificateAuthorities:
          return std::make_unique<Certificate_Authorities>(reader, size);
 
-      case Extension_Code::CertSignatureAlgorithms:
-         return std::make_unique<Signature_Algorithms_Cert>(reader, size);
-
       case Extension_Code::KeyShare:
          return std::make_unique<Key_Share>(reader, size, message_type);
 #endif
@@ -561,8 +561,6 @@ Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader,
    : m_schemes(parse_signature_algorithms(reader, extension_size))
    {}
 
-#if defined(BOTAN_HAS_TLS_13)
-
 std::vector<uint8_t> Signature_Algorithms_Cert::serialize(Connection_Side /*whoami*/) const
    {
    return serialize_signature_algorithms(m_schemes);
@@ -573,8 +571,6 @@ Signature_Algorithms_Cert::Signature_Algorithms_Cert(TLS_Data_Reader& reader,
    : m_schemes(parse_signature_algorithms(reader, extension_size))
    {}
 
-#endif
-
 Session_Ticket::Session_Ticket(TLS_Data_Reader& reader,
                                uint16_t extension_size) : m_ticket(reader.get_elem<uint8_t, std::vector<uint8_t>>(extension_size))
    {}
diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h
index 5fd080d27..9688da29e 100644
--- a/src/lib/tls/tls_extensions.h
+++ b/src/lib/tls/tls_extensions.h
@@ -56,6 +56,7 @@ enum class Extension_Code : uint16_t {
    SupportedGroups                     = 10,
    EcPointFormats                      = 11,
    SignatureAlgorithms                 = 13,
+   CertSignatureAlgorithms             = 50,
    UseSrtp                             = 14,
    ApplicationLayerProtocolNegotiation = 16,
 
@@ -78,7 +79,6 @@ enum class Extension_Code : uint16_t {
    CertificateAuthorities              = 47,
    // OidFilters                          = 48,  // NYI
 
-   CertSignatureAlgorithms             = 50,
    KeyShare                            = 51,
 #endif
 
@@ -337,8 +337,6 @@ class BOTAN_UNSTABLE_API Signature_Algorithms final : public Extension
       std::vector<Signature_Scheme> m_schemes;
    };
 
-#if defined(BOTAN_HAS_TLS_13)
-
 /**
 * Signature_Algorithms_Cert for TLS 1.3 (RFC 8446)
 *
@@ -348,6 +346,9 @@ class BOTAN_UNSTABLE_API Signature_Algorithms final : public Extension
 *    extension applies to signatures in certificates, and the
 *    "signature_algorithms" extension, which originally appeared in TLS 1.2,
 *    applies to signatures in CertificateVerify messages.
+*
+* RFC 8446 4.2.3
+*    TLS 1.2 implementations SHOULD also process this extension.
 */
 class BOTAN_UNSTABLE_API Signature_Algorithms_Cert final : public Extension
    {
@@ -372,8 +373,6 @@ class BOTAN_UNSTABLE_API Signature_Algorithms_Cert final : public Extension
       std::vector<Signature_Scheme> m_schemes;
    };
 
-#endif
-
 /**
 * Used to indicate SRTP algorithms for DTLS (RFC 5764)
 */
diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h
index 422bbb8e6..8a9dc9ab5 100644
--- a/src/lib/tls/tls_messages.h
+++ b/src/lib/tls/tls_messages.h
@@ -105,6 +105,7 @@ class BOTAN_UNSTABLE_API Client_Hello : public Handshake_Message
       bool offered_suite(uint16_t ciphersuite) const;
 
       std::vector<Signature_Scheme> signature_schemes() const;
+      std::vector<Signature_Scheme> certificate_signature_schemes() const;
 
       std::vector<Group_Params> supported_ecc_curves() const;
 
@@ -684,6 +685,8 @@ class BOTAN_UNSTABLE_API Certificate_Request_13 final : public Handshake_Message
 
       std::vector<X509_DN> acceptable_CAs() const;
       const std::vector<Signature_Scheme>& signature_schemes() const;
+      const std::vector<Signature_Scheme>& certificate_signature_schemes() const;
+
       const Extensions& extensions() const { return m_extensions; }
 
       std::vector<uint8_t> serialize() const override;
@@ -691,8 +694,8 @@ class BOTAN_UNSTABLE_API Certificate_Request_13 final : public Handshake_Message
       const std::vector<uint8_t> context() const { return m_context; }
 
    private:
-      Certificate_Request_13(std::vector<Signature_Scheme> signature_schemes,
-                             std::vector<X509_DN> acceptable_CAs,
+      Certificate_Request_13(std::vector<X509_DN> acceptable_CAs,
+                             const Policy& policy,
                              Callbacks& callbacks);
 
    private:
diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp
index 9bfcd8562..66b101f44 100644
--- a/src/lib/tls/tls_policy.cpp
+++ b/src/lib/tls/tls_policy.cpp
@@ -42,6 +42,12 @@ std::vector<Signature_Scheme> Policy::acceptable_signature_schemes() const
    return this->allowed_signature_schemes();
    }
 
+std::optional<std::vector<Signature_Scheme>> Policy::acceptable_certificate_signature_schemes() const
+   {
+   // the restrictions of ::acceptable_signature_schemes() shall apply
+   return std::nullopt;
+   }
+
 std::vector<std::string> Policy::allowed_ciphers() const
    {
    return {
diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h
index eb550df97..1a9468337 100644
--- a/src/lib/tls/tls_policy.h
+++ b/src/lib/tls/tls_policy.h
@@ -67,6 +67,8 @@ class BOTAN_PUBLIC_API(2,0) Policy
       */
       virtual std::vector<Signature_Scheme> acceptable_signature_schemes() const;
 
+      virtual std::optional<std::vector<Signature_Scheme>> acceptable_certificate_signature_schemes() const;
+
       /**
       * The minimum signature strength we will accept
       * Returning 80 allows RSA 1024 and SHA-1. Values larger than 80 disable SHA-1 support.
diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp
index 067e0d982..ae30ee4c2 100644
--- a/src/lib/tls/tls_signature_scheme.cpp
+++ b/src/lib/tls/tls_signature_scheme.cpp
@@ -11,6 +11,9 @@
 #include <botan/tls_exceptn.h>
 #include <botan/tls_version.h>
 #include <botan/internal/stl_util.h>
+#include <botan/hash.h>
+#include <botan/der_enc.h>
+#include <botan/internal/emsa.h>
 
 namespace Botan::TLS {
 
@@ -208,7 +211,7 @@ std::string Signature_Scheme::algorithm_name() const noexcept
       }
    }
 
-AlgorithmIdentifier Signature_Scheme::algorithm_identifier() const noexcept
+AlgorithmIdentifier Signature_Scheme::key_algorithm_identifier() const noexcept
    {
    switch(m_code)
       {
@@ -237,6 +240,14 @@ AlgorithmIdentifier Signature_Scheme::algorithm_identifier() const noexcept
       }
    }
 
+AlgorithmIdentifier Signature_Scheme::algorithm_identifier() const noexcept
+   {
+   auto emsa = EMSA::create(padding_string());
+   if(!emsa)
+      { return AlgorithmIdentifier(); }
+   return emsa->config_for_x509(algorithm_name(), hash_function_name());
+   }
+
 std::optional<Signature_Format> Signature_Scheme::format() const noexcept
    {
    switch(m_code)
@@ -310,4 +321,12 @@ bool Signature_Scheme::is_suitable_for(const Private_Key &private_key) const noe
    return true;
    }
 
+std::vector<AlgorithmIdentifier> to_algorithm_identifiers(const std::vector<Signature_Scheme>& schemes)
+   {
+   std::vector<AlgorithmIdentifier> result;
+   std::transform(schemes.begin(), schemes.end(), std::back_inserter(result),
+                  [](const auto& scheme) { return scheme.algorithm_identifier(); });
+   return result;
+   }
+
 }  // Botan::TLS
diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h
index c06d22fb9..1b1fd9cdb 100644
--- a/src/lib/tls/tls_signature_scheme.h
+++ b/src/lib/tls/tls_signature_scheme.h
@@ -82,6 +82,7 @@ public:
    std::string hash_function_name() const noexcept;
    std::string padding_string() const noexcept;
    std::string algorithm_name() const noexcept;
+   AlgorithmIdentifier key_algorithm_identifier() const noexcept;
    AlgorithmIdentifier algorithm_identifier() const noexcept;
    std::optional<Signature_Format> format() const noexcept;
 
@@ -95,6 +96,8 @@ private:
    Signature_Scheme::Code m_code;
 };
 
+std::vector<AlgorithmIdentifier> to_algorithm_identifiers(const std::vector<Signature_Scheme>& schemes);
+
 }  // namespace Botan::TLS
 
 #endif  // BOTAN_TLS_SIGNATURE_SCHEME_H_


Test patch:
diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp
index 0a723508b..d61a0319b 100644
--- a/src/tests/test_tls_rfc8448.cpp
+++ b/src/tests/test_tls_rfc8448.cpp
@@ -460,10 +460,11 @@ class Test_Credentials : public Botan::Credentials_Manager
 
       std::vector<Botan::X509_Certificate> cert_chain(
          const std::vector<std::string>& cert_key_types,
+         const std::vector<AlgorithmIdentifier>& cert_signature_schemes,
          const std::string& type,
          const std::string& context) override
          {
-         BOTAN_UNUSED(cert_key_types, context);
+         BOTAN_UNUSED(cert_key_types, cert_signature_schemes, context);
          return
             {
             (type == "tls-client")
diff --git a/src/tests/test_tls_signature_scheme.cpp b/src/tests/test_tls_signature_scheme.cpp
index d087aa6ab..f833fd774 100644
--- a/src/tests/test_tls_signature_scheme.cpp
+++ b/src/tests/test_tls_signature_scheme.cpp
@@ -39,7 +39,7 @@ std::vector<Test::Result> test_signature_scheme()
 
             result.confirm("format handles all cases", s.format().has_value());
             result.confirm("algorithm_identifier handles all cases",
-               Botan::AlgorithmIdentifier() != s.algorithm_identifier());
+               Botan::AlgorithmIdentifier() != s.key_algorithm_identifier());
             })
       );
       }
@@ -57,7 +57,7 @@ std::vector<Test::Result> test_signature_scheme()
 
       result.confirm("format deals with bogus schemes", !bogus.format().has_value());
       result.confirm("algorithm_identifier deals with bogus schemes",
-         Botan::AlgorithmIdentifier() == bogus.algorithm_identifier());
+         Botan::AlgorithmIdentifier() == bogus.key_algorithm_identifier());
       })
    );
 
diff --git a/src/tests/unit_tls.cpp b/src/tests/unit_tls.cpp
index 29d64346b..401a3fd4b 100644
--- a/src/tests/unit_tls.cpp
+++ b/src/tests/unit_tls.cpp
@@ -92,6 +92,7 @@ class Credentials_Manager_Test final : public Botan::Credentials_Manager
 
       std::vector<Botan::X509_Certificate> find_cert_chain(
          const std::vector<std::string>& cert_key_types,
+         const std::vector<Botan::AlgorithmIdentifier>& /*unused*/,
          const std::vector<Botan::X509_DN>& acceptable_CAs,
          const std::string& type,
          const std::string& context) override