55#include < map>
66#include < string_view>
77
8+ #include < cryptopp/osrng.h>
89#include < fmt/chrono.h>
910#include < fmt/format.h>
1011#include < openssl/ssl.h>
@@ -31,6 +32,11 @@ USERVER_NAMESPACE_BEGIN
3132namespace clients ::http {
3233
3334namespace {
35+ namespace nolint {
36+ // NOLINT(bugprone-forward-declaration-namespace) due to CryptoPP::Source
37+ using UnusedConfigSourceFwd = dynamic_config::Source&;
38+ } // namespace nolint
39+
3440// / Default timeout
3541constexpr auto kDefaultTimeout = std::chrono::milliseconds{100 };
3642// / Maximum number of redirects
@@ -156,6 +162,24 @@ class MaybeOwnedUrl final {
156162 const curl::url* url_ptr_{nullptr };
157163};
158164
165+ // Instance-wide password used to avoid passing unencrypted keys
166+ [[maybe_unused]] const std::string& GetPkeyPassword () {
167+ static const std::string password = [] {
168+ CryptoPP::DefaultAutoSeededRNG prng;
169+ std::string random_bytes;
170+ random_bytes.resize (32 );
171+ // password should not contain '\0' (cURL only accepts C-style strings)
172+ while (random_bytes.find (' \0 ' ) != std::string::npos) {
173+ static_assert (sizeof (std::string::value_type) == 1 ,
174+ " string does not consist of bytes" );
175+ prng.GenerateBlock (reinterpret_cast <unsigned char *>(random_bytes.data ()),
176+ random_bytes.size ());
177+ }
178+ return random_bytes;
179+ }();
180+ return password;
181+ }
182+
159183} // namespace
160184
161185RequestState::RequestState (
@@ -208,9 +232,17 @@ void RequestState::ca_info(const std::string& file_path) {
208232}
209233
210234void RequestState::ca (crypto::Certificate cert) {
211- ca_ = std::move (cert);
212- easy ().set_ssl_ctx_function (&RequestState::on_certificate_request);
213- easy ().set_ssl_ctx_data (this );
235+ UINVARIANT (cert, " No certificate" );
236+ if constexpr (curl::easy::is_set_ca_info_blob_available) {
237+ auto cert_pem = cert.GetPemString ();
238+ UINVARIANT (cert_pem, " Could not serialize certificate" );
239+ easy ().set_ca_info_blob_copy (*cert_pem);
240+ } else {
241+ // Legacy non-portable way, broken since 7.87.0
242+ ca_ = std::move (cert);
243+ easy ().set_ssl_ctx_function (&RequestState::on_certificate_request);
244+ easy ().set_ssl_ctx_data (this );
245+ }
214246}
215247
216248void RequestState::crl_file (const std::string& file_path) {
@@ -222,40 +254,54 @@ void RequestState::client_key_cert(crypto::PrivateKey pkey,
222254 UINVARIANT (pkey, " No private key" );
223255 UINVARIANT (cert, " No certificate" );
224256
225- pkey_ = std::move (pkey);
226- cert_ = std::move (cert);
227-
228- // FIXME: until cURL 7.71 there is no sane way to pass TLS keys from memory.
229- // Because of this, we provide our own callback. As a consequence, cURL has
230- // no knowledge of the key used and may reuse this connection for a request
231- // with a different key or without one.
232- // To avoid this until we can upgrade we set the EGD socket option to
233- // an unusable certificate-specific value. This option should have no effect
234- // on systems targeted by userver anyway but it is accounted when checking
235- // cached connection eligibility which is exactly what we need.
236-
237- // must be larger than sizeof(sockaddr_un::sun_path)
238- static constexpr size_t kCertIdLength = 255 ;
239-
240- // backwards incompatibility
257+ if constexpr (curl::easy::is_set_ssl_cert_blob_available &&
258+ curl::easy::is_set_ssl_key_blob_available) {
259+ auto cert_pem = cert.GetPemString ();
260+ UINVARIANT (cert_pem, " Could not serialize certificate" );
261+ easy ().set_ssl_cert_blob_copy (*cert_pem);
262+ easy ().set_ssl_cert_type (" PEM" );
263+ auto key_pem = pkey.GetPemString (GetPkeyPassword ());
264+ UINVARIANT (key_pem, " Could not serialize private key" );
265+ easy ().set_ssl_key_blob_copy (*key_pem);
266+ easy ().set_ssl_key_passwd (GetPkeyPassword ());
267+ easy ().set_ssl_key_type (" PEM" );
268+ } else {
269+ // Legacy non-portable way, broken since 7.84.0
270+ pkey_ = std::move (pkey);
271+ cert_ = std::move (cert);
272+
273+ // FIXME: until cURL 7.71 there is no sane way to pass TLS keys from memory.
274+ // Because of this, we provide our own callback. As a consequence, cURL has
275+ // no knowledge of the key used and may reuse this connection for a request
276+ // with a different key or without one.
277+ // To avoid this until we can upgrade we set the EGD socket option to
278+ // an unusable certificate-specific value. This option should have no effect
279+ // on systems targeted by userver anyway but it is accounted when checking
280+ // cached connection eligibility which is exactly what we need.
281+
282+ // must be larger than sizeof(sockaddr_un::sun_path)
283+ static constexpr size_t kCertIdLength = 255 ;
284+
285+ // backwards incompatibility
241286#if OPENSSL_VERSION_NUMBER >= 0x010100000L
242- const
287+ const
243288#endif
244- ASN1_BIT_STRING* cert_sig = nullptr ;
245- X509_get0_signature (&cert_sig, nullptr , cert_.GetNative ());
246- UINVARIANT (cert_sig, " Cannot get X509 certificate signature" );
247-
248- std::string cert_id;
249- cert_id.reserve (kCertIdLength );
250- utils::encoding::ToHex (
251- std::string_view{reinterpret_cast <const char *>(cert_sig->data ),
252- std::min<size_t >(cert_sig->length , kCertIdLength / 2 )},
253- cert_id);
254- cert_id.resize (kCertIdLength , ' =' );
255- easy ().set_egd_socket (cert_id);
256-
257- easy ().set_ssl_ctx_function (&RequestState::on_certificate_request);
258- easy ().set_ssl_ctx_data (this );
289+ ASN1_BIT_STRING* cert_sig = nullptr ;
290+ X509_get0_signature (&cert_sig, nullptr , cert_.GetNative ());
291+ UINVARIANT (cert_sig, " Cannot get X509 certificate signature" );
292+
293+ std::string cert_id;
294+ cert_id.reserve (kCertIdLength );
295+ utils::encoding::ToHex (
296+ std::string_view{reinterpret_cast <const char *>(cert_sig->data ),
297+ std::min<size_t >(cert_sig->length , kCertIdLength / 2 )},
298+ cert_id);
299+ cert_id.resize (kCertIdLength , ' =' );
300+ easy ().set_egd_socket (cert_id);
301+
302+ easy ().set_ssl_ctx_function (&RequestState::on_certificate_request);
303+ easy ().set_ssl_ctx_data (this );
304+ }
259305}
260306
261307void RequestState::http_version (curl::easy::http_version_t version) {
0 commit comments