Skip to content

Commit 3abfc13

Browse files
Hannes Rantzschreneme
andcommitted
TLS 1.3 Client Authentication in main handshake
This allows for client authentication using certificates in the main handshake. Post-handshake authentication is out of scope of this commit Co-authored-by: René Meusel <rene.meusel@nexenio.com>
1 parent eeb3230 commit 3abfc13

22 files changed

Lines changed: 676 additions & 128 deletions

src/bogo_shim/bogo_shim.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ std::string map_to_bogo_error(const std::string& e)
8282
{ "Application data before handshake done", ":APPLICATION_DATA_INSTEAD_OF_HANDSHAKE:" },
8383
{ "Bad Hello_Request, has non-zero size", ":BAD_HELLO_REQUEST:" },
8484
{ "Bad code for TLS alert level", ":UNKNOWN_ALERT_TYPE:" },
85+
{ "Bad encoding on signature algorithms extension", ":DECODE_ERROR:" },
8586
{ "Bad extension size", ":DECODE_ERROR:" },
8687
{ "Bad length in hello verify request", ":DECODE_ERROR:" },
8788
{ "Bad lengths in DTLS header", ":BAD_HANDSHAKE_RECORD:" },
@@ -99,6 +100,8 @@ std::string map_to_bogo_error(const std::string& e)
99100
{ "Certificate key type did not match ciphersuite", ":WRONG_CERTIFICATE_TYPE:" },
100101
{ "Certificate usage constraints do not allow this ciphersuite", ":KEY_USAGE_BIT_INCORRECT:" },
101102
{ "Certificate: Message malformed", ":DECODE_ERROR:" },
103+
{ "Certificate_Request context must be empty in the main handshake", ":DECODE_ERROR:" },
104+
{ "Certificate_Request message did not provide a signature_algorithms extension", ":DECODE_ERROR:" },
102105
{ "Channel_Impl_12::key_material_export cannot export during renegotiation", "failed to export keying material" },
103106
{ "Client cert verify failed", ":BAD_SIGNATURE:" },
104107
{ "Client certificate does not support signing", ":KEY_USAGE_BIT_INCORRECT:" },
@@ -123,6 +126,8 @@ std::string map_to_bogo_error(const std::string& e)
123126
{ "Empty ALPN protocol not allowed", ":PARSE_TLSEXT:" },
124127
{ "Encoding error: Cannot encode PSS string, output length too small", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
125128
{ "Expected TLS but got a record with DTLS version", ":WRONG_VERSION_NUMBER:" },
129+
{ "Failed to agree on a signature algorithm", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
130+
{ "Failed to negotiate a common signature algorithm for client authentication", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
126131
{ "Finished message didn't verify", ":DIGEST_CHECK_FAILED:" },
127132
{ "Have data remaining in buffer after ClientHello", ":EXCESS_HANDSHAKE_DATA:" },
128133
{ "Have data remaining in buffer after Finished", ":EXCESS_HANDSHAKE_DATA:" },
@@ -824,7 +829,10 @@ class Shim_Policy final : public Botan::TLS::Policy
824829
{
825830
const Botan::TLS::Signature_Scheme scheme(pref);
826831
if(!scheme.is_available())
832+
{
833+
shim_log("skipping inavailable but preferred signature scheme: " + std::to_string(pref));
827834
continue;
835+
}
828836
pref_hash.push_back(scheme.hash_function_name());
829837
}
830838

@@ -876,10 +884,17 @@ class Shim_Policy final : public Botan::TLS::Policy
876884
schemes.emplace_back(static_cast<uint16_t>(pref));
877885
}
878886

879-
// BoGo gets sad if these are not included in our signature_algorithms extension
887+
// The relevant tests (*-Sign-Negotiate-*) want to configure a preference
888+
// for the scheme of our signing operation (-signing-prefs). However, this
889+
// policy method (`allowed_signature_schemes`) also restricts the peer's
890+
// signing operation. If we weren't to add a few 'common' algorithms, initial
891+
// security parameter negotiation would fail.
892+
// By placing the BoGo-configured scheme first we make sure our implementation
893+
// meets BoGo's expectation when it is our turn to sign.
880894
if(!m_args.flag_set("server"))
881895
{
882896
schemes.emplace_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256);
897+
schemes.emplace_back(Botan::TLS::Signature_Scheme::RSA_PSS_SHA256);
883898
schemes.emplace_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256);
884899
}
885900

src/bogo_shim/config.json

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"PartialFinishedWithServerHelloDone": "Unexpected record vs excess handshake data",
1313
"HelloRetryRequest-DuplicateCurve-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'",
1414
"HelloRetryRequest-DuplicateCookie-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'",
15-
"EncryptedExtensionsWithKeyShare-TLS13": "expects 'unsupported extension' but RFC requires 'illegal parameter'"
15+
"EncryptedExtensionsWithKeyShare-TLS13": "expects 'unsupported extension' but RFC requires 'illegal parameter'",
16+
"ClientSkipCertificateVerify-TLS13": "would require ambiguous error mapping"
1617
},
1718

1819
"DisabledTests": {
@@ -113,23 +114,7 @@
113114
"*EarlyData*": "No TLS 1.3 Early Data, yet",
114115
"TLS13-1RTT-Client-*": "No TLS 1.3 Early Data, yet",
115116

116-
"FailCertCallback-Client-TLS13": "No client auth in TLS 1.3, yet",
117-
"Client-Sign*-TLS13": "No client auth in TLS 1.3, yet",
118-
"TLS13-Client-ClientAuth-": "No client auth in TLS 1.3, yet",
119-
"ClientAuth-*-TLS13": "No client auth in TLS 1.3, yet",
120-
"TLS13-Client-ClientAuth-*": "No client auth in TLS 1.3, yet",
121-
"NoClientCertificate-TLS13": "No client auth in TLS 1.3, yet",
122-
"NoCommonAlgorithms-TLS13": "No client auth in TLS 1.3, yet",
123-
"ClientAuth-*-TLS13-*": "No client auth in TLS 1.3, yet",
124-
"TrailingMessageData-TLS13-CertificateRequest-TLS": "No client auth in TLS 1.3, yet",
125-
"RequestContextInHandshake-TLS13": "No client auth in TLS 1.3, yet",
126-
"UnknownExtensionInCertificateRequest-TLS13": "No client auth in TLS 1.3, yet",
127-
"MissingSignatureAlgorithmsInCertificateRequest-TLS13": "No client auth in TLS 1.3, yet",
128-
"ClientSkipCertificateVerify-TLS13": "No client auth in TLS 1.3, yet",
129-
"SendReceiveIntermediate-Client-TLS13": "No client auth in TLS 1.3, yet",
130-
"TLS13-Client-CertReq-CA-List": "No client auth in TLS 1.3, yet",
131-
"SendNoClientCertificateExtensions-TLS13": "No client auth in TLS 1.3, yet",
132-
117+
"SendNoClientCertificateExtensions-TLS13": "-signed-cert-timestamps currently not supported in the shim",
133118
"KeyUpdate-RequestACK-UnfinishedWrite": "-read-with-unfinished-write currently not supported in the shim",
134119

135120
"*Binder*": "No TLS 1.3",

src/lib/tls/msg_cert_req.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
namespace Botan::TLS {
1919

20-
Handshake_Type Certificate_Req::type() const
20+
Handshake_Type Certificate_Request_12::type() const
2121
{
2222
return CERTIFICATE_REQUEST;
2323
}
@@ -56,7 +56,7 @@ uint8_t cert_type_name_to_code(const std::string& name)
5656
/**
5757
* Create a new Certificate Request message
5858
*/
59-
Certificate_Req::Certificate_Req(Handshake_IO& io,
59+
Certificate_Request_12::Certificate_Request_12(Handshake_IO& io,
6060
Handshake_Hash& hash,
6161
const Policy& policy,
6262
const std::vector<X509_DN>& ca_certs) :
@@ -70,7 +70,7 @@ Certificate_Req::Certificate_Req(Handshake_IO& io,
7070
/**
7171
* Deserialize a Certificate Request message
7272
*/
73-
Certificate_Req::Certificate_Req(const std::vector<uint8_t>& buf)
73+
Certificate_Request_12::Certificate_Request_12(const std::vector<uint8_t>& buf)
7474
{
7575
if(buf.size() < 4)
7676
throw Decoding_Error("Certificate_Req: Bad certificate request");
@@ -115,25 +115,25 @@ Certificate_Req::Certificate_Req(const std::vector<uint8_t>& buf)
115115
}
116116
}
117117

118-
const std::vector<std::string>& Certificate_Req::acceptable_cert_types() const
118+
const std::vector<std::string>& Certificate_Request_12::acceptable_cert_types() const
119119
{
120120
return m_cert_key_types;
121121
}
122122

123-
const std::vector<X509_DN>& Certificate_Req::acceptable_CAs() const
123+
const std::vector<X509_DN>& Certificate_Request_12::acceptable_CAs() const
124124
{
125125
return m_names;
126126
}
127127

128-
const std::vector<Signature_Scheme>& Certificate_Req::signature_schemes() const
128+
const std::vector<Signature_Scheme>& Certificate_Request_12::signature_schemes() const
129129
{
130130
return m_schemes;
131131
}
132132

133133
/**
134134
* Serialize a Certificate Request message
135135
*/
136-
std::vector<uint8_t> Certificate_Req::serialize() const
136+
std::vector<uint8_t> Certificate_Request_12::serialize() const
137137
{
138138
std::vector<uint8_t> buf;
139139

src/lib/tls/msg_cert_verify.cpp

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Botan is released under the Simplified BSD License (see license.txt)
99
*/
1010

11+
#include "botan/internal/stl_util.h"
1112
#include <botan/tls_messages.h>
1213

1314
#include <botan/internal/tls_handshake_io.h>
@@ -19,14 +20,52 @@
1920

2021
namespace Botan::TLS {
2122

23+
namespace {
24+
25+
std::vector<uint8_t> message(Connection_Side side, const Transcript_Hash& hash)
26+
{
27+
std::vector<uint8_t> msg(64, 0x20);
28+
msg.reserve(64 + 32 + 1 + hash.size());
29+
30+
const std::string context_string = (side == Botan::TLS::Connection_Side::SERVER)
31+
? "TLS 1.3, server CertificateVerify"
32+
: "TLS 1.3, client CertificateVerify";
33+
34+
msg.insert(msg.end(), context_string.cbegin(), context_string.cend());
35+
msg.push_back(0x00);
36+
37+
msg.insert(msg.end(), hash.cbegin(), hash.cend());
38+
return msg;
39+
}
40+
41+
Signature_Scheme choose_signature_scheme(
42+
const Private_Key& key,
43+
const std::vector<Signature_Scheme>& allowed_schemes,
44+
const std::vector<Signature_Scheme>& peer_allowed_schemes)
45+
{
46+
for(Signature_Scheme scheme : allowed_schemes)
47+
{
48+
if(scheme.is_available()
49+
&& scheme.is_suitable_for(key)
50+
&& value_exists(peer_allowed_schemes, scheme))
51+
{
52+
return scheme;
53+
}
54+
}
55+
56+
throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Failed to agree on a signature algorithm");
57+
}
58+
59+
}
60+
2261
/*
23-
* Create a new Certificate Verify message
62+
* Create a new Certificate Verify message for TLS 1.2
2463
*/
25-
Certificate_Verify::Certificate_Verify(Handshake_IO& io,
26-
Handshake_State& state,
27-
const Policy& policy,
28-
RandomNumberGenerator& rng,
29-
const Private_Key* priv_key)
64+
Certificate_Verify_12::Certificate_Verify_12(Handshake_IO& io,
65+
Handshake_State& state,
66+
const Policy& policy,
67+
RandomNumberGenerator& rng,
68+
const Private_Key* priv_key)
3069
{
3170
BOTAN_ASSERT_NONNULL(priv_key);
3271

@@ -106,8 +145,35 @@ bool Certificate_Verify_12::verify(const X509_Certificate& cert,
106145

107146
#if defined(BOTAN_HAS_TLS_13)
108147

148+
/*
149+
* Create a new Certificate Verify message for TLS 1.3
150+
*/
151+
Certificate_Verify_13::Certificate_Verify_13(
152+
const std::vector<Signature_Scheme>& peer_allowed_schemes,
153+
Connection_Side whoami,
154+
const Private_Key& key,
155+
const Policy& policy,
156+
const Transcript_Hash& hash,
157+
Callbacks& callbacks,
158+
RandomNumberGenerator& rng)
159+
: m_side(whoami)
160+
{
161+
m_scheme = choose_signature_scheme(key, policy.allowed_signature_schemes(), peer_allowed_schemes);
162+
BOTAN_ASSERT_NOMSG(m_scheme.is_available());
163+
164+
// we need to verify that the provided private key is strong enough for TLS 1.3
165+
166+
m_signature =
167+
callbacks.tls_sign_message(key,
168+
rng,
169+
m_scheme.padding_string(),
170+
m_scheme.format().value(),
171+
message(m_side, hash));
172+
}
173+
174+
109175
Certificate_Verify_13::Certificate_Verify_13(const std::vector<uint8_t>& buf,
110-
const Connection_Side side)
176+
const Connection_Side side)
111177
: Certificate_Verify(buf)
112178
, m_side(side)
113179
{
@@ -133,24 +199,12 @@ bool Certificate_Verify_13::verify(const X509_Certificate& cert,
133199
if(m_scheme.algorithm_identifier() != cert.subject_public_key_algo())
134200
{ throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Signature algorithm does not match certificate's public key"); }
135201

136-
std::vector<uint8_t> msg(64, 0x20);
137-
msg.reserve(64 + 32 + 1 + transcript_hash.size());
138-
139-
const std::string context_string = (m_side == Botan::TLS::Connection_Side::SERVER)
140-
? "TLS 1.3, server CertificateVerify"
141-
: "TLS 1.3, client CertificateVerify";
142-
143-
msg.insert(msg.end(), context_string.cbegin(), context_string.cend());
144-
msg.push_back(0x00);
145-
146-
msg.insert(msg.end(), transcript_hash.cbegin(), transcript_hash.cend());
147-
148202
const auto key = cert.load_subject_public_key();
149203
const bool signature_valid =
150204
callbacks.tls_verify_message(*key,
151205
m_scheme.padding_string(),
152206
m_scheme.format().value(),
153-
msg,
207+
message(m_side, transcript_hash),
154208
m_signature);
155209

156210
#if defined(BOTAN_UNSAFE_FUZZER_MODE)

src/lib/tls/msg_certificate_13.cpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ void Certificate_13::validate_extensions(const std::set<Handshake_Extension_Type
4747
{ throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Certificate Entry contained an extension that was not offered"); }
4848
}
4949

50+
const X509_Certificate& Certificate_13::leaf() const
51+
{
52+
BOTAN_STATE_CHECK(!empty());
53+
return m_entries.front().certificate;
54+
}
55+
5056
void Certificate_13::verify(Callbacks& callbacks,
5157
const Policy& policy,
5258
Credentials_Manager& creds,
@@ -90,6 +96,17 @@ void Certificate_13::verify(Callbacks& callbacks,
9096
callbacks.tls_verify_cert_chain(certs, ocsp_responses, trusted_CAs, usage, hostname, policy);
9197
}
9298

99+
Certificate_13::Certificate_13(const std::vector<X509_Certificate>& certs,
100+
const Connection_Side side,
101+
const std::vector<uint8_t> &request_context)
102+
: m_request_context(request_context)
103+
, m_side(side)
104+
{
105+
// TODO: proper extensions
106+
for (const auto& c : certs)
107+
{ m_entries.emplace_back(Certificate_Entry{c, Extensions()}); }
108+
}
109+
93110
/**
94111
* Deserialize a Certificate message
95112
*/
@@ -155,7 +172,7 @@ Certificate_13::Certificate_13(const std::vector<uint8_t>& buf,
155172
// message as well.
156173
if(entry.extensions.contains_implemented_extensions_other_than({
157174
TLSEXT_CERT_STATUS_REQUEST,
158-
// SIGNED_CERTIFICATE_TIMESTAMP
175+
// TLSEXT_SIGNED_CERTIFICATE_TIMESTAMP
159176
}))
160177
{
161178
throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Certificate Entry contained an extension that is not allowed");
@@ -196,8 +213,20 @@ Certificate_13::Certificate_13(const std::vector<uint8_t>& buf,
196213
*/
197214
std::vector<uint8_t> Certificate_13::serialize() const
198215
{
199-
// Needed only for server implementation or client authentication
200-
throw Not_Implemented("NYI");
216+
std::vector<uint8_t> buf;
217+
218+
append_tls_length_value(buf, m_request_context, 1);
219+
220+
std::vector<uint8_t> entries;
221+
for(const auto& entry : m_entries)
222+
{
223+
append_tls_length_value(entries, entry.certificate.BER_encode(), 3);
224+
append_tls_length_value(entries, entry.extensions.serialize(m_side), 2);
225+
}
226+
227+
append_tls_length_value(buf, entries, 3);
228+
229+
return buf;
201230
}
202231

203232
}

0 commit comments

Comments
 (0)