Skip to content

Commit dd1d398

Browse files
committed
RestServer: use the full certificate chain
... instead of just the leaf. Without this, verification of the ssl connection will fail in browsers which do not have the intermediaries in their cache. To verify run MajordomoRest_example in a directory containing a valid certificate chain in `demo_public.crt` and corresponding key in `demo_private.key` and then run `openssl s_client -showcerts -connect localhost:8080` and observe that the full chain is present instead of just the leaf. Signed-off-by: Alexander Krimm <[email protected]>
1 parent bfbb343 commit dd1d398

File tree

3 files changed

+50
-21
lines changed

3 files changed

+50
-21
lines changed

src/majordomo/include/majordomo/RestServer.hpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ inline int alpn_select_proto_cb(SSL * /*ssl*/, const unsigned char **out, unsign
223223
return SSL_TLSEXT_ERR_OK;
224224
}
225225

226-
inline std::expected<SSL_CTX_Ptr, std::string> create_ssl_ctx(EVP_PKEY *key, X509 *cert) {
226+
inline std::expected<SSL_CTX_Ptr, std::string> create_ssl_ctx(EVP_PKEY *key, std::span<X509_Ptr> cert) {
227227
auto ssl_ctx = SSL_CTX_Ptr(SSL_CTX_new(TLS_server_method()), SSL_CTX_free);
228228
if (!ssl_ctx) {
229229
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<SSL_CTX_Ptr, std::string> create_ssl_ctx(EVP_PKEY *key, X50
236236
if (SSL_CTX_use_PrivateKey(ssl_ctx.get(), key) <= 0) {
237237
return std::unexpected(std::format("Could not configure private key"));
238238
}
239-
if (SSL_CTX_use_certificate(ssl_ctx.get(), cert) != 1) {
239+
if (SSL_CTX_use_certificate(ssl_ctx.get(), cert[0].get()) != 1) {
240240
return std::unexpected(std::format("Could not configure certificate file"));
241241
}
242+
if (cert.size() > 1) {
243+
for (std::size_t i = 1; i < cert.size(); ++i) {
244+
if (SSL_CTX_add_extra_chain_cert(ssl_ctx.get(), cert[i].get()) != 1) {
245+
return std::unexpected(std::format("Could not add certificate chain file {}", i));
246+
}
247+
}
248+
}
242249

243250
if (!SSL_CTX_check_private_key(ssl_ctx.get())) {
244251
return std::unexpected("Private key does not match the certificate");
@@ -2211,7 +2218,7 @@ struct RestServer {
22112218
Http3ServerSocket _quicServerSocket;
22122219
SSL_CTX_Ptr _sslCtxTcp = SSL_CTX_Ptr(nullptr, SSL_CTX_free);
22132220
EVP_PKEY_Ptr _key = EVP_PKEY_Ptr(nullptr, EVP_PKEY_free);
2214-
X509_Ptr _cert = X509_Ptr(nullptr, X509_free);
2221+
std::vector<X509_Ptr> _cert;
22152222
std::shared_ptr<SharedData> _sharedData = std::make_shared<SharedData>();
22162223
std::map<int, std::unique_ptr<Http2Session>> _h2Sessions;
22172224
std::unordered_map<ngtcp2_cid, std::shared_ptr<Http3Session<RestServer>>> _h3Sessions;
@@ -2231,7 +2238,7 @@ struct RestServer {
22312238
RestServer(RestServer &&) = default;
22322239
RestServer &operator=(RestServer &&) = default;
22332240

2234-
RestServer(SSL_CTX_Ptr sslCtxTcp, EVP_PKEY_Ptr key, X509_Ptr cert)
2241+
RestServer(SSL_CTX_Ptr sslCtxTcp, EVP_PKEY_Ptr key, std::vector<X509_Ptr> cert)
22352242
: _sslCtxTcp(std::move(sslCtxTcp)), _key(std::move(key)), _cert(std::move(cert)) {
22362243
if (_sslCtxTcp) {
22372244
SSL_library_init();
@@ -2241,7 +2248,7 @@ struct RestServer {
22412248
}
22422249

22432250
static std::expected<RestServer, std::string> unencrypted() {
2244-
return RestServer(SSL_CTX_Ptr(nullptr, SSL_CTX_free), EVP_PKEY_Ptr(nullptr, EVP_PKEY_free), X509_Ptr(nullptr, X509_free));
2251+
return RestServer(SSL_CTX_Ptr(nullptr, SSL_CTX_free), EVP_PKEY_Ptr(nullptr, EVP_PKEY_free), {});
22452252
}
22462253

22472254
static std::expected<RestServer, std::string> sslWithBuffers(std::string_view certBuffer, std::string_view keyBuffer) {
@@ -2253,7 +2260,7 @@ struct RestServer {
22532260
if (!maybeKey) {
22542261
return std::unexpected(maybeKey.error());
22552262
}
2256-
auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert->get());
2263+
auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert.value());
22572264
if (!maybeSslCtxTcp) {
22582265
return std::unexpected(maybeSslCtxTcp.error());
22592266
}
@@ -2269,7 +2276,7 @@ struct RestServer {
22692276
if (!maybeKey) {
22702277
return std::unexpected(maybeKey.error());
22712278
}
2272-
auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert->get());
2279+
auto maybeSslCtxTcp = create_ssl_ctx(maybeKey->get(), maybeCert.value());
22732280
if (!maybeSslCtxTcp) {
22742281
return std::unexpected(maybeSslCtxTcp.error());
22752282
}

src/majordomo/include/majordomo/TlsServerSession_Ossl.hpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ class TLSServerContext {
191191
return *this;
192192
}
193193

194-
int init(const opencmw::rest::detail::EVP_PKEY_Ptr &key, const opencmw::rest::detail::X509_Ptr &cert, AppProtocol app_proto) {
194+
int init(const opencmw::rest::detail::EVP_PKEY_Ptr &key, const std::vector<opencmw::rest::detail::X509_Ptr> &cert, AppProtocol app_proto) {
195195
constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
196196

197197
ssl_ctx_ = SSL_CTX_new(TLS_server_method());
@@ -226,9 +226,18 @@ class TLSServerContext {
226226
return -1;
227227
}
228228

229-
if (SSL_CTX_use_certificate(ssl_ctx_, cert.get()) != 1) {
230-
std::cerr << "SSL_CTX_use_certificate_chain_file: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
231-
return -1;
229+
if (!cert.empty()) {
230+
if (SSL_CTX_use_certificate(ssl_ctx_, cert[0].get()) != 1) {
231+
std::cerr << "SSL_CTX_use_certificate_chain_file: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
232+
return -1;
233+
}
234+
235+
for (std::size_t i = 1; i < cert.size(); ++i) {
236+
if (SSL_CTX_add_extra_chain_cert(ssl_ctx_, X509_up_ref(cert[i].get()) ? cert[i].get() : nullptr) != 1) {
237+
std::cerr << "Could not add certificate chain file " << i << ": " << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
238+
return -1;
239+
}
240+
}
232241
}
233242

234243
if (SSL_CTX_check_private_key(ssl_ctx_) != 1) {

src/rest/include/rest/RestUtils.hpp

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,30 +83,43 @@ inline std::expected<X509_STORE_Ptr, std::string> createCertificateStore(std::st
8383
return cert_store;
8484
}
8585

86-
inline std::expected<X509_Ptr, std::string> readServerCertificateFromBuffer(std::string_view X509_ca_bundle) {
86+
inline std::expected<std::vector<X509_Ptr>, std::string> readServerCertificateFromBuffer(std::string_view X509_ca_bundle) {
8787
BIO *certBio = BIO_new(BIO_s_mem());
8888
BIO_write(certBio, X509_ca_bundle.data(), static_cast<int>(X509_ca_bundle.size()));
89-
auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free);
89+
std::vector<X509_Ptr> certs;
90+
while (true) {
91+
auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free);
92+
if (!certX509) {
93+
break;
94+
}
95+
certs.push_back(std::move(certX509));
96+
}
9097
BIO_free(certBio);
91-
if (!certX509) {
98+
if (certs.empty()) {
9299
return std::unexpected(std::format("failed to read certificate from buffer:\n#---start---\n{}\n#---end---\n", X509_ca_bundle));
93100
}
94-
return certX509;
101+
return certs;
95102
}
96103

97-
inline std::expected<X509_Ptr, std::string> readServerCertificateFromFile(std::filesystem::path fpath) {
104+
inline std::expected<std::vector<X509_Ptr>, std::string> readServerCertificateFromFile(std::filesystem::path fpath) {
98105
auto path = fpath.string();
99-
BIO *certBio
100-
= BIO_new_file(path.data(), "r");
106+
BIO *certBio = BIO_new_file(path.data(), "r");
101107
if (!certBio) {
102108
return std::unexpected(std::format("failed to read certificate from file {}: {}", path, ERR_error_string(ERR_get_error(), nullptr)));
103109
}
104-
auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free);
110+
std::vector<X509_Ptr> certs;
111+
while (true) {
112+
auto certX509 = X509_Ptr(PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr), X509_free);
113+
if (!certX509) {
114+
break;
115+
}
116+
certs.push_back(std::move(certX509));
117+
}
105118
BIO_free(certBio);
106-
if (!certX509) {
119+
if (certs.empty()) {
107120
return std::unexpected(std::format("failed to read certificate key from file: {}", path));
108121
}
109-
return certX509;
122+
return certs;
110123
}
111124

112125
inline std::expected<EVP_PKEY_Ptr, std::string> readServerPrivateKeyFromBuffer(std::string_view x509_private_key) {

0 commit comments

Comments
 (0)