diff --git a/core/include/userver/engine/io/tls_wrapper.hpp b/core/include/userver/engine/io/tls_wrapper.hpp index 61ef1d28bf0d..aaecf95080c1 100644 --- a/core/include/userver/engine/io/tls_wrapper.hpp +++ b/core/include/userver/engine/io/tls_wrapper.hpp @@ -42,7 +42,7 @@ class [[nodiscard]] TlsWrapper final : public RwBase { /// Starts a TLS server on an opened socket static TlsWrapper StartTlsServer( Socket&& socket, - const crypto::Certificate& cert, + const crypto::CertificatesChain& cert_chain, const crypto::PrivateKey& key, Deadline deadline, const std::vector& extra_cert_authorities = {} diff --git a/core/include/userver/server/component.hpp b/core/include/userver/server/component.hpp index 3d1302e61426..2539dc358d69 100644 --- a/core/include/userver/server/component.hpp +++ b/core/include/userver/server/component.hpp @@ -63,7 +63,7 @@ namespace components { /// task_processor | task processor to process incoming requests | - /// backlog | max count of new connections pending acceptance | 1024 /// tls.ca | paths to TLS CAs for client authentication | - -/// tls.cert | path to TLS server certificate | - +/// tls.cert | path to TLS server certificate or certificate chain | - /// tls.private-key | path to TLS server certificate private key | - /// tls.private-key-passphrase-name | passphrase name located in secdist's "passphrases" section | - /// handler-defaults.max_url_size | max path/URL size or empty to not limit | 8192 diff --git a/core/src/clients/http/client_crl_test.cpp b/core/src/clients/http/client_crl_test.cpp index cd602c367ec8..4da4af3c8278 100644 --- a/core/src/clients/http/client_crl_test.cpp +++ b/core/src/clients/http/client_crl_test.cpp @@ -317,7 +317,7 @@ struct TlsServer { auto tls_server = engine::io::TlsWrapper::StartTlsServer( std::move(socket), - crypto::Certificate::LoadFromString(kServerCertificate), + crypto::LoadCertficatesChainFromString(kServerCertificate), crypto::PrivateKey::LoadFromString(kRevokedServerPrivateKey), deadline, cas diff --git a/core/src/engine/io/tls_wrapper.cpp b/core/src/engine/io/tls_wrapper.cpp index a91e0879692a..991e3bf15e80 100644 --- a/core/src/engine/io/tls_wrapper.cpp +++ b/core/src/engine/io/tls_wrapper.cpp @@ -498,7 +498,7 @@ TlsWrapper TlsWrapper::StartTlsClient( TlsWrapper TlsWrapper::StartTlsServer( Socket&& socket, - const crypto::Certificate& cert, + const crypto::CertificatesChain& cert_chain, const crypto::PrivateKey& key, Deadline deadline, const std::vector& extra_cert_authorities @@ -513,10 +513,31 @@ TlsWrapper TlsWrapper::StartTlsServer( LOG_INFO() << "Client SSL cert will not be verified"; } - if (1 != SSL_CTX_use_certificate(ssl_ctx.get(), cert.GetNative())) { + if (cert_chain.empty()) { + throw TlsException(crypto::FormatSslError("Empty certificate chain provided")); + } + + if (1 != SSL_CTX_use_certificate(ssl_ctx.get(), cert_chain.begin()->GetNative())) { throw TlsException(crypto::FormatSslError("Failed to set up server TLS wrapper: SSL_CTX_use_certificate")); } + if (cert_chain.size() > 1) { + auto cert_it = std::next(cert_chain.begin()); + for (; cert_it != cert_chain.end(); ++cert_it) { + // cast in openssl1.0 macro expansion + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + if (SSL_CTX_add_extra_chain_cert(ssl_ctx.get(), cert_it->GetNative()) <= 0) { + throw TlsException( + crypto::FormatSslError("Failed to set up server TLS wrapper: SSL_CTX_add_extra_chain_cert") + ); + } + + // After SSL_CTX_add_extra_chain_cert we should not free the cert + const auto ret = X509_up_ref(cert_it->GetNative()); + UASSERT(ret == 1); + } + } + if (1 != SSL_CTX_use_PrivateKey(ssl_ctx.get(), key.GetNative())) { throw TlsException(crypto::FormatSslError("Failed to set up server TLS wrapper: SSL_CTX_use_PrivateKey")); } diff --git a/core/src/engine/io/tls_wrapper_benchmark.cpp b/core/src/engine/io/tls_wrapper_benchmark.cpp index 481c03a8f6af..193fe117fb47 100644 --- a/core/src/engine/io/tls_wrapper_benchmark.cpp +++ b/core/src/engine/io/tls_wrapper_benchmark.cpp @@ -97,7 +97,7 @@ constexpr auto kDeadlineMaxTime = std::chrono::seconds{60}; [&reading, deadline](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), deadline ); @@ -141,7 +141,7 @@ BENCHMARK(tls_write_all_buffered)->RangeMultiplier(2)->Range(1 << 6, 1 << 12)->U [&reading, deadline](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), deadline ); diff --git a/core/src/engine/io/tls_wrapper_test.cpp b/core/src/engine/io/tls_wrapper_test.cpp index 1fe17de5c128..fd0bf3d06a40 100644 --- a/core/src/engine/io/tls_wrapper_test.cpp +++ b/core/src/engine/io/tls_wrapper_test.cpp @@ -135,6 +135,82 @@ Z49kghXJ5KAS4jOB6SxClxbR5Tpc1E3khduX6aGau1VOkgPJxfdqHHqsyUc1RH/Z v2R63aFo/UfQSQ4dhC0o2Vy74DyLwnO3pH8wudfBJ8/LX/Uz/A== -----END CERTIFICATE-----)"; +// Certificates for testing were generated via the following command: +// openssl req -x509 -sha256 -nodes -newkey rsa:2048 +// -days 3650 -subj '/CN=tlswrapper_test_other' +// -keyout testing_priv.key -out testing_cert.crt +// +// intermediate certificate was generated by +// openssl x509 -req -in <(openssl req -new -key testing_priv.key -subj "/CN=intermediate_test") -CA testing_cert.crt +// -CAkey testing_priv.key -CAcreateserial -out intermediate_cert.crt -days 1825 -sha256 full chain generated by cat +// intermediate_cert.crt testing_cert.crt > fullchain.crt NOTE: Ubuntu 20.04 requires RSA of at least 2048 length + +constexpr auto chain_private_key = R"(-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtQd5O3Zow01ZC +vbuUv9s7jm48KwSmVje+k887w/h5fNR+lah44owNw6P/7RQ4uMmgGPxtIxdkZNvI +p5TjBcqMWK3OP4ReG3c/Ak+A1b6NOC5KborngfO0K9uzV/9Pa53UfcWoqSyqcc91 +G7zzDGuYHOs3FKk53fTNaRUYd5UMX8zZkTdTPpOJWDWZHIOYr6sgt7Py+NtVp70D +zS4/qIHD68oTjTYwp8DCQ3qljoTvSoYudKC+Xnm9lXnc2INJXmxYUFZBkq5vMczY +0mNFw4PNyEQyfyEOseaPFrAF/AWIiQWmYfe6M4gitc9Ct4cu7+VdLqDN3/D2bsbG +ceKzCbZzAgMBAAECggEAO56m+UyYeqS+0kin/A/pSR1CIcJL31Fb7WC/tzlAj828 +8bJePvr2ZuYj0TWr97je6RCwDH4+1nU+jFXejiC4CoOZi5ef3SJmbnBFG3hyEfZ7 +N3HCqte1HRLaj2SAnrvRnAWLtvZAQIbZdNsOsjRb8gRBjLq3YQpX6zd14u2DhLYB +vQxVfONtOGeUx/UUiss2MpJfrqoUoGVb+/onG1yAEbtWOs0Ig/dljopvcgkeCz9J +mB2MD5yv5a7sLBOcgyTpvE6ODgXzs+ZtS3TybGS+ld2rFrDlcInY1zXSP5iaW6Z5 +lSUeolx5w1lVjRcc9WdybEOYpkV76EqX4v9XbGIxAQKBgQDUUX36l3YQDkBgZOvb +FnQaGUUAUiRenJedW5FMKWxBh2tXNUIa3aw7GtPhf4/8JLCaFrYF4WLkDfb0CfSY +wbunEjqa1Fa/CFmuagBHg6ClIiGX2/p9t2iQEEMZA+qjeAeFgfYy86e4t1oRn6iA +EED7j/7szqZvWrHtcv4Z9HP0YwKBgQDQ5xY0i+ouWJ7nGWEZ2A2arixL6KSAPIlm +NAPoYZGTWYXl259vCrJmnqCRpqtCLkCwhtFbDewPtf1PTWYLJUUWcjdljpuKfPpu +sIseA3CSRIfggW1X3A/txtscWZQhctie+jU9BFqiSaG/Mto+GdH4BFT4oUFKmfA/ +J2rEKVyqsQKBgDoTCEhxAWQm4cj8Ed9dZuh0nQEXdsdCQd5S241fjzLlXaD++lPq +6l9IWUhG4hVv27ZqG+PD4I7Mmw3pYzQdWby7KbiL+CZMnGsup2DoShqhGVs2Wm/k +qP8u04uWHKoV/Mix4avSJcBKtqI3b5mH2J52pp4TcEbpId33JDXpPYZNAoGBAMif +5jd41+LCwXj4asTDNe2DsI8GUlXFzb8V3VrjuUdmBq4GCkw+Xa8oUNUQ2BCrEv11 +vMJR0JAWG7x5fLLfjEZOUt154+9Qr8J2UmT0sLwIjOYT5ssmUTXucKf9b8Hf5iJn +8ZE0CUcqp+hUEjzp1zj2EBTn6SiYRp6gYG0bvB9BAoGBANLAq1ZJw7JsZ6rCvduA +GgdtFZePPol1/iQX7YXgP0jP1i3gqRKa7M621u8Hunl5BAqQ/1fu/7qAExzDcmkn +OmJhJDUFdNcQ6+4LCpj5JVed/J1Tnh8vUaRbYV+9l0y5WolcuAY+hfJuB4K0mUXL +11/L+DCO5OYM9ud/kl+ttDWj +-----END PRIVATE KEY-----)"; + +constexpr auto cert_chain = R"(-----BEGIN CERTIFICATE----- +MIIC5TCCAc2gAwIBAgIUEAEQSbPfQymprdTsER15SJz+eaowDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPdGxzd3JhcHBlcl90ZXN0MB4XDTI1MDEwNDExMjgzMFoX +DTMwMDEwMzExMjgzMFowHDEaMBgGA1UEAwwRaW50ZXJtZWRpYXRlX3Rlc3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtQd5O3Zow01ZCvbuUv9s7jm48 +KwSmVje+k887w/h5fNR+lah44owNw6P/7RQ4uMmgGPxtIxdkZNvIp5TjBcqMWK3O +P4ReG3c/Ak+A1b6NOC5KborngfO0K9uzV/9Pa53UfcWoqSyqcc91G7zzDGuYHOs3 +FKk53fTNaRUYd5UMX8zZkTdTPpOJWDWZHIOYr6sgt7Py+NtVp70DzS4/qIHD68oT +jTYwp8DCQ3qljoTvSoYudKC+Xnm9lXnc2INJXmxYUFZBkq5vMczY0mNFw4PNyEQy +fyEOseaPFrAF/AWIiQWmYfe6M4gitc9Ct4cu7+VdLqDN3/D2bsbGceKzCbZzAgMB +AAGjITAfMB0GA1UdDgQWBBQBq2+OTbGp4TvtWVkYe4LfWybT+jANBgkqhkiG9w0B +AQsFAAOCAQEAYaebsazdrABtiJF+FmLwmX9RJkLBxf4cxuSHzhieNeOSyVecCAmF +eJ1/4VKSWpDKfIk6pG8CWFO3EUCkytCof/idlbTqoYZ/WckzsYGrzxPTGSLw1Oen +/vnDFEE6cnIqJEB6v3LzfQrLBXr57+I7/SvpSGin9DuxfTQplNAt8lxDuiwJZz0+ +IrzRwLB9HzXo8xvG9v88L35SW8SwWds6R5hjpZ928qOP5JbosI6ccQ6fgu8hSy5D +H9ZIE4FyGrLTbA1reSjT43yBYBzX0H4JsQnzGjbraGk5HYLFETVKbCYurYo+4Rl5 +sYDUeR5wwfcKZCH43JERoG4DVI7yIKIzZQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDFTCCAf2gAwIBAgIUXOIkjRBGF8HhB43My6tlrFcOoJcwDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPdGxzd3JhcHBlcl90ZXN0MB4XDTI1MDEwNDExMTMxMVoX +DTM1MDEwMjExMTMxMVowGjEYMBYGA1UEAwwPdGxzd3JhcHBlcl90ZXN0MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArUHeTt2aMNNWQr27lL/bO45uPCsE +plY3vpPPO8P4eXzUfpWoeOKMDcOj/+0UOLjJoBj8bSMXZGTbyKeU4wXKjFitzj+E +Xht3PwJPgNW+jTguSm6K54HztCvbs1f/T2ud1H3FqKksqnHPdRu88wxrmBzrNxSp +Od30zWkVGHeVDF/M2ZE3Uz6TiVg1mRyDmK+rILez8vjbVae9A80uP6iBw+vKE402 +MKfAwkN6pY6E70qGLnSgvl55vZV53NiDSV5sWFBWQZKubzHM2NJjRcODzchEMn8h +DrHmjxawBfwFiIkFpmH3ujOIIrXPQreHLu/lXS6gzd/w9m7GxnHiswm2cwIDAQAB +o1MwUTAdBgNVHQ4EFgQUAatvjk2xqeE77VlZGHuC31sm0/owHwYDVR0jBBgwFoAU +Aatvjk2xqeE77VlZGHuC31sm0/owDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEAOs+WfsO7k2zZS3CMAOHwxZzXy9PvdCzZFzJfjkKRh22zjZ1dmf8f +dlMGPqAENae3zuUd4ErFztNg6bGKW66j5JCQvfdqrj2XUUpTuJJWVcsvtxamDFz8 +2h2+ky35xop4GLv3E9BPZvXSZ4wwiFbehI79tPwz3Qn4rsaowxM+bhMIgf+Wt4hS +nyLZGLR3MYH5wY6kCErZobxlXukvxS/+RTobvyo/9wbKjx1qaOXfuMKsFK3hpYzq +uwddqXEoNvE0VFCib7dsBnBUEeKhnVioD0vpDfvrCLNTm8VOg3xVz902WNnpgSdu +ud5lWYZNZ6ygIOvwFz1VST30jT4Lj3Bmrg== +-----END CERTIFICATE-----)"; constexpr auto kShortTimeout = std::chrono::milliseconds{10}; } // namespace @@ -158,8 +234,8 @@ UTEST(TlsWrapper, InitListSmall) { [deadline, kDataA, kDataB, kDataC, kDataD](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), - crypto::PrivateKey::LoadFromString(key), + crypto::LoadCertficatesChainFromString(cert_chain), + crypto::PrivateKey::LoadFromString(chain_private_key), deadline ); if (tls_server.WriteAll({kDataA, kDataB, kDataC, kDataD}, deadline) != @@ -198,7 +274,7 @@ UTEST(TlsWrapper, InitListLarge) { [deadline, kDataA, kDataB, kDataC, kDataD](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), deadline ); @@ -234,7 +310,7 @@ UTEST(TlsWrapper, InitListSmallThenLarge) { [deadline, kDataSmall, kDataLarge](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), deadline ); @@ -266,7 +342,7 @@ UTEST_MT(TlsWrapper, Smoke, 2) { try { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); @@ -313,7 +389,7 @@ UTEST_MT(TlsWrapper, DocTest, 2) { [deadline](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), deadline ); @@ -345,7 +421,7 @@ UTEST(TlsWrapper, Move) { try { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); @@ -395,7 +471,7 @@ UTEST(TlsWrapper, ConnectTimeout) { EXPECT_THROW( static_cast(io::TlsWrapper::StartTlsServer( std::move(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), Deadline::FromDuration(kShortTimeout) )), @@ -414,7 +490,7 @@ UTEST_MT(TlsWrapper, IoTimeout, 2) { [test_deadline, &timeout_happened](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); @@ -451,7 +527,7 @@ UTEST(TlsWrapper, Cancel) { [test_deadline](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); @@ -478,7 +554,7 @@ UTEST_MT(TlsWrapper, CertKeyMismatch, 2) { UEXPECT_THROW( static_cast(io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(other_key), test_deadline )), @@ -505,7 +581,7 @@ UTEST_MT(TlsWrapper, NonTlsClient, 2) { UEXPECT_THROW( static_cast(io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(other_key), test_deadline )), @@ -546,7 +622,7 @@ UTEST_MT(TlsWrapper, DoubleSmoke, 4) { [test_deadline](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); @@ -567,7 +643,7 @@ UTEST_MT(TlsWrapper, DoubleSmoke, 4) { [test_deadline](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(other_cert), + crypto::LoadCertficatesChainFromString(other_cert), crypto::PrivateKey::LoadFromString(other_key), test_deadline ); @@ -620,7 +696,7 @@ UTEST(TlsWrapper, InvalidSocket) { UEXPECT_THROW(static_cast(io::TlsWrapper::StartTlsClient({}, {}, test_deadline)), io::TlsException); UEXPECT_THROW( static_cast(io::TlsWrapper::StartTlsServer( - {}, crypto::Certificate::LoadFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline + {}, crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline )), io::TlsException ); @@ -637,7 +713,7 @@ UTEST(TlsWrapper, PeerShutdown) { try { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); @@ -679,7 +755,7 @@ UTEST(TlsWrapper, PeerDisconnect) { try { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::Certificate::LoadFromString(cert), + crypto::LoadCertficatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline ); diff --git a/core/src/server/component.cpp b/core/src/server/component.cpp index 84fe6fa82232..98ac8fad9651 100644 --- a/core/src/server/component.cpp +++ b/core/src/server/component.cpp @@ -125,7 +125,7 @@ additionalProperties: false description: path to TLS CA cert: type: string - description: path to TLS certificate + description: path to TLS certificate chain private-key: type: string description: path to TLS certificate private key diff --git a/core/src/server/net/listener_config.cpp b/core/src/server/net/listener_config.cpp index 0c2c7afa7a5e..4d2d3dadc478 100644 --- a/core/src/server/net/listener_config.cpp +++ b/core/src/server/net/listener_config.cpp @@ -35,7 +35,7 @@ PortConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To(engine::io::TlsWrapper::StartTlsServer( std::move(peer_socket), - port_config.tls_cert, + port_config.tls_cert_chain, port_config.tls_private_key, {}, port_config.tls_certificate_authorities diff --git a/universal/include/userver/crypto/certificate.hpp b/universal/include/userver/crypto/certificate.hpp index 228599e8ecaa..c3e75926dc69 100644 --- a/universal/include/userver/crypto/certificate.hpp +++ b/universal/include/userver/crypto/certificate.hpp @@ -3,6 +3,7 @@ /// @file userver/crypto/certificate.hpp /// @brief @copybrief crypto::Certificate +#include #include #include #include @@ -44,6 +45,15 @@ class Certificate { std::shared_ptr cert_; }; +using CertificatesChain = std::list; + +/// Accepts a string that contains a chain of certificates (primary and intermediate), checks that +/// it's correct, loads it into OpenSSL structures and returns as a +/// list of 'Certificate's. +/// +/// @throw crypto::KeyParseError if failed to load the certificate. +CertificatesChain LoadCertficatesChainFromString(std::string_view chain); + } // namespace crypto USERVER_NAMESPACE_END diff --git a/universal/src/crypto/certificate.cpp b/universal/src/crypto/certificate.cpp index 0c2830ec538f..ef79d807ebf5 100644 --- a/universal/src/crypto/certificate.cpp +++ b/universal/src/crypto/certificate.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,27 @@ Certificate Certificate::LoadFromString(std::string_view certificate) { return Certificate{std::move(cert)}; } +CertificatesChain LoadCertficatesChainFromString(std::string_view chain) { + CertificatesChain certificates; + constexpr std::string_view kBeginMarker = "-----BEGIN CERTIFICATE-----"; + constexpr std::string_view kEndMarker = "-----END CERTIFICATE-----"; + + std::size_t start = 0; + while ((start = chain.find(kBeginMarker, start)) != std::string::npos) { + auto end = chain.find(kEndMarker, start); + UINVARIANT(end != std::string::npos, "No matching end marker found for certificate"); + + end += kEndMarker.length(); + certificates.push_back(Certificate::LoadFromString(chain.substr(start, end - start))); + start = end; // Move past the current certificate + } + if (certificates.empty()) { + throw KeyParseError(FormatSslError("There are no certificates in chain")); + } + + return certificates; +} + } // namespace crypto USERVER_NAMESPACE_END