diff --git a/src/majordomo/include/majordomo/RestServer.hpp b/src/majordomo/include/majordomo/RestServer.hpp index 10d3a799..65160aa8 100644 --- a/src/majordomo/include/majordomo/RestServer.hpp +++ b/src/majordomo/include/majordomo/RestServer.hpp @@ -223,7 +223,7 @@ inline int alpn_select_proto_cb(SSL * /*ssl*/, const unsigned char **out, unsign return SSL_TLSEXT_ERR_OK; } -inline std::expected create_ssl_ctx(EVP_PKEY *key, X509 *cert) { +inline std::expected create_ssl_ctx(EVP_PKEY *key, std::span cert) { auto ssl_ctx = SSL_CTX_Ptr(SSL_CTX_new(TLS_server_method()), SSL_CTX_free); if (!ssl_ctx) { return std::unexpected(std::format("Could not create SSL/TLS context: {}", ERR_error_string(ERR_get_error(), nullptr))); @@ -236,9 +236,16 @@ inline std::expected create_ssl_ctx(EVP_PKEY *key, X50 if (SSL_CTX_use_PrivateKey(ssl_ctx.get(), key) <= 0) { return std::unexpected(std::format("Could not configure private key")); } - if (SSL_CTX_use_certificate(ssl_ctx.get(), cert) != 1) { + if (SSL_CTX_use_certificate(ssl_ctx.get(), cert[0].get()) != 1) { return std::unexpected(std::format("Could not configure certificate file")); } + if (cert.size() > 1) { + for (std::size_t i = 1; i < cert.size(); ++i) { + if (SSL_CTX_add_extra_chain_cert(ssl_ctx.get(), cert[i].get()) != 1) { + return std::unexpected(std::format("Could not add certificate chain file {}", i)); + } + } + } if (!SSL_CTX_check_private_key(ssl_ctx.get())) { return std::unexpected("Private key does not match the certificate"); @@ -2211,7 +2218,7 @@ struct RestServer { Http3ServerSocket _quicServerSocket; SSL_CTX_Ptr _sslCtxTcp = SSL_CTX_Ptr(nullptr, SSL_CTX_free); EVP_PKEY_Ptr _key = EVP_PKEY_Ptr(nullptr, EVP_PKEY_free); - X509_Ptr _cert = X509_Ptr(nullptr, X509_free); + std::vector _cert; std::shared_ptr _sharedData = std::make_shared(); std::map> _h2Sessions; std::unordered_map>> _h3Sessions; @@ -2231,7 +2238,7 @@ struct RestServer { RestServer(RestServer &&) = default; RestServer &operator=(RestServer &&) = default; - RestServer(SSL_CTX_Ptr sslCtxTcp, EVP_PKEY_Ptr key, X509_Ptr cert) + RestServer(SSL_CTX_Ptr sslCtxTcp, EVP_PKEY_Ptr key, std::vector cert) : _sslCtxTcp(std::move(sslCtxTcp)), _key(std::move(key)), _cert(std::move(cert)) { if (_sslCtxTcp) { SSL_library_init(); @@ -2241,7 +2248,7 @@ struct RestServer { } static std::expected unencrypted() { - return RestServer(SSL_CTX_Ptr(nullptr, SSL_CTX_free), EVP_PKEY_Ptr(nullptr, EVP_PKEY_free), X509_Ptr(nullptr, X509_free)); + return RestServer(SSL_CTX_Ptr(nullptr, SSL_CTX_free), EVP_PKEY_Ptr(nullptr, EVP_PKEY_free), {}); } static std::expected sslWithBuffers(std::string_view certBuffer, std::string_view keyBuffer) { @@ -2253,7 +2260,7 @@ struct RestServer { if (!maybeKey) { return std::unexpected(maybeKey.error()); } - auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert->get()); + auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert.value()); if (!maybeSslCtxTcp) { return std::unexpected(maybeSslCtxTcp.error()); } @@ -2269,7 +2276,7 @@ struct RestServer { if (!maybeKey) { return std::unexpected(maybeKey.error()); } - auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert->get()); + auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert.value()); if (!maybeSslCtxTcp) { return std::unexpected(maybeSslCtxTcp.error()); } diff --git a/src/majordomo/include/majordomo/TlsServerSession_Ossl.hpp b/src/majordomo/include/majordomo/TlsServerSession_Ossl.hpp index c75c3629..d6856fa1 100644 --- a/src/majordomo/include/majordomo/TlsServerSession_Ossl.hpp +++ b/src/majordomo/include/majordomo/TlsServerSession_Ossl.hpp @@ -191,7 +191,7 @@ class TLSServerContext { return *this; } - int init(const opencmw::rest::detail::EVP_PKEY_Ptr &key, const opencmw::rest::detail::X509_Ptr &cert, AppProtocol app_proto) { + int init(const opencmw::rest::detail::EVP_PKEY_Ptr &key, const std::vector &cert, AppProtocol app_proto) { constexpr static unsigned char sid_ctx[] = "ngtcp2 server"; ssl_ctx_ = SSL_CTX_new(TLS_server_method()); @@ -226,9 +226,18 @@ class TLSServerContext { return -1; } - if (SSL_CTX_use_certificate(ssl_ctx_, cert.get()) != 1) { - std::cerr << "SSL_CTX_use_certificate_chain_file: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; + if (!cert.empty()) { + if (SSL_CTX_use_certificate(ssl_ctx_, cert[0].get()) != 1) { + std::cerr << "SSL_CTX_use_certificate_chain_file: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + for (std::size_t i = 1; i < cert.size(); ++i) { + if (SSL_CTX_add_extra_chain_cert(ssl_ctx_, X509_up_ref(cert[i].get()) ? cert[i].get() : nullptr) != 1) { + std::cerr << "Could not add certificate chain file " << i << ": " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + } } if (SSL_CTX_check_private_key(ssl_ctx_) != 1) { diff --git a/src/rest/include/rest/RestUtils.hpp b/src/rest/include/rest/RestUtils.hpp index f7b800a3..0cb8d7b6 100644 --- a/src/rest/include/rest/RestUtils.hpp +++ b/src/rest/include/rest/RestUtils.hpp @@ -83,30 +83,43 @@ inline std::expected createCertificateStore(std::st return cert_store; } -inline std::expected readServerCertificateFromBuffer(std::string_view X509_ca_bundle) { +inline std::expected, std::string> readServerCertificateFromBuffer(std::string_view X509_ca_bundle) { BIO *certBio = BIO_new(BIO_s_mem()); BIO_write(certBio, X509_ca_bundle.data(), static_cast(X509_ca_bundle.size())); - auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free); + std::vector certs; + while (true) { + auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free); + if (!certX509) { + break; + } + certs.push_back(std::move(certX509)); + } BIO_free(certBio); - if (!certX509) { + if (certs.empty()) { return std::unexpected(std::format("failed to read certificate from buffer:\n#---start---\n{}\n#---end---\n", X509_ca_bundle)); } - return certX509; + return certs; } -inline std::expected readServerCertificateFromFile(std::filesystem::path fpath) { +inline std::expected, std::string> readServerCertificateFromFile(std::filesystem::path fpath) { auto path = fpath.string(); - BIO *certBio - = BIO_new_file(path.data(), "r"); + BIO *certBio = BIO_new_file(path.data(), "r"); if (!certBio) { return std::unexpected(std::format("failed to read certificate from file {}: {}", path, ERR_error_string(ERR_get_error(), nullptr))); } - auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free); + std::vector certs; + while (true) { + auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free); + if (!certX509) { + break; + } + certs.push_back(std::move(certX509)); + } BIO_free(certBio); - if (!certX509) { + if (certs.empty()) { return std::unexpected(std::format("failed to read certificate key from file: {}", path)); } - return certX509; + return certs; } inline std::expected readServerPrivateKeyFromBuffer(std::string_view x509_private_key) {