mirror of
https://github.com/rsyslog/rsyslog.git
synced 2026-03-19 14:09:30 +01:00
- Replaced function pointer casting with direct handler calls for type safety - Fixes crashes (BUS errors) on ARM64 macOS 14+ due to strict calling conventions - Root cause identified by ThreadSanitizer - Eliminates undefined behavior, improves code safety
1292 lines
51 KiB
C
1292 lines
51 KiB
C
/* 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 "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"
|
|
|
|
/* 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)
|
|
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);
|
|
rsRetVal net_ossl_chkpeername(net_ossl_t *pThis, X509 *certpeer, uchar *fromHostIP);
|
|
|
|
|
|
/*--------------------------------------MT OpenSSL helpers ------------------------------------------*/
|
|
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;
|
|
}
|
|
/*-------------------------------------- MT OpenSSL helpers -----------------------------------------*/
|
|
|
|
|
|
/*--------------------------------------OpenSSL helpers ------------------------------------------*/
|
|
|
|
/* globally initialize OpenSSL
|
|
*/
|
|
void osslGlblInit(void) {
|
|
DBGPRINTF("osslGlblInit: ENTER\n");
|
|
|
|
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!");
|
|
}
|
|
|
|
/* 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 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 */
|
|
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 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
|
|
}
|
|
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
|
|
/* Enable Support for automatic ephemeral/temporary DH parameter selection. */
|
|
SSL_CTX_set_dh_auto(pThis->ctx, 1);
|
|
#endif
|
|
|
|
#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
|
|
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)
|
|
/* 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 added Command: %s:'%s' "
|
|
"in net_ossl_apply_tlscgfcmd with error '%d'",
|
|
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"
|
|
"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;
|
|
}
|
|
|
|
|
|
/* Perform a match on ONE peer name obtained from the certificate. This name
|
|
* is checked against the set of configured credentials. *pbFoundPositiveMatch is
|
|
* set to 1 if the ID matches. *pbFoundPositiveMatch must have been initialized
|
|
* to 0 by the caller (this is a performance enhancement as we expect to be
|
|
* called multiple times).
|
|
* TODO: implemet wildcards?
|
|
* rgerhards, 2008-05-26
|
|
*/
|
|
static rsRetVal net_ossl_chkonepeername(net_ossl_t *pThis,
|
|
X509 *certpeer,
|
|
uchar *pszPeerID,
|
|
int *pbFoundPositiveMatch) {
|
|
permittedPeers_t *pPeer;
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
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);
|
|
|
|
/* Obtain Namex509 name from subject */
|
|
x509name = X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(certpeer), NULL, 0);
|
|
|
|
if (pThis->pPermPeers) { /* do we have configured peer IDs? */
|
|
pPeer = pThis->pPermPeers;
|
|
while (pPeer != NULL) {
|
|
CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch));
|
|
if (*pbFoundPositiveMatch) break;
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
/* if we did not succeed so far, try ossl X509_check_host
|
|
* ( Includes check against SubjectAlternativeName )
|
|
* if prioritizeSAN set, only check against SAN
|
|
*/
|
|
if (pThis->bSANpriority == 1) {
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100004L && !defined(LIBRESSL_VERSION_NUMBER)
|
|
x509flags = X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
|
|
#else
|
|
dbgprintf("net_ossl_chkonepeername: PrioritizeSAN not supported before OpenSSL 1.1.0\n");
|
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10100004L
|
|
}
|
|
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_chkonepeername: 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_chkonepeername",
|
|
"X509_check_host");
|
|
ABORT_FINALIZE(RS_RET_NO_ERRCODE);
|
|
}
|
|
#endif
|
|
/* Check next peer */
|
|
pPeer = pPeer->pNext;
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
uchar lnBuf[256];
|
|
int bFoundPositiveMatch;
|
|
cstr_t *pStr = NULL;
|
|
char *x509name = NULL;
|
|
|
|
ISOBJ_TYPE_assert(pThis, net_ossl);
|
|
|
|
bFoundPositiveMatch = 0;
|
|
CHKiRet(rsCStrConstruct(&pStr));
|
|
|
|
/* Obtain Namex509 name from subject */
|
|
x509name = X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(certpeer), NULL, 0);
|
|
|
|
dbgprintf("net_ossl_chkpeername: checking - peername '%s' on server '%s'\n", x509name, fromHostIP);
|
|
snprintf((char *)lnBuf, sizeof(lnBuf), "name: %s; ", x509name);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
CHKiRet(net_ossl_chkonepeername(pThis, certpeer, (uchar *)x509name, &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);
|
|
|
|
iVerErr = SSL_get_verify_result(ssl);
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
int err = X509_STORE_CTX_get_error(store);
|
|
SSL *ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
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);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
#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);
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
// 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) {
|
|
#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
|
|
}
|
|
/* ------------------------------ 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
|
|
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);
|
|
}
|
|
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)
|
|
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
|
|
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:
|
|
*/
|