/* nsd_gtls.c * * An implementation of the nsd interface for GnuTLS. * * Copyright (C) 2007-2021 Rainer Gerhards and Adiscon GmbH. * * This file is part of the rsyslog runtime library. * * The rsyslog runtime library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The rsyslog runtime library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the rsyslog runtime library. If not, see . * * A copy of the GPL can be found in the file "COPYING" in this distribution. * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. */ #include "config.h" #include #include #include #include #include #include #if GNUTLS_VERSION_NUMBER <= 0x020b00 # include #endif #include #include #include #include #include #include "rsyslog.h" #include "syslogd-types.h" #include "module-template.h" #include "cfsysline.h" #include "obj.h" #include "stringbuf.h" #include "errmsg.h" #include "net.h" #include "datetime.h" #include "netstrm.h" #include "netstrms.h" #include "nsd_ptcp.h" #include "nsdsel_gtls.h" #include "nsd_gtls.h" #include "unicode-helper.h" #include "rsconf.h" #if GNUTLS_VERSION_NUMBER <= 0x020b00 GCRY_THREAD_OPTION_PTHREAD_IMPL; #endif MODULE_TYPE_LIB MODULE_TYPE_KEEP /* static data */ DEFobjStaticHelpers DEFobjCurrIf(glbl) DEFobjCurrIf(net) DEFobjCurrIf(datetime) DEFobjCurrIf(nsd_ptcp) /* Static Helper variables for certless communication */ static gnutls_anon_client_credentials_t anoncred; /**< client anon credentials */ static gnutls_anon_server_credentials_t anoncredSrv; /**< server anon credentials */ static int dhBits = 2048; /**< number of bits for Diffie-Hellman key */ static int dhMinBits = 512; /**< minimum number of bits for Diffie-Hellman key */ static pthread_mutex_t mutGtlsStrerror; /*< a mutex protecting the potentially non-reentrant gtlStrerror() function */ static gnutls_dh_params_t dh_params; /**< server DH parameters for anon mode */ /* a macro to abort if GnuTLS error is not acceptable. We split this off from * CHKgnutls() to avoid some Coverity report in cases where we know GnuTLS * failed. Note: gnuRet must already be set accordingly! */ #define ABORTgnutls { \ uchar *pErr = gtlsStrerror(gnuRet); \ LogError(0, RS_RET_GNUTLS_ERR, "unexpected GnuTLS error %d in %s:%d: %s\n", \ gnuRet, __FILE__, __LINE__, pErr); \ free(pErr); \ ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \ } /* a macro to check GnuTLS calls against unexpected errors */ #define CHKgnutls(x) { \ gnuRet = (x); \ if(gnuRet == GNUTLS_E_FILE_ERROR) { \ LogError(0, RS_RET_GNUTLS_ERR, "error reading file - a common cause is that the " \ "file does not exist"); \ ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \ } else if(gnuRet != 0) { \ ABORTgnutls; \ } \ } /* ------------------------------ GnuTLS specifics ------------------------------ */ /* This defines a log function to be provided to GnuTLS. It hopefully * helps us track down hard to find problems. * rgerhards, 2008-06-20 */ static void logFunction(int level, const char *msg) { dbgprintf("GnuTLS log msg, level %d: %s\n", level, msg); } /* read in the whole content of a file. The caller is responsible for * freeing the buffer. To prevent DOS, this function can NOT read * files larger than 1MB (which still is *very* large). * rgerhards, 2008-05-26 */ static rsRetVal readFile(const uchar *const pszFile, gnutls_datum_t *const pBuf) { int fd; struct stat stat_st; DEFiRet; assert(pszFile != NULL); assert(pBuf != NULL); pBuf->data = NULL; if((fd = open((char*)pszFile, O_RDONLY)) == -1) { LogError(errno, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile); ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); } if(fstat(fd, &stat_st) == -1) { LogError(errno, RS_RET_FILE_NO_STAT, "can not stat file '%s'", pszFile); ABORT_FINALIZE(RS_RET_FILE_NO_STAT); } /* 1MB limit */ if(stat_st.st_size > 1024 * 1024) { LogError(0, RS_RET_FILE_TOO_LARGE, "file '%s' too large, max 1MB", pszFile); ABORT_FINALIZE(RS_RET_FILE_TOO_LARGE); } CHKmalloc(pBuf->data = malloc(stat_st.st_size)); pBuf->size = stat_st.st_size; if(read(fd, pBuf->data, stat_st.st_size) != stat_st.st_size) { LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile); ABORT_FINALIZE(RS_RET_IO_ERROR); } finalize_it: if(fd != -1) close(fd); if(iRet != RS_RET_OK) { if(pBuf->data != NULL) { free(pBuf->data); pBuf->data = NULL; pBuf->size = 0; } } RETiRet; } /* Load the certificate and the private key into our own store. We need to do * this in the client case, to support fingerprint authentication. In that case, * we may be presented no matching root certificate, but we must provide ours. * The only way to do that is via the cert callback interface, but for it we * need to load certificates into our private store. * rgerhards, 2008-05-26 */ static rsRetVal gtlsLoadOurCertKey(nsd_gtls_t *pThis) { DEFiRet; int gnuRet; gnutls_datum_t data = { NULL, 0 }; const uchar *keyFile; const uchar *certFile; ISOBJ_TYPE_assert(pThis, nsd_gtls); certFile = (pThis->pszCertFile == NULL) ? glbl.GetDfltNetstrmDrvrCertFile(runConf) : pThis->pszCertFile; keyFile = (pThis->pszKeyFile == NULL) ? glbl.GetDfltNetstrmDrvrKeyFile(runConf) : pThis->pszKeyFile; if(certFile == NULL || keyFile == NULL) { /* in this case, we can not set our certificate. If we are * a client and the server is running in "anon" auth mode, this * may be well acceptable. In other cases, we will see some * more error messages down the road. -- rgerhards, 2008-07-02 */ dbgprintf("gtlsLoadOurCertKey our certificate is not set, file name values are cert: '%s', key: '%s'\n", certFile, keyFile); ABORT_FINALIZE(RS_RET_CERTLESS); } /* try load certificate */ CHKiRet(readFile(certFile, &data)); pThis->nOurCerts = sizeof(pThis->pOurCerts) / sizeof(gnutls_x509_crt_t); gnuRet = gnutls_x509_crt_list_import(pThis->pOurCerts, &pThis->nOurCerts, &data, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); if(gnuRet < 0) { ABORTgnutls; } pThis->bOurCertIsInit = 1; free(data.data); data.data = NULL; /* try load private key */ CHKiRet(readFile(keyFile, &data)); CHKgnutls(gnutls_x509_privkey_init(&pThis->ourKey)); pThis->bOurKeyIsInit = 1; CHKgnutls(gnutls_x509_privkey_import(pThis->ourKey, &data, GNUTLS_X509_FMT_PEM)); free(data.data); finalize_it: if(iRet == RS_RET_CERTLESS) { dbgprintf("gtlsLoadOurCertKey certless exit\n"); pThis->bOurCertIsInit = 0; pThis->bOurKeyIsInit = 0; } else if(iRet != RS_RET_OK) { dbgprintf("gtlsLoadOurCertKey error exit\n"); if(data.data != NULL) free(data.data); if(pThis->bOurCertIsInit) { for(unsigned i=0; inOurCerts; ++i) { gnutls_x509_crt_deinit(pThis->pOurCerts[i]); } pThis->bOurCertIsInit = 0; } if(pThis->bOurKeyIsInit) { gnutls_x509_privkey_deinit(pThis->ourKey); pThis->bOurKeyIsInit = 0; } } else { dbgprintf("gtlsLoadOurCertKey Successfully Loaded cert '%s' and key: '%s'\n", certFile, keyFile); } RETiRet; } /* This callback must be associated with a session by calling * gnutls_certificate_client_set_retrieve_function(session, cert_callback), * before a handshake. We will always return the configured certificate, * even if it does not match the peer's trusted CAs. This is necessary * to use self-signed certs in fingerprint mode. And, yes, this usage * of the callback is quite a hack. But it seems the only way to * obey to the IETF -transport-tls I-D. * Note: GnuTLS requires the function to return 0 on success and * -1 on failure. * rgerhards, 2008-05-27 */ static int gtlsClientCertCallback(gnutls_session_t session, __attribute__((unused)) const gnutls_datum_t* req_ca_rdn, int __attribute__((unused)) nreqs, __attribute__((unused)) const gnutls_pk_algorithm_t* sign_algos, int __attribute__((unused)) sign_algos_length, #if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION gnutls_retr2_st* st #else gnutls_retr_st *st #endif ) { nsd_gtls_t *pThis; pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session); #if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION st->cert_type = GNUTLS_CRT_X509; #else st->type = GNUTLS_CRT_X509; #endif st->ncerts = pThis->nOurCerts; st->cert.x509 = pThis->pOurCerts; st->key.x509 = pThis->ourKey; st->deinit_all = 0; return 0; } /* This function extracts some information about this session's peer * certificate. Works for X.509 certificates only. Adds all * of the info to a cstr_t, which is handed over to the caller. * Caller must destruct it when no longer needed. * rgerhards, 2008-05-21 */ static rsRetVal gtlsGetCertInfo(nsd_gtls_t *const pThis, cstr_t **ppStr) { uchar szBufA[1024]; uchar *szBuf = szBufA; size_t szBufLen = sizeof(szBufA), tmp; unsigned int algo, bits; time_t expiration_time, activation_time; const gnutls_datum_t *cert_list; unsigned cert_list_size = 0; gnutls_x509_crt_t cert; cstr_t *pStr = NULL; int gnuRet; DEFiRet; unsigned iAltName; assert(ppStr != NULL); ISOBJ_TYPE_assert(pThis, nsd_gtls); if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509) return RS_RET_TLS_CERT_ERR; cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size); CHKiRet(rsCStrConstructFromszStrf(&pStr, "peer provided %d certificate(s). ", cert_list_size)); if(cert_list_size > 0) { /* we only print information about the first certificate */ CHKgnutls(gnutls_x509_crt_init(&cert)); CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); expiration_time = gnutls_x509_crt_get_expiration_time(cert); activation_time = gnutls_x509_crt_get_activation_time(cert); ctime_r(&activation_time, (char*)szBuf); szBuf[ustrlen(szBuf) - 1] = '\0'; /* strip linefeed */ CHKiRet(rsCStrAppendStrf(pStr, "Certificate 1 info: " "certificate valid from %s ", szBuf)); ctime_r(&expiration_time, (char*)szBuf); szBuf[ustrlen(szBuf) - 1] = '\0'; /* strip linefeed */ CHKiRet(rsCStrAppendStrf(pStr, "to %s; ", szBuf)); /* Extract some of the public key algorithm's parameters */ algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits); CHKiRet(rsCStrAppendStrf(pStr, "Certificate public key: %s; ", gnutls_pk_algorithm_get_name(algo))); /* names */ tmp = szBufLen; if(gnutls_x509_crt_get_dn(cert, (char*)szBuf, &tmp) == GNUTLS_E_SHORT_MEMORY_BUFFER) { szBufLen = tmp; szBuf = malloc(tmp); gnutls_x509_crt_get_dn(cert, (char*)szBuf, &tmp); } CHKiRet(rsCStrAppendStrf(pStr, "DN: %s; ", szBuf)); tmp = szBufLen; if(gnutls_x509_crt_get_issuer_dn(cert, (char*)szBuf, &tmp) == GNUTLS_E_SHORT_MEMORY_BUFFER) { szBufLen = tmp; szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp); gnutls_x509_crt_get_issuer_dn(cert, (char*)szBuf, &tmp); } CHKiRet(rsCStrAppendStrf(pStr, "Issuer DN: %s; ", szBuf)); /* dNSName alt name */ iAltName = 0; while(1) { /* loop broken below */ tmp = szBufLen; gnuRet = gnutls_x509_crt_get_subject_alt_name(cert, iAltName, szBuf, &tmp, NULL); if(gnuRet == GNUTLS_E_SHORT_MEMORY_BUFFER) { szBufLen = tmp; szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp); continue; } else if(gnuRet < 0) break; else if(gnuRet == GNUTLS_SAN_DNSNAME) { /* we found it! */ CHKiRet(rsCStrAppendStrf(pStr, "SAN:DNSname: %s; ", szBuf)); /* do NOT break, because there may be multiple dNSName's! */ } ++iAltName; } gnutls_x509_crt_deinit(cert); } cstrFinalize(pStr); *ppStr = pStr; finalize_it: if(iRet != RS_RET_OK) { if(pStr != NULL) rsCStrDestruct(&pStr); } if(szBuf != szBufA) free(szBuf); RETiRet; } #if 0 /* we may need this in the future - code needs to be looked at then! */ /* This function will print some details of the * given pThis->sess. */ static rsRetVal print_info(nsd_gtls_t *pThis) { const char *tmp; gnutls_credentials_type cred; gnutls_kx_algorithm kx; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); /* print the key exchange's algorithm name */ kx = gnutls_kx_get(pThis->sess); tmp = gnutls_kx_get_name(kx); dbgprintf("- Key Exchange: %s\n", tmp); /* Check the authentication type used and switch * to the appropriate. */ cred = gnutls_auth_get_type(pThis->sess); switch (cred) { case GNUTLS_CRD_ANON: /* anonymous authentication */ dbgprintf("- Anonymous DH using prime of %d bits\n", gnutls_dh_get_prime_bits(pThis->sess)); break; case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */ /* Check if we have been using ephemeral Diffie Hellman. */ if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) { dbgprintf("\n- Ephemeral DH using prime of %d bits\n", gnutls_dh_get_prime_bits(pThis->sess)); } /* if the certificate list is available, then * print some information about it. */ gtlsPrintCert(pThis); break; case GNUTLS_CRD_SRP: /* certificate authentication */ dbgprintf("GNUTLS_CRD_SRP/IA"); break; case GNUTLS_CRD_PSK: /* certificate authentication */ dbgprintf("GNUTLS_CRD_PSK"); break; case GNUTLS_CRD_IA: /* certificate authentication */ dbgprintf("GNUTLS_CRD_IA"); break; } /* switch */ /* print the protocol's name (ie TLS 1.0) */ tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(pThis->sess)); dbgprintf("- Protocol: %s\n", tmp); /* print the certificate type of the peer. * ie X.509 */ tmp = gnutls_certificate_type_get_name( gnutls_certificate_type_get(pThis->sess)); dbgprintf("- Certificate Type: %s\n", tmp); /* print the compression algorithm (if any) */ tmp = gnutls_compression_get_name( gnutls_compression_get(pThis->sess)); dbgprintf("- Compression: %s\n", tmp); /* print the name of the cipher used. * ie 3DES. */ tmp = gnutls_cipher_get_name(gnutls_cipher_get(pThis->sess)); dbgprintf("- Cipher: %s\n", tmp); /* Print the MAC algorithms name. * ie SHA1 */ tmp = gnutls_mac_get_name(gnutls_mac_get(pThis->sess)); dbgprintf("- MAC: %s\n", tmp); RETiRet; } #endif /* 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 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; } /* a thread-safe variant of gnutls_strerror * The caller must free the returned string. * rgerhards, 2008-04-30 */ uchar *gtlsStrerror(int error) { uchar *pErr; pthread_mutex_lock(&mutGtlsStrerror); pErr = (uchar*) strdup(gnutls_strerror(error)); pthread_mutex_unlock(&mutGtlsStrerror); return pErr; } /* try to receive a record from the remote peer. This works with * our own abstraction and handles local buffering and EAGAIN. * See details on local buffering in Rcv(9 header-comment. * This function MUST only be called when the local buffer is * empty. Calling it otherwise will cause losss of current buffer * data. * rgerhards, 2008-06-24 */ rsRetVal gtlsRecordRecv(nsd_gtls_t *pThis) { ssize_t lenRcvd; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); DBGPRINTF("gtlsRecordRecv: start (Pending Data: %zd | Wanted Direction: %s)\n", gnutls_record_check_pending(pThis->sess), (gnutls_record_get_direction(pThis->sess) == gtlsDir_READ ? "READ" : "WRITE") ); lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF); if(lenRcvd >= 0) { DBGPRINTF("gtlsRecordRecv: gnutls_record_recv received %zd bytes\n", lenRcvd); pThis->lenRcvBuf = lenRcvd; pThis->ptrRcvBuf = 0; /* Check for additional data in SSL buffer */ size_t stBytesLeft = gnutls_record_check_pending(pThis->sess); if (stBytesLeft > 0 ){ DBGPRINTF("gtlsRecordRecv: %zd Bytes pending after gnutls_record_recv, expand buffer.\n", stBytesLeft); /* realloc buffer size and preserve char content */ char *const newbuf = realloc(pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF+stBytesLeft); CHKmalloc(newbuf); pThis->pszRcvBuf = newbuf; /* 2nd read will read missing bytes from the current SSL Packet */ lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf+NSD_GTLS_MAX_RCVBUF, stBytesLeft); if(lenRcvd > 0) { DBGPRINTF("gtlsRecordRecv: 2nd SSL_read received %zd bytes\n", (NSD_GTLS_MAX_RCVBUF+lenRcvd)); pThis->lenRcvBuf = NSD_GTLS_MAX_RCVBUF+lenRcvd; } else { if (lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) { goto sslerragain; /* Go to ERR AGAIN handling */ } else { /* Do all other error handling */ int gnuRet = lenRcvd; ABORTgnutls; } } } } else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) { sslerragain: /* Check if the underlaying file descriptor needs to read or write data!*/ if (gnutls_record_get_direction(pThis->sess) == gtlsDir_READ) { pThis->rtryCall = gtlsRtry_recv; dbgprintf("GnuTLS receive requires a retry, this most probably is OK and no error condition\n"); ABORT_FINALIZE(RS_RET_RETRY); } else { uchar *pErr = gtlsStrerror(lenRcvd); LogError(0, RS_RET_GNUTLS_ERR, "GnuTLS receive error %zd has wrong read direction(wants write) " "- this could be caused by a broken connection. GnuTLS reports: %s\n", lenRcvd, pErr); free(pErr); ABORT_FINALIZE(RS_RET_GNUTLS_ERR); } } else { int gnuRet = lenRcvd; ABORTgnutls; } finalize_it: dbgprintf("gtlsRecordRecv return. nsd %p, iRet %d, lenRcvd %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, (int) lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf); RETiRet; } /* add our own certificate to the certificate set, so that the peer * can identify us. Please note that we try to use mutual authentication, * so we always add a cert, even if we are in the client role (later, * this may be controlled by a config setting). * rgerhards, 2008-05-15 */ static rsRetVal gtlsAddOurCert(nsd_gtls_t *const pThis) { int gnuRet = 0; const uchar *keyFile; const uchar *certFile; uchar *pGnuErr; /* for GnuTLS error reporting */ DEFiRet; certFile = (pThis->pszCertFile == NULL) ? glbl.GetDfltNetstrmDrvrCertFile(runConf) : pThis->pszCertFile; keyFile = (pThis->pszKeyFile == NULL) ? glbl.GetDfltNetstrmDrvrKeyFile(runConf) : pThis->pszKeyFile; dbgprintf("GTLS certificate file: '%s'\n", certFile); dbgprintf("GTLS key file: '%s'\n", keyFile); if(certFile == NULL) { LogMsg(0, RS_RET_CERT_MISSING, LOG_WARNING, "warning: certificate file is not set"); } if(keyFile == NULL) { LogMsg(0, RS_RET_CERTKEY_MISSING, LOG_WARNING, "warning: key file is not set"); } /* set certificate in gnutls */ if(certFile != NULL && keyFile != NULL) { CHKgnutls(gnutls_certificate_set_x509_key_file(pThis->xcred, (char*)certFile, (char*)keyFile, GNUTLS_X509_FMT_PEM)); } finalize_it: if(iRet != RS_RET_OK && iRet != RS_RET_CERT_MISSING && iRet != RS_RET_CERTKEY_MISSING) { pGnuErr = gtlsStrerror(gnuRet); errno = 0; LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', " "key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile); free(pGnuErr); } RETiRet; } /* * removecomment ifdef out if needed */ #ifdef false static void print_cipher_suite_list(const char *priorities) { size_t i; int ret; unsigned int idx; const char *name; const char *err; unsigned char id[2]; gnutls_protocol_t version; gnutls_priority_t pcache; if (priorities != NULL) { printf("print_cipher_suite_list: Cipher suites for %s\n", priorities); ret = gnutls_priority_init(&pcache, priorities, &err); if (ret < 0) { fprintf(stderr, "print_cipher_suite_list: Syntax error at: %s\n", err); exit(1); } for (i = 0;; i++) { ret = gnutls_priority_get_cipher_suite_index(pcache, i, &idx); if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) break; if (ret == GNUTLS_E_UNKNOWN_CIPHER_SUITE) continue; name = gnutls_cipher_suite_info(idx, id, NULL, NULL, NULL, &version); if (name != NULL) dbgprintf("print_cipher_suite_list: %-50s\t0x%02x, 0x%02x\t%s\n", name, (unsigned char) id[0], (unsigned char) id[1], gnutls_protocol_get_name(version)); } return; } } #endif /* initialize GnuTLS credential structure (certs etc) */ static rsRetVal gtlsInitCred(nsd_gtls_t *const pThis ) { int gnuRet; const uchar *cafile, *crlfile; DEFiRet; /* X509 stuff */ if (pThis->xcred == NULL) { /* Allocate only ONCE */ CHKgnutls(gnutls_certificate_allocate_credentials(&pThis->xcred)); } /* sets the trusted cas file */ cafile = (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"); } else { dbgprintf("GTLS CA file: '%s'\n", cafile); gnuRet = gnutls_certificate_set_x509_trust_file(pThis->xcred, (char*)cafile, GNUTLS_X509_FMT_PEM); if(gnuRet == GNUTLS_E_FILE_ERROR) { LogError(0, RS_RET_GNUTLS_ERR, "error reading certificate file '%s' - a common cause is that the " "file does not exist", cafile); ABORT_FINALIZE(RS_RET_GNUTLS_ERR); } else if(gnuRet < 0) { /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */ uchar *pErr = gtlsStrerror(gnuRet); LogError(0, RS_RET_GNUTLS_ERR, "unexpected GnuTLS error reading CA certificate file %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); free(pErr); ABORT_FINALIZE(RS_RET_GNUTLS_ERR); } } crlfile = (pThis->pszCRLFile == NULL) ? glbl.GetDfltNetstrmDrvrCRLF(runConf) : pThis->pszCRLFile; if(crlfile == NULL) { dbgprintf("Certificate revocation list (CRL) file not set."); } else { dbgprintf("GTLS CRL file: '%s'\n", crlfile); gnuRet = gnutls_certificate_set_x509_crl_file(pThis->xcred, (char*)crlfile, GNUTLS_X509_FMT_PEM); if(gnuRet == GNUTLS_E_FILE_ERROR) { LogError(0, RS_RET_GNUTLS_ERR, "error reading Certificate revocation list (CRL) '%s' - a common cause is that the " "file does not exist", crlfile); ABORT_FINALIZE(RS_RET_GNUTLS_ERR); } else if(gnuRet < 0) { /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */ uchar *pErr = gtlsStrerror(gnuRet); LogError(0, RS_RET_GNUTLS_ERR, "unexpected GnuTLS error reading Certificate revocation list (CRL) %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); free(pErr); ABORT_FINALIZE(RS_RET_GNUTLS_ERR); } } finalize_it: RETiRet; } /* globally initialize GnuTLS */ static rsRetVal gtlsGlblInit(void) { int gnuRet; DEFiRet; dbgprintf("gtlsGlblInit: Running Version: '%#010x'\n", GNUTLS_VERSION_NUMBER); /* gcry_control must be called first, so that the thread system is correctly set up */ #if GNUTLS_VERSION_NUMBER <= 0x020b00 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); #endif CHKgnutls(gnutls_global_init()); if(GetGnuTLSLoglevel(runConf) > 0){ gnutls_global_set_log_function(logFunction); gnutls_global_set_log_level(GetGnuTLSLoglevel(runConf)); /* 0 (no) to 9 (most), 10 everything */ } /* Init Anon cipher helpers */ CHKgnutls(gnutls_dh_params_init(&dh_params)); CHKgnutls(gnutls_dh_params_generate2(dh_params, dhBits)); /* Allocate ANON Client Cred */ CHKgnutls(gnutls_anon_allocate_client_credentials(&anoncred)); /* Allocate ANON Server Cred */ CHKgnutls(gnutls_anon_allocate_server_credentials(&anoncredSrv)); gnutls_anon_set_server_dh_params(anoncredSrv, dh_params); finalize_it: RETiRet; } static rsRetVal gtlsInitSession(nsd_gtls_t *pThis) { DEFiRet; int gnuRet = 0; gnutls_session_t session; gnutls_init(&session, GNUTLS_SERVER); pThis->bHaveSess = 1; pThis->bIsInitiator = 0; pThis->sess = session; /* Moved CertKey Loading to top */ # if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION /* store a pointer to ourselfs (needed by callback) */ gnutls_session_set_ptr(pThis->sess, (void*)pThis); iRet = gtlsLoadOurCertKey(pThis); /* first load .pem files */ if(iRet == RS_RET_OK) { dbgprintf("gtlsInitSession: enable certificate checking (VerifyDepth=%d)\n", pThis->DrvrVerifyDepth); gnutls_certificate_set_retrieve_function(pThis->xcred, gtlsClientCertCallback); if (pThis->DrvrVerifyDepth != 0){ gnutls_certificate_set_verify_limits(pThis->xcred, 8200, pThis->DrvrVerifyDepth); } } else if(iRet == RS_RET_CERTLESS) { dbgprintf("gtlsInitSession: certificates not configured, not loaded.\n"); } else { ABORT_FINALIZE(iRet); /* we have an error case! */ } # endif /* avoid calling all the priority functions, since the defaults are adequate. */ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, pThis->xcred)); /* check for anon authmode */ if (pThis->authMode == GTLS_AUTH_CERTANON) { dbgprintf("gtlsInitSession: anon authmode, gnutls_credentials_set GNUTLS_CRD_ANON\n"); CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_ANON, anoncredSrv)); gnutls_dh_set_prime_bits(pThis->sess, dhMinBits); } /* request client certificate if any. */ gnutls_certificate_server_set_request( pThis->sess, GNUTLS_CERT_REQUEST); finalize_it: if(iRet != RS_RET_OK && iRet != RS_RET_CERTLESS) { LogError(0, iRet, "gtlsInitSession failed to INIT Session %d", gnuRet); } RETiRet; } /* Obtain the CN from the DN field and hand it back to the caller * (which is responsible for destructing it). We try to follow * RFC2253 as far as it makes sense for our use-case. This function * is considered a compromise providing good-enough correctness while * limiting code size and complexity. If a problem occurs, we may enhance * this function. A (pointer to a) certificate must be caller-provided. * If no CN is contained in the cert, no string is returned * (*ppstrCN remains NULL). *ppstrCN MUST be NULL on entry! * rgerhards, 2008-05-22 */ static rsRetVal gtlsGetCN(gnutls_x509_crt_t *pCert, cstr_t **ppstrCN) { DEFiRet; int gnuRet; int i; int bFound; cstr_t *pstrCN = NULL; size_t size; /* big var the last, so we hope to have all we usually neeed within one mem cache line */ uchar szDN[1024]; /* this should really be large enough for any non-malicious case... */ assert(pCert != NULL); assert(ppstrCN != NULL); assert(*ppstrCN == NULL); size = sizeof(szDN); CHKgnutls(gnutls_x509_crt_get_dn(*pCert, (char*)szDN, &size)); /* now search for the CN part */ i = 0; bFound = 0; while(!bFound && szDN[i] != '\0') { /* note that we do not overrun our string due to boolean shortcut * operations. If we have '\0', the if does not match and evaluation * stops. Order of checks is obviously important! */ if(szDN[i] == 'C' && szDN[i+1] == 'N' && szDN[i+2] == '=') { bFound = 1; i += 2; } i++; } if(!bFound) { FINALIZE; /* we are done */ } /* we found a common name, now extract it */ CHKiRet(cstrConstruct(&pstrCN)); while(szDN[i] != '\0' && szDN[i] != ',') { if(szDN[i] == '\\') { /* hex escapes are not implemented */ ++i; /* escape char processed */ if(szDN[i] == '\0') ABORT_FINALIZE(RS_RET_CERT_INVALID_DN); CHKiRet(cstrAppendChar(pstrCN, szDN[i])); } else { CHKiRet(cstrAppendChar(pstrCN, szDN[i])); } ++i; /* char processed */ } cstrFinalize(pstrCN); /* we got it - we ignore the rest of the DN string (if any). So we may * not detect if it contains more than one CN */ *ppstrCN = pstrCN; finalize_it: if(iRet != RS_RET_OK) { if(pstrCN != NULL) cstrDestruct(&pstrCN); } RETiRet; } /* Check the peer's ID in fingerprint auth mode. * rgerhards, 2008-05-22 */ static rsRetVal gtlsChkPeerFingerprint(nsd_gtls_t *pThis, gnutls_x509_crt_t *pCert) { uchar fingerprint[20]; uchar fingerprintSha256[32]; size_t size; size_t sizeSha256; cstr_t *pstrFingerprint = NULL; cstr_t *pstrFingerprintSha256 = NULL; int bFoundPositiveMatch; permittedPeers_t *pPeer; int gnuRet; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); /* obtain the SHA1 fingerprint */ size = sizeof(fingerprint); sizeSha256 = sizeof(fingerprintSha256); CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size)); CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA256, fingerprintSha256, &sizeSha256)); CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint, "SHA1")); CHKiRet(GenFingerprintStr(fingerprintSha256, sizeSha256, &pstrFingerprintSha256, "SHA256")); dbgprintf("peer's certificate SHA1 fingerprint: %s\n", cstrGetSzStrNoNULL(pstrFingerprint)); dbgprintf("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("gtlsChkPeerFingerprint: peer's certificate SHA1 MATCH found: %s\n", pPeer->pszID); bFoundPositiveMatch = 1; } else if(!rsCStrSzStrCmp(pstrFingerprintSha256 , pPeer->pszID, strlen((char*) pPeer->pszID))) { dbgprintf("gtlsChkPeerFingerprint: peer's certificate SHA256 MATCH found: %s\n", pPeer->pszID); bFoundPositiveMatch = 1; } else { pPeer = pPeer->pNext; } } if(!bFoundPositiveMatch) { dbgprintf("invalid peer fingerprint, not permitted to talk to it\n"); if(pThis->bReportAuthErr == 1) { errno = 0; LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are " "not permitted to talk to it", cstrGetSzStrNoNULL(pstrFingerprint)); pThis->bReportAuthErr = 0; } ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); } finalize_it: if(pstrFingerprint != NULL) cstrDestruct(&pstrFingerprint); 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 gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatch) { permittedPeers_t *pPeer; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); assert(pszPeerID != NULL); assert(pbFoundPositiveMatch != NULL); if(pThis->pPermPeers) { /* do we have configured peer IDs? */ pPeer = pThis->pPermPeers; while(pPeer != NULL) { CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch)); if(*pbFoundPositiveMatch) break; pPeer = pPeer->pNext; } } else { /* we do not have configured peer IDs, so we use defaults */ if( pThis->pszConnectHost && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) { *pbFoundPositiveMatch = 1; } } finalize_it: RETiRet; } /* Check the peer's ID in name auth mode. * rgerhards, 2008-05-22 */ static rsRetVal gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt_t *pCert) { uchar lnBuf[256]; char szAltName[1024]; /* this is sufficient for the DNSNAME... */ int iAltName; size_t szAltNameLen; int bFoundPositiveMatch; int bHaveSAN = 0; cstr_t *pStr = NULL; cstr_t *pstrCN = NULL; int gnuRet; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); bFoundPositiveMatch = 0; CHKiRet(rsCStrConstruct(&pStr)); /* first search through the dNSName subject alt names */ iAltName = 0; while(!bFoundPositiveMatch) { /* loop broken below */ szAltNameLen = sizeof(szAltName); gnuRet = gnutls_x509_crt_get_subject_alt_name(*pCert, iAltName, szAltName, &szAltNameLen, NULL); if(gnuRet < 0) break; else if(gnuRet == GNUTLS_SAN_DNSNAME) { bHaveSAN = 1; dbgprintf("subject alt dnsName: '%s'\n", szAltName); snprintf((char*)lnBuf, sizeof(lnBuf), "DNSname: %s; ", szAltName); CHKiRet(rsCStrAppendStr(pStr, lnBuf)); CHKiRet(gtlsChkOnePeerName(pThis, (uchar*)szAltName, &bFoundPositiveMatch)); /* do NOT break, because there may be multiple dNSName's! */ } ++iAltName; } /* Check also CN only if not configured per stricter RFC 6125 or no SAN present*/ if(!bFoundPositiveMatch && (!pThis->bSANpriority || !bHaveSAN)) { CHKiRet(gtlsGetCN(pCert, &pstrCN)); if(pstrCN != NULL) { /* NULL if there was no CN present */ dbgprintf("gtls now checking auth for CN '%s'\n", cstrGetSzStrNoNULL(pstrCN)); snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", cstrGetSzStrNoNULL(pstrCN)); CHKiRet(rsCStrAppendStr(pStr, lnBuf)); CHKiRet(gtlsChkOnePeerName(pThis, cstrGetSzStrNoNULL(pstrCN), &bFoundPositiveMatch)); } } if(!bFoundPositiveMatch) { dbgprintf("invalid peer name, not permitted to talk to it\n"); if(pThis->bReportAuthErr == 1) { cstrFinalize(pStr); errno = 0; LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized - " "not permitted to talk to it. Names: %s", cstrGetSzStrNoNULL(pStr)); pThis->bReportAuthErr = 0; } ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); } finalize_it: if(pStr != NULL) rsCStrDestruct(&pStr); if(pstrCN != NULL) rsCStrDestruct(&pstrCN); RETiRet; } /* check the ID of the remote peer - used for both fingerprint and * name authentication. This is common code. Will call into specific * drivers once the certificate has been obtained. * rgerhards, 2008-05-08 */ static rsRetVal gtlsChkPeerID(nsd_gtls_t *pThis) { const gnutls_datum_t *cert_list; unsigned int list_size = 0; gnutls_x509_crt_t cert; int bMustDeinitCert = 0; int gnuRet; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); /* This function only works for X.509 certificates. */ if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509) return RS_RET_TLS_CERT_ERR; cert_list = gnutls_certificate_get_peers(pThis->sess, &list_size); if(list_size < 1) { if(pThis->bReportAuthErr == 1) { uchar *fromHost = NULL; errno = 0; pThis->bReportAuthErr = 0; nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost); LogError(0, RS_RET_TLS_NO_CERT, "error: peer %s did not provide a certificate, " "not permitted to talk to it", fromHost); free(fromHost); } ABORT_FINALIZE(RS_RET_TLS_NO_CERT); } /* If we reach this point, we have at least one valid certificate. * We always use only the first certificate. As of GnuTLS documentation, the * first certificate always contains the remote peer's own certificate. All other * certificates are issuer's certificates (up the chain). We are only interested * in the first certificate, which is our peer. -- rgerhards, 2008-05-08 */ CHKgnutls(gnutls_x509_crt_init(&cert)); bMustDeinitCert = 1; /* indicate cert is initialized and must be freed on exit */ CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); /* Now we see which actual authentication code we must call. */ if(pThis->authMode == GTLS_AUTH_CERTFINGERPRINT) { CHKiRet(gtlsChkPeerFingerprint(pThis, &cert)); } else { assert(pThis->authMode == GTLS_AUTH_CERTNAME); CHKiRet(gtlsChkPeerName(pThis, &cert)); } finalize_it: if(bMustDeinitCert) gnutls_x509_crt_deinit(cert); RETiRet; } /* Verify the validity of the remote peer's certificate. * rgerhards, 2008-05-21 */ static rsRetVal gtlsChkPeerCertValidity(nsd_gtls_t *pThis) { DEFiRet; const char *pszErrCause; int gnuRet; cstr_t *pStr = NULL; unsigned stateCert; const gnutls_datum_t *cert_list; unsigned cert_list_size = 0; gnutls_x509_crt_t cert; unsigned i; time_t ttCert; time_t ttNow; sbool bAbort = RSFALSE; int iAbortCode = RS_RET_OK; ISOBJ_TYPE_assert(pThis, nsd_gtls); /* check if we have at least one cert */ cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size); if(cert_list_size < 1) { errno = 0; uchar *fromHost = NULL; nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost); LogError(0, RS_RET_TLS_NO_CERT, "peer %s did not provide a certificate, not permitted to talk to it", fromHost); free(fromHost); ABORT_FINALIZE(RS_RET_TLS_NO_CERT); } #ifdef EXTENDED_CERT_CHECK_AVAILABLE if (pThis->dataTypeCheck == GTLS_NONE) { #endif CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert)); #ifdef EXTENDED_CERT_CHECK_AVAILABLE } else { /* we have configured data to check in addition to cert */ gnutls_typed_vdata_st data; data.type = GNUTLS_DT_KEY_PURPOSE_OID; if (pThis->bIsInitiator) { /* client mode */ data.data = (uchar *)GNUTLS_KP_TLS_WWW_SERVER; } else { /* server mode */ data.data = (uchar *)GNUTLS_KP_TLS_WWW_CLIENT; } data.size = ustrlen(data.data); CHKgnutls(gnutls_certificate_verify_peers(pThis->sess, &data, 1, &stateCert)); } #endif if(stateCert & GNUTLS_CERT_INVALID) { /* Default abort code */ iAbortCode = RS_RET_CERT_INVALID; /* provide error details if we have them */ if (stateCert & GNUTLS_CERT_EXPIRED ) { dbgprintf("GnuTLS returned GNUTLS_CERT_EXPIRED, handling mode %d ...\n", pThis->permitExpiredCerts); /* Handle expired certs */ if (pThis->permitExpiredCerts == GTLS_EXPIRED_DENY) { bAbort = RSTRUE; iAbortCode = RS_RET_CERT_EXPIRED; } else if (pThis->permitExpiredCerts == GTLS_EXPIRED_WARN) { LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING, "Warning, certificate expired but expired certs are permitted"); } else { dbgprintf("GnuTLS returned GNUTLS_CERT_EXPIRED, but expired certs are permitted.\n"); } pszErrCause = "certificate expired"; } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) { pszErrCause = "signer not found"; bAbort = RSTRUE; } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) { pszErrCause = "signer is not a CA"; bAbort = RSTRUE; } else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) { pszErrCause = "insecure algorithm"; bAbort = RSTRUE; } else if(stateCert & GNUTLS_CERT_REVOKED) { pszErrCause = "certificate revoked"; bAbort = RSTRUE; iAbortCode = RS_RET_CERT_REVOKED; #ifdef EXTENDED_CERT_CHECK_AVAILABLE } else if(stateCert & GNUTLS_CERT_PURPOSE_MISMATCH) { pszErrCause = "key purpose OID does not match"; bAbort = RSTRUE; #endif } else { pszErrCause = "GnuTLS returned no specific reason"; dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate " "status is %d\n", stateCert); bAbort = RSTRUE; } } if (bAbort == RSTRUE) { uchar *fromHost = NULL; nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost); LogError(0, NO_ERRCODE, "not permitted to talk to peer '%s', certificate invalid: %s", fromHost, pszErrCause); free(fromHost); gtlsGetCertInfo(pThis, &pStr); LogError(0, NO_ERRCODE, "invalid cert info: %s", cstrGetSzStrNoNULL(pStr)); cstrDestruct(&pStr); ABORT_FINALIZE(iAbortCode); } /* get current time for certificate validation */ if(datetime.GetTime(&ttNow) == -1) ABORT_FINALIZE(RS_RET_SYS_ERR); /* as it looks, we need to validate the expiration dates ourselves... * We need to loop through all certificates as we need to make sure the * interim certificates are also not expired. */ for(i = 0 ; i < cert_list_size ; ++i) { CHKgnutls(gnutls_x509_crt_init(&cert)); CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER)); ttCert = gnutls_x509_crt_get_activation_time(cert); if(ttCert == -1) ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); else if(ttCert > ttNow) { uchar *fromHost = NULL; nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost); LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer '%s': " "certificate %d not yet active", fromHost, i); free(fromHost); gtlsGetCertInfo(pThis, &pStr); LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "invalid cert info: %s", cstrGetSzStrNoNULL(pStr)); cstrDestruct(&pStr); ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE); } gnutls_x509_crt_deinit(cert); } finalize_it: RETiRet; } /* check if it is OK to talk to the remote peer * rgerhards, 2008-05-21 */ rsRetVal gtlsChkPeerAuth(nsd_gtls_t *pThis) { DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); /* call the actual function based on current auth mode */ switch(pThis->authMode) { case GTLS_AUTH_CERTNAME: /* if we check the name, we must ensure the cert is valid */ CHKiRet(gtlsChkPeerCertValidity(pThis)); CHKiRet(gtlsChkPeerID(pThis)); break; case GTLS_AUTH_CERTFINGERPRINT: CHKiRet(gtlsChkPeerID(pThis)); break; case GTLS_AUTH_CERTVALID: CHKiRet(gtlsChkPeerCertValidity(pThis)); break; case GTLS_AUTH_CERTANON: FINALIZE; break; } finalize_it: RETiRet; } /* globally de-initialize GnuTLS */ static rsRetVal gtlsGlblExit(void) { DEFiRet; gnutls_anon_free_server_credentials(anoncredSrv); gnutls_dh_params_deinit(dh_params); gnutls_global_deinit(); RETiRet; } /* end a GnuTLS session * The function checks if we have a session and ends it only if so. So it can * always be called, even if there currently is no session. */ static rsRetVal gtlsEndSess(nsd_gtls_t *pThis) { int gnuRet; DEFiRet; if(pThis->bHaveSess) { if(pThis->bIsInitiator) { gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_WR); while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) { gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_WR); } } gnutls_deinit(pThis->sess); pThis->bHaveSess = 0; } RETiRet; } /* a small wrapper for gnutls_transport_set_ptr(). The main intension for * creating this wrapper is to get the annoying "cast to pointer from different * size" compiler warning just once. There seems to be no way around it, see: * http://lists.gnu.org/archive/html/help-gnutls/2008-05/msg00000.html * rgerhards, 2008.05-07 */ #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" static inline void gtlsSetTransportPtr(nsd_gtls_t *pThis, int sock) { /* Note: the compiler warning for the next line is OK - see header comment! */ gnutls_transport_set_ptr(pThis->sess, (gnutls_transport_ptr_t) sock); } #pragma GCC diagnostic warning "-Wint-to-pointer-cast" /* ---------------------------- end GnuTLS specifics ---------------------------- */ /* Standard-Constructor */ BEGINobjConstruct(nsd_gtls) /* be sure to specify the object type also in END macro! */ iRet = nsd_ptcp.Construct(&pThis->pTcp); pThis->bReportAuthErr = 1; ENDobjConstruct(nsd_gtls) /* destructor for the nsd_gtls object */ PROTOTYPEobjDestruct(nsd_gtls); BEGINobjDestruct(nsd_gtls) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDestruct(nsd_gtls) if(pThis->iMode == 1) { gtlsEndSess(pThis); } if(pThis->pTcp != NULL) { nsd_ptcp.Destruct(&pThis->pTcp); } free(pThis->pszConnectHost); free(pThis->pszRcvBuf); free((void*) pThis->pszCAFile); free((void*) pThis->pszCRLFile); if(pThis->bOurCertIsInit) for(unsigned i=0; inOurCerts; ++i) { gnutls_x509_crt_deinit(pThis->pOurCerts[i]); } if(pThis->bOurKeyIsInit) gnutls_x509_privkey_deinit(pThis->ourKey); if(pThis->bHaveSess) gnutls_deinit(pThis->sess); if(pThis->xcred != NULL && (pThis->bIsInitiator || (!pThis->xcred_is_copy && (!pThis->bIsInitiator || pThis->bHaveSess))) ) { gnutls_certificate_free_credentials(pThis->xcred); free((void*) pThis->pszKeyFile); free((void*) pThis->pszCertFile); } ENDobjDestruct(nsd_gtls) /* Set the driver mode. For us, this has the following meaning: * 0 - work in plain tcp mode, without tls (e.g. before a STARTTLS) * 1 - work in TLS mode * rgerhards, 2008-04-28 */ static rsRetVal SetMode(nsd_t *const pNsd, const int mode) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); dbgprintf("(tls) mode: %d\n", mode); if(mode != 0 && mode != 1) { LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by " "gtls netstream driver", mode); ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE); } pThis->iMode = mode; finalize_it: RETiRet; } /* Set the authentication mode. For us, the following is supported: * anon - no certificate checks whatsoever (discouraged, but supported) * x509/certvalid - (just) check certificate validity * x509/fingerprint - certificate fingerprint * x509/name - cerfificate name check * mode == NULL is valid and defaults to x509/name * rgerhards, 2008-05-16 */ static rsRetVal SetAuthMode(nsd_t *pNsd, uchar *mode) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) { pThis->authMode = GTLS_AUTH_CERTNAME; } else if(!strcasecmp((char*) mode, "x509/fingerprint")) { pThis->authMode = GTLS_AUTH_CERTFINGERPRINT; } else if(!strcasecmp((char*) mode, "x509/certvalid")) { pThis->authMode = GTLS_AUTH_CERTVALID; } else if(!strcasecmp((char*) mode, "anon")) { pThis->authMode = GTLS_AUTH_CERTANON; } else { LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by " "gtls netstream driver", mode); ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); } dbgprintf("SetAuthMode to %s\n", (mode != NULL ? (char*)mode : "NULL")); /* TODO: clear stored IDs! */ finalize_it: RETiRet; } /* Set the PermitExpiredCerts mode. For us, the following is supported: * on - fail if certificate is expired * off - ignore expired certificates * warn - warn if certificate is expired * alorbach, 2018-12-20 */ static rsRetVal SetPermitExpiredCerts(nsd_t *pNsd, uchar *mode) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); /* default is set to off! */ if(mode == NULL || !strcasecmp((char*)mode, "off")) { pThis->permitExpiredCerts = GTLS_EXPIRED_DENY; } else if(!strcasecmp((char*) mode, "warn")) { pThis->permitExpiredCerts = GTLS_EXPIRED_WARN; } else if(!strcasecmp((char*) mode, "on")) { pThis->permitExpiredCerts = GTLS_EXPIRED_PERMIT; } else { LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: permitexpiredcerts mode '%s' not supported by " "gtls netstream driver", mode); ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); } dbgprintf("SetPermitExpiredCerts: Set Mode %s/%d\n", (mode != NULL ? (char*)mode : "NULL"), pThis->permitExpiredCerts); /* TODO: clear stored IDs! */ finalize_it: RETiRet; } /* Set permitted peers. It is depending on the auth mode if this are * fingerprints or names. -- rgerhards, 2008-05-19 */ static rsRetVal SetPermPeers(nsd_t *pNsd, permittedPeers_t *pPermPeers) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(pPermPeers == NULL) FINALIZE; if(pThis->authMode != GTLS_AUTH_CERTFINGERPRINT && pThis->authMode != GTLS_AUTH_CERTNAME) { LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by " "gtls netstream driver in the configured authentication mode - ignored"); ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE); } pThis->pPermPeers = pPermPeers; finalize_it: RETiRet; } /* gnutls priority string * PascalWithopf 2017-08-16 */ static rsRetVal SetGnutlsPriorityString(nsd_t *pNsd, uchar *gnutlsPriorityString) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); pThis->gnutlsPriorityString = gnutlsPriorityString; dbgprintf("gnutlsPriorityString: set to '%s'\n", (gnutlsPriorityString != NULL ? (char*)gnutlsPriorityString : "NULL")); RETiRet; } /* Set the driver cert extended key usage check setting * 0 - ignore contents of extended key usage * 1 - verify that cert contents is compatible with appropriate OID * jvymazal, 2019-08-16 */ static rsRetVal SetCheckExtendedKeyUsage(nsd_t *pNsd, int ChkExtendedKeyUsage) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(ChkExtendedKeyUsage != 0 && ChkExtendedKeyUsage != 1) { LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver ChkExtendedKeyUsage %d " "not supported by gtls netstream driver", ChkExtendedKeyUsage); ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); } pThis->dataTypeCheck = ChkExtendedKeyUsage; finalize_it: RETiRet; } /* Set the driver name checking strictness * 0 - less strict per RFC 5280, section 4.1.2.6 - either SAN or CN match is good * 1 - more strict per RFC 6125 - if any SAN present it must match (CN is ignored) * jvymazal, 2019-08-16 */ static rsRetVal SetPrioritizeSAN(nsd_t *pNsd, int prioritizeSan) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(prioritizeSan != 0 && prioritizeSan != 1) { LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver prioritizeSan %d " "not supported by gtls netstream driver", prioritizeSan); ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); } pThis->bSANpriority = prioritizeSan; finalize_it: RETiRet; } /* Set the driver tls verifyDepth * alorbach, 2019-12-20 */ static rsRetVal SetTlsVerifyDepth(nsd_t *pNsd, int verifyDepth) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if (verifyDepth == 0) { FINALIZE; } assert(verifyDepth >= 2); pThis->DrvrVerifyDepth = verifyDepth; finalize_it: RETiRet; } static rsRetVal SetTlsCAFile(nsd_t *pNsd, const uchar *const caFile) { DEFiRet; nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(caFile == NULL) { pThis->pszCAFile = NULL; } else { CHKmalloc(pThis->pszCAFile = (const uchar*) strdup((const char*) caFile)); } finalize_it: RETiRet; } static rsRetVal SetTlsCRLFile(nsd_t *pNsd, const uchar *const crlFile) { DEFiRet; nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(crlFile == NULL) { pThis->pszCRLFile = NULL; } else { CHKmalloc(pThis->pszCRLFile = (const uchar*) strdup((const char*) crlFile)); } finalize_it: RETiRet; } static rsRetVal SetTlsKeyFile(nsd_t *pNsd, const uchar *const pszFile) { DEFiRet; nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(pszFile == NULL) { pThis->pszKeyFile = NULL; } else { CHKmalloc(pThis->pszKeyFile = (const uchar*) strdup((const char*) pszFile)); } finalize_it: RETiRet; } static rsRetVal SetTlsCertFile(nsd_t *pNsd, const uchar *const pszFile) { DEFiRet; nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(pszFile == NULL) { pThis->pszCertFile = NULL; } else { CHKmalloc(pThis->pszCertFile = (const uchar*) strdup((const char*) pszFile)); } finalize_it: RETiRet; } /* Provide access to the underlying OS socket. This is primarily * useful for other drivers (like nsd_gtls) who utilize ourselfs * for some of their functionality. -- rgerhards, 2008-04-18 */ static rsRetVal SetSock(nsd_t *pNsd, int sock) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); assert(sock >= 0); nsd_ptcp.SetSock(pThis->pTcp, sock); RETiRet; } /* Keep Alive Options */ static rsRetVal SetKeepAliveIntvl(nsd_t *pNsd, int keepAliveIntvl) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); assert(keepAliveIntvl >= 0); nsd_ptcp.SetKeepAliveIntvl(pThis->pTcp, keepAliveIntvl); RETiRet; } /* Keep Alive Options */ static rsRetVal SetKeepAliveProbes(nsd_t *pNsd, int keepAliveProbes) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); assert(keepAliveProbes >= 0); nsd_ptcp.SetKeepAliveProbes(pThis->pTcp, keepAliveProbes); RETiRet; } /* Keep Alive Options */ static rsRetVal SetKeepAliveTime(nsd_t *pNsd, int keepAliveTime) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert((pThis), nsd_gtls); assert(keepAliveTime >= 0); nsd_ptcp.SetKeepAliveTime(pThis->pTcp, keepAliveTime); RETiRet; } /* abort a connection. This is meant to be called immediately * before the Destruct call. -- rgerhards, 2008-03-24 */ static rsRetVal Abort(nsd_t *pNsd) { nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; DEFiRet; ISOBJ_TYPE_assert((pThis), nsd_gtls); if(pThis->iMode == 0) { nsd_ptcp.Abort(pThis->pTcp); } RETiRet; } /* Callback after netstrm obj init in nsd_ptcp - permits us to add some data */ static rsRetVal LstnInitDrvr(netstrm_t *const pThis) { DEFiRet; CHKiRet(gtlsInitCred((nsd_gtls_t*) pThis->pDrvrData)); CHKiRet(gtlsAddOurCert((nsd_gtls_t*) pThis->pDrvrData)); finalize_it: RETiRet; } /* initialize the tcp socket for a listner * Here, we use the ptcp driver - because there is nothing special * at this point with GnuTLS. Things become special once we accept * a session, but not during listener setup. * gerhards, 2008-04-25 */ static rsRetVal ATTR_NONNULL(1,3,5) LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), const int iSessMax, const tcpLstnParams_t *const cnf_params) { DEFiRet; pNS->fLstnInitDrvr = LstnInitDrvr; iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, iSessMax, cnf_params); //finalize_it: RETiRet; } /* This function checks if the connection is still alive - well, kind of... * This is a dummy here. For details, check function common in ptcp driver. * rgerhards, 2008-06-09 */ static rsRetVal CheckConnection(nsd_t __attribute__((unused)) *pNsd) { nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert(pThis, nsd_gtls); dbgprintf("CheckConnection for %p\n", pNsd); return nsd_ptcp.CheckConnection(pThis->pTcp); } /* get the remote hostname. The returned hostname must be freed by the caller. * rgerhards, 2008-04-25 */ static rsRetVal GetRemoteHName(nsd_t *pNsd, uchar **ppszHName) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert(pThis, nsd_gtls); iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, ppszHName); RETiRet; } /* Provide access to the sockaddr_storage of the remote peer. This * is needed by the legacy ACL system. --- gerhards, 2008-12-01 */ static rsRetVal GetRemAddr(nsd_t *pNsd, struct sockaddr_storage **ppAddr) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert(pThis, nsd_gtls); iRet = nsd_ptcp.GetRemAddr(pThis->pTcp, ppAddr); RETiRet; } /* get the remote host's IP address. Caller must Destruct the object. */ static rsRetVal GetRemoteIP(nsd_t *pNsd, prop_t **ip) { DEFiRet; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert(pThis, nsd_gtls); iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ip); RETiRet; } /* accept an incoming connection request - here, we do the usual accept * handling. TLS specific handling is done thereafter (and if we run in TLS * mode at this time). * rgerhards, 2008-04-25 */ static rsRetVal AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew) { DEFiRet; int gnuRet; nsd_gtls_t *pNew = NULL; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; const char *error_position = NULL; ISOBJ_TYPE_assert((pThis), nsd_gtls); CHKiRet(nsd_gtlsConstruct(&pNew)); // TODO: prevent construct/destruct! CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp)); CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp)); if(pThis->iMode == 0) { /* we are in non-TLS mode, so we are done */ *ppNew = (nsd_t*) pNew; FINALIZE; } /* copy Properties to pnew first */ pNew->authMode = pThis->authMode; pNew->permitExpiredCerts = pThis->permitExpiredCerts; pNew->pPermPeers = pThis->pPermPeers; pNew->gnutlsPriorityString = pThis->gnutlsPriorityString; pNew->DrvrVerifyDepth = pThis->DrvrVerifyDepth; pNew->dataTypeCheck = pThis->dataTypeCheck; pNew->bSANpriority = pThis->bSANpriority; pNew->pszCertFile = pThis->pszCertFile; pNew->pszKeyFile = pThis->pszKeyFile; pNew->xcred = pThis->xcred; // TODO: verify once again; xcred is read only at this stage pNew->xcred_is_copy = 1; // do not free on pNew Destruction /* if we reach this point, we are in TLS mode */ iRet = gtlsInitSession(pNew); if (iRet != RS_RET_OK) { if (iRet == RS_RET_CERTLESS) { dbgprintf("AcceptConnReq certless mode\n"); /* Set status to OK */ iRet = RS_RET_OK; } else { goto finalize_it; } } gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock); dbgprintf("AcceptConnReq bOurCertIsInit=%hu bOurKeyIsInit=%hu \n", pNew->bOurCertIsInit, pNew->bOurKeyIsInit); /* here is the priorityString set */ if(pNew->gnutlsPriorityString != NULL) { dbgprintf("AcceptConnReq setting configured priority string (ciphers)\n"); if(gnutls_priority_set_direct(pNew->sess, (const char*) pNew->gnutlsPriorityString, &error_position)==GNUTLS_E_INVALID_REQUEST) { LogError(0, RS_RET_GNUTLS_ERR, "Syntax Error in" " Priority String: \"%s\"\n", error_position); } } else { if(pThis->authMode == GTLS_AUTH_CERTANON) { /* Allow ANON Ciphers */ dbgprintf("AcceptConnReq setting anon ciphers Try1: %s\n", GTLS_ANON_PRIO_NOTLSV13); if(gnutls_priority_set_direct(pNew->sess,(const char*) GTLS_ANON_PRIO_NOTLSV13, &error_position)==GNUTLS_E_INVALID_REQUEST) { dbgprintf("AcceptConnReq setting anon ciphers Try2 (TLS1.3 unknown): %s\n", GTLS_ANON_PRIO); CHKgnutls(gnutls_priority_set_direct(pNew->sess, GTLS_ANON_PRIO, &error_position)); } /* Uncomment for DEBUG print_cipher_suite_list("NORMAL:+ANON-DH:+ANON-ECDH:+COMP-ALL"); */ } else { /* Use default priorities */ dbgprintf("AcceptConnReq setting default ciphers\n"); CHKgnutls(gnutls_set_default_priority(pNew->sess)); } } /* we now do the handshake. This is a bit complicated, because we are * on non-blocking sockets. Usually, the handshake will not complete * immediately, so that we need to retry it some time later. */ gnuRet = gnutls_handshake(pNew->sess); if(gnuRet == GNUTLS_E_AGAIN || gnuRet == GNUTLS_E_INTERRUPTED) { pNew->rtryCall = gtlsRtry_handshake; dbgprintf("GnuTLS handshake does not complete immediately - " "setting to retry (this is OK and normal)\n"); } else if(gnuRet == 0) { /* we got a handshake, now check authorization */ CHKiRet(gtlsChkPeerAuth(pNew)); } else { uchar *pGnuErr = gtlsStrerror(gnuRet); LogError(0, RS_RET_TLS_HANDSHAKE_ERR, "gnutls returned error on handshake: %s\n", pGnuErr); free(pGnuErr); ABORT_FINALIZE(RS_RET_TLS_HANDSHAKE_ERR); } pNew->iMode = 1; /* this session is now in TLS mode! */ *ppNew = (nsd_t*) pNew; finalize_it: if(iRet != RS_RET_OK) { if (error_position != NULL) { dbgprintf("AcceptConnReq error_position=%s\n", error_position); } if(pNew != NULL) nsd_gtlsDestruct(&pNew); } RETiRet; } /* receive data from a tcp socket * The lenBuf parameter must contain the max buffer size on entry and contains * the number of octets read on exit. This function * never blocks, not even when called on a blocking socket. That is important * for client sockets, which are set to block during send, but should not * block when trying to read data. -- rgerhards, 2008-03-17 * The function now follows the usual iRet calling sequence. * With GnuTLS, we may need to restart a recv() system call. If so, we need * to supply the SAME buffer on the retry. We can not assure this, as the * caller is free to call us with any buffer location (and in current * implementation, it is on the stack and extremely likely to change). To * work-around this problem, we allocate a buffer ourselfs and always receive * into that buffer. We pass data on to the caller only after we have received it. * To save some space, we allocate that internal buffer only when it is actually * needed, which means when we reach this function for the first time. To keep * the algorithm simple, we always supply data only from the internal buffer, * even if it is a single byte. As we have a stream, the caller must be prepared * to accept messages in any order, so we do not need to take care about this. * Please note that the logic also forces us to do some "faking" in select(), as * we must provide a fake "is ready for readign" status if we have data inside our * buffer. -- rgerhards, 2008-06-23 */ static rsRetVal Rcv(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf, int *const oserr) { DEFiRet; ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert(pThis, nsd_gtls); if(pThis->bAbortConn) ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); if(pThis->iMode == 0) { CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf, oserr)); FINALIZE; } /* --- in TLS mode now --- */ /* Buffer logic applies only if we are in TLS mode. Here we * assume that we will switch from plain to TLS, but never back. This * assumption may be unsafe, but it is the model for the time being and I * do not see any valid reason why we should switch back to plain TCP after * we were in TLS mode. However, in that case we may lose something that * is already in the receive buffer ... risk accepted. -- rgerhards, 2008-06-23 */ if(pThis->pszRcvBuf == NULL) { /* we have no buffer, so we need to malloc one */ CHKmalloc(pThis->pszRcvBuf = malloc(NSD_GTLS_MAX_RCVBUF)); pThis->lenRcvBuf = -1; } /* now check if we have something in our buffer. If so, we satisfy * the request from buffer contents. */ if(pThis->lenRcvBuf == -1) { /* no data present, must read */ CHKiRet(gtlsRecordRecv(pThis)); } if(pThis->lenRcvBuf == 0) { /* EOS */ *oserr = errno; ABORT_FINALIZE(RS_RET_CLOSED); } /* if we reach this point, data is present in the buffer and must be copied */ iBytesCopy = pThis->lenRcvBuf - pThis->ptrRcvBuf; if(iBytesCopy > *pLenBuf) { iBytesCopy = *pLenBuf; } else { pThis->lenRcvBuf = -1; /* buffer will be emptied below */ } memcpy(pBuf, pThis->pszRcvBuf + pThis->ptrRcvBuf, iBytesCopy); pThis->ptrRcvBuf += iBytesCopy; *pLenBuf = iBytesCopy; finalize_it: if (iRet != RS_RET_OK && iRet != RS_RET_RETRY) { /* We need to free the receive buffer in error error case unless a retry is wanted. , if we * allocated one. -- rgerhards, 2008-12-03 -- moved here by alorbach, 2015-12-01 */ *pLenBuf = 0; free(pThis->pszRcvBuf); pThis->pszRcvBuf = NULL; } dbgprintf("gtlsRcv return. nsd %p, iRet %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, pThis->lenRcvBuf, pThis->ptrRcvBuf); RETiRet; } /* send a buffer. On entry, pLenBuf contains the number of octets to * write. On exit, it contains the number of octets actually written. * If this number is lower than on entry, only a partial buffer has * been written. * rgerhards, 2008-03-19 */ static rsRetVal Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) { int iSent; int wantsWriteData = 0; nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); if(pThis->bAbortConn) ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); if(pThis->iMode == 0) { CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf)); FINALIZE; } /* in TLS mode now */ while(1) { /* loop broken inside */ iSent = gnutls_record_send(pThis->sess, pBuf, *pLenBuf); if(iSent >= 0) { *pLenBuf = iSent; break; } if(iSent != GNUTLS_E_INTERRUPTED && iSent != GNUTLS_E_AGAIN) { /* Check if the underlaying file descriptor needs to read or write data!*/ wantsWriteData = gnutls_record_get_direction(pThis->sess); uchar *pErr = gtlsStrerror(iSent); LogError(0, RS_RET_GNUTLS_ERR, "unexpected GnuTLS error %d, wantsWriteData=%d - this " "could be caused by a broken connection. GnuTLS reports: %s\n", iSent, wantsWriteData, pErr); free(pErr); gnutls_perror(iSent); ABORT_FINALIZE(RS_RET_GNUTLS_ERR); } } finalize_it: RETiRet; } /* Enable KEEPALIVE handling on the socket. * rgerhards, 2009-06-02 */ static rsRetVal EnableKeepAlive(nsd_t *pNsd) { nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; ISOBJ_TYPE_assert(pThis, nsd_gtls); return nsd_ptcp.EnableKeepAlive(pThis->pTcp); } /* * SNI should not be used if the hostname is a bare IP address */ static int SetServerNameIfPresent(nsd_gtls_t *pThis, uchar *host) { struct sockaddr_in sa; struct sockaddr_in6 sa6; int inet_pton_ret = inet_pton(AF_INET, CHAR_CONVERT(host), &(sa.sin_addr)); if (inet_pton_ret == 0) { // host wasn't a bare IPv4 address: try IPv6 inet_pton_ret = inet_pton(AF_INET6, CHAR_CONVERT(host), &(sa6.sin6_addr)); } switch(inet_pton_ret) { case 1: // host is a valid IP address: don't use SNI return 0; case 0: // host isn't a valid IP address: assume it's a domain name, use SNI return gnutls_server_name_set(pThis->sess, GNUTLS_NAME_DNS, host, ustrlen(host)); default: // unexpected error return -1; } } /* open a connection to a remote host (server). With GnuTLS, we always * open a plain tcp socket and then, if in TLS mode, do a handshake on it. * rgerhards, 2008-03-19 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /* TODO: FIX Warnings! */ static rsRetVal Connect(nsd_t *pNsd, int family, uchar *port, uchar *host, char *device) { nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; int sock; int gnuRet; const char *error_position; # ifdef HAVE_GNUTLS_CERTIFICATE_TYPE_SET_PRIORITY static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 }; # endif DEFiRet; dbgprintf("Connect to %s:%s\n", host, port); ISOBJ_TYPE_assert(pThis, nsd_gtls); assert(port != NULL); assert(host != NULL); CHKiRet(gtlsInitCred(pThis)); CHKiRet(gtlsAddOurCert(pThis)); CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host, device)); if(pThis->iMode == 0) FINALIZE; /* we reach this point if in TLS mode */ CHKgnutls(gnutls_init(&pThis->sess, GNUTLS_CLIENT)); pThis->bHaveSess = 1; pThis->bIsInitiator = 1; CHKgnutls(SetServerNameIfPresent(pThis, host)); /* in the client case, we need to set a callback that ensures our certificate * will be presented to the server even if it is not signed by one of the server's * trusted roots. This is necessary to support fingerprint authentication. */ /* store a pointer to ourselfs (needed by callback) */ gnutls_session_set_ptr(pThis->sess, (void*)pThis); iRet = gtlsLoadOurCertKey(pThis); /* first load .pem files */ if(iRet == RS_RET_OK) { # if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION gnutls_certificate_set_retrieve_function(pThis->xcred, gtlsClientCertCallback); # else gnutls_certificate_client_set_retrieve_function(pThis->xcred, gtlsClientCertCallback); # endif dbgprintf("Connect: enable certificate checking (VerifyDepth=%d)\n", pThis->DrvrVerifyDepth); if (pThis->DrvrVerifyDepth != 0) { gnutls_certificate_set_verify_limits(pThis->xcred, 8200, pThis->DrvrVerifyDepth); } } else if(iRet == RS_RET_CERTLESS) { dbgprintf("Connect: certificates not configured, not loaded.\n"); } else { LogError(0, iRet, "Connect failed to INIT Session %d", gnuRet); ABORT_FINALIZE(iRet);; /* we have an error case! */ } /*priority string setzen*/ if(pThis->gnutlsPriorityString != NULL) { dbgprintf("Connect: setting configured priority string (ciphers)\n"); if(gnutls_priority_set_direct(pThis->sess, (const char*) pThis->gnutlsPriorityString, &error_position)==GNUTLS_E_INVALID_REQUEST) { LogError(0, RS_RET_GNUTLS_ERR, "Syntax Error in" " Priority String: \"%s\"\n", error_position); } } else { if(pThis->authMode == GTLS_AUTH_CERTANON || pThis->bOurCertIsInit == 0) { /* Allow ANON Ciphers */ dbgprintf("Connect: setting anon ciphers Try1: %s\n", GTLS_ANON_PRIO_NOTLSV13); if(gnutls_priority_set_direct(pThis->sess,(const char*) GTLS_ANON_PRIO_NOTLSV13, &error_position)==GNUTLS_E_INVALID_REQUEST) { dbgprintf("Connect: setting anon ciphers Try2 (TLS1.3 unknown): %s\n", GTLS_ANON_PRIO); CHKgnutls(gnutls_priority_set_direct(pThis->sess, GTLS_ANON_PRIO, &error_position)); } /* Uncomment for DEBUG print_cipher_suite_list("NORMAL:+ANON-DH:+ANON-ECDH:+COMP-ALL"); */ } else { /* Use default priorities */ dbgprintf("Connect: setting default ciphers\n"); CHKgnutls(gnutls_set_default_priority(pThis->sess)); } } # ifdef HAVE_GNUTLS_CERTIFICATE_TYPE_SET_PRIORITY /* The gnutls_certificate_type_set_priority function is deprecated * and not available in recent GnuTLS versions. However, there is no * doc how to properly replace it with gnutls_priority_set_direct. * A lot of folks have simply removed it, when they also called * gnutls_set_default_priority. This is what we now also do. If * this causes problems or someone has an idea of how to replace * the deprecated function in a better way, please let us know! * In any case, we use it as long as it is available and let * not insult us by the deprecation warnings. * 2015-05-18 rgerhards */ CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority)); # endif /* put the x509 credentials to the current session */ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, pThis->xcred)); /* check for anon authmode */ if (pThis->authMode == GTLS_AUTH_CERTANON) { dbgprintf("Connect: anon authmode, gnutls_credentials_set GNUTLS_CRD_ANON\n"); CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_ANON, anoncred)); gnutls_dh_set_prime_bits(pThis->sess, dhMinBits); } /* assign the socket to GnuTls */ CHKiRet(nsd_ptcp.GetSock(pThis->pTcp, &sock)); gtlsSetTransportPtr(pThis, sock); /* we need to store the hostname as an alternate mean of authentication if no * permitted peer names are given. Using the hostname is quite useful. It permits * auto-configuration of security if a commen root cert is present. -- rgerhards, 2008-05-26 */ CHKmalloc(pThis->pszConnectHost = (uchar*)strdup((char*)host)); /* and perform the handshake */ CHKgnutls(gnutls_handshake(pThis->sess)); dbgprintf("GnuTLS handshake succeeded\n"); /* now check if the remote peer is permitted to talk to us - ideally, we * should do this during the handshake, but GnuTLS does not yet provide * the necessary callbacks -- rgerhards, 2008-05-26 */ CHKiRet(gtlsChkPeerAuth(pThis)); finalize_it: if(iRet != RS_RET_OK) { if(pThis->bHaveSess) { gnutls_deinit(pThis->sess); pThis->bHaveSess = 0; /* Free memory using gnutls api first*/ gnutls_certificate_free_credentials(pThis->xcred); pThis->xcred = NULL; /* Free other memory */ free(pThis->pszConnectHost); pThis->pszConnectHost = NULL; } } RETiRet; } #pragma GCC diagnostic pop /* queryInterface function */ BEGINobjQueryInterface(nsd_gtls) CODESTARTobjQueryInterface(nsd_gtls) if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); } /* ok, we have the right interface, so let's fill it * Please note that we may also do some backwards-compatibility * work here (if we can support an older interface version - that, * of course, also affects the "if" above). */ pIf->Construct = (rsRetVal(*)(nsd_t**)) nsd_gtlsConstruct; pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_gtlsDestruct; pIf->Abort = Abort; pIf->LstnInit = LstnInit; pIf->AcceptConnReq = AcceptConnReq; pIf->Rcv = Rcv; pIf->Send = Send; pIf->Connect = Connect; pIf->SetSock = SetSock; pIf->SetMode = SetMode; pIf->SetAuthMode = SetAuthMode; pIf->SetPermitExpiredCerts = SetPermitExpiredCerts; pIf->SetPermPeers =SetPermPeers; pIf->CheckConnection = CheckConnection; pIf->GetRemoteHName = GetRemoteHName; pIf->GetRemoteIP = GetRemoteIP; pIf->GetRemAddr = GetRemAddr; pIf->EnableKeepAlive = EnableKeepAlive; pIf->SetKeepAliveIntvl = SetKeepAliveIntvl; pIf->SetKeepAliveProbes = SetKeepAliveProbes; pIf->SetKeepAliveTime = SetKeepAliveTime; pIf->SetGnutlsPriorityString = SetGnutlsPriorityString; pIf->SetCheckExtendedKeyUsage = SetCheckExtendedKeyUsage; pIf->SetPrioritizeSAN = SetPrioritizeSAN; pIf->SetTlsVerifyDepth = SetTlsVerifyDepth; pIf->SetTlsCAFile = SetTlsCAFile; pIf->SetTlsCRLFile = SetTlsCRLFile; pIf->SetTlsKeyFile = SetTlsKeyFile; pIf->SetTlsCertFile = SetTlsCertFile; finalize_it: ENDobjQueryInterface(nsd_gtls) /* exit our class */ BEGINObjClassExit(nsd_gtls, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ CODESTARTObjClassExit(nsd_gtls) gtlsGlblExit(); /* shut down GnuTLS */ /* release objects we no longer need */ objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME); objRelease(net, LM_NET_FILENAME); objRelease(glbl, CORE_COMPONENT); objRelease(datetime, CORE_COMPONENT); ENDObjClassExit(nsd_gtls) /* Initialize the nsd_gtls class. Must be called as the very first method * before anything else is called inside this class. * rgerhards, 2008-02-19 */ BEGINObjClassInit(nsd_gtls, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ /* request objects we use */ CHKiRet(objUse(datetime, CORE_COMPONENT)); CHKiRet(objUse(glbl, CORE_COMPONENT)); CHKiRet(objUse(net, LM_NET_FILENAME)); CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME)); /* now do global TLS init stuff */ CHKiRet(gtlsGlblInit()); ENDObjClassInit(nsd_gtls) /* --------------- here now comes the plumbing that makes as a library module --------------- */ BEGINmodExit CODESTARTmodExit nsdsel_gtlsClassExit(); nsd_gtlsClassExit(); pthread_mutex_destroy(&mutGtlsStrerror); ENDmodExit BEGINqueryEtryPt CODESTARTqueryEtryPt CODEqueryEtryPt_STD_LIB_QUERIES ENDqueryEtryPt BEGINmodInit() CODESTARTmodInit *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ /* Initialize all classes that are in our module - this includes ourselfs */ CHKiRet(nsd_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ CHKiRet(nsdsel_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ pthread_mutex_init(&mutGtlsStrerror, NULL); ENDmodInit /* vi:set ai: */