mirror of
https://github.com/rsyslog/rsyslog.git
synced 2025-12-20 12:00:43 +01:00
While this sounds scary, the situation can not happen in practice. We use non-blocking IO only for server-based gtls session setup. As TLS requires the exchange of multiple frames before the handshake completes, it simply is impossible to do this in one step. However, it is useful to have the code path correct even for this case - otherwise, we may run into problems if the code is changed some time later (e.g. to use blocking sockets). Signed-off-by: Rainer Gerhards <rgerhards@adiscon.com>
1712 lines
49 KiB
C
1712 lines
49 KiB
C
/* nsd_gtls.c
|
|
*
|
|
* An implementation of the nsd interface for GnuTLS.
|
|
*
|
|
* Copyright (C) 2007, 2008 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <gnutls/gnutls.h>
|
|
#include <gnutls/x509.h>
|
|
#include <gcrypt.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 "cfsysline.h"
|
|
#include "obj.h"
|
|
#include "stringbuf.h"
|
|
#include "errmsg.h"
|
|
#include "net.h"
|
|
#include "nsd_ptcp.h"
|
|
#include "nsdsel_gtls.h"
|
|
#include "nsd_gtls.h"
|
|
|
|
/* things to move to some better place/functionality - TODO */
|
|
#define DH_BITS 1024
|
|
#define CRLFILE "crl.pem"
|
|
|
|
|
|
GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
|
MODULE_TYPE_LIB
|
|
|
|
/* static data */
|
|
DEFobjStaticHelpers
|
|
DEFobjCurrIf(errmsg)
|
|
DEFobjCurrIf(glbl)
|
|
DEFobjCurrIf(net)
|
|
DEFobjCurrIf(nsd_ptcp)
|
|
|
|
static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - already done */
|
|
|
|
static pthread_mutex_t mutGtlsStrerror; /**< a mutex protecting the potentially non-reentrant gtlStrerror() function */
|
|
|
|
/* a macro to check GnuTLS calls against unexpected errors */
|
|
#define CHKgnutls(x) \
|
|
if((gnuRet = (x)) != 0) { \
|
|
uchar *pErr = gtlsStrerror(gnuRet); \
|
|
dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); \
|
|
free(pErr); \
|
|
ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \
|
|
}
|
|
|
|
|
|
/* ------------------------------ GnuTLS specifics ------------------------------ */
|
|
static gnutls_certificate_credentials xcred;
|
|
static gnutls_dh_params dh_params;
|
|
|
|
#ifdef DEBUG
|
|
/* 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);
|
|
}
|
|
#endif /* #ifdef DEBUG */
|
|
|
|
|
|
/* 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(uchar *pszFile, gnutls_datum_t *pBuf)
|
|
{
|
|
int fd;
|
|
struct stat stat_st;
|
|
DEFiRet;
|
|
|
|
assert(pszFile != NULL);
|
|
assert(pBuf != NULL);
|
|
|
|
pBuf->data = NULL;
|
|
|
|
if((fd = open((char*)pszFile, 0)) == -1) {
|
|
errmsg.LogError(0, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile);
|
|
ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
|
|
|
|
}
|
|
|
|
if(fstat(fd, &stat_st) == -1) {
|
|
errmsg.LogError(0, 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) {
|
|
errmsg.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) {
|
|
errmsg.LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile);
|
|
ABORT_FINALIZE(RS_RET_IO_ERROR);
|
|
}
|
|
|
|
close(fd);
|
|
|
|
finalize_it:
|
|
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 };
|
|
uchar *keyFile;
|
|
uchar *certFile;
|
|
|
|
ISOBJ_TYPE_assert(pThis, nsd_gtls);
|
|
|
|
certFile = glbl.GetDfltNetstrmDrvrCertFile();
|
|
keyFile = glbl.GetDfltNetstrmDrvrKeyFile();
|
|
|
|
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("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));
|
|
CHKgnutls(gnutls_x509_crt_init(&pThis->ourCert));
|
|
pThis->bOurCertIsInit = 1;
|
|
CHKgnutls(gnutls_x509_crt_import(pThis->ourCert, &data, GNUTLS_X509_FMT_PEM));
|
|
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_OK) {
|
|
if(data.data != NULL)
|
|
free(data.data);
|
|
if(pThis->bOurCertIsInit)
|
|
gnutls_x509_crt_deinit(pThis->ourCert);
|
|
if(pThis->bOurKeyIsInit)
|
|
gnutls_x509_privkey_deinit(pThis->ourKey);
|
|
}
|
|
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 session,
|
|
__attribute__((unused)) const gnutls_datum* req_ca_rdn, int __attribute__((unused)) nreqs,
|
|
__attribute__((unused)) const gnutls_pk_algorithm* sign_algos, int __attribute__((unused)) sign_algos_length,
|
|
gnutls_retr_st *st)
|
|
{
|
|
nsd_gtls_t *pThis;
|
|
|
|
pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session);
|
|
|
|
st->type = GNUTLS_CRT_X509;
|
|
st->ncerts = 1;
|
|
st->cert.x509 = &pThis->ourCert;
|
|
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 *pThis, cstr_t **ppStr)
|
|
{
|
|
char dn[128];
|
|
uchar lnBuf[256];
|
|
size_t size;
|
|
unsigned int algo, bits;
|
|
time_t expiration_time, activation_time;
|
|
const gnutls_datum *cert_list;
|
|
unsigned cert_list_size = 0;
|
|
gnutls_x509_crt cert;
|
|
cstr_t *pStr = NULL;
|
|
int gnuRet;
|
|
DEFiRet;
|
|
unsigned iAltName;
|
|
size_t szAltNameLen;
|
|
char szAltName[1024]; /* this is sufficient for the DNSNAME... */
|
|
|
|
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(rsCStrConstruct(&pStr));
|
|
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "peer provided %d certificate(s). ", cert_list_size);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
|
|
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));
|
|
|
|
CHKiRet(rsCStrAppendStr(pStr, (uchar*)"Certificate 1 info: "));
|
|
|
|
expiration_time = gnutls_x509_crt_get_expiration_time(cert);
|
|
activation_time = gnutls_x509_crt_get_activation_time(cert);
|
|
ctime_r(&activation_time, dn);
|
|
dn[strlen(dn) - 1] = '\0'; /* strip linefeed */
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "certificate valid from %s ", dn);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
|
|
ctime_r(&expiration_time, dn);
|
|
dn[strlen(dn) - 1] = '\0'; /* strip linefeed */
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "to %s; ", dn);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
|
|
/* Extract some of the public key algorithm's parameters */
|
|
algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits);
|
|
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "Certificate public key: %s; ",
|
|
gnutls_pk_algorithm_get_name(algo));
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
|
|
/* names */
|
|
size = sizeof(dn);
|
|
gnutls_x509_crt_get_dn(cert, dn, &size);
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "DN: %s; ", dn);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
|
|
size = sizeof(dn);
|
|
gnutls_x509_crt_get_issuer_dn(cert, dn, &size);
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "Issuer DN: %s; ", dn);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
|
|
/* dNSName alt name */
|
|
iAltName = 0;
|
|
while(1) { /* loop broken below */
|
|
szAltNameLen = sizeof(szAltName);
|
|
gnuRet = gnutls_x509_crt_get_subject_alt_name(cert, iAltName,
|
|
szAltName, &szAltNameLen, NULL);
|
|
if(gnuRet < 0)
|
|
break;
|
|
else if(gnuRet == GNUTLS_SAN_DNSNAME) {
|
|
/* we found it! */
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "SAN:DNSname: %s; ", szAltName);
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
/* do NOT break, because there may be multiple dNSName's! */
|
|
}
|
|
++iAltName;
|
|
}
|
|
|
|
gnutls_x509_crt_deinit(cert);
|
|
}
|
|
|
|
CHKiRet(rsCStrFinish(pStr));
|
|
*ppStr = pStr;
|
|
|
|
finalize_it:
|
|
if(iRet != RS_RET_OK) {
|
|
if(pStr != NULL)
|
|
rsCStrDestruct(&pStr);
|
|
}
|
|
|
|
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)
|
|
{
|
|
cstr_t *pStr = NULL;
|
|
uchar buf[4];
|
|
size_t i;
|
|
DEFiRet;
|
|
|
|
CHKiRet(rsCStrConstruct(&pStr));
|
|
CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4));
|
|
for(i = 0 ; i < sizeFingerprint ; ++i) {
|
|
snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]);
|
|
CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3));
|
|
}
|
|
CHKiRet(rsCStrFinish(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);
|
|
lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF);
|
|
if(lenRcvd >= 0) {
|
|
pThis->lenRcvBuf = lenRcvd;
|
|
pThis->ptrRcvBuf = 0;
|
|
} else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) {
|
|
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 {
|
|
int gnuRet; /* TODO: build a specific function for GnuTLS error reporting */
|
|
CHKgnutls(lenRcvd); /* this will abort the function */
|
|
}
|
|
|
|
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(void)
|
|
{
|
|
int gnuRet;
|
|
uchar *keyFile;
|
|
uchar *certFile;
|
|
uchar *pGnuErr; /* for GnuTLS error reporting */
|
|
DEFiRet;
|
|
|
|
certFile = glbl.GetDfltNetstrmDrvrCertFile();
|
|
keyFile = glbl.GetDfltNetstrmDrvrKeyFile();
|
|
dbgprintf("GTLS certificate file: '%s'\n", certFile);
|
|
dbgprintf("GTLS key file: '%s'\n", keyFile);
|
|
CHKgnutls(gnutls_certificate_set_x509_key_file(xcred, (char*)certFile, (char*)keyFile, GNUTLS_X509_FMT_PEM));
|
|
|
|
finalize_it:
|
|
if(iRet != RS_RET_OK) {
|
|
pGnuErr = gtlsStrerror(gnuRet);
|
|
errno = 0;
|
|
errmsg.LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', "
|
|
"key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile);
|
|
free(pGnuErr);
|
|
}
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
/* globally initialize GnuTLS */
|
|
static rsRetVal
|
|
gtlsGlblInit(void)
|
|
{
|
|
int gnuRet;
|
|
uchar *cafile;
|
|
DEFiRet;
|
|
|
|
/* gcry_control must be called first, so that the thread system is correctly set up */
|
|
gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
|
|
CHKgnutls(gnutls_global_init());
|
|
|
|
/* X509 stuff */
|
|
CHKgnutls(gnutls_certificate_allocate_credentials(&xcred));
|
|
|
|
/* sets the trusted cas file */
|
|
cafile = glbl.GetDfltNetstrmDrvrCAF();
|
|
dbgprintf("GTLS CA file: '%s'\n", cafile);
|
|
gnuRet = gnutls_certificate_set_x509_trust_file(xcred, (char*)cafile, GNUTLS_X509_FMT_PEM);
|
|
if(gnuRet < 0) {
|
|
/* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */
|
|
uchar *pErr = gtlsStrerror(gnuRet);
|
|
dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr);
|
|
free(pErr);
|
|
ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
|
|
}
|
|
|
|
# ifdef DEBUG
|
|
#if 0 /* do this in special cases only. WARNING: if active, it may reveal sensitive information! */
|
|
/* intialize log function - set a level only for hard-to-find bugs */
|
|
gnutls_global_set_log_function(logFunction);
|
|
gnutls_global_set_log_level(10); /* 0 (no) to 9 (most), 10 everything */
|
|
# endif
|
|
# endif
|
|
|
|
finalize_it:
|
|
RETiRet;
|
|
}
|
|
|
|
static rsRetVal
|
|
gtlsInitSession(nsd_gtls_t *pThis)
|
|
{
|
|
DEFiRet;
|
|
int gnuRet;
|
|
gnutls_session session;
|
|
|
|
gnutls_init(&session, GNUTLS_SERVER);
|
|
pThis->bHaveSess = 1;
|
|
pThis->bIsInitiator = 0;
|
|
|
|
/* avoid calling all the priority functions, since the defaults are adequate. */
|
|
CHKgnutls(gnutls_set_default_priority(session));
|
|
CHKgnutls(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred));
|
|
|
|
/* request client certificate if any. */
|
|
gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUEST);
|
|
gnutls_dh_set_prime_bits(session, DH_BITS);
|
|
|
|
pThis->sess = session;
|
|
|
|
finalize_it:
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
static rsRetVal
|
|
generate_dh_params(void)
|
|
{
|
|
int gnuRet;
|
|
DEFiRet;
|
|
/* Generate Diffie Hellman parameters - for use with DHE
|
|
* kx algorithms. These should be discarded and regenerated
|
|
* once a day, once a week or once a month. Depending on the
|
|
* security requirements.
|
|
*/
|
|
CHKgnutls(gnutls_dh_params_init( &dh_params));
|
|
CHKgnutls(gnutls_dh_params_generate2( dh_params, DH_BITS));
|
|
finalize_it:
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
/* set up all global things that are needed for server operations
|
|
* rgerhards, 2008-04-30
|
|
*/
|
|
static rsRetVal
|
|
gtlsGlblInitLstn(void)
|
|
{
|
|
DEFiRet;
|
|
|
|
if(bGlblSrvrInitDone == 0) {
|
|
/* we do not use CRLs right now, and I doubt we'll ever do. This functionality is
|
|
* considered legacy. -- rgerhards, 2008-05-05
|
|
*/
|
|
/*CHKgnutls(gnutls_certificate_set_x509_crl_file(xcred, CRLFILE, GNUTLS_X509_FMT_PEM));*/
|
|
CHKiRet(generate_dh_params());
|
|
gnutls_certificate_set_dh_params(xcred, dh_params); /* this is void */
|
|
bGlblSrvrInitDone = 1; /* we are all set now */
|
|
|
|
/* now we need to add our certificate */
|
|
CHKiRet(gtlsAddOurCert());
|
|
}
|
|
|
|
finalize_it:
|
|
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(nsd_gtls_t *pThis, gnutls_x509_crt *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... */
|
|
|
|
ISOBJ_TYPE_assert(pThis, nsd_gtls);
|
|
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(rsCStrConstruct(&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(rsCStrAppendChar(pstrCN, szDN[i]));
|
|
} else {
|
|
CHKiRet(rsCStrAppendChar(pstrCN, szDN[i]));
|
|
}
|
|
++i; /* char processed */
|
|
}
|
|
CHKiRet(rsCStrFinish(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)
|
|
rsCStrDestruct(&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 *pCert)
|
|
{
|
|
uchar fingerprint[20];
|
|
size_t size;
|
|
cstr_t *pstrFingerprint = NULL;
|
|
int bFoundPositiveMatch;
|
|
permittedPeers_t *pPeer;
|
|
int gnuRet;
|
|
DEFiRet;
|
|
|
|
ISOBJ_TYPE_assert(pThis, nsd_gtls);
|
|
|
|
/* obtain the SHA1 fingerprint */
|
|
size = sizeof(fingerprint);
|
|
CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size));
|
|
CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint));
|
|
dbgprintf("peer's certificate SHA1 fingerprint: %s\n", rsCStrGetSzStr(pstrFingerprint));
|
|
|
|
/* 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))) {
|
|
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;
|
|
errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are "
|
|
"not permitted to talk to it", rsCStrGetSzStr(pstrFingerprint));
|
|
pThis->bReportAuthErr = 0;
|
|
}
|
|
ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
|
|
}
|
|
|
|
finalize_it:
|
|
if(pstrFingerprint != NULL)
|
|
rsCStrDestruct(&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 *pCert)
|
|
{
|
|
uchar lnBuf[256];
|
|
char szAltName[1024]; /* this is sufficient for the DNSNAME... */
|
|
int iAltName;
|
|
size_t szAltNameLen;
|
|
int bFoundPositiveMatch;
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
if(!bFoundPositiveMatch) {
|
|
/* if we did not succeed so far, we try the CN part of the DN... */
|
|
CHKiRet(gtlsGetCN(pThis, pCert, &pstrCN));
|
|
if(pstrCN != NULL) { /* NULL if there was no CN present */
|
|
dbgprintf("gtls now checking auth for CN '%s'\n", rsCStrGetSzStr(pstrCN));
|
|
snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", rsCStrGetSzStr(pstrCN));
|
|
CHKiRet(rsCStrAppendStr(pStr, lnBuf));
|
|
CHKiRet(gtlsChkOnePeerName(pThis, rsCStrGetSzStr(pstrCN), &bFoundPositiveMatch));
|
|
}
|
|
}
|
|
|
|
if(!bFoundPositiveMatch) {
|
|
dbgprintf("invalid peer name, not permitted to talk to it\n");
|
|
if(pThis->bReportAuthErr == 1) {
|
|
CHKiRet(rsCStrFinish(pStr));
|
|
errno = 0;
|
|
errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized - "
|
|
"not permitted to talk to it. Names: %s",
|
|
rsCStrGetSzStr(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 *cert_list;
|
|
unsigned int list_size = 0;
|
|
gnutls_x509_crt 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) {
|
|
errno = 0;
|
|
errmsg.LogError(0, RS_RET_TLS_NO_CERT, "error: peer did not provide a certificate, "
|
|
"not permitted to talk to it");
|
|
pThis->bReportAuthErr = 0;
|
|
}
|
|
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;
|
|
char *pszErrCause;
|
|
int gnuRet;
|
|
cstr_t *pStr;
|
|
unsigned stateCert;
|
|
const gnutls_datum *cert_list;
|
|
unsigned cert_list_size = 0;
|
|
gnutls_x509_crt cert;
|
|
unsigned i;
|
|
time_t ttCert;
|
|
time_t ttNow;
|
|
|
|
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;
|
|
errmsg.LogError(0, RS_RET_TLS_NO_CERT, "peer did not provide a certificate, not permitted to talk to it");
|
|
ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
|
|
}
|
|
|
|
CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert));
|
|
|
|
if(stateCert & GNUTLS_CERT_INVALID) {
|
|
/* provide error details if we have them */
|
|
if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) {
|
|
pszErrCause = "signer not found";
|
|
} else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) {
|
|
pszErrCause = "signer is not a CA";
|
|
} else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) {
|
|
pszErrCause = "insecure algorithm";
|
|
} else if(stateCert & GNUTLS_CERT_REVOKED) {
|
|
pszErrCause = "certificate revoked";
|
|
} else {
|
|
pszErrCause = "GnuTLS returned no specific reason";
|
|
dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate "
|
|
"status is %d\n", stateCert);
|
|
}
|
|
errmsg.LogError(0, NO_ERRCODE, "not permitted to talk to peer, certificate invalid: %s",
|
|
pszErrCause);
|
|
gtlsGetCertInfo(pThis, &pStr);
|
|
errmsg.LogError(0, NO_ERRCODE, "invalid cert info: %s", rsCStrGetSzStr(pStr));
|
|
rsCStrDestruct(&pStr);
|
|
ABORT_FINALIZE(RS_RET_CERT_INVALID);
|
|
}
|
|
|
|
/* get current time for certificate validation */
|
|
if(time(&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) {
|
|
errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer: certificate %d not yet active", i);
|
|
gtlsGetCertInfo(pThis, &pStr);
|
|
errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "invalid cert info: %s", rsCStrGetSzStr(pStr));
|
|
rsCStrDestruct(&pStr);
|
|
ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE);
|
|
}
|
|
|
|
ttCert = gnutls_x509_crt_get_expiration_time(cert);
|
|
if(ttCert == -1)
|
|
ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
|
|
else if(ttCert < ttNow) {
|
|
errmsg.LogError(0, RS_RET_CERT_EXPIRED, "not permitted to talk to peer: certificate %d expired", i);
|
|
gtlsGetCertInfo(pThis, &pStr);
|
|
errmsg.LogError(0, RS_RET_CERT_EXPIRED, "invalid cert info: %s", rsCStrGetSzStr(pStr));
|
|
rsCStrDestruct(&pStr);
|
|
ABORT_FINALIZE(RS_RET_CERT_EXPIRED);
|
|
}
|
|
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;
|
|
/* X509 stuff */
|
|
gnutls_certificate_free_credentials(xcred);
|
|
gnutls_global_deinit(); /* we are done... */
|
|
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_RDWR);
|
|
while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) {
|
|
gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR);
|
|
}
|
|
}
|
|
gnutls_deinit(pThis->sess);
|
|
}
|
|
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 */
|
|
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);
|
|
}
|
|
|
|
if(pThis->pszConnectHost != NULL) {
|
|
free(pThis->pszConnectHost);
|
|
}
|
|
|
|
if(pThis->pszRcvBuf == NULL) {
|
|
free(pThis->pszRcvBuf);
|
|
}
|
|
|
|
if(pThis->bOurCertIsInit)
|
|
gnutls_x509_crt_deinit(pThis->ourCert);
|
|
if(pThis->bOurKeyIsInit)
|
|
gnutls_x509_privkey_deinit(pThis->ourKey);
|
|
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 *pNsd, int mode)
|
|
{
|
|
DEFiRet;
|
|
nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
|
|
|
|
ISOBJ_TYPE_assert((pThis), nsd_gtls);
|
|
if(mode != 0 && mode != 1) {
|
|
errmsg.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 {
|
|
errmsg.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);
|
|
}
|
|
|
|
/* TODO: clear stored IDs! */
|
|
|
|
finalize_it:
|
|
dbgprintf("gtls auth mode %d set\n", pThis->authMode);
|
|
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) {
|
|
errmsg.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;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
|
|
/* 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
|
|
LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
|
|
uchar *pLstnPort, uchar *pLstnIP, int iSessMax)
|
|
{
|
|
DEFiRet;
|
|
CHKiRet(gtlsGlblInitLstn());
|
|
iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax);
|
|
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 void
|
|
CheckConnection(nsd_t __attribute__((unused)) *pNsd)
|
|
{
|
|
/* dummy, do nothing */
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
/* get the remote host's IP address. The returned string must be freed by the
|
|
* caller. -- rgerhards, 2008-04-25
|
|
*/
|
|
static rsRetVal
|
|
GetRemoteIP(nsd_t *pNsd, uchar **ppszIP)
|
|
{
|
|
DEFiRet;
|
|
nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
|
|
ISOBJ_TYPE_assert(pThis, nsd_gtls);
|
|
iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ppszIP);
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
/* if we reach this point, we are in TLS mode */
|
|
CHKiRet(gtlsInitSession(pNew));
|
|
gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock);
|
|
pNew->authMode = pThis->authMode;
|
|
pNew->pPermPeers = pThis->pPermPeers;
|
|
|
|
/* 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 {
|
|
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(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)
|
|
{
|
|
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));
|
|
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 */
|
|
*pLenBuf = 0;
|
|
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:
|
|
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;
|
|
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) {
|
|
dbgprintf("unexpected GnuTLS error %d in %s:%d\n", iSent, __FILE__, __LINE__);
|
|
gnutls_perror(iSent); /* TODO: can we do better? */
|
|
ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
|
|
}
|
|
}
|
|
|
|
finalize_it:
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
/* 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
|
|
*/
|
|
static rsRetVal
|
|
Connect(nsd_t *pNsd, int family, uchar *port, uchar *host)
|
|
{
|
|
nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
|
|
int sock;
|
|
int gnuRet;
|
|
/* TODO: later? static const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };*/
|
|
static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 };
|
|
DEFiRet;
|
|
|
|
ISOBJ_TYPE_assert(pThis, nsd_gtls);
|
|
assert(port != NULL);
|
|
assert(host != NULL);
|
|
|
|
CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host));
|
|
|
|
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;
|
|
|
|
/* 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) {
|
|
gnutls_certificate_client_set_retrieve_function(xcred, gtlsClientCertCallback);
|
|
} else if(iRet != RS_RET_CERTLESS) {
|
|
FINALIZE; /* we have an error case! */
|
|
}
|
|
|
|
/* Use default priorities */
|
|
CHKgnutls(gnutls_set_default_priority(pThis->sess));
|
|
CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority));
|
|
|
|
/* put the x509 credentials to the current session */
|
|
CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, xcred));
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
/* 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->SetPermPeers =SetPermPeers;
|
|
pIf->CheckConnection = CheckConnection;
|
|
pIf->GetRemoteHName = GetRemoteHName;
|
|
pIf->GetRemoteIP = GetRemoteIP;
|
|
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(errmsg, 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(errmsg, 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:
|
|
*/
|