/
githubmirror
/
rsyslog
Обзор
Документация
Войти
/
githubmirror
/
rsyslog
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
runtime/net_ossl.c
2 719 строк
98 KB
Rainer Gerhards
ossl: avoid caching OCSP responses past nextUpdate
26 май 2026, 10:48
26 май 2026, 10:48
95549de
Код
Авторство
О чём код?
/* net.c * Implementation of network-related stuff. * * File begun on 2023-08-29 by Alorbach (extracted from net.c) * * Copyright 2023 Andre Lorbach and Adiscon GmbH. * * This file is part of rsyslog. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * -or- * see COPYING.ASL20 in the source distribution * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h> #include <strings.h> #include <errno.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <pthread.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/select.h> #include <sys/time.h> #if defined(__GLIBC__) #include <time.h> #endif #include "rsyslog.h" #include "syslogd-types.h" #include "module-template.h" #include "parse.h" #include "srUtils.h" #include "obj.h" #include "errmsg.h" #include "net.h" #include "net_ossl.h" #include "nsd_ptcp.h" #include "rsconf.h" #define OCSP_TIMEOUT 5 #define OCSP_CACHE_MAX_ENTRIES 100 #define OCSP_CACHE_DEFAULT_TTL 3600 /* static data */ DEFobjStaticHelpers; DEFobjCurrIf(glbl) DEFobjCurrIf(net) DEFobjCurrIf(nsd_ptcp) /* Prototypes for openssl helper functions */ void net_ossl_lastOpenSSLErrorMsg( uchar *fromHost, int ret, SSL *ssl, int severity, const char *pszCallSource, const char *pszOsslApi); void net_ossl_set_ssl_verify_callback(SSL *pSsl, int flags); void net_ossl_set_ctx_verify_callback(SSL_CTX *pCtx, int flags); void net_ossl_set_bio_callback(BIO *conn); int net_ossl_verify_callback(int status, X509_STORE_CTX *store); #if (OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)) || defined(ENABLE_WOLFSSL) rsRetVal net_ossl_apply_tlscgfcmd(net_ossl_t *pThis, uchar *tlscfgcmd); #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L rsRetVal net_ossl_chkpeercertvalidity(net_ossl_t *pThis, SSL *ssl, uchar *fromHostIP); X509 *net_ossl_getpeercert(net_ossl_t *pThis, SSL *ssl, uchar *fromHostIP); rsRetVal net_ossl_peerfingerprint(net_ossl_t *pThis, X509 *certpeer, uchar *fromHostIP); #ifndef ENABLE_WOLFSSL void ocsp_cache_cleanup(void); #endif rsRetVal net_ossl_chkpeername(net_ossl_t *pThis, X509 *certpeer, uchar *fromHostIP); #ifdef ENABLE_WOLFSSL static rsRetVal net_ossl_wolfssl_enable_leaf_crl(SSL_CTX *ctx, const char *crlFile) { WOLFSSL_CERT_MANAGER *cm; DEFiRet; cm = wolfSSL_CTX_GetCertManager(ctx); if (cm == NULL) { LogError(0, RS_RET_CRL_INVALID, "wolfSSL: CertManager unavailable after loading CRL '%s'", crlFile); ABORT_FINALIZE(RS_RET_CRL_INVALID); } /* wolfSSL_X509_STORE_add_crl enables chain-wide CRL checks, while the * DER loader does not enable CRL checks at all. Normalize both paths to * OpenSSL's X509_V_FLAG_CRL_CHECK behavior: verify the leaf certificate. */ if (wolfSSL_CertManagerDisableCRL(cm) != WOLFSSL_SUCCESS) { LogError(0, RS_RET_CRL_INVALID, "wolfSSL: CRL checking could not be reset for CRL '%s'", crlFile); ABORT_FINALIZE(RS_RET_CRL_INVALID); } if (wolfSSL_CertManagerEnableCRL(cm, WOLFSSL_CRL_CHECK) != WOLFSSL_SUCCESS) { LogError(0, RS_RET_CRL_INVALID, "wolfSSL: CRL checking could not be enabled for CRL '%s'", crlFile); ABORT_FINALIZE(RS_RET_CRL_INVALID); } finalize_it: RETiRet; } #endif /*--------------------------------------MT OpenSSL helpers ------------------------------------------*/ #ifndef ENABLE_WOLFSSL #if defined(__GLIBC__) typedef struct ocsp_gai_req_s { struct gaicb request; struct addrinfo hints; char *host; char *port; } ocsp_gai_req_t; static void ocsp_gai_req_free(ocsp_gai_req_t *req) { if (req == NULL) return; if (req->request.ar_result != NULL) freeaddrinfo(req->request.ar_result); free(req->host); free(req->port); free(req); } static void *ocsp_gai_cleanup_thread(void *arg) { ocsp_gai_req_t *req = (ocsp_gai_req_t *)arg; const struct gaicb *requests[1]; requests[0] = &req->request; (void)gai_suspend(requests, 1, NULL); (void)gai_error(&req->request); ocsp_gai_req_free(req); return NULL; } static int ocsp_gai_cleanup_async(ocsp_gai_req_t *req) { pthread_t tid; if (pthread_create(&tid, NULL, ocsp_gai_cleanup_thread, req) != 0) { const struct gaicb *requests[1]; requests[0] = &req->request; (void)gai_suspend(requests, 1, NULL); (void)gai_error(&req->request); ocsp_gai_req_free(req); } else { pthread_detach(tid); } return EAI_AGAIN; } #endif static int ocsp_getaddrinfo(const char *host, const char *port, const struct addrinfo *hints, struct addrinfo **res) { #if defined(__GLIBC__) ocsp_gai_req_t *req; struct gaicb *requests[1]; struct timespec timeout; int ret; req = calloc(1, sizeof(*req)); if (req == NULL) return EAI_MEMORY; req->host = strdup(host); req->port = strdup(port); if (req->host == NULL || req->port == NULL) { ocsp_gai_req_free(req); return EAI_MEMORY; } req->hints = *hints; req->request.ar_name = req->host; req->request.ar_service = req->port; req->request.ar_request = &req->hints; requests[0] = &req->request; ret = getaddrinfo_a(GAI_NOWAIT, requests, 1, NULL); if (ret != 0) { ocsp_gai_req_free(req); return ret; } timeout.tv_sec = OCSP_TIMEOUT; timeout.tv_nsec = 0; ret = gai_suspend((const struct gaicb *const *)requests, 1, &timeout); if (ret == EAI_AGAIN) { ret = gai_cancel(&req->request); if (ret == EAI_CANCELED) { ocsp_gai_req_free(req); return EAI_AGAIN; } if (ret == EAI_NOTCANCELED) { return ocsp_gai_cleanup_async(req); } if (ret == EAI_ALLDONE) { ret = 0; } } if (ret != 0) { ocsp_gai_req_free(req); return ret; } ret = gai_error(&req->request); if (ret == 0) { *res = req->request.ar_result; req->request.ar_result = NULL; } else { (void)gai_cancel(&req->request); } ocsp_gai_req_free(req); return ret; #else return getaddrinfo(host, port, hints, res); #endif } static MUTEX_TYPE *mutex_buf = NULL; static sbool openssl_initialized = 0; // Avoid multiple initialization / deinitialization void locking_function(int mode, int n, __attribute__((unused)) const char *file, __attribute__((unused)) int line) { if (mode & CRYPTO_LOCK) MUTEX_LOCK(mutex_buf[n]); else MUTEX_UNLOCK(mutex_buf[n]); } unsigned long id_function(void) { return ((unsigned long)THREAD_ID); } struct CRYPTO_dynlock_value *dyn_create_function(__attribute__((unused)) const char *file, __attribute__((unused)) int line) { struct CRYPTO_dynlock_value *value; value = (struct CRYPTO_dynlock_value *)malloc(sizeof(struct CRYPTO_dynlock_value)); if (!value) return NULL; MUTEX_SETUP(value->mutex); return value; } void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, __attribute__((unused)) const char *file, __attribute__((unused)) int line) { if (mode & CRYPTO_LOCK) MUTEX_LOCK(l->mutex); else MUTEX_UNLOCK(l->mutex); } void dyn_destroy_function(struct CRYPTO_dynlock_value *l, __attribute__((unused)) const char *file, __attribute__((unused)) int line) { MUTEX_CLEANUP(l->mutex); free(l); } /* set up support functions for openssl multi-threading. This must * be done at library initialisation. If the function fails, * processing can not continue normally. On failure, 0 is * returned, on success 1. */ int opensslh_THREAD_setup(void) { int i; if (openssl_initialized == 1) { DBGPRINTF("openssl: multithread setup already initialized\n"); return 1; } mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE)); if (mutex_buf == NULL) return 0; for (i = 0; i < CRYPTO_num_locks(); i++) MUTEX_SETUP(mutex_buf[i]); #if OPENSSL_VERSION_NUMBER < 0x10100000L CRYPTO_set_id_callback(id_function); #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ CRYPTO_set_locking_callback(locking_function); /* The following three CRYPTO_... functions are the OpenSSL functions for registering the callbacks we implemented above */ CRYPTO_set_dynlock_create_callback(dyn_create_function); CRYPTO_set_dynlock_lock_callback(dyn_lock_function); CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); DBGPRINTF("openssl: multithread setup finished\n"); openssl_initialized = 1; return 1; } /* shut down openssl - do this only when you are totally done * with openssl. */ int opensslh_THREAD_cleanup(void) { int i; if (openssl_initialized == 0) { DBGPRINTF("openssl: multithread cleanup already done\n"); return 1; } if (!mutex_buf) return 0; #if OPENSSL_VERSION_NUMBER < 0x10100000L CRYPTO_set_id_callback(NULL); #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ CRYPTO_set_locking_callback(NULL); CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); for (i = 0; i < CRYPTO_num_locks(); i++) MUTEX_CLEANUP(mutex_buf[i]); free(mutex_buf); mutex_buf = NULL; DBGPRINTF("openssl: multithread cleanup finished\n"); openssl_initialized = 0; return 1; } #else int opensslh_THREAD_setup(void) { return 1; } int opensslh_THREAD_cleanup(void) { return 1; } #endif /* !ENABLE_WOLFSSL */ /*-------------------------------------- MT OpenSSL helpers -----------------------------------------*/ /*--------------------------------------OpenSSL helpers ------------------------------------------*/ /* globally initialize OpenSSL */ void osslGlblInit(void) { DBGPRINTF("osslGlblInit: ENTER\n"); #ifdef ENABLE_WOLFSSL if (!SSL_library_init()) { LogError(0, RS_RET_NO_ERRCODE, "Error: OpenSSL initialization failed!"); } #else if ((opensslh_THREAD_setup() == 0) || #if OPENSSL_VERSION_NUMBER < 0x10100000L /* Setup OpenSSL library < 1.1.0 */ !SSL_library_init() #else /* Setup OpenSSL library >= 1.1.0 with system default settings */ OPENSSL_init_ssl(0, NULL) == 0 #endif ) { LogError(0, RS_RET_NO_ERRCODE, "Error: OpenSSL initialization failed!"); } #endif #if defined(ENABLE_WOLFSSL) && defined(DEBUG_WOLFSSL) wolfSSL_Debugging_ON(); #endif /* Load readable error strings */ SSL_load_error_strings(); #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) /* * ERR_load_*(), ERR_func_error_string(), ERR_get_error_line(), ERR_get_error_line_data(), ERR_get_state() * OpenSSL now loads error strings automatically so these functions are not needed. * SEE FOR MORE: * https://www.openssl.org/docs/manmaster/man7/migration_guide.html * */ #else /* Load error strings into mem*/ ERR_load_BIO_strings(); ERR_load_crypto_strings(); #endif PRAGMA_DIAGNOSTIC_PUSH PRAGMA_IGNORE_Wdeprecated_declarations #ifndef OPENSSL_NO_ENGINE // Initialize OpenSSL engine library ENGINE_load_builtin_engines(); /* Register all of them for every algorithm they collectively implement */ ENGINE_register_all_complete(); // Iterate through all available engines ENGINE *osslEngine = ENGINE_get_first(); const char *engine_id = NULL; const char *engine_name = NULL; while (osslEngine) { // Print engine ID and name if the engine is loaded if (ENGINE_get_init_function(osslEngine)) { // Check if engine is initialized engine_id = ENGINE_get_id(osslEngine); engine_name = ENGINE_get_name(osslEngine); DBGPRINTF("osslGlblInit: Loaded Engine: ID = %s, Name = %s\n", engine_id, engine_name); } osslEngine = ENGINE_get_next(osslEngine); } // Free the engine reference when done ENGINE_free(osslEngine); #else DBGPRINTF("osslGlblInit: OpenSSL compiled without ENGINE support - ENGINE support disabled\n"); #endif /* OPENSSL_NO_ENGINE */ PRAGMA_DIAGNOSTIC_POP } /* globally de-initialize OpenSSL */ void osslGlblExit(void) { DBGPRINTF("openssl: entering osslGlblExit\n"); #ifndef ENABLE_WOLFSSL ocsp_cache_cleanup(); #endif #ifndef OPENSSL_NO_ENGINE ENGINE_cleanup(); #endif ERR_free_strings(); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); } /* initialize openssl context; called on * - listener creation * - outbound connection creation * Once created, the ctx object is used by-subobjects (accepted inbound connections) */ static rsRetVal net_ossl_osslCtxInit(net_ossl_t *pThis, const SSL_METHOD *method) { DEFiRet; int bHaveCA; int bHaveCRL; int bHaveCert; int bHaveKey; int bHaveExtraCAFiles; const char *caFile, *crlFile, *certFile, *keyFile; char *extraCaFiles, *extraCaFile; /* Setup certificates */ caFile = (char *)((pThis->pszCAFile == NULL) ? glbl.GetDfltNetstrmDrvrCAF(runConf) : pThis->pszCAFile); if (caFile == NULL) { LogMsg(0, RS_RET_CA_CERT_MISSING, LOG_WARNING, "Warning: CA certificate is not set"); bHaveCA = 0; } else { dbgprintf("osslCtxInit: OSSL CA file: '%s'\n", caFile); bHaveCA = 1; } crlFile = (char *)((pThis->pszCRLFile == NULL) ? glbl.GetDfltNetstrmDrvrCRLF(runConf) : pThis->pszCRLFile); if (crlFile == NULL) { bHaveCRL = 0; } else { dbgprintf("osslCtxInit: OSSL CRL file: '%s'\n", crlFile); bHaveCRL = 1; } certFile = (char *)((pThis->pszCertFile == NULL) ? glbl.GetDfltNetstrmDrvrCertFile(runConf) : pThis->pszCertFile); if (certFile == NULL) { LogMsg(0, RS_RET_CERT_MISSING, LOG_WARNING, "Warning: Certificate file is not set"); bHaveCert = 0; } else { dbgprintf("osslCtxInit: OSSL CERT file: '%s'\n", certFile); bHaveCert = 1; } keyFile = (char *)((pThis->pszKeyFile == NULL) ? glbl.GetDfltNetstrmDrvrKeyFile(runConf) : pThis->pszKeyFile); if (keyFile == NULL) { LogMsg(0, RS_RET_CERTKEY_MISSING, LOG_WARNING, "Warning: Key file is not set"); bHaveKey = 0; } else { dbgprintf("osslCtxInit: OSSL KEY file: '%s'\n", keyFile); bHaveKey = 1; } extraCaFiles = (char *)((pThis->pszExtraCAFiles == NULL) ? glbl.GetNetstrmDrvrCAExtraFiles(runConf) : pThis->pszExtraCAFiles); if (extraCaFiles == NULL) { bHaveExtraCAFiles = 0; } else { dbgprintf("osslCtxInit: OSSL EXTRA CA files: '%s'\n", extraCaFiles); bHaveExtraCAFiles = 1; } /* Create main CTX Object based on method parameter */ if (pThis->ctx != NULL) { SSL_CTX_free(pThis->ctx); pThis->ctx = NULL; } pThis->ctx = SSL_CTX_new(method); if (bHaveExtraCAFiles == 1) { while ((extraCaFile = strsep(&extraCaFiles, ","))) { if (SSL_CTX_load_verify_locations(pThis->ctx, extraCaFile, NULL) != 1) { LogError(0, RS_RET_TLS_CERT_ERR, "Error: Extra Certificate file could not be accessed. " "Check at least: 1) file path is correct, 2) file exist, " "3) permissions are correct, 4) file content is correct. " "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "SSL_CTX_load_verify_locations"); ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); } } } if (bHaveCA == 1 && SSL_CTX_load_verify_locations(pThis->ctx, caFile, NULL) != 1) { LogError(0, RS_RET_TLS_CERT_ERR, "Error: CA certificate could not be accessed. " "Check at least: 1) file path is correct, 2) file exist, " "3) permissions are correct, 4) file content is correct. " "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "SSL_CTX_load_verify_locations"); ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); } if (bHaveCRL == 1) { #if defined(ENABLE_WOLFSSL) /* wolfSSL_CTX_LoadCRLFile / LoadCRLBuffer drop every PEM block after * the first; wolfSSL_PEM_X509_INFO_read_bio iterates correctly. */ WOLFSSL_BIO *crlBio = NULL; WOLF_STACK_OF(WOLFSSL_X509_INFO) *crlStack = NULL; WOLFSSL_X509_STORE *crlStore = NULL; int loaded = 0; int crlIdx; crlBio = wolfSSL_BIO_new_file(crlFile, "rb"); if (crlBio == NULL) { LogError(errno, RS_RET_CRL_MISSING, "Error: CRL '%s' could not be opened", crlFile); ABORT_FINALIZE(RS_RET_CRL_MISSING); } crlStore = wolfSSL_CTX_get_cert_store(pThis->ctx); crlStack = wolfSSL_PEM_X509_INFO_read_bio(crlBio, NULL, NULL, NULL); if (crlStack != NULL) { if (crlStore != NULL) { for (crlIdx = 0; crlIdx < wolfSSL_sk_X509_INFO_num(crlStack); crlIdx++) { WOLFSSL_X509_INFO *info = wolfSSL_sk_X509_INFO_value(crlStack, crlIdx); if (info == NULL || info->crl == NULL) continue; if (wolfSSL_X509_STORE_add_crl(crlStore, info->crl) == WOLFSSL_SUCCESS) { loaded++; } else { LogMsg(0, RS_RET_CRL_INVALID, LOG_WARNING, "wolfSSL: CRL block #%d in '%s' failed to add to store, skipping", crlIdx + 1, crlFile); } } } wolfSSL_sk_pop_free(crlStack, NULL); } wolfSSL_BIO_free(crlBio); if (loaded == 0) { /* No PEM CRL blocks parsed - try the raw file as DER (single CRL) */ if (wolfSSL_CTX_LoadCRLFile(pThis->ctx, crlFile, WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { LogError(0, RS_RET_CRL_INVALID, "Error: CRL '%s' could not be loaded (tried PEM bundle and DER). " "Check at least: 1) file path is correct, 2) file exists, " "3) permissions are correct, 4) file content is correct.", crlFile); ABORT_FINALIZE(RS_RET_CRL_INVALID); } loaded = 1; } CHKiRet(net_ossl_wolfssl_enable_leaf_crl(pThis->ctx, crlFile)); dbgprintf("osslCtxInit: loaded %d CRL(s) from '%s' into wolfSSL CertManager\n", loaded, crlFile); #elif OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) // Get X509_STORE reference X509_STORE *store = SSL_CTX_get_cert_store(pThis->ctx); if (!X509_STORE_load_file(store, crlFile)) { LogError(0, RS_RET_CRL_INVALID, "Error: CRL could not be accessed. " "Check at least: 1) file path is correct, 2) file exist, " "3) permissions are correct, 4) file content is correct. " "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "X509_STORE_load_file"); ABORT_FINALIZE(RS_RET_CRL_INVALID); } X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); #else #if OPENSSL_VERSION_NUMBER >= 0x10002000L // Get X509_STORE reference X509_STORE *store = SSL_CTX_get_cert_store(pThis->ctx); // Load the CRL PEM file FILE *fp = fopen(crlFile, "r"); if (fp == NULL) { LogError(0, RS_RET_CRL_MISSING, "Error: CRL could not be accessed. " "Check at least: 1) file path is correct, 2) file exist, " "3) permissions are correct, 4) file content is correct. " "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "fopen"); ABORT_FINALIZE(RS_RET_CRL_MISSING); } X509_CRL *crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL); fclose(fp); if (crl == NULL) { LogError(0, RS_RET_CRL_INVALID, "Error: Unable to read CRL." "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "PEM_read_X509_CRL"); ABORT_FINALIZE(RS_RET_CRL_INVALID); } // Add the CRL to the X509_STORE if (!X509_STORE_add_crl(store, crl)) { LogError(0, RS_RET_CRL_INVALID, "Error: Unable to add CRL to store." "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "X509_STORE_add_crl"); X509_CRL_free(crl); ABORT_FINALIZE(RS_RET_CRL_INVALID); } // Set the X509_STORE to the SSL_CTX // SSL_CTX_set_cert_store(pThis->ctx, store); // Enable CRL checking X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new(); X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK); SSL_CTX_set1_param(pThis->ctx, param); X509_VERIFY_PARAM_free(param); #else LogError(0, RS_RET_SYS_ERR, "Warning: TLS library does not support X509_STORE_load_file" "(requires OpenSSL 3.x or higher). Cannot use Certificate revocation list (CRL) '%s'.", crlFile); #endif #endif } #ifdef ENABLE_WOLFSSL if (pThis->bTlsRevocationCheck) { WOLFSSL_CERT_MANAGER *cm = wolfSSL_CTX_GetCertManager(pThis->ctx); if (cm == NULL || wolfSSL_CertManagerEnableOCSP(cm, WOLFSSL_OCSP_CHECKALL) != WOLFSSL_SUCCESS) { LogError(0, RS_RET_SYS_ERR, "Error: unable to enable OCSP revocation checking in wolfSSL " "(was wolfSSL built with --enable-ocsp?)"); ABORT_FINALIZE(RS_RET_SYS_ERR); } dbgprintf("osslCtxInit: wolfSSL OCSP revocation checking enabled\n"); } #endif if (bHaveCert == 1 && SSL_CTX_use_certificate_chain_file(pThis->ctx, certFile) != 1) { LogError(0, RS_RET_TLS_CERT_ERR, "Error: Certificate file could not be accessed. " "Check at least: 1) file path is correct, 2) file exist, " "3) permissions are correct, 4) file content is correct. " "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "SSL_CTX_use_certificate_chain_file"); ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); } if (bHaveKey == 1 && SSL_CTX_use_PrivateKey_file(pThis->ctx, keyFile, SSL_FILETYPE_PEM) != 1) { LogError(0, RS_RET_TLS_KEY_ERR, "Error: Key could not be accessed. " "Check at least: 1) file path is correct, 2) file exist, " "3) permissions are correct, 4) file content is correct. " "OpenSSL error info may follow in next messages"); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "SSL_CTX_use_PrivateKey_file"); ABORT_FINALIZE(RS_RET_TLS_KEY_ERR); } /* Set CTX Options */ SSL_CTX_set_options(pThis->ctx, SSL_OP_NO_SSLv2); /* Disable insecure SSLv2 Protocol */ SSL_CTX_set_options(pThis->ctx, SSL_OP_NO_SSLv3); /* Disable insecure SSLv3 Protocol */ SSL_CTX_sess_set_cache_size(pThis->ctx, 1024); /* TODO: make configurable? */ /* Set default VERIFY Options for OpenSSL CTX - and CALLBACK */ if (pThis->authMode == OSSL_AUTH_CERTANON) { dbgprintf("osslCtxInit: SSL_VERIFY_NONE\n"); net_ossl_set_ctx_verify_callback(pThis->ctx, SSL_VERIFY_NONE); } else { dbgprintf("osslCtxInit: SSL_VERIFY_PEER\n"); net_ossl_set_ctx_verify_callback(pThis->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT); } SSL_CTX_set_timeout(pThis->ctx, 30); /* Default Session Timeout, TODO: Make configureable */ SSL_CTX_set_mode(pThis->ctx, SSL_MODE_AUTO_RETRY); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(ENABLE_WOLFSSL) /* Enable Support for automatic ephemeral/temporary DH parameter selection. */ SSL_CTX_set_dh_auto(pThis->ctx, 1); #endif #ifndef ENABLE_WOLFSSL #if OPENSSL_VERSION_NUMBER >= 0x10002000L #if OPENSSL_VERSION_NUMBER <= 0x101010FFL /* Enable Support for automatic EC temporary key parameter selection. */ SSL_CTX_set_ecdh_auto(pThis->ctx, 1); #else /* * SSL_CTX_set_ecdh_auto and SSL_CTX_set_tmp_ecdh are depreceated in higher * OpenSSL Versions, so we no more need them - see for more: * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_ecdh_auto.html */ #endif #else dbgprintf( "osslCtxInit: openssl to old, cannot use SSL_CTX_set_ecdh_auto." "Using SSL_CTX_set_tmp_ecdh with NID_X9_62_prime256v1/() instead.\n"); SSL_CTX_set_tmp_ecdh(pThis->ctx, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); #endif #endif finalize_it: RETiRet; } /* Helper function to print usefull OpenSSL errors */ void net_ossl_lastOpenSSLErrorMsg( uchar *fromHost, int ret, SSL *ssl, int severity, const char *pszCallSource, const char *pszOsslApi) { unsigned long un_error = 0; int iSSLErr = 0; if (ssl == NULL) { /* Output Error Info*/ DBGPRINTF("lastOpenSSLErrorMsg: Error in '%s' with ret=%d\n", pszCallSource, ret); } else { /* if object is set, get error code */ iSSLErr = SSL_get_error(ssl, ret); /* Output Debug as well */ DBGPRINTF( "lastOpenSSLErrorMsg: %s Error in '%s': '%s(%d)' with ret=%d, errno=%d(%s), sslapi='%s'\n", (iSSLErr == SSL_ERROR_SSL ? "SSL_ERROR_SSL" : (iSSLErr == SSL_ERROR_SYSCALL ? "SSL_ERROR_SYSCALL" : "SSL_ERROR_UNKNOWN")), pszCallSource, ERR_error_string(iSSLErr, NULL), iSSLErr, ret, errno, strerror(errno), pszOsslApi); /* Output error message */ LogMsg(0, RS_RET_NO_ERRCODE, severity, "%s Error in '%s': '%s(%d)' with ret=%d, errno=%d(%s), sslapi='%s'\n", (iSSLErr == SSL_ERROR_SSL ? "SSL_ERROR_SSL" : (iSSLErr == SSL_ERROR_SYSCALL ? "SSL_ERROR_SYSCALL" : "SSL_ERROR_UNKNOWN")), pszCallSource, ERR_error_string(iSSLErr, NULL), iSSLErr, ret, errno, strerror(errno), pszOsslApi); } /* Loop through ERR_get_error */ while ((un_error = ERR_get_error()) > 0) { LogMsg(0, RS_RET_NO_ERRCODE, severity, "net_ossl:remote '%s' OpenSSL Error Stack: %s", fromHost, ERR_error_string(un_error, NULL)); } } #if (OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)) || defined(ENABLE_WOLFSSL) /* initialize tls config commands in openssl context */ rsRetVal net_ossl_apply_tlscgfcmd(net_ossl_t *pThis, uchar *tlscfgcmd) { DEFiRet; char *pCurrentPos; char *pNextPos; char *pszCmd; char *pszValue; int iConfErr; if (tlscfgcmd == NULL) { FINALIZE; } dbgprintf("net_ossl_apply_tlscgfcmd: Apply tlscfgcmd: '%s'\n", tlscfgcmd); /* Set working pointer */ pCurrentPos = (char *)tlscfgcmd; if (pCurrentPos != NULL && strlen(pCurrentPos) > 0) { // Create CTX Config Helper SSL_CONF_CTX *cctx; cctx = SSL_CONF_CTX_new(); if (pThis->sslState == osslServer) { SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER); } else { SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT); } SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS); SSL_CONF_CTX_set_ssl_ctx(cctx, pThis->ctx); do { pNextPos = index(pCurrentPos, '='); if (pNextPos != NULL) { while (*pCurrentPos != '\0' && (*pCurrentPos == ' ' || *pCurrentPos == '\t')) pCurrentPos++; pszCmd = strndup(pCurrentPos, pNextPos - pCurrentPos); pCurrentPos = pNextPos + 1; pNextPos = index(pCurrentPos, '\n'); pNextPos = (pNextPos == NULL ? index(pCurrentPos, ';') : pNextPos); pszValue = (pNextPos == NULL ? strdup(pCurrentPos) : strndup(pCurrentPos, pNextPos - pCurrentPos)); pCurrentPos = (pNextPos == NULL ? NULL : pNextPos + 1); /* Add SSL Conf Command */ iConfErr = SSL_CONF_cmd(cctx, pszCmd, pszValue); if (iConfErr > 0) { dbgprintf( "net_ossl_apply_tlscgfcmd: Successfully added Command " "'%s':'%s'\n", pszCmd, pszValue); } else { LogError(0, RS_RET_SYS_ERR, "Failed to add OpenSSL command %s:'%s' " "in net_ossl_apply_tlscgfcmd with error '%d'. " "The command or value may be unsupported by " "this native OpenSSL build.", pszCmd, pszValue, iConfErr); } free(pszCmd); free(pszValue); } else { /* Abort further parsing */ pCurrentPos = NULL; } } while (pCurrentPos != NULL); /* Finalize SSL Conf */ iConfErr = SSL_CONF_CTX_finish(cctx); if (!iConfErr) { LogError(0, RS_RET_SYS_ERR, "Error: setting openssl command parameters: %s. " "Requested settings may be unavailable on this native " "OpenSSL build. " "OpenSSL error info may follow in next messages", tlscfgcmd); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "net_ossl_apply_tlscgfcmd", "SSL_CONF_CTX_finish"); } SSL_CONF_CTX_free(cctx); } finalize_it: RETiRet; } #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L /* Convert a fingerprint to printable data. The conversion is carried out * according IETF I-D syslog-transport-tls-12. The fingerprint string is * returned in a new cstr object. It is the caller's responsibility to * destruct that object. * rgerhards, 2008-05-08 */ static rsRetVal net_ossl_genfingerprintstr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr, const char *prefix) { cstr_t *pStr = NULL; uchar buf[4]; size_t i; DEFiRet; CHKiRet(rsCStrConstruct(&pStr)); CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar *)prefix, strlen(prefix))); for (i = 0; i < sizeFingerprint; ++i) { snprintf((char *)buf, sizeof(buf), ":%2.2X", pFingerprint[i]); CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3)); } cstrFinalize(pStr); *ppStr = pStr; finalize_it: if (iRet != RS_RET_OK) { if (pStr != NULL) rsCStrDestruct(&pStr); } RETiRet; } static const unsigned char *ossl_asn1_string_data(const ASN1_STRING *const str) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(ENABLE_WOLFSSL) return ASN1_STRING_get0_data(str); #else return ASN1_STRING_data((ASN1_STRING *)str); #endif } static rsRetVal ossl_asn1_string_to_cstr(ASN1_STRING *const str, uchar **const ppszOut) { uchar *pszOut = NULL; int len; const unsigned char *data = NULL; #ifndef ENABLE_WOLFSSL unsigned char *utf8 = NULL; #endif DEFiRet; *ppszOut = NULL; if (str == NULL) { FINALIZE; } #ifndef ENABLE_WOLFSSL len = ASN1_STRING_to_UTF8(&utf8, str); if (len < 0 || utf8 == NULL) { FINALIZE; } data = utf8; #else len = ASN1_STRING_length(str); data = ossl_asn1_string_data(str); #endif if (len < 0 || data == NULL) { FINALIZE; } if (memchr(data, '\0', (size_t)len) != NULL) { FINALIZE; } CHKmalloc(pszOut = malloc((size_t)len + 1)); memcpy(pszOut, data, (size_t)len); pszOut[len] = '\0'; *ppszOut = pszOut; pszOut = NULL; finalize_it: free(pszOut); #ifndef ENABLE_WOLFSSL if (utf8 != NULL) { OPENSSL_free(utf8); } #endif RETiRet; } static rsRetVal ossl_append_identity(cstr_t *const pStr, const char *const prefix, const uchar *const identity) { DEFiRet; CHKiRet(rsCStrAppendStrWithLen(pStr, (const uchar *)prefix, strlen(prefix))); CHKiRet(rsCStrAppendStrWithLen(pStr, identity, strlen((const char *)identity))); CHKiRet(rsCStrAppendStrWithLen(pStr, (const uchar *)"; ", 2)); finalize_it: RETiRet; } static rsRetVal net_ossl_matchpeeridentity(net_ossl_t *pThis, X509 *certpeer, const uchar *pszPeerID, int *pbFoundPositiveMatch, const sbool allowX509CheckHost) { permittedPeers_t *pPeer; #if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(ENABLE_WOLFSSL) int osslRet; unsigned int x509flags = 0; #endif char *x509name = NULL; DEFiRet; if (certpeer == NULL) { ABORT_FINALIZE(RS_RET_TLS_NO_CERT); } ISOBJ_TYPE_assert(pThis, net_ossl); assert(pszPeerID != NULL); assert(pbFoundPositiveMatch != NULL); if (pThis->pPermPeers) { /* do we have configured peer IDs? */ for (pPeer = pThis->pPermPeers; pPeer != NULL; pPeer = pPeer->pNext) { CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch)); if (*pbFoundPositiveMatch) break; #if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(ENABLE_WOLFSSL) /* if we did not succeed so far, try ossl X509_check_host * ( Includes check against SubjectAlternativeName ) * if prioritizeSAN set, only check against SAN */ if (!allowX509CheckHost || pPeer->etryType == PERM_PEER_TYPE_WILDCARD) { continue; } else if (pThis->bSANpriority == 1) { #if OPENSSL_VERSION_NUMBER >= 0x10100004L && !defined(LIBRESSL_VERSION_NUMBER) && \ defined(X509_CHECK_FLAG_NEVER_CHECK_SUBJECT) x509flags = X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; #else /* Security hard-stop: with older OpenSSL-compatible APIs (notably wolfSSL * compatibility mode), we cannot request SAN-only matching. Calling * X509_check_host() with default flags may fall back to CN, which would * violate PrioritizeSAN semantics and RFC 6125 behavior. */ dbgprintf("net_ossl_matchpeeridentity: PrioritizeSAN not supported by this OpenSSL-compatible API\n"); continue; #endif // OPENSSL_VERSION_NUMBER >= 0x10100004L } /* Obtain Namex509 name from subject */ if (x509name == NULL) { x509name = X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(certpeer), NULL, 0); } osslRet = X509_check_host(certpeer, (const char *)pPeer->pszID, strlen((const char *)pPeer->pszID), x509flags, NULL); if (osslRet == 1) { /* Found Peer cert in allowed Peerslist */ dbgprintf("net_ossl_matchpeeridentity: Client ('%s') is allowed (X509_check_host)\n", x509name); *pbFoundPositiveMatch = 1; break; } else if (osslRet < 0) { net_ossl_lastOpenSSLErrorMsg(NULL, osslRet, NULL, LOG_ERR, "net_ossl_matchpeeridentity", "X509_check_host"); ABORT_FINALIZE(RS_RET_NO_ERRCODE); } #endif } } else { LogMsg(0, RS_RET_TLS_NO_CERT, LOG_WARNING, "net_ossl_chkonepeername: Peername check could not be done: " "no peernames configured."); } finalize_it: if (x509name != NULL) { OPENSSL_free(x509name); } RETiRet; } static rsRetVal net_ossl_chkonepeername(net_ossl_t *pThis, X509 *certpeer, const uchar *pszPeerID, int *pbFoundPositiveMatch) { DEFiRet; CHKiRet(net_ossl_matchpeeridentity(pThis, certpeer, pszPeerID, pbFoundPositiveMatch, 1)); finalize_it: RETiRet; } static rsRetVal net_ossl_match_sans( net_ossl_t *pThis, X509 *certpeer, cstr_t *pStr, int *bHaveSAN, int *bFoundPositiveMatch) { DEFiRet; GENERAL_NAMES *names = NULL; GENERAL_NAME *name; ASN1_STRING *dns; ASN1_OCTET_STRING *ip; uchar *dnsName = NULL; const unsigned char *ipData; const char *addr; char ipAddress[INET6_ADDRSTRLEN]; int count; int i; if (*bFoundPositiveMatch) { FINALIZE; } names = X509_get_ext_d2i(certpeer, NID_subject_alt_name, NULL, NULL); if (names == NULL) { FINALIZE; } count = sk_GENERAL_NAME_num(names); /* PrioritizeSAN suppresses CN fallback when any SAN exists, even if it is * not a DNS/IP identity usable by x509/name matching. */ if (count > 0) { *bHaveSAN = 1; } for (i = 0; i < count && !*bFoundPositiveMatch; ++i) { name = sk_GENERAL_NAME_value(names, i); if (name == NULL) { continue; } if (name->type == GEN_DNS) { dns = name->d.dNSName; CHKiRet(ossl_asn1_string_to_cstr(dns, &dnsName)); if (dnsName == NULL) { continue; } dbgprintf("net_ossl_match_sans: subject alt dnsName: '%s'\n", dnsName); CHKiRet(ossl_append_identity(pStr, "DNSname: ", dnsName)); CHKiRet(net_ossl_matchpeeridentity(pThis, certpeer, dnsName, bFoundPositiveMatch, 0)); free(dnsName); dnsName = NULL; } else if (name->type == GEN_IPADD) { ip = name->d.iPAddress; ipData = ossl_asn1_string_data(ip); if (ipData == NULL) { continue; } if (ASN1_STRING_length(ip) == 4) { addr = inet_ntop(AF_INET, ipData, ipAddress, sizeof(ipAddress)); } else if (ASN1_STRING_length(ip) == 16) { addr = inet_ntop(AF_INET6, ipData, ipAddress, sizeof(ipAddress)); } else { continue; } if (addr == NULL) { continue; } dbgprintf("net_ossl_match_sans: subject alt ipAddr: '%s'\n", ipAddress); CHKiRet(ossl_append_identity(pStr, "IPaddress: ", (const uchar *)ipAddress)); CHKiRet(net_ossl_matchpeeridentity(pThis, certpeer, (const uchar *)ipAddress, bFoundPositiveMatch, 0)); } } finalize_it: free(dnsName); if (names != NULL) { GENERAL_NAMES_free(names); } RETiRet; } static rsRetVal net_ossl_match_cn(net_ossl_t *pThis, X509 *certpeer, cstr_t *pStr, int *bFoundPositiveMatch) { X509_NAME *subject; X509_NAME_ENTRY *entry; ASN1_STRING *cn; uchar *cnName = NULL; int idx; DEFiRet; subject = RSYSLOG_X509_NAME_oneline(certpeer); if (subject == NULL) { FINALIZE; } idx = X509_NAME_get_index_by_NID(subject, NID_commonName, -1); if (idx < 0) { FINALIZE; } entry = X509_NAME_get_entry(subject, idx); if (entry == NULL) { FINALIZE; } cn = X509_NAME_ENTRY_get_data(entry); CHKiRet(ossl_asn1_string_to_cstr(cn, &cnName)); if (cnName == NULL) { FINALIZE; } dbgprintf("net_ossl_match_cn: now checking auth for CN '%s'\n", cnName); CHKiRet(ossl_append_identity(pStr, "CN: ", cnName)); CHKiRet(net_ossl_matchpeeridentity(pThis, certpeer, cnName, bFoundPositiveMatch, 0)); finalize_it: free(cnName); RETiRet; } /* Check the peer's ID in fingerprint auth mode. * rgerhards, 2008-05-22 */ rsRetVal net_ossl_peerfingerprint(net_ossl_t *pThis, X509 *certpeer, uchar *fromHostIP) { DEFiRet; unsigned int n; uchar fingerprint[20 /*EVP_MAX_MD_SIZE**/]; uchar fingerprintSha256[32 /*EVP_MAX_MD_SIZE**/]; size_t size; size_t sizeSha256; cstr_t *pstrFingerprint = NULL; cstr_t *pstrFingerprintSha256 = NULL; int bFoundPositiveMatch; permittedPeers_t *pPeer; const EVP_MD *fdig = EVP_sha1(); const EVP_MD *fdigSha256 = EVP_sha256(); ISOBJ_TYPE_assert(pThis, net_ossl); if (certpeer == NULL) { ABORT_FINALIZE(RS_RET_TLS_NO_CERT); } /* obtain the SHA1 fingerprint */ size = sizeof(fingerprint); if (!X509_digest(certpeer, fdig, fingerprint, &n)) { dbgprintf("net_ossl_peerfingerprint: error X509cert is not valid!\n"); ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); } sizeSha256 = sizeof(fingerprintSha256); if (!X509_digest(certpeer, fdigSha256, fingerprintSha256, &n)) { dbgprintf("net_ossl_peerfingerprint: error X509cert is not valid!\n"); ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); } CHKiRet(net_ossl_genfingerprintstr(fingerprint, size, &pstrFingerprint, "SHA1")); dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA1 fingerprint: %s\n", cstrGetSzStrNoNULL(pstrFingerprint)); CHKiRet(net_ossl_genfingerprintstr(fingerprintSha256, sizeSha256, &pstrFingerprintSha256, "SHA256")); dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA256 fingerprint: %s\n", cstrGetSzStrNoNULL(pstrFingerprintSha256)); /* now search through the permitted peers to see if we can find a permitted one */ bFoundPositiveMatch = 0; pPeer = pThis->pPermPeers; while (pPeer != NULL && !bFoundPositiveMatch) { if (!rsCStrSzStrCmp(pstrFingerprint, pPeer->pszID, strlen((char *)pPeer->pszID))) { dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA1 MATCH found: %s\n", pPeer->pszID); bFoundPositiveMatch = 1; } else if (!rsCStrSzStrCmp(pstrFingerprintSha256, pPeer->pszID, strlen((char *)pPeer->pszID))) { dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA256 MATCH found: %s\n", pPeer->pszID); bFoundPositiveMatch = 1; } else { dbgprintf("net_ossl_peerfingerprint: NOMATCH peer certificate: %s\n", pPeer->pszID); pPeer = pPeer->pNext; } } if (!bFoundPositiveMatch) { dbgprintf("net_ossl_peerfingerprint: invalid peer fingerprint, not permitted to talk to it\n"); if (pThis->bReportAuthErr == 1) { errno = 0; LogMsg(0, RS_RET_INVALID_FINGERPRINT, LOG_WARNING, "net_ossl:TLS session terminated with remote syslog server '%s': " "Fingerprint check failed, not permitted to talk to %s", fromHostIP, cstrGetSzStrNoNULL(pstrFingerprint)); pThis->bReportAuthErr = 0; } ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); } finalize_it: if (pstrFingerprint != NULL) cstrDestruct(&pstrFingerprint); if (pstrFingerprintSha256 != NULL) cstrDestruct(&pstrFingerprintSha256); RETiRet; } /* Check the peer's ID in name auth mode. */ rsRetVal net_ossl_chkpeername(net_ossl_t *pThis, X509 *certpeer, uchar *fromHostIP) { DEFiRet; int bFoundPositiveMatch; int bHaveSAN; cstr_t *pStr = NULL; char *x509name = NULL; ISOBJ_TYPE_assert(pThis, net_ossl); bFoundPositiveMatch = 0; bHaveSAN = 0; CHKiRet(rsCStrConstruct(&pStr)); /* Obtain Namex509 name from subject */ x509name = X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(certpeer), NULL, 0); if (x509name == NULL) { LogMsg(0, RS_RET_CERT_INVALID_DN, LOG_WARNING, "net_ossl_chkpeername: could not obtain X.509 subject name"); ABORT_FINALIZE(RS_RET_CERT_INVALID_DN); } dbgprintf("net_ossl_chkpeername: checking - peername '%s' on server '%s'\n", x509name, fromHostIP); CHKiRet(ossl_append_identity(pStr, "name: ", (const uchar *)x509name)); CHKiRet(net_ossl_chkonepeername(pThis, certpeer, (uchar *)x509name, &bFoundPositiveMatch)); CHKiRet(net_ossl_match_sans(pThis, certpeer, pStr, &bHaveSAN, &bFoundPositiveMatch)); if (!bFoundPositiveMatch && (!pThis->bSANpriority || !bHaveSAN)) { CHKiRet(net_ossl_match_cn(pThis, certpeer, pStr, &bFoundPositiveMatch)); } if (!bFoundPositiveMatch) { dbgprintf("net_ossl_chkpeername: invalid peername, not permitted to talk to it\n"); if (pThis->bReportAuthErr == 1) { cstrFinalize(pStr); errno = 0; LogMsg(0, RS_RET_INVALID_FINGERPRINT, LOG_WARNING, "net_ossl:TLS session terminated with remote syslog server: " "peer name not authorized, not permitted to talk to %s", cstrGetSzStrNoNULL(pStr)); pThis->bReportAuthErr = 0; } ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); } else { dbgprintf("net_ossl_chkpeername: permitted to talk!\n"); } finalize_it: if (x509name != NULL) { OPENSSL_free(x509name); } if (pStr != NULL) rsCStrDestruct(&pStr); RETiRet; } /* check the ID of the remote peer - used for both fingerprint and * name authentication. */ X509 *net_ossl_getpeercert(net_ossl_t *pThis, SSL *ssl, uchar *fromHostIP) { X509 *certpeer; ISOBJ_TYPE_assert(pThis, net_ossl); /* Get peer certificate from SSL */ certpeer = SSL_get_peer_certificate(ssl); if (certpeer == NULL) { if (pThis->bReportAuthErr == 1 && 1) { errno = 0; pThis->bReportAuthErr = 0; LogMsg(0, RS_RET_TLS_NO_CERT, LOG_WARNING, "net_ossl:TLS session terminated with remote syslog server '%s': " "Peer check failed, peer did not provide a certificate.", fromHostIP); } } return certpeer; } /* Verify the validity of the remote peer's certificate. */ rsRetVal net_ossl_chkpeercertvalidity(net_ossl_t __attribute__((unused)) * pThis, SSL *ssl, uchar *fromHostIP) { DEFiRet; int iVerErr = X509_V_OK; ISOBJ_TYPE_assert(pThis, net_ossl); PermitExpiredCerts *pPermitExpiredCerts = (PermitExpiredCerts *)SSL_get_ex_data(ssl, 1); #ifdef ENABLE_WOLFSSL iVerErr = wolfSSL_get_verify_result(ssl); #else iVerErr = SSL_get_verify_result(ssl); #endif if (iVerErr != X509_V_OK) { if (iVerErr == X509_V_ERR_CERT_HAS_EXPIRED) { if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_DENY) { LogMsg(0, RS_RET_CERT_EXPIRED, LOG_INFO, "net_ossl:TLS session terminated with remote syslog server '%s': " "not permitted to talk to peer, certificate invalid: " "Certificate expired: %s", fromHostIP, X509_verify_cert_error_string(iVerErr)); ABORT_FINALIZE(RS_RET_CERT_EXPIRED); } else if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_WARN) { LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, "net_ossl:CertValidity check - warning talking to peer '%s': " "certificate expired: %s", fromHostIP, X509_verify_cert_error_string(iVerErr)); } else { dbgprintf( "net_ossl_chkpeercertvalidity: talking to peer '%s': " "certificate expired: %s\n", fromHostIP, X509_verify_cert_error_string(iVerErr)); } /* Else do nothing */ } else if (iVerErr == X509_V_ERR_CERT_REVOKED) { LogMsg(0, RS_RET_CERT_REVOKED, LOG_INFO, "net_ossl:TLS session terminated with remote syslog server '%s': " "not permitted to talk to peer, certificate invalid: " "certificate revoked '%s'", fromHostIP, X509_verify_cert_error_string(iVerErr)); ABORT_FINALIZE(RS_RET_CERT_EXPIRED); } else { LogMsg(0, RS_RET_CERT_INVALID, LOG_INFO, "net_ossl:TLS session terminated with remote syslog server '%s': " "not permitted to talk to peer, certificate validation failed: %s", fromHostIP, X509_verify_cert_error_string(iVerErr)); ABORT_FINALIZE(RS_RET_CERT_INVALID); } } else { dbgprintf("net_ossl_chkpeercertvalidity: client certificate validation success: %s\n", X509_verify_cert_error_string(iVerErr)); } finalize_it: RETiRet; } /*--------------------------------------OCSP Support ------------------------------------------*/ #ifndef ENABLE_WOLFSSL /* OCSP is not available in WolfSSL builds */ /* OCSP Response Cache * * Concurrency & Locking: * - Cache is shared across all workers (global state) * - Protected by ocsp_cache_mutex (pthread_mutex_t) * - All cache operations (lookup/store/cleanup) acquire mutex before access * - Cache entries store OCSP responses with expiration times * * Non-blocking I/O: * - Socket operations use non-blocking mode during connect with timeout * - Once connected, sockets use SO_RCVTIMEO/SO_SNDTIMEO for bounded I/O * - Maximum timeout per OCSP responder: OCSP_TIMEOUT (5 seconds) * * Cache Strategy: * - Key: certificate serial number + issuer name hash (SHA-256) * - TTL: Based on OCSP response nextUpdate field, or OCSP_CACHE_DEFAULT_TTL (1 hour) * - Eviction: Prefers expired entries, then FIFO from tail when cache reaches MAX_ENTRIES * - Expired entries removed during lookup to maintain cache efficiency * - Thread-safe: All cache operations protected by mutex */ typedef struct ocsp_cache_entry_s { char *cache_key; /* cert serial + issuer hash */ int cert_status; /* V_OCSP_CERTSTATUS_* */ time_t expires_at; /* when this entry expires */ struct ocsp_cache_entry_s *next; } ocsp_cache_entry_t; static ocsp_cache_entry_t *ocsp_cache_head = NULL; static int ocsp_cache_count = 0; static pthread_mutex_t ocsp_cache_mutex = PTHREAD_MUTEX_INITIALIZER; static char *ocsp_make_cache_key(X509 *cert, X509 *issuer) { DEFiRet; ASN1_INTEGER *serial = X509_get_serialNumber(cert); unsigned char md[EVP_MAX_MD_SIZE]; unsigned int md_len = 0; unsigned char key_md[EVP_MAX_MD_SIZE]; unsigned int key_md_len = 0; char *key = NULL; int key_len; BIGNUM *bn = NULL; char *serial_hex = NULL; if (!serial || !issuer) ABORT_FINALIZE(RS_RET_ERR); /* Hash issuer name for compact key */ X509_NAME *issuer_name = X509_get_subject_name(issuer); if (!X509_NAME_digest(issuer_name, EVP_sha256(), md, &md_len)) { ABORT_FINALIZE(RS_RET_ERR); } /* Hash issuer public key to avoid collisions across key rollovers */ if (!X509_pubkey_digest(issuer, EVP_sha256(), key_md, &key_md_len)) { ABORT_FINALIZE(RS_RET_ERR); } /* Format: serial-number:issuer-name-hash:issuer-key-hash */ bn = ASN1_INTEGER_to_BN(serial, NULL); if (!bn) ABORT_FINALIZE(RS_RET_ERR); serial_hex = BN_bn2hex(bn); if (!serial_hex) ABORT_FINALIZE(RS_RET_ERR); key_len = strlen(serial_hex) + (md_len * 2) + (key_md_len * 2) + 3; CHKmalloc(key = malloc(key_len)); char *p = key; int written = snprintf(p, key_len, "%s:", serial_hex); if (written < 0 || written >= key_len) { ABORT_FINALIZE(RS_RET_ERR); } p += written; size_t remaining = (size_t)key_len - (size_t)written; for (unsigned int i = 0; i < md_len; i++) { if (remaining < 3) { ABORT_FINALIZE(RS_RET_ERR); } written = snprintf(p, remaining, "%02x", md[i]); if (written != 2) { ABORT_FINALIZE(RS_RET_ERR); } p += written; remaining -= (size_t)written; } if (remaining < 2) { ABORT_FINALIZE(RS_RET_ERR); } *p++ = ':'; *p = '\0'; remaining--; for (unsigned int i = 0; i < key_md_len; i++) { if (remaining < 3) { ABORT_FINALIZE(RS_RET_ERR); } written = snprintf(p, remaining, "%02x", key_md[i]); if (written != 2) { ABORT_FINALIZE(RS_RET_ERR); } p += written; remaining -= (size_t)written; } finalize_it: if (iRet != RS_RET_OK) { free(key); key = NULL; } if (bn) BN_free(bn); if (serial_hex) OPENSSL_free(serial_hex); return key; } static int ocsp_cache_lookup(const char *cache_key, int *cert_status) { int found = 0; time_t now = time(NULL); pthread_mutex_lock(&ocsp_cache_mutex); ocsp_cache_entry_t **pp = &ocsp_cache_head; while (*pp) { ocsp_cache_entry_t *entry = *pp; if (strcmp(entry->cache_key, cache_key) == 0) { if (entry->expires_at > now) { *cert_status = entry->cert_status; found = 1; dbgprintf("OCSP: Cache hit for key %s, status=%d\n", cache_key, *cert_status); } else { /* Remove expired entry */ dbgprintf("OCSP: Removing expired cache entry for key %s\n", cache_key); *pp = entry->next; free(entry->cache_key); free(entry); ocsp_cache_count--; } break; } pp = &(*pp)->next; } pthread_mutex_unlock(&ocsp_cache_mutex); return found; } static void ocsp_cache_store(const char *cache_key, int cert_status, time_t ttl) { DEFiRet; pthread_mutex_lock(&ocsp_cache_mutex); /* Check if already exists and update */ ocsp_cache_entry_t *entry = ocsp_cache_head; while (entry) { if (strcmp(entry->cache_key, cache_key) == 0) { entry->cert_status = cert_status; entry->expires_at = time(NULL) + ttl; dbgprintf("OCSP: Cache updated for key %s\n", cache_key); pthread_mutex_unlock(&ocsp_cache_mutex); return; } entry = entry->next; } /* Try to evict expired entries first before removing valid ones */ if (ocsp_cache_count >= OCSP_CACHE_MAX_ENTRIES) { time_t now = time(NULL); ocsp_cache_entry_t **pp = &ocsp_cache_head; ocsp_cache_entry_t *expired_entry = NULL; /* Find first expired entry */ while (*pp) { if ((*pp)->expires_at <= now) { expired_entry = *pp; *pp = expired_entry->next; free(expired_entry->cache_key); free(expired_entry); ocsp_cache_count--; break; } pp = &(*pp)->next; } /* If no expired entries found, evict from tail (true FIFO) */ if (!expired_entry && ocsp_cache_head) { pp = &ocsp_cache_head; while ((*pp)->next) { pp = &(*pp)->next; } ocsp_cache_entry_t *to_remove = *pp; *pp = NULL; free(to_remove->cache_key); free(to_remove); ocsp_cache_count--; } } /* Add new entry */ ocsp_cache_entry_t *new_entry = NULL; CHKmalloc(new_entry = malloc(sizeof(ocsp_cache_entry_t))); CHKmalloc(new_entry->cache_key = strdup(cache_key)); new_entry->cert_status = cert_status; new_entry->expires_at = time(NULL) + ttl; new_entry->next = ocsp_cache_head; ocsp_cache_head = new_entry; ocsp_cache_count++; dbgprintf("OCSP: Cache stored for key %s, count=%d\n", cache_key, ocsp_cache_count); finalize_it: if (iRet == RS_RET_OUT_OF_MEMORY) { LogError(0, RS_RET_OUT_OF_MEMORY, "OCSP: Failed to allocate cache entry\n"); } if (iRet != RS_RET_OK) { if (new_entry) { free(new_entry->cache_key); free(new_entry); } } pthread_mutex_unlock(&ocsp_cache_mutex); } void ocsp_cache_cleanup(void) { pthread_mutex_lock(&ocsp_cache_mutex); ocsp_cache_entry_t *entry = ocsp_cache_head; while (entry) { ocsp_cache_entry_t *next = entry->next; free(entry->cache_key); free(entry); entry = next; } ocsp_cache_head = NULL; ocsp_cache_count = 0; pthread_mutex_unlock(&ocsp_cache_mutex); } /* * CRL is not implemented! * * This is just a sanity-check stub, to fail on CRL-only certificates. * CRL-only certificate means: Certificate CRL DP, but not OCSP/AuthorityInfoAccess information. * * Returns: * 0: if CRL check failed (Error, or Revoked when is_revoked is set) * 1: if the certificate status is "GOOD" * 2: if the certificate holds no CRL URL */ static int crl_check(X509 *current_cert, int *is_revoked) { int ret = 0; STACK_OF(DIST_POINT) * crldp; STACK_OF(OPENSSL_STRING) * ocsp_uris; /* Reset revoked status. Needs to be set in case CRL gets implemented. */ if (is_revoked) *is_revoked = 0; crldp = X509_get_ext_d2i(current_cert, NID_crl_distribution_points, NULL, NULL); ocsp_uris = X509_get1_ocsp(current_cert); if (crldp && ocsp_uris == NULL) { LogError(0, RS_RET_NO_ERRCODE, "CRL support is not implemented. " "CRL-only certificates can't be validated!\n"); ret = 0; /* abort verification. CRL-only not supported. */ } else if (!crldp) { ret = 2; } if (ocsp_uris) X509_email_free(ocsp_uris); if (crldp) sk_DIST_POINT_pop_free(crldp, DIST_POINT_free); return ret; } /* * Find the issuer certificate, which is mandatory to generate * a OCSP request. * * Query first locally configured and trusted certificates (e.g CAs). * * Last resort: Use the certificate chain of the peer, to get hold of * untrusted certificates (e.g. intermediate certificates). * * The resulting issuer certificate might be an untrusted certificate, * which should be used to generate OCSP requests. */ static X509 *ocsp_find_issuer(X509 *target_cert, const char *cert_name, SSL_CTX *ctx, STACK_OF(X509) * untrusted_peer_certs) { X509 *issuer = NULL; X509_STORE *store = SSL_CTX_get_cert_store(ctx); STACK_OF(X509_OBJECT) * objs; /* find issuer among local trusted issuers */ if (store != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L objs = X509_STORE_get0_objects(store); for (int i = 0; i < sk_X509_OBJECT_num(objs); i++) { X509 *cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i)); if (cert && X509_check_issued(cert, target_cert) == X509_V_OK) { issuer = cert; break; } } #else /* OpenSSL 1.0.2 compatibility: direct access to store fields */ objs = store->objs; for (int i = 0; i < sk_X509_OBJECT_num(objs); i++) { X509_OBJECT *x509obj = sk_X509_OBJECT_value(objs, i); X509 *cert = x509obj->type == X509_LU_X509 ? x509obj->data.x509 : NULL; if (cert && X509_check_issued(cert, target_cert) == X509_V_OK) { issuer = cert; break; } } #endif } if (!issuer) { /* Look for intermediate certificates in the remote cert-chain */ for (int i = 0; i < sk_X509_num(untrusted_peer_certs); i++) { X509 *cert = sk_X509_value(untrusted_peer_certs, i); if (X509_check_issued(cert, target_cert) == X509_V_OK) { issuer = cert; break; } } } /* No issuer, no revocation check possible */ if (!issuer) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Could not find any issuer for: \"%s\"\n", cert_name); } return issuer; } static const char *ocsp_get_response_status_err(int c) { switch (c) { case OCSP_RESPONSE_STATUS_SUCCESSFUL: return "Successful"; case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST: return "Malformed request"; case OCSP_RESPONSE_STATUS_INTERNALERROR: return "Internal error in OCSP responder"; case OCSP_RESPONSE_STATUS_TRYLATER: return "Try again later"; case OCSP_RESPONSE_STATUS_SIGREQUIRED: return "Must sign the request"; case OCSP_RESPONSE_STATUS_UNAUTHORIZED: return "Request unauthorized"; default: return "Unknown Response Status"; } } /* * Returns 1 if the cert status is "GOOD" * Stores result in cache if successful */ static int ocsp_check_validate_response_and_cert(OCSP_RESPONSE *rsp, OCSP_REQUEST *req, STACK_OF(X509) * untrusted_peer_certs, OCSP_CERTID *id, SSL_CTX *ctx, const char *cert_name, X509 *cert, X509 *issuer, int *is_revoked) { int s, ret = 0; long leeway_sec = 300; /* 5 Minutes */ long maxage = -1; /* No maximum age for ThisUpdate response */ int status, reason; ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; OCSP_BASICRESP *bs = NULL; X509_STORE *store = SSL_CTX_get_cert_store(ctx); bs = OCSP_response_get1_basic(rsp); if (bs == NULL) { LogError(0, RS_RET_NO_ERRCODE, "Failed to decode the basic OCSP response.\n"); goto err; } s = OCSP_check_nonce(req, bs); if (s == -1) { LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, "Warning: no Nonce in OCSP response from peer. " "(No replay attack protection available)\n"); } else if (s == 0) { LogError(0, RS_RET_NO_ERRCODE, "Nonce mismatch in OCSP response.\n"); goto err; } s = OCSP_basic_verify(bs, untrusted_peer_certs, store, 0); if (s <= 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP response verification failed.\n"); goto err; } if (!OCSP_resp_find_status(bs, id, &status, &reason, &rev, &thisupd, &nextupd)) { LogError(0, RS_RET_NO_ERRCODE, "Failed to get OCSP response status for: %s\n", cert_name); goto err; } s = OCSP_check_validity(thisupd, nextupd, leeway_sec, maxage); if (s == 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP response is not valid (either expired or not yet valid).\n"); goto err; } /* Check certificate status */ switch (status) { case V_OCSP_CERTSTATUS_GOOD: dbgprintf("OCSP: Certificate status is GOOD for: %s\n", cert_name); ret = 1; break; case V_OCSP_CERTSTATUS_REVOKED: LogMsg(0, RS_RET_CERT_REVOKED, LOG_ERR, "OCSP: Certificate status is REVOKED for: %s (reason: %s)\n", cert_name, OCSP_crl_reason_str(reason)); if (is_revoked) *is_revoked = 1; ret = 0; break; case V_OCSP_CERTSTATUS_UNKNOWN: LogError(0, RS_RET_NO_ERRCODE, "OCSP: Certificate status is UNKNOWN for: %s\n", cert_name); ret = 0; break; default: LogError(0, RS_RET_NO_ERRCODE, "OCSP: Unknown certificate status for: %s\n", cert_name); ret = 0; break; } /* Store result in cache */ char *cache_key = ocsp_make_cache_key(cert, issuer); if (cache_key) { int should_cache = 1; time_t cache_ttl = OCSP_CACHE_DEFAULT_TTL; /* Use nextUpdate if available for more accurate cache expiry */ if (nextupd) { /* Use ASN1_TIME_diff() which properly handles UTC times without timezone issues */ int pday, psec; if (ASN1_TIME_diff(&pday, &psec, NULL, nextupd) == 1) { /* nextupd is in the future */ time_t seconds_until_expiry = (pday * 86400) + psec; if (seconds_until_expiry > 0) { cache_ttl = seconds_until_expiry; } else { /* avoid caching stale responses accepted only via OCSP leeway */ should_cache = 0; } } else { dbgprintf("OCSP: ASN1_TIME_diff() failed, using default TTL\n"); } } if (should_cache) { ocsp_cache_store(cache_key, status, cache_ttl); } else { dbgprintf("OCSP: nextUpdate is not in the future, skipping cache store\n"); } free(cache_key); } err: if (bs) OCSP_BASICRESP_free(bs); return ret; } static BIO *ocsp_connect(const char *host, const char *port, const char *device) { BIO *bio = NULL; int sock = -1; struct addrinfo hints, *res = NULL, *rp; int s; int flags; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; s = ocsp_getaddrinfo(host, port, &hints, &res); if (s != 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: getaddrinfo failed for %s:%s: %s\n", host, port, gai_strerror(s)); goto err; } for (rp = res; rp != NULL; rp = rp->ai_next) { sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock == -1) continue; if (device && *device) { #ifdef SO_BINDTODEVICE if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device)) < 0) { LogError(errno, RS_RET_NO_ERRCODE, "OCSP: Failed to bind socket to device %s\n", device); close(sock); sock = -1; continue; } #else LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, "OCSP: SO_BINDTODEVICE not supported, ignoring device parameter\n"); #endif } /* Set socket to non-blocking for timeout support */ flags = fcntl(sock, F_GETFL, 0); if (flags != -1) { fcntl(sock, F_SETFL, flags | O_NONBLOCK); } if (connect(sock, rp->ai_addr, rp->ai_addrlen) == 0) { break; /* Success */ } if (errno == EINPROGRESS) { fd_set wfds; struct timeval tv; FD_ZERO(&wfds); FD_SET(sock, &wfds); tv.tv_sec = OCSP_TIMEOUT; tv.tv_usec = 0; s = select(sock + 1, NULL, &wfds, NULL, &tv); if (s > 0) { int error = 0; socklen_t len = sizeof(error); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) { break; /* Success */ } } } close(sock); sock = -1; } if (sock == -1 || rp == NULL) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to connect to %s:%s\n", host, port); goto err; } /* Set socket back to blocking mode with read/write timeout */ flags = fcntl(sock, F_GETFL, 0); if (flags != -1) { fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); } /* Set socket read and write timeouts to prevent indefinite blocking during OCSP I/O */ struct timeval timeout; timeout.tv_sec = OCSP_TIMEOUT; timeout.tv_usec = 0; if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, "OCSP: Failed to set socket read timeout (errno %d), continuing without timeout\n", errno); } if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, "OCSP: Failed to set socket write timeout (errno %d), continuing without timeout\n", errno); } bio = BIO_new_socket(sock, BIO_CLOSE); if (!bio) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to create BIO\n"); close(sock); goto err; } err: if (res) freeaddrinfo(res); return bio; } static OCSP_RESPONSE *ocsp_send_and_receive(BIO *bio, const char *host, const char *path, OCSP_REQUEST *req) { OCSP_RESPONSE *rsp = NULL; char *req_data = NULL; unsigned char *rsp_data = NULL; int req_len; int rv; /* Encode OCSP request */ req_len = i2d_OCSP_REQUEST(req, (unsigned char **)&req_data); if (req_len <= 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to encode OCSP request\n"); goto err; } /* Send HTTP POST request */ rv = BIO_printf(bio, "POST %s HTTP/1.0\r\n" "Host: %s\r\n" "Content-Type: application/ocsp-request\r\n" "Content-Length: %d\r\n" "\r\n", path, host, req_len); if (rv <= 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to send HTTP header\n"); goto err; } /* Send request data */ if (BIO_write(bio, req_data, req_len) != req_len) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to send OCSP request data\n"); goto err; } if (BIO_flush(bio) != 1) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to flush BIO\n"); goto err; } /* Read HTTP response - skip headers */ char buf[1024]; int in_headers = 1; long content_length = -1; const long MAX_OCSP_RESPONSE_SIZE = 1024 * 1024; /* 1MB limit */ size_t rsp_len = 0; const size_t rsp_limit = (size_t)MAX_OCSP_RESPONSE_SIZE; const size_t read_limit = rsp_limit + 1; while (in_headers) { rv = BIO_gets(bio, buf, sizeof(buf)); if (rv <= 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to read HTTP response headers\n"); goto err; } /* Parse Content-Length header */ if (strncasecmp(buf, "Content-Length:", 15) == 0) { char *endptr; errno = 0; content_length = strtol(buf + 15, &endptr, 10); if (errno != 0 || endptr == buf + 15 || content_length < 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Invalid Content-Length header\n"); goto err; } if (content_length > MAX_OCSP_RESPONSE_SIZE) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Response Content-Length (%ld) exceeds maximum allowed size (%ld)\n", content_length, MAX_OCSP_RESPONSE_SIZE); goto err; } } /* Empty line marks end of headers */ if (strcmp(buf, "\r\n") == 0 || strcmp(buf, "\n") == 0) { in_headers = 0; } } rsp_data = malloc(read_limit); if (rsp_data == NULL) { LogError(0, RS_RET_OUT_OF_MEMORY, "OCSP: Failed to allocate response buffer\n"); goto err; } while (rsp_len < read_limit) { size_t wanted = read_limit - rsp_len; if (content_length >= 0) { size_t remaining = (size_t)content_length - rsp_len; if (remaining == 0) { break; } if (wanted > remaining) { wanted = remaining; } } rv = BIO_read(bio, rsp_data + rsp_len, (int)wanted); if (rv < 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to read HTTP response body\n"); goto err; } if (rv == 0) { break; } rsp_len += (size_t)rv; } if (rsp_len > rsp_limit) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Response exceeds maximum allowed size (%ld)\n", MAX_OCSP_RESPONSE_SIZE); goto err; } if (content_length > 0 && rsp_len != (size_t)content_length) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Incomplete response body\n"); goto err; } if (rsp_len == 0) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Empty response body\n"); goto err; } const unsigned char *p = rsp_data; rsp = d2i_OCSP_RESPONSE(NULL, &p, (long)rsp_len); if (!rsp) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to parse OCSP response\n"); goto err; } err: if (req_data) OPENSSL_free(req_data); free(rsp_data); return rsp; } static int ocsp_is_supported_protocol(const char *url) { return (strncmp(url, "http://", 7) == 0); } static int ocsp_request_per_responder(const char *url, X509 *cert, X509 *issuer, STACK_OF(X509) * untrusted_peer_certs, SSL_CTX *ctx, const char *device, int *is_revoked) { int ret = 0; char *host = NULL, *port = NULL, *path = NULL; int use_ssl = 0; BIO *bio = NULL; OCSP_REQUEST *req = NULL; OCSP_RESPONSE *rsp = NULL; OCSP_CERTID *id = NULL; char cert_name[256]; X509_NAME_oneline(X509_get_subject_name(cert), cert_name, sizeof(cert_name)); /* Parse URL */ if (!OCSP_parse_url((char *)url, &host, &port, &path, &use_ssl)) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to parse URL: %s\n", url); goto err; } /* Only HTTP is supported */ if (use_ssl) { dbgprintf("OCSP: HTTPS not supported, skipping %s\n", url); goto err; } dbgprintf("OCSP: Connecting to %s:%s%s\n", host, port, path); /* Connect to OCSP responder */ bio = ocsp_connect(host, port, device); if (!bio) { goto err; } /* Create OCSP request */ req = OCSP_REQUEST_new(); if (!req) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to create OCSP request\n"); goto err; } id = OCSP_cert_to_id(NULL, cert, issuer); if (!id) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to create OCSP certificate ID\n"); goto err; } if (!OCSP_request_add0_id(req, id)) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Failed to add certificate ID to OCSP request\n"); OCSP_CERTID_free(id); goto err; } /* Add nonce extension for replay attack protection */ OCSP_request_add1_nonce(req, NULL, -1); /* Send request and receive response */ rsp = ocsp_send_and_receive(bio, host, path, req); if (!rsp) { goto err; } /* Check response status */ int response_status = OCSP_response_status(rsp); if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { LogError(0, RS_RET_NO_ERRCODE, "OCSP: Response status: %s\n", ocsp_get_response_status_err(response_status)); goto err; } /* Validate response and check certificate status */ ret = ocsp_check_validate_response_and_cert(rsp, req, untrusted_peer_certs, id, ctx, cert_name, cert, issuer, is_revoked); err: if (rsp) OCSP_RESPONSE_free(rsp); if (req) OCSP_REQUEST_free(req); if (bio) BIO_free_all(bio); if (host) OPENSSL_free(host); if (port) OPENSSL_free(port); if (path) OPENSSL_free(path); return ret; } static int ocsp_check( X509 *current_cert, STACK_OF(X509) * untrusted_peer_certs, SSL_CTX *ctx, const char *device, int *is_revoked) { int ret = 0; char cert_name[256]; X509 *issuer = NULL; STACK_OF(OPENSSL_STRING) *ocsp_responders = NULL; const char *url = NULL; int at_least_one_responder = 0; char *cache_key = NULL; int cached_status; X509_NAME_oneline(X509_get_subject_name(current_cert), cert_name, sizeof(cert_name)); /* Reset revoked status */ if (is_revoked) *is_revoked = 0; /* * 1. Lookup the issuer cert of the current certificate, required to marshal a OCSP request. */ if (!(issuer = ocsp_find_issuer(current_cert, cert_name, ctx, untrusted_peer_certs))) goto err; /* * 2. Check cache first to avoid network I/O */ cache_key = ocsp_make_cache_key(current_cert, issuer); if (cache_key && ocsp_cache_lookup(cache_key, &cached_status)) { if (cached_status == V_OCSP_CERTSTATUS_GOOD) { dbgprintf("OCSP: Cache indicates certificate is GOOD for: %s\n", cert_name); ret = 1; goto err; } else if (cached_status == V_OCSP_CERTSTATUS_REVOKED) { LogMsg(0, RS_RET_CERT_REVOKED, LOG_ERR, "OCSP: Cached status shows certificate is REVOKED for: %s\n", cert_name); if (is_revoked) *is_revoked = 1; goto err; } /* UNKNOWN status: fall through to network check */ } /* * 3. Sanity check prior generating OCSP request. * * - No OCSP URL, no OCSP revocation verification required, skip the current cert. * - Only HTTP protocol is supported. If no OCSP responder with a supported protocol * is available, this MUST fail the OCSP verification and abort the TLS handshake. */ ocsp_responders = X509_get1_ocsp(current_cert); if (ocsp_responders == NULL) { ret = 2; goto err; /* continue with cert path validation */ } for (int i = 0; i < sk_OPENSSL_STRING_num(ocsp_responders); i++) { url = sk_OPENSSL_STRING_value(ocsp_responders, i); if (ocsp_is_supported_protocol(url)) { at_least_one_responder = 1; break; } } if (!at_least_one_responder) { LogError(0, RS_RET_NO_ERRCODE, "None of the OCSP responders are supported by " "this implementation:\n"); for (int i = 0; i < sk_OPENSSL_STRING_num(ocsp_responders); i++) { url = sk_OPENSSL_STRING_value(ocsp_responders, i); LogError(0, RS_RET_NO_ERRCODE, "\t%s\n", url); } goto err; } /* * Try all supported OCSP responders one by one. * * One successful OCSP status response is enough. Not all responders * need to be queried. */ for (int i = 0; i < sk_OPENSSL_STRING_num(ocsp_responders); i++) { url = sk_OPENSSL_STRING_value(ocsp_responders, i); if (ocsp_request_per_responder(url, current_cert, issuer, untrusted_peer_certs, ctx, device, is_revoked)) { ret = 1; goto err; } /* When revoked don't try other sources/OCSP responders */ if (is_revoked && *is_revoked) goto err; /* If the status is unknown, try the next available responder */ } err: if (ocsp_responders) X509_email_free(ocsp_responders); if (cache_key) free(cache_key); return ret; } #endif /* !ENABLE_WOLFSSL */ /*--------------------------------------End OCSP Support ------------------------------------------*/ /* Verify Callback for X509 Certificate validation. Force visibility as this function is not called anywhere but * only used as callback! */ int net_ossl_verify_callback(int status, X509_STORE_CTX *store) { char szdbgdata1[256]; char szdbgdata2[256]; dbgprintf("verify_callback: status %d\n", status); if (status == 0) { /* Retrieve all needed pointers */ X509 *cert = X509_STORE_CTX_get_current_cert(store); int depth = X509_STORE_CTX_get_error_depth(store); SSL *ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx()); int err = X509_STORE_CTX_get_error(store); int iVerifyMode = SSL_get_verify_mode(ssl); nsd_t *pNsdTcp = (nsd_t *)SSL_get_ex_data(ssl, 0); PermitExpiredCerts *pPermitExpiredCerts = (PermitExpiredCerts *)SSL_get_ex_data(ssl, 1); dbgprintf("verify_callback: Certificate validation failed, Mode (%d)!\n", iVerifyMode); X509_NAME_oneline(X509_get_issuer_name(cert), szdbgdata1, sizeof(szdbgdata1)); X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(cert), szdbgdata2, sizeof(szdbgdata2)); uchar *fromHost = NULL; if (pNsdTcp != NULL) { nsd_ptcp.GetRemoteHName(pNsdTcp, &fromHost); } if (iVerifyMode != SSL_VERIFY_NONE) { /* Handle expired Certificates **/ if (err == X509_V_OK || err == X509_V_ERR_CERT_HAS_EXPIRED) { if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_PERMIT) { dbgprintf( "verify_callback: EXPIRED cert but PERMITTED at depth: %d \n\t" "issuer = %s\n\t" "subject = %s\n\t" "err %d:%s\n", depth, szdbgdata1, szdbgdata2, err, X509_verify_cert_error_string(err)); /* Set Status to OK*/ status = 1; } else if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_WARN) { LogMsg(0, RS_RET_CERT_EXPIRED, LOG_WARNING, "Certificate EXPIRED warning at depth: %d \n\t" "issuer = %s\n\t" "subject = %s\n\t" "err %d:%s peer '%s'", depth, szdbgdata1, szdbgdata2, err, X509_verify_cert_error_string(err), fromHost); /* Set Status to OK*/ status = 1; } else /* also default - if (pPermitExpiredCerts == OSSL_EXPIRED_DENY)*/ { LogMsg(0, RS_RET_CERT_EXPIRED, LOG_ERR, "Certificate EXPIRED at depth: %d \n\t" "issuer = %s\n\t" "subject = %s\n\t" "err %d:%s\n\t" "not permitted to talk to peer '%s', certificate invalid: " "certificate expired", depth, szdbgdata1, szdbgdata2, err, X509_verify_cert_error_string(err), fromHost); } } else if (err == X509_V_ERR_CERT_REVOKED) { LogMsg(0, RS_RET_CERT_REVOKED, LOG_ERR, "Certificate REVOKED at depth: %d \n\t" "issuer = %s\n\t" "subject = %s\n\t" "err %d:%s\n\t" "not permitted to talk to peer '%s', certificate invalid: " "certificate revoked", depth, szdbgdata1, szdbgdata2, err, X509_verify_cert_error_string(err), fromHost); } else { /* all other error codes cause failure */ LogMsg(0, RS_RET_NO_ERRCODE, LOG_ERR, "Certificate error at depth: %d \n\t" "issuer = %s\n\t" "subject = %s\n\t" "err %d:%s - peer '%s'", depth, szdbgdata1, szdbgdata2, err, X509_verify_cert_error_string(err), fromHost); } } else { /* do not verify certs in ANON mode, just log into debug */ dbgprintf( "verify_callback: Certificate validation DISABLED but Error at depth: %d \n\t" "issuer = %s\n\t" "subject = %s\n\t" "err %d:%s\n", depth, szdbgdata1, szdbgdata2, err, X509_verify_cert_error_string(err)); /* Set Status to OK*/ status = 1; } free(fromHost); } if (status == 0) return 0; /* Verification failed */ #ifndef ENABLE_WOLFSSL /* OCSP revocation checking is not available in WolfSSL builds */ /* * Certificate revocation checks (only if enabled via StreamDriver.TlsRevocationCheck) */ X509 *cert = X509_STORE_CTX_get_current_cert(store); SSL *ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx()); /* Check if revocation checking is enabled * Note: Using index 3 to avoid collision with imdtls which uses index 2 * Index allocation: 0=pTcp, 1=permitExpiredCerts, 2=imdtls instance, 3=revocationCheck */ int *pTlsRevocationCheck = (int *)SSL_get_ex_data(ssl, 3); int tlsRevocationCheck = (pTlsRevocationCheck != NULL) ? *pTlsRevocationCheck : 0; if (tlsRevocationCheck == 0) { /* Revocation checking is disabled, skip OCSP/CRL checks */ return status; } STACK_OF(X509) *untrusted_peer_certs = X509_STORE_CTX_get1_chain(store); SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); int ret, is_revoked = 0; const char *device = NULL; /* Try to get device name from nsd object if available */ /* Note: device binding is not currently supported in the refactored code */ /* This is left here for future enhancement */ /* 1. OCSP with caching and non-blocking I/O */ /* Note: OCSP checks use non-blocking sockets with OCSP_TIMEOUT (5 sec) per responder. * Results are cached to minimize network I/O on subsequent TLS handshakes. * See: https://github.com/rsyslog/rsyslog/issues/6469 */ ret = ocsp_check(cert, untrusted_peer_certs, ctx, device, &is_revoked); if (ret == 1) { /* Status is OK */ status = 1; goto done; } else if (ret == 2) { /* No OCSP URL, give CRL a chance */ status = 1; } else if (is_revoked) { /* Cert is revoked, fail verification */ status = 0; goto done; } else { /* If OCSP failed, but cert was not revoked, then the Status might be still OK. * Try alternative sources (e.g. CRL), in compliance with RFC 6960, Chapter 2.2, Page 7-8. */ status = 0; } /* 2. CRL */ /* CRL support is not implemented. * This stub will fail if the cert holds a CRL Distribution Point. */ ret = crl_check(cert, &is_revoked); if (ret == 1) { /* Status is OK */ status = 1; } else if (ret == 2) { /* No CRL, keep the existing status */ ; } else { /* Cert is revoked, or check failed -> fail verification */ status = 0; } done: if (untrusted_peer_certs) sk_X509_pop_free(untrusted_peer_certs, X509_free); #endif /* !ENABLE_WOLFSSL */ return status; } #ifndef ENABLE_WOLFSSL #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) static long RSYSLOG_BIO_debug_callback_ex(BIO *bio, int cmd, const char __attribute__((unused)) * argp, size_t __attribute__((unused)) len, int argi, long __attribute__((unused)) argl, int ret, size_t __attribute__((unused)) * processed) #else static long RSYSLOG_BIO_debug_callback( BIO *bio, int cmd, const char __attribute__((unused)) * argp, int argi, long __attribute__((unused)) argl, long ret) #endif { long ret2 = ret; long r = 1; if (BIO_CB_RETURN & cmd) r = ret; dbgprintf("openssl debugmsg: BIO[%p]: ", (void *)bio); switch (cmd) { case BIO_CB_FREE: dbgprintf("Free - %s\n", RSYSLOG_BIO_method_name(bio)); break; /* Disabled due API changes for OpenSSL 1.1.0+ */ #if OPENSSL_VERSION_NUMBER < 0x10100000L case BIO_CB_READ: if (bio->method->type & BIO_TYPE_DESCRIPTOR) dbgprintf("read(%d,%lu) - %s fd=%d\n", RSYSLOG_BIO_number_read(bio), (unsigned long)argi, RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_read(bio)); else dbgprintf("read(%d,%lu) - %s\n", RSYSLOG_BIO_number_read(bio), (unsigned long)argi, RSYSLOG_BIO_method_name(bio)); break; case BIO_CB_WRITE: if (bio->method->type & BIO_TYPE_DESCRIPTOR) dbgprintf("write(%d,%lu) - %s fd=%d\n", RSYSLOG_BIO_number_written(bio), (unsigned long)argi, RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_written(bio)); else dbgprintf("write(%d,%lu) - %s\n", RSYSLOG_BIO_number_written(bio), (unsigned long)argi, RSYSLOG_BIO_method_name(bio)); break; #else case BIO_CB_READ: dbgprintf("read %s\n", RSYSLOG_BIO_method_name(bio)); break; case BIO_CB_WRITE: dbgprintf("write %s\n", RSYSLOG_BIO_method_name(bio)); break; #endif case BIO_CB_PUTS: dbgprintf("puts() - %s\n", RSYSLOG_BIO_method_name(bio)); break; case BIO_CB_GETS: dbgprintf("gets(%lu) - %s\n", (unsigned long)argi, RSYSLOG_BIO_method_name(bio)); break; case BIO_CB_CTRL: dbgprintf("ctrl(%lu) - %s\n", (unsigned long)argi, RSYSLOG_BIO_method_name(bio)); break; case BIO_CB_RETURN | BIO_CB_READ: dbgprintf("read return %ld\n", ret2); break; case BIO_CB_RETURN | BIO_CB_WRITE: dbgprintf("write return %ld\n", ret2); break; case BIO_CB_RETURN | BIO_CB_GETS: dbgprintf("gets return %ld\n", ret2); break; case BIO_CB_RETURN | BIO_CB_PUTS: dbgprintf("puts return %ld\n", ret2); break; case BIO_CB_RETURN | BIO_CB_CTRL: dbgprintf("ctrl return %ld\n", ret2); break; default: dbgprintf("bio callback - unknown type (%d)\n", cmd); break; } return (r); } #endif /* !ENABLE_WOLFSSL */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(ENABLE_WOLFSSL) // Requires at least OpenSSL v1.1.1 PRAGMA_DIAGNOSTIC_PUSH PRAGMA_IGNORE_Wunused_parameter static int net_ossl_generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) { unsigned char result[EVP_MAX_MD_SIZE]; unsigned int resultlength; unsigned char *sslHello; unsigned int length; sslHello = (unsigned char *)"rsyslog"; length = strlen((char *)sslHello); // Generate the cookie using SHA256 hash if (!EVP_Digest(sslHello, length, result, &resultlength, EVP_sha256(), NULL)) { return 0; } memcpy(cookie, result, resultlength); *cookie_len = resultlength; dbgprintf("net_ossl_generate_cookie: generate cookie SUCCESS\n"); return 1; } PRAGMA_DIAGNOSTIC_POP static int net_ossl_verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len) { unsigned char cookie_gen[EVP_MAX_MD_SIZE]; unsigned int cookie_gen_len; // Generate a cookie using the same method as in net_ossl_generate_cookie if (!net_ossl_generate_cookie(ssl, cookie_gen, &cookie_gen_len)) { dbgprintf("net_ossl_verify_cookie: generate cookie FAILED\n"); return 0; } // Check if the generated cookie matches the cookie received if (cookie_len == cookie_gen_len && memcmp(cookie, cookie_gen, cookie_len) == 0) { dbgprintf("net_ossl_verify_cookie: compare cookie SUCCESS\n"); return 1; } dbgprintf("net_ossl_verify_cookie: compare cookie FAILED\n"); return 0; } static rsRetVal net_ossl_init_engine(__attribute__((unused)) net_ossl_t *pThis) { // OpenSSL Engine Support feature relies on an outdated version of OpenSSL and is // strictly experimental. No support or guarantees are provided. Use at your own risk. DEFiRet; #ifndef OPENSSL_NO_ENGINE const char *engine_id = NULL; const char *engine_name = NULL; PRAGMA_DIAGNOSTIC_PUSH PRAGMA_IGNORE_Wdeprecated_declarations // Get the default RSA engine ENGINE *default_engine = ENGINE_get_default_RSA(); if (default_engine) { engine_id = ENGINE_get_id(default_engine); engine_name = ENGINE_get_name(default_engine); DBGPRINTF("net_ossl_init_engine: Default RSA Engine: ID = %s, Name = %s\n", engine_id, engine_name); // Free the engine reference when done ENGINE_free(default_engine); } else { DBGPRINTF("net_ossl_init_engine: No default RSA Engine set.\n"); } /* Setting specific Engine */ if (runConf != NULL && glbl.GetDfltOpensslEngine(runConf) != NULL) { default_engine = ENGINE_by_id((char *)glbl.GetDfltOpensslEngine(runConf)); if (default_engine && ENGINE_init(default_engine)) { /* engine initialised */ ENGINE_set_default_DSA(default_engine); ENGINE_set_default_ciphers(default_engine); /* Switch to Engine */ DBGPRINTF("net_ossl_init_engine: Changed default Engine to %s\n", glbl.GetDfltOpensslEngine(runConf)); /* Release the functional reference from ENGINE_init() */ ENGINE_finish(default_engine); } else { LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: ENGINE_init failed to load Engine '%s'" "ossl netstream driver", glbl.GetDfltOpensslEngine(runConf)); net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "net_ossl_init_engine", "ENGINE_init"); } // Free the engine reference when done ENGINE_free(default_engine); } else { DBGPRINTF("net_ossl_init_engine: use openssl default Engine"); } PRAGMA_DIAGNOSTIC_POP #else DBGPRINTF("net_ossl_init_engine: OpenSSL compiled without ENGINE support - ENGINE support disabled\n"); #endif /* OPENSSL_NO_ENGINE */ RETiRet; } static rsRetVal net_ossl_ctx_init_cookie(net_ossl_t *pThis) { DEFiRet; // Set our cookie generation and verification callbacks SSL_CTX_set_options(pThis->ctx, SSL_OP_COOKIE_EXCHANGE); SSL_CTX_set_cookie_generate_cb(pThis->ctx, net_ossl_generate_cookie); SSL_CTX_set_cookie_verify_cb(pThis->ctx, net_ossl_verify_cookie); RETiRet; } #endif // OPENSSL_VERSION_NUMBER >= 0x10100000L /* ------------------------------ end OpenSSL helpers ------------------------------------------*/ /* ------------------------------ OpenSSL Callback set helpers ---------------------------------*/ void net_ossl_set_ssl_verify_callback(SSL *pSsl, int flags) { /* Enable certificate valid checking */ SSL_set_verify(pSsl, flags, net_ossl_verify_callback); } void net_ossl_set_ctx_verify_callback(SSL_CTX *pCtx, int flags) { /* Enable certificate valid checking */ SSL_CTX_set_verify(pCtx, flags, net_ossl_verify_callback); } void net_ossl_set_bio_callback(BIO *conn) { #ifndef ENABLE_WOLFSSL #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) BIO_set_callback_ex(conn, RSYSLOG_BIO_debug_callback_ex); #else BIO_set_callback(conn, RSYSLOG_BIO_debug_callback); #endif // OPENSSL_VERSION_NUMBER >= 0x10100000L #else (void)conn; #endif } /* ------------------------------ End OpenSSL Callback set helpers -----------------------------*/ /* Standard-Constructor */ BEGINobjConstruct(net_ossl) /* be sure to specify the object type also in END macro! */ DBGPRINTF("net_ossl_construct: [%p]\n", pThis); pThis->bReportAuthErr = 1; pThis->bSANpriority = 0; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(ENABLE_WOLFSSL) CHKiRet(net_ossl_init_engine(pThis)); finalize_it: #endif ENDobjConstruct(net_ossl) /* destructor for the net_ossl object */ BEGINobjDestruct(net_ossl) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDestruct(net_ossl); DBGPRINTF("net_ossl_destruct: [%p]\n", pThis); /* Free SSL obj also if we do not have a session - or are NOT in TLS mode! */ if (pThis->ssl != NULL) { DBGPRINTF("net_ossl_destruct: [%p] FREE pThis->ssl \n", pThis); SSL_free(pThis->ssl); pThis->ssl = NULL; } if (pThis->ctx != NULL && !pThis->ctx_is_copy) { SSL_CTX_free(pThis->ctx); pThis->ctx = NULL; } free((void *)pThis->pszCAFile); free((void *)pThis->pszCRLFile); free((void *)pThis->pszKeyFile); free((void *)pThis->pszCertFile); free((void *)pThis->pszExtraCAFiles); ENDobjDestruct(net_ossl) /* queryInterface function */ BEGINobjQueryInterface(net_ossl) CODESTARTobjQueryInterface(net_ossl); DBGPRINTF("netosslQueryInterface\n"); if (pIf->ifVersion != net_osslCURR_IF_VERSION) { /* check for current version, increment on each change */ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); } pIf->Construct = (rsRetVal(*)(net_ossl_t **))net_osslConstruct; pIf->Destruct = (rsRetVal(*)(net_ossl_t **))net_osslDestruct; pIf->osslCtxInit = net_ossl_osslCtxInit; pIf->osslChkpeername = net_ossl_chkpeername; pIf->osslPeerfingerprint = net_ossl_peerfingerprint; pIf->osslGetpeercert = net_ossl_getpeercert; pIf->osslChkpeercertvalidity = net_ossl_chkpeercertvalidity; #if (OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)) || defined(ENABLE_WOLFSSL) pIf->osslApplyTlscgfcmd = net_ossl_apply_tlscgfcmd; #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L pIf->osslSetBioCallback = net_ossl_set_bio_callback; pIf->osslSetCtxVerifyCallback = net_ossl_set_ctx_verify_callback; pIf->osslSetSslVerifyCallback = net_ossl_set_ssl_verify_callback; pIf->osslLastOpenSSLErrorMsg = net_ossl_lastOpenSSLErrorMsg; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(ENABLE_WOLFSSL) pIf->osslCtxInitCookie = net_ossl_ctx_init_cookie; pIf->osslInitEngine = net_ossl_init_engine; #endif finalize_it: ENDobjQueryInterface(net_ossl) /* exit our class */ BEGINObjClassExit(net_ossl, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ CODESTARTObjClassExit(net_ossl); DBGPRINTF("netosslClassExit\n"); /* release objects we no longer need */ objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME); objRelease(net, LM_NET_FILENAME); objRelease(glbl, CORE_COMPONENT); /* shut down OpenSSL */ osslGlblExit(); ENDObjClassExit(net_ossl) /* Initialize the net_ossl class. Must be called as the very first method * before anything else is called inside this class. */ BEGINObjClassInit(net_ossl, 1, OBJ_IS_CORE_MODULE) /* class, version */ DBGPRINTF("net_osslClassInit\n"); // request objects we use CHKiRet(objUse(glbl, CORE_COMPONENT)); CHKiRet(objUse(net, LM_NET_FILENAME)); CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME)); // Do global TLS init stuff osslGlblInit(); ENDObjClassInit(net_ossl) /* --------------- here now comes the plumbing that makes as a library module --------------- */ /* vi:set ai: */