/src/botan/src/lib/tls/tls_callbacks.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * TLS Callbacks |
3 | | * (C) 2016 Jack Lloyd |
4 | | * 2017 Harry Reimann, Rohde & Schwarz Cybersecurity |
5 | | * 2022 René Meusel, Hannes Rantzsch - neXenio GmbH |
6 | | * 2023 René Meusel - Rohde & Schwarz Cybersecurity |
7 | | * |
8 | | * Botan is released under the Simplified BSD License (see license.txt) |
9 | | */ |
10 | | |
11 | | #include <botan/tls_callbacks.h> |
12 | | |
13 | | #include <botan/dh.h> |
14 | | #include <botan/dl_group.h> |
15 | | #include <botan/ecdh.h> |
16 | | #include <botan/ocsp.h> |
17 | | #include <botan/pk_algs.h> |
18 | | #include <botan/tls_algos.h> |
19 | | #include <botan/tls_exceptn.h> |
20 | | #include <botan/tls_policy.h> |
21 | | #include <botan/x509path.h> |
22 | | #include <botan/internal/ct_utils.h> |
23 | | #include <botan/internal/stl_util.h> |
24 | | |
25 | | #if defined(BOTAN_HAS_CURVE_25519) |
26 | | #include <botan/curve25519.h> |
27 | | #endif |
28 | | |
29 | | #if defined(BOTAN_HAS_KYBER) |
30 | | #include <botan/kyber.h> |
31 | | #endif |
32 | | |
33 | | #if defined(BOTAN_HAS_TLS_13_PQC) |
34 | | #include <botan/internal/hybrid_public_key.h> |
35 | | #endif |
36 | | |
37 | | namespace Botan { |
38 | | |
39 | 105k | void TLS::Callbacks::tls_inspect_handshake_msg(const Handshake_Message& /*unused*/) { |
40 | | // default is no op |
41 | 105k | } |
42 | | |
43 | 0 | std::string TLS::Callbacks::tls_server_choose_app_protocol(const std::vector<std::string>& /*unused*/) { |
44 | 0 | return ""; |
45 | 0 | } |
46 | | |
47 | 4.03k | std::string TLS::Callbacks::tls_peer_network_identity() { |
48 | 4.03k | return ""; |
49 | 4.03k | } |
50 | | |
51 | 47.1k | std::chrono::system_clock::time_point TLS::Callbacks::tls_current_timestamp() { |
52 | 47.1k | return std::chrono::system_clock::now(); |
53 | 47.1k | } |
54 | | |
55 | | void TLS::Callbacks::tls_modify_extensions(Extensions& /*unused*/, |
56 | | Connection_Side /*unused*/, |
57 | 23.4k | Handshake_Type /*unused*/) {} |
58 | | |
59 | | void TLS::Callbacks::tls_examine_extensions(const Extensions& /*unused*/, |
60 | | Connection_Side /*unused*/, |
61 | 21.2k | Handshake_Type /*unused*/) {} |
62 | | |
63 | 207 | bool TLS::Callbacks::tls_should_persist_resumption_information(const Session& session) { |
64 | | // RFC 5077 3.3 |
65 | | // The ticket_lifetime_hint field contains a hint from the server about |
66 | | // how long the ticket should be stored. A value of zero is reserved to |
67 | | // indicate that the lifetime of the ticket is unspecified. |
68 | | // |
69 | | // RFC 8446 4.6.1 |
70 | | // [A ticket_lifetime] of zero indicates that the ticket should be discarded |
71 | | // immediately. |
72 | | // |
73 | | // By default we opt to keep all sessions, except for TLS 1.3 with a lifetime |
74 | | // hint of zero. |
75 | 207 | return session.lifetime_hint().count() > 0 || session.version().is_pre_tls_13(); |
76 | 207 | } |
77 | | |
78 | | void TLS::Callbacks::tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain, |
79 | | const std::vector<std::optional<OCSP::Response>>& ocsp_responses, |
80 | | const std::vector<Certificate_Store*>& trusted_roots, |
81 | | Usage_Type usage, |
82 | | std::string_view hostname, |
83 | 0 | const TLS::Policy& policy) { |
84 | 0 | if(cert_chain.empty()) { |
85 | 0 | throw Invalid_Argument("Certificate chain was empty"); |
86 | 0 | } |
87 | | |
88 | 0 | Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(), |
89 | 0 | policy.minimum_signature_strength()); |
90 | |
|
91 | 0 | Path_Validation_Result result = x509_path_validate(cert_chain, |
92 | 0 | restrictions, |
93 | 0 | trusted_roots, |
94 | 0 | (usage == Usage_Type::TLS_SERVER_AUTH ? hostname : ""), |
95 | 0 | usage, |
96 | 0 | tls_current_timestamp(), |
97 | 0 | tls_verify_cert_chain_ocsp_timeout(), |
98 | 0 | ocsp_responses); |
99 | |
|
100 | 0 | if(!result.successful_validation()) { |
101 | 0 | throw TLS_Exception(Alert::BadCertificate, "Certificate validation failure: " + result.result_string()); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | void TLS::Callbacks::tls_verify_raw_public_key(const Public_Key& raw_public_key, |
106 | | Usage_Type usage, |
107 | | std::string_view hostname, |
108 | 0 | const TLS::Policy& policy) { |
109 | 0 | BOTAN_UNUSED(raw_public_key, usage, hostname, policy); |
110 | | // There is no good default implementation for authenticating raw public key. |
111 | | // Applications that wish to use them for authentication, must override this. |
112 | 0 | throw TLS_Exception(Alert::CertificateUnknown, "Application did not provide a means to validate the raw public key"); |
113 | 0 | } |
114 | | |
115 | 0 | std::optional<OCSP::Response> TLS::Callbacks::tls_parse_ocsp_response(const std::vector<uint8_t>& raw_response) { |
116 | 0 | try { |
117 | 0 | return OCSP::Response(raw_response); |
118 | 0 | } catch(const Decoding_Error&) { |
119 | | // ignore parsing errors and just ignore the broken OCSP response |
120 | 0 | return std::nullopt; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | std::vector<std::vector<uint8_t>> TLS::Callbacks::tls_provide_cert_chain_status( |
125 | 0 | const std::vector<X509_Certificate>& chain, const Certificate_Status_Request& csr) { |
126 | 0 | std::vector<std::vector<uint8_t>> result(chain.size()); |
127 | 0 | if(!chain.empty()) { |
128 | 0 | result[0] = tls_provide_cert_status(chain, csr); |
129 | 0 | } |
130 | 0 | return result; |
131 | 0 | } |
132 | | |
133 | | std::vector<uint8_t> TLS::Callbacks::tls_sign_message(const Private_Key& key, |
134 | | RandomNumberGenerator& rng, |
135 | | std::string_view padding, |
136 | | Signature_Format format, |
137 | 0 | const std::vector<uint8_t>& msg) { |
138 | 0 | PK_Signer signer(key, rng, padding, format); |
139 | |
|
140 | 0 | return signer.sign_message(msg, rng); |
141 | 0 | } |
142 | | |
143 | | bool TLS::Callbacks::tls_verify_message(const Public_Key& key, |
144 | | std::string_view padding, |
145 | | Signature_Format format, |
146 | | const std::vector<uint8_t>& msg, |
147 | 0 | const std::vector<uint8_t>& sig) { |
148 | 0 | PK_Verifier verifier(key, padding, format); |
149 | |
|
150 | 0 | return verifier.verify_message(msg, sig); |
151 | 0 | } |
152 | | |
153 | 0 | std::unique_ptr<Private_Key> TLS::Callbacks::tls_kem_generate_key(TLS::Group_Params group, RandomNumberGenerator& rng) { |
154 | 0 | #if defined(BOTAN_HAS_KYBER) |
155 | 0 | if(group.is_pure_kyber()) { |
156 | 0 | return std::make_unique<Kyber_PrivateKey>(rng, KyberMode(group.to_string().value())); |
157 | 0 | } |
158 | 0 | #endif |
159 | | |
160 | 0 | #if defined(BOTAN_HAS_TLS_13_PQC) |
161 | 0 | if(group.is_pqc_hybrid()) { |
162 | 0 | return Hybrid_KEM_PrivateKey::generate_from_group(group, rng); |
163 | 0 | } |
164 | 0 | #endif |
165 | | |
166 | 0 | return tls_generate_ephemeral_key(group, rng); |
167 | 0 | } |
168 | | |
169 | | KEM_Encapsulation TLS::Callbacks::tls_kem_encapsulate(TLS::Group_Params group, |
170 | | const std::vector<uint8_t>& encoded_public_key, |
171 | | RandomNumberGenerator& rng, |
172 | 0 | const Policy& policy) { |
173 | 0 | if(group.is_kem()) { |
174 | 0 | auto kem_pub_key = [&]() -> std::unique_ptr<Public_Key> { |
175 | |
|
176 | 0 | #if defined(BOTAN_HAS_TLS_13_PQC) |
177 | 0 | if(group.is_pqc_hybrid()) { |
178 | 0 | return Hybrid_KEM_PublicKey::load_for_group(group, encoded_public_key); |
179 | 0 | } |
180 | 0 | #endif |
181 | | |
182 | 0 | #if defined(BOTAN_HAS_KYBER) |
183 | 0 | if(group.is_pure_kyber()) { |
184 | 0 | return std::make_unique<Kyber_PublicKey>(encoded_public_key, KyberMode(group.to_string().value())); |
185 | 0 | } |
186 | 0 | #endif |
187 | | |
188 | 0 | throw TLS_Exception(Alert::IllegalParameter, "KEM is not supported"); |
189 | 0 | }(); |
190 | |
|
191 | 0 | return PK_KEM_Encryptor(*kem_pub_key, "Raw").encrypt(rng); |
192 | 0 | } |
193 | | |
194 | | // TODO: We could use the KEX_to_KEM_Adapter to remove the case distinction |
195 | | // of KEM and KEX. However, the workarounds in this adapter class |
196 | | // should first be addressed. |
197 | 0 | auto ephemeral_keypair = tls_generate_ephemeral_key(group, rng); |
198 | 0 | return KEM_Encapsulation(ephemeral_keypair->public_value(), |
199 | 0 | tls_ephemeral_key_agreement(group, *ephemeral_keypair, encoded_public_key, rng, policy)); |
200 | 0 | } |
201 | | |
202 | | secure_vector<uint8_t> TLS::Callbacks::tls_kem_decapsulate(TLS::Group_Params group, |
203 | | const Private_Key& private_key, |
204 | | const std::vector<uint8_t>& encapsulated_bytes, |
205 | | RandomNumberGenerator& rng, |
206 | 0 | const Policy& policy) { |
207 | 0 | if(group.is_kem()) { |
208 | 0 | PK_KEM_Decryptor kemdec(private_key, rng, "Raw"); |
209 | 0 | return kemdec.decrypt(encapsulated_bytes, 0, {}); |
210 | 0 | } |
211 | | |
212 | 0 | try { |
213 | 0 | auto& key_agreement_key = dynamic_cast<const PK_Key_Agreement_Key&>(private_key); |
214 | 0 | return tls_ephemeral_key_agreement(group, key_agreement_key, encapsulated_bytes, rng, policy); |
215 | 0 | } catch(const std::bad_cast&) { |
216 | 0 | throw Invalid_Argument("provided ephemeral key is not a PK_Key_Agreement_Key"); |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | | namespace { |
221 | | |
222 | 33.3k | bool is_dh_group(const std::variant<TLS::Group_Params, DL_Group>& group) { |
223 | 33.3k | return std::holds_alternative<DL_Group>(group) || std::get<TLS::Group_Params>(group).is_dh_named_group(); |
224 | 33.3k | } |
225 | | |
226 | 0 | DL_Group get_dl_group(const std::variant<TLS::Group_Params, DL_Group>& group) { |
227 | 0 | BOTAN_ASSERT_NOMSG(is_dh_group(group)); |
228 | | |
229 | | // TLS 1.2 allows specifying arbitrary DL_Group parameters in-lieu of |
230 | | // a standardized DH group identifier. TLS 1.3 just offers pre-defined |
231 | | // groups. |
232 | 0 | return std::visit( |
233 | 0 | overloaded{[](const DL_Group& dl_group) { return dl_group; }, |
234 | 0 | [&](TLS::Group_Params group_param) { return DL_Group(group_param.to_string().value()); }}, |
235 | 0 | group); |
236 | 0 | } |
237 | | |
238 | | } // namespace |
239 | | |
240 | | std::unique_ptr<PK_Key_Agreement_Key> TLS::Callbacks::tls_generate_ephemeral_key( |
241 | 19.1k | const std::variant<TLS::Group_Params, DL_Group>& group, RandomNumberGenerator& rng) { |
242 | 19.1k | if(is_dh_group(group)) { |
243 | 0 | const DL_Group dl_group = get_dl_group(group); |
244 | 0 | return std::make_unique<DH_PrivateKey>(rng, dl_group); |
245 | 0 | } |
246 | | |
247 | 19.1k | BOTAN_ASSERT_NOMSG(std::holds_alternative<TLS::Group_Params>(group)); |
248 | 19.1k | const auto group_params = std::get<TLS::Group_Params>(group); |
249 | | |
250 | 19.1k | if(group_params.is_ecdh_named_curve()) { |
251 | 18.6k | const EC_Group ec_group(group_params.to_string().value()); |
252 | 18.6k | return std::make_unique<ECDH_PrivateKey>(rng, ec_group); |
253 | 18.6k | } |
254 | | |
255 | 586 | #if defined(BOTAN_HAS_CURVE_25519) |
256 | 586 | if(group_params.is_x25519()) { |
257 | 586 | return std::make_unique<X25519_PrivateKey>(rng); |
258 | 586 | } |
259 | 0 | #endif |
260 | | |
261 | 0 | if(group_params.is_kem()) { |
262 | 0 | throw TLS_Exception(Alert::IllegalParameter, "cannot generate an ephemeral KEX key for a KEM"); |
263 | 0 | } |
264 | | |
265 | 0 | throw TLS_Exception(Alert::DecodeError, "cannot create a key offering without a group definition"); |
266 | 0 | } |
267 | | |
268 | | secure_vector<uint8_t> TLS::Callbacks::tls_ephemeral_key_agreement( |
269 | | const std::variant<TLS::Group_Params, DL_Group>& group, |
270 | | const PK_Key_Agreement_Key& private_key, |
271 | | const std::vector<uint8_t>& public_value, |
272 | | RandomNumberGenerator& rng, |
273 | 14.1k | const Policy& policy) { |
274 | 14.1k | auto agree = [&](const PK_Key_Agreement_Key& sk, const auto& pk) { |
275 | 8.30k | PK_Key_Agreement ka(sk, rng, "Raw"); |
276 | 8.30k | return ka.derive_key(0, pk.public_value()).bits_of(); |
277 | 8.30k | }; Unexecuted instantiation: tls_callbacks.cpp:auto Botan::TLS::Callbacks::tls_ephemeral_key_agreement(std::__1::variant<Botan::TLS::Group_Params, Botan::DL_Group> const&, Botan::PK_Key_Agreement_Key const&, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, Botan::RandomNumberGenerator&, Botan::TLS::Policy const&)::$_2::operator()<Botan::DH_PublicKey>(Botan::PK_Key_Agreement_Key const&, Botan::DH_PublicKey const&) const tls_callbacks.cpp:auto Botan::TLS::Callbacks::tls_ephemeral_key_agreement(std::__1::variant<Botan::TLS::Group_Params, Botan::DL_Group> const&, Botan::PK_Key_Agreement_Key const&, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, Botan::RandomNumberGenerator&, Botan::TLS::Policy const&)::$_2::operator()<Botan::ECDH_PublicKey>(Botan::PK_Key_Agreement_Key const&, Botan::ECDH_PublicKey const&) const Line | Count | Source | 274 | 8.30k | auto agree = [&](const PK_Key_Agreement_Key& sk, const auto& pk) { | 275 | 8.30k | PK_Key_Agreement ka(sk, rng, "Raw"); | 276 | 8.30k | return ka.derive_key(0, pk.public_value()).bits_of(); | 277 | 8.30k | }; |
tls_callbacks.cpp:auto Botan::TLS::Callbacks::tls_ephemeral_key_agreement(std::__1::variant<Botan::TLS::Group_Params, Botan::DL_Group> const&, Botan::PK_Key_Agreement_Key const&, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, Botan::RandomNumberGenerator&, Botan::TLS::Policy const&)::$_2::operator()<Botan::Curve25519_PublicKey>(Botan::PK_Key_Agreement_Key const&, Botan::Curve25519_PublicKey const&) const Line | Count | Source | 274 | 1 | auto agree = [&](const PK_Key_Agreement_Key& sk, const auto& pk) { | 275 | 1 | PK_Key_Agreement ka(sk, rng, "Raw"); | 276 | 1 | return ka.derive_key(0, pk.public_value()).bits_of(); | 277 | 1 | }; |
|
278 | | |
279 | 14.1k | if(is_dh_group(group)) { |
280 | | // TLS 1.2 allows specifying arbitrary DL_Group parameters in-lieu of |
281 | | // a standardized DH group identifier. |
282 | 0 | const auto dl_group = get_dl_group(group); |
283 | |
|
284 | 0 | auto Y = BigInt::decode(public_value); |
285 | | |
286 | | /* |
287 | | * A basic check for key validity. As we do not know q here we |
288 | | * cannot check that Y is in the right subgroup. However since |
289 | | * our key is ephemeral there does not seem to be any |
290 | | * advantage to bogus keys anyway. |
291 | | */ |
292 | 0 | if(Y <= 1 || Y >= dl_group.get_p() - 1) { |
293 | 0 | throw TLS_Exception(Alert::IllegalParameter, "Server sent bad DH key for DHE exchange"); |
294 | 0 | } |
295 | | |
296 | 0 | DH_PublicKey peer_key(dl_group, Y); |
297 | 0 | policy.check_peer_key_acceptable(peer_key); |
298 | |
|
299 | 0 | return agree(private_key, peer_key); |
300 | 0 | } |
301 | | |
302 | 14.1k | BOTAN_ASSERT_NOMSG(std::holds_alternative<TLS::Group_Params>(group)); |
303 | 14.1k | const auto group_params = std::get<TLS::Group_Params>(group); |
304 | | |
305 | 14.1k | if(group_params.is_ecdh_named_curve()) { |
306 | 14.1k | const EC_Group ec_group(group_params.to_string().value()); |
307 | 14.1k | ECDH_PublicKey peer_key(ec_group, ec_group.OS2ECP(public_value)); |
308 | 14.1k | policy.check_peer_key_acceptable(peer_key); |
309 | | |
310 | 14.1k | return agree(private_key, peer_key); |
311 | 14.1k | } |
312 | | |
313 | 4 | #if defined(BOTAN_HAS_CURVE_25519) |
314 | 4 | if(group_params.is_x25519()) { |
315 | 4 | if(public_value.size() != 32) { |
316 | 3 | throw TLS_Exception(Alert::HandshakeFailure, "Invalid X25519 key size"); |
317 | 3 | } |
318 | | |
319 | 1 | Curve25519_PublicKey peer_key(public_value); |
320 | 1 | policy.check_peer_key_acceptable(peer_key); |
321 | | |
322 | 1 | return agree(private_key, peer_key); |
323 | 4 | } |
324 | 0 | #endif |
325 | | |
326 | 0 | throw TLS_Exception(Alert::IllegalParameter, "Did not recognize the key exchange group"); |
327 | 4 | } |
328 | | |
329 | | } // namespace Botan |