Fix epoll dispatch dropping INotify events, ConsoleServer self-deadlock, and add TLS SNI support
- INotify/Thread epoll dispatch was silently dropping every live core::INotify event (an isKnownINotify registry fixes it) - this had broken mail delivery for over a week, masked by routine restarts. - TCPServer::lock self-deadlocked every admin-console connection via a plain std::mutex being re-entered; switched to recursive_mutex. - TLS.cpp/h: SNI-based certificate selection (addSNIContext), the groundwork for multi-tenant TLS (qualityiron.vegas alongside barant.com on one APIServer process). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d058322cc6
commit
1d3d30cd05
8 files changed
+222
-55
No files matched your search
@@ -69,61 +69,100 @@ namespace core {
|
||||
|
||||
void TLS::createContext(TLSInfo *tlsInfo) {
|
||||
|
||||
if(tlsInfo) {
|
||||
if(tlsInfo) {
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
lockarray = (pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
|
||||
for(int i = 0; i < CRYPTO_num_locks(); ++i)
|
||||
pthread_mutex_init(&(lockarray[i]), NULL);
|
||||
for(int i = 0; i < CRYPTO_num_locks(); ++i)
|
||||
pthread_mutex_init(&(lockarray[i]), NULL);
|
||||
CRYPTO_set_id_callback((unsigned long (*)())thread_id);
|
||||
CRYPTO_set_locking_callback((void (*)(int, int, const char *, int))lock_callback);
|
||||
SSLeay_add_ssl_algorithms();
|
||||
RAND_load_file("/dev/hwrng", 1024);
|
||||
if(!(ctx = SSL_CTX_new(SSLv23_server_method())))
|
||||
throw coreutils::Exception("Error while setting server method SSLv23.");
|
||||
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
|
||||
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
|
||||
// SSL_CTX_set_generate_session_id(ctx, generate_session_id);
|
||||
SSL_CTX_set_cipher_list(ctx, "ECDH-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA");
|
||||
// _chain_file (not _file) - loads every certificate in the file,
|
||||
// not just the first. A real Let's Encrypt fullchain.pem is the
|
||||
// leaf cert followed by the intermediate CA cert concatenated in
|
||||
// one file specifically so a server can hand both to the client
|
||||
// in one shot; SSL_CTX_use_certificate_file() only ever reads the
|
||||
// first PEM block, silently dropping the intermediate - confirmed
|
||||
// via a real handshake against this exact fullchain.pem: clients
|
||||
// got "unable to get local issuer certificate" because the server
|
||||
// only ever sent the leaf, never the intermediate needed to chain
|
||||
// up to a root the client already trusts.
|
||||
if(SSL_CTX_use_certificate_chain_file(ctx, tlsInfo->certificate.c_str()) <= 0)
|
||||
throw coreutils::Exception("Error looking up certificate.");
|
||||
if(SSL_CTX_use_PrivateKey_file(ctx, tlsInfo->key.c_str(), SSL_FILETYPE_PEM) < 0)
|
||||
throw coreutils::Exception("Error with private key.");
|
||||
if(SSL_CTX_check_private_key(ctx) != 1)
|
||||
throw coreutils::Exception("Private key does not match certificate.");
|
||||
// Client-certificate verification (mutual TLS) is opt-in, not the
|
||||
// default - a public-facing site (e.g. a real Let's Encrypt
|
||||
// server cert with no corresponding client CA of its own) has no
|
||||
// client certificate to hand out to ordinary visitors, so
|
||||
// requesting one via SSL_VERIFY_PEER would surface a "select a
|
||||
// certificate" prompt real browsers can't meaningfully answer.
|
||||
// Only enable it when a caCertificate is actually configured -
|
||||
// existing internal/mTLS use of this class (any config that DOES
|
||||
// set tls.caCertificate) keeps behaving exactly as before.
|
||||
if(tlsInfo->cACertificate.getLength() > 0) {
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
|
||||
SSL_CTX_set_verify_depth(ctx, 1);
|
||||
if(!SSL_CTX_load_verify_locations(ctx, tlsInfo->cACertificate.c_str(), NULL))
|
||||
throw coreutils::Exception("Cannot verify locations.");
|
||||
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(tlsInfo->cACertificate.c_str()));
|
||||
coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated (client certificate required).";
|
||||
} else {
|
||||
coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated (no client certificate required).";
|
||||
}
|
||||
RAND_load_file("/dev/hwrng", 1024);
|
||||
ctx = buildContext(tlsInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The per-context cert/key/cipher/verify setup factored out of
|
||||
// createContext() above so addSNIContext() (below) can build
|
||||
// additional contexts - one per extra hostname - without repeating
|
||||
// the ONE-TIME, process-wide OpenSSL library init that stays in
|
||||
// createContext() itself (SSL_library_init(), the lockarray, RAND
|
||||
// seeding): that part must only ever run once per process, not once
|
||||
// per hostname.
|
||||
SSL_CTX *TLS::buildContext(TLSInfo *tlsInfo) {
|
||||
SSL_CTX *newCtx;
|
||||
if(!(newCtx = SSL_CTX_new(SSLv23_server_method())))
|
||||
throw coreutils::Exception("Error while setting server method SSLv23.");
|
||||
SSL_CTX_set_mode(newCtx, SSL_MODE_RELEASE_BUFFERS | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
SSL_CTX_set_options(newCtx, SSL_OP_NO_TICKET);
|
||||
SSL_CTX_set_session_cache_mode(newCtx, SSL_SESS_CACHE_SERVER);
|
||||
SSL_CTX_set_cipher_list(newCtx, "ECDH-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA");
|
||||
// _chain_file (not _file) - loads every certificate in the file,
|
||||
// not just the first. A real Let's Encrypt fullchain.pem is the
|
||||
// leaf cert followed by the intermediate CA cert concatenated in
|
||||
// one file specifically so a server can hand both to the client
|
||||
// in one shot; SSL_CTX_use_certificate_file() only ever reads the
|
||||
// first PEM block, silently dropping the intermediate - confirmed
|
||||
// via a real handshake against this exact fullchain.pem: clients
|
||||
// got "unable to get local issuer certificate" because the server
|
||||
// only ever sent the leaf, never the intermediate needed to chain
|
||||
// up to a root the client already trusts.
|
||||
if(SSL_CTX_use_certificate_chain_file(newCtx, tlsInfo->certificate.c_str()) <= 0)
|
||||
throw coreutils::Exception("Error looking up certificate.");
|
||||
if(SSL_CTX_use_PrivateKey_file(newCtx, tlsInfo->key.c_str(), SSL_FILETYPE_PEM) < 0)
|
||||
throw coreutils::Exception("Error with private key.");
|
||||
if(SSL_CTX_check_private_key(newCtx) != 1)
|
||||
throw coreutils::Exception("Private key does not match certificate.");
|
||||
// Client-certificate verification (mutual TLS) is opt-in, not the
|
||||
// default - a public-facing site (e.g. a real Let's Encrypt
|
||||
// server cert with no corresponding client CA of its own) has no
|
||||
// client certificate to hand out to ordinary visitors, so
|
||||
// requesting one via SSL_VERIFY_PEER would surface a "select a
|
||||
// certificate" prompt real browsers can't meaningfully answer.
|
||||
// Only enable it when a caCertificate is actually configured -
|
||||
// existing internal/mTLS use of this class (any config that DOES
|
||||
// set tls.caCertificate) keeps behaving exactly as before.
|
||||
if(tlsInfo->cACertificate.getLength() > 0) {
|
||||
SSL_CTX_set_verify(newCtx, SSL_VERIFY_PEER, NULL);
|
||||
SSL_CTX_set_verify_depth(newCtx, 1);
|
||||
if(!SSL_CTX_load_verify_locations(newCtx, tlsInfo->cACertificate.c_str(), NULL))
|
||||
throw coreutils::Exception("Cannot verify locations.");
|
||||
SSL_CTX_set_client_CA_list(newCtx, SSL_load_client_CA_file(tlsInfo->cACertificate.c_str()));
|
||||
coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated (client certificate required).";
|
||||
} else {
|
||||
coreutils::Log(coreutils::LOG_INFO) << "Server key authenticated (no client certificate required).";
|
||||
}
|
||||
return newCtx;
|
||||
}
|
||||
|
||||
int TLS::sniCallback(SSL *ssl, int *ad, void *arg) {
|
||||
TLS *self = (TLS *)arg;
|
||||
const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if(name) {
|
||||
auto it = self->sniContexts.find(name);
|
||||
if(it != self->sniContexts.end())
|
||||
SSL_set_SSL_CTX(ssl, it->second);
|
||||
// No match (an unconfigured hostname, or a typo'd Host that still
|
||||
// resolves here via DNS) - fall through and keep whatever SSL_CTX
|
||||
// this SSL* already has, which is the base `ctx` SSL_new() was
|
||||
// constructed from. Exactly the same cert a pre-SNI client (or
|
||||
// one sending no SNI at all) already gets today.
|
||||
}
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
void TLS::addSNIContext(const std::string &hostname, TLSInfo *tlsInfo) {
|
||||
SSL_CTX *newCtx = buildContext(tlsInfo);
|
||||
sniContexts[hostname] = newCtx;
|
||||
if(!sniCallbackRegistered) {
|
||||
SSL_CTX_set_tlsext_servername_callback(ctx, sniCallback);
|
||||
SSL_CTX_set_tlsext_servername_arg(ctx, this);
|
||||
sniCallbackRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TLS::registerSocket(int fd) {
|
||||
int ret;
|
||||
|
||||
|
||||
Reference in new issue
Block a user