From 68a2c3d512615f217d8c6454a679849083c80f00 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 21 May 2008 14:59:24 +0200 Subject: implemented x509/certvalid "authentication" --- doc/ns_gtls.html | 13 ++- runtime/nsd_gtls.c | 273 +++++++++++++++++++++++++++++++++++++++++++++++++- runtime/nsd_gtls.h | 4 +- runtime/nsdsel_gtls.c | 2 +- runtime/rsyslog.h | 1 + 5 files changed, 286 insertions(+), 7 deletions(-) diff --git a/doc/ns_gtls.html b/doc/ns_gtls.html index 46e2e238..46671f4a 100644 --- a/doc/ns_gtls.html +++ b/doc/ns_gtls.html @@ -24,6 +24,8 @@ described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft
  • x509/fingerprint - certificate fingerprint authentication as described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft
  • +
  • x509/certvalid +- certificate validation only
  • x509/name - certificate validation and subject name authentication as described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft @@ -31,8 +33,13 @@ described in IETF's draft-ietf-syslog-transport-tls-12 Internet draft Note: "anon" does not permit to authenticate the remote peer. As such, this mode is vulnerable to man in the middle attacks as well as -unauthorized access. It is recommended NOT to use this mode.
    -
    +unauthorized access. It is recommended NOT to use this mode.

    +

    x509/certvalid is a nonstandard mode. It validates the remote +peers certificate, but does not check the subject name. This is +weak authentication that may be useful in scenarios where multiple +devices are deployed and it is sufficient proof of authenticy when +their certificates are signed by the CA the server trusts. This is +better than anon authentication, but still not recommended. Known Problems

    Even in x509/fingerprint mode, both the client and sever certificate currently must be signed by the same root CA. This is an @@ -48,4 +55,4 @@ Copyright Gerhards and Adiscon. Released under the GNU GPL version 3 or higher.

    - \ No newline at end of file + diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index fd7a502a..b5431a2c 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -74,6 +74,182 @@ static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - static gnutls_certificate_credentials xcred; static gnutls_dh_params dh_params; +/* 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; + + 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 */ + 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)); + + 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 @@ -253,7 +429,7 @@ finalize_it: /* check the fingerprint of the remote peer's certificate. * rgerhards, 2008-05-08 */ -rsRetVal +static rsRetVal gtlsChkFingerprint(nsd_gtls_t *pThis) { cstr_t *pstrFingerprint = NULL; @@ -334,6 +510,96 @@ dbgprintf("exit fingerprint check, iRet %d\n", iRet); } +/* 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; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + gnuRet = gnutls_certificate_verify_peers(pThis->sess); + if(gnuRet < 1) + CHKgnutls(gnuRet); + + if(gnuRet & GNUTLS_CERT_INVALID) { + /* provide error details if we have them */ + if(gnuRet & GNUTLS_CERT_SIGNER_NOT_FOUND) { + pszErrCause = "signer not found"; + } else if(gnuRet & GNUTLS_CERT_SIGNER_NOT_FOUND) { + pszErrCause = "signer is not a CA"; + } else if(gnuRet & GNUTLS_CERT_SIGNER_NOT_CA) { + pszErrCause = "insecure algorithm"; + } else if(gnuRet & GNUTLS_CERT_REVOKED) { + pszErrCause = "certificate revoked"; + } else { + pszErrCause = "no specific reason"; + } + errmsg.LogError(NO_ERRCODE, "not permitted to talk to peer, certificate invalid: %s", + pszErrCause); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(NO_ERRCODE, "info on invalid cert: %s", rsCStrGetSzStr(pStr)); + rsCStrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_INVALID); + } + +finalize_it: + RETiRet; +} + + +/* Perform a name check on the remote peer. This includes certificate + * validity checking. + * rgerhards, 2008-05-21 + */ +static rsRetVal +gtlsChkPeerName(nsd_gtls_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + CHKiRet(gtlsChkPeerCertValidity(pThis)); + +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: + CHKiRet(gtlsChkPeerName(pThis)); + break; + case GTLS_AUTH_CERTFINGERPRINT: + CHKiRet(gtlsChkFingerprint(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) @@ -434,6 +700,7 @@ finalize_it: /* 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 @@ -450,6 +717,8 @@ SetAuthMode(nsd_t *pNsd, uchar *mode) 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 { @@ -756,7 +1025,7 @@ Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) dbgprintf("GnuTLS handshake succeeded\n"); /* now check if the remote peer is permitted to talk to us */ - CHKiRet(gtlsChkFingerprint(pThis)); + CHKiRet(gtlsChkPeerAuth(pThis)); finalize_it: if(iRet != RS_RET_OK) { diff --git a/runtime/nsd_gtls.h b/runtime/nsd_gtls.h index 1f3eb6b1..59109e68 100644 --- a/runtime/nsd_gtls.h +++ b/runtime/nsd_gtls.h @@ -42,7 +42,8 @@ struct nsd_gtls_s { enum { GTLS_AUTH_CERTNAME = 0, GTLS_AUTH_CERTFINGERPRINT = 1, - GTLS_AUTH_CERTANON = 2 + GTLS_AUTH_CERTVALID = 2, + GTLS_AUTH_CERTANON = 3 } authMode; gtlsRtryCall_t rtryCall;/**< what must we retry? */ int bIsInitiator; /**< 0 if socket is the server end (listener), 1 if it is the initiator */ @@ -62,6 +63,7 @@ struct nsd_gtls_s { PROTOTYPEObj(nsd_gtls); /* some prototypes for things used by our nsdsel_gtls helper class */ uchar *gtlsStrerror(int error); +rsRetVal gtlsChkPeerAuth(nsd_gtls_t *pThis); /* the name of our library binary */ #define LM_NSD_GTLS_FILENAME "lmnsd_gtls" diff --git a/runtime/nsdsel_gtls.c b/runtime/nsdsel_gtls.c index 96456564..7b359950 100644 --- a/runtime/nsdsel_gtls.c +++ b/runtime/nsdsel_gtls.c @@ -131,7 +131,7 @@ doRetry(nsd_gtls_t *pNsd) if(gnuRet == 0) { pNsd->rtryCall = gtlsRtry_None; /* we are done */ /* we got a handshake, now check authorization */ - CHKiRet(gtlsChkFingerprint(pNsd)); + CHKiRet(gtlsChkPeerAuth(pNsd)); } break; default: diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index c06b01c3..dfa14f35 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -229,6 +229,7 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_VALUE_NOT_IN_THIS_MODE = -2087, /**< a provided value is invalid for the curret mode */ RS_RET_INVALID_FINGERPRINT = -2088, /**< a fingerprint is not valid for this use case */ RS_RET_CONNECTION_ABORTREQ = -2089, /**< connection was abort requested due to previous error */ + RS_RET_CERT_INVALID = -2090, /**< a x509 certificate failed validation */ /* RainerScript error messages (range 1000.. 1999) */ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ -- cgit v1.2.3 From 8c927a854e9afcaf5e1dd0ff6d69e353256ac8a0 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 21 May 2008 18:04:54 +0200 Subject: fixed invalid prototype --- runtime/netstrms.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/netstrms.c b/runtime/netstrms.c index 3e5b7819..b060d5c2 100644 --- a/runtime/netstrms.c +++ b/runtime/netstrms.c @@ -160,7 +160,7 @@ SetDrvrPermPeers(netstrms_t *pThis, permittedPeers_t *pPermPeers) * of sense here. * rgerhards, 2008-05-19 */ -static uchar* +static permittedPeers_t* GetDrvrPermPeers(netstrms_t *pThis) { ISOBJ_TYPE_assert(pThis, netstrms); -- cgit v1.2.3 From 0b2e858a42e6ca49e68570c9b13ede74493e48db Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 21 May 2008 18:18:20 +0200 Subject: added code to pull the subjectAltName - dNSName --- runtime/netstrms.c | 1 - runtime/nsd_gtls.c | 25 +++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/runtime/netstrms.c b/runtime/netstrms.c index b060d5c2..2b754ecc 100644 --- a/runtime/netstrms.c +++ b/runtime/netstrms.c @@ -174,7 +174,6 @@ SetDrvrAuthMode(netstrms_t *pThis, uchar *mode) { DEFiRet; ISOBJ_TYPE_assert(pThis, netstrms); -RUNLOG_VAR("%s", mode); CHKmalloc(pThis->pszDrvrAuthMode = (uchar*)strdup((char*)mode)); finalize_it: RETiRet; diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index b5431a2c..525a6374 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -94,6 +94,9 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) cstr_t *pStr = NULL; int gnuRet; DEFiRet; + unsigned iAltName; + char szAltName[1024]; /* this is sufficient for the DNSNAME... */ + size_t szAltNameLen; assert(ppStr != NULL); ISOBJ_TYPE_assert(pThis, nsd_gtls); @@ -144,10 +147,28 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) size = sizeof(dn); gnutls_x509_crt_get_issuer_dn( cert, dn, &size); - snprintf((char*)lnBuf, sizeof(lnBuf), "Issuer DN: %s", dn); + snprintf((char*)lnBuf, sizeof(lnBuf), "Issuer DN: %s; ", dn); CHKiRet(rsCStrAppendStr(pStr, lnBuf)); - gnutls_x509_crt_deinit( cert); + /* 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)); -- cgit v1.2.3 From 57b203223506ab723e5c4fe46d56156a71adecde Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Thu, 22 May 2008 18:48:09 +0200 Subject: added x509/name authentication (so far based on dnsName only) --- runtime/nsd_gtls.c | 195 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 137 insertions(+), 58 deletions(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index 525a6374..59cd398c 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -141,12 +141,12 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) /* names */ size = sizeof(dn); - gnutls_x509_crt_get_dn( cert, dn, &size); + 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); + gnutls_x509_crt_get_issuer_dn(cert, dn, &size); snprintf((char*)lnBuf, sizeof(lnBuf), "Issuer DN: %s; ", dn); CHKiRet(rsCStrAppendStr(pStr, lnBuf)); @@ -167,7 +167,6 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) ++iAltName; } - gnutls_x509_crt_deinit(cert); } @@ -447,54 +446,25 @@ finalize_it: } -/* check the fingerprint of the remote peer's certificate. - * rgerhards, 2008-05-08 +/* Check the peer's ID in fingerprint auth mode. + * rgerhards, 2008-05-22 */ static rsRetVal -gtlsChkFingerprint(nsd_gtls_t *pThis) +gtlsChkPeerFingerprint(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) { - cstr_t *pstrFingerprint = NULL; uchar fingerprint[20]; size_t size; - const gnutls_datum *cert_list; - unsigned int list_size = 0; - gnutls_x509_crt cert; - int bMustDeinitCert = 0; - int gnuRet; + cstr_t *pstrFingerprint = NULL; int bFoundPositiveMatch; permittedPeers_t *pPeer; + int gnuRet; DEFiRet; ISOBJ_TYPE_assert(pThis, nsd_gtls); - /* first check if we need to do fingerprint authentication - if not, we - * are already set ;) -- rgerhards, 2008-05-21 - */ - if(pThis->authMode != GTLS_AUTH_CERTFINGERPRINT) - FINALIZE; - - /* 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) - 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). However, we do not match - * against some issuer fingerprint but only ourselfs. -- 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)); - /* obtain the SHA1 fingerprint */ size = sizeof(fingerprint); - CHKgnutls(gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, fingerprint, &size)); + 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)); @@ -521,9 +491,133 @@ gtlsChkFingerprint(nsd_gtls_t *pThis) } finalize_it: -dbgprintf("exit fingerprint check, iRet %d\n", iRet); if(pstrFingerprint != NULL) rsCStrDestruct(&pstrFingerprint); + 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; + permittedPeers_t *pPeer; + cstr_t *pStr = 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)); + /* we found it - now we need to loop through the list of permitted + * peer IDs. As soon as we have a positive match, we are all set. + */ + pPeer = pThis->pPermPeers; + while(pPeer != NULL && !bFoundPositiveMatch) { + if(!strcmp(szAltName, (char*)pPeer->pszID)) { + bFoundPositiveMatch = 1; + } else { + pPeer = pPeer->pNext; + } + } + /* do NOT break, because there may be multiple dNSName's! */ + } + ++iAltName; + } + + if(!bFoundPositiveMatch) { + dbgprintf("invalid peer name, not permitted to talk to it\n"); + if(pThis->bReportAuthErr == 1) { + CHKiRet(rsCStrFinish(pStr)); + errno = 0; + errmsg.LogError(NO_ERRCODE, "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); + 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(NO_ERRCODE, "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); @@ -573,23 +667,6 @@ finalize_it: } -/* Perform a name check on the remote peer. This includes certificate - * validity checking. - * rgerhards, 2008-05-21 - */ -static rsRetVal -gtlsChkPeerName(nsd_gtls_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, nsd_gtls); - CHKiRet(gtlsChkPeerCertValidity(pThis)); - -finalize_it: - RETiRet; -} - - /* check if it is OK to talk to the remote peer * rgerhards, 2008-05-21 */ @@ -603,10 +680,12 @@ gtlsChkPeerAuth(nsd_gtls_t *pThis) /* call the actual function based on current auth mode */ switch(pThis->authMode) { case GTLS_AUTH_CERTNAME: - CHKiRet(gtlsChkPeerName(pThis)); + /* if we check the name, we must ensure the cert is valid */ + CHKiRet(gtlsChkPeerCertValidity(pThis)); + CHKiRet(gtlsChkPeerID(pThis)); break; case GTLS_AUTH_CERTFINGERPRINT: - CHKiRet(gtlsChkFingerprint(pThis)); + CHKiRet(gtlsChkPeerID(pThis)); break; case GTLS_AUTH_CERTVALID: CHKiRet(gtlsChkPeerCertValidity(pThis)); -- cgit v1.2.3 From 492fb2ffe2541b0de30997ee188d0bc8c868f18d Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Thu, 22 May 2008 18:58:04 +0200 Subject: changed config directive name to reflect different use $ActionSendStreamDriverCertFingerprint is now $ActionSendStreamDriverPermittedPeer and can be used both for fingerprint and name authentication (similar to the input side) --- ChangeLog | 6 ++++++ doc/rsyslog_conf.html | 4 ++-- tools/omfwd.c | 38 +++++++++++++++----------------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index cc599b52..032d7b29 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +- implemented x509/certvalid gtls auth mode +- implemented x509/name gtls auth mode +- changed config directive name to reflect different use + $ActionSendStreamDriverCertFingerprint is now + $ActionSendStreamDriverPermittedPeer and can be used both for + fingerprint and name authentication (similar to the input side) --------------------------------------------------------------------------- Version 3.19.3 (rgerhards), 2008-05-?? - added ability to authenticate the server against its certificate diff --git a/doc/rsyslog_conf.html b/doc/rsyslog_conf.html index 8cd79cd1..efb3ad0c 100644 --- a/doc/rsyslog_conf.html +++ b/doc/rsyslog_conf.html @@ -123,7 +123,7 @@ default 60000 (1 minute)]
  • $ActionSendStreamDriver <driver basename> just like $DefaultNetstreamDriver, but for the specific action
  • $ActionSendStreamDriverMode <mode>, default 0, mode to use with the stream driver (driver-specific)
  • $ActionSendStreamDriverAuthMode <mode>,  authentication mode to use with the stream driver -(driver-specific)
  • $ActionSendStreamDriverCertFingerprint <sha1-fingerprint>,  accepted fingerprint +(driver-specific)
  • $ActionSendStreamDriverPermittedPeer <ID>,  accepted fingerprint (SHA1) or name of remote peer (driver-specific) - directive may go away!
  • $AllowedSender
  • $ControlCharacterEscapePrefix
  • @@ -1201,4 +1201,4 @@ additional and database support). For obvious reasons, the syntax for defining such features is available in rsyslogd, only.
     

    - \ No newline at end of file + diff --git a/tools/omfwd.c b/tools/omfwd.c index a902fe3b..6544c2ba 100644 --- a/tools/omfwd.c +++ b/tools/omfwd.c @@ -79,8 +79,7 @@ typedef struct _instanceData { netstrm_t *pNetstrm; /* our output netstream */ uchar *pszStrmDrvr; uchar *pszStrmDrvrAuthMode; - permittedPeers_t *pPermPeersRootFingerprint; - permittedPeers_t *pPermPeersRootNames; + permittedPeers_t *pPermPeers; int iStrmDrvrMode; char *f_hname; int *pSockArray; /* sockets to use for UDP */ @@ -101,8 +100,7 @@ static uchar *pszStrmDrvr = NULL; /* name of the stream driver to use */ static int iStrmDrvrMode = 0; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */ static uchar *pszStrmDrvrAuthMode = NULL; /* authentication mode to use */ -static permittedPeers_t *pPermPeersRootFingerprint = NULL; -static permittedPeers_t *pPermPeersRootNames = NULL; +static permittedPeers_t *pPermPeers = NULL; /* get the syslog forward port from selector_t. The passed in * struct must be one that is setup for forwarding. @@ -156,10 +154,8 @@ CODESTARTfreeInstance free(pData->pszStrmDrvr); if(pData->pszStrmDrvrAuthMode != NULL) free(pData->pszStrmDrvrAuthMode); - if(pData->pPermPeersRootFingerprint != NULL) - net.DestructPermittedPeers(&pData->pPermPeersRootFingerprint); - if(pData->pPermPeersRootNames != NULL) - net.DestructPermittedPeers(&pData->pPermPeersRootNames); + if(pData->pPermPeers != NULL) + net.DestructPermittedPeers(&pData->pPermPeers); ENDfreeInstance @@ -216,13 +212,13 @@ static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len) } -/* set the cert fingerprint -- rgerhards, 2008-05-19 +/* set the permitted peers -- rgerhards, 2008-05-19 */ static rsRetVal -setFingerprint(void __attribute__((unused)) *pVal, uchar *pszID) +setPermittedPeer(void __attribute__((unused)) *pVal, uchar *pszID) { DEFiRet; - CHKiRet(net.AddPermittedPeer(&pPermPeersRootFingerprint, pszID)); + CHKiRet(net.AddPermittedPeer(&pPermPeers, pszID)); finalize_it: RETiRet; } @@ -298,8 +294,8 @@ static rsRetVal TCPSendInit(void *pvData) if(pData->pszStrmDrvrAuthMode != NULL) { CHKiRet(netstrm.SetDrvrAuthMode(pData->pNetstrm, pData->pszStrmDrvrAuthMode)); } - if(pData->pPermPeersRootFingerprint != NULL) { - CHKiRet(netstrm.SetDrvrPermPeers(pData->pNetstrm, pData->pPermPeersRootFingerprint)); + if(pData->pPermPeers != NULL) { + CHKiRet(netstrm.SetDrvrPermPeers(pData->pNetstrm, pData->pPermPeers)); } /* params set, now connect */ CHKiRet(netstrm.Connect(pData->pNetstrm, glbl.GetDefPFFamily(), @@ -606,13 +602,9 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) if(pszStrmDrvrAuthMode != NULL) CHKmalloc(pData->pszStrmDrvrAuthMode = (uchar*)strdup((char*)pszStrmDrvrAuthMode)); - if(pPermPeersRootFingerprint != NULL) { - pData->pPermPeersRootFingerprint = pPermPeersRootFingerprint; - pPermPeersRootFingerprint = NULL; - } - if(pPermPeersRootNames != NULL) { - pData->pPermPeersRootNames = pPermPeersRootNames; - pPermPeersRootNames = NULL; + if(pPermPeers != NULL) { + pData->pPermPeers = pPermPeers; + pPermPeers = NULL; } } @@ -638,8 +630,8 @@ freeConfigVars(void) free(pszStrmDrvrAuthMode); pszStrmDrvrAuthMode = NULL; } - if(pPermPeersRootFingerprint != NULL) { - free(pPermPeersRootFingerprint); + if(pPermPeers != NULL) { + free(pPermPeers); } } @@ -690,7 +682,7 @@ CODEmodInit_QueryRegCFSLineHdlr CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriver", 0, eCmdHdlrGetWord, NULL, &pszStrmDrvr, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdrivermode", 0, eCmdHdlrInt, NULL, &iStrmDrvrMode, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriverauthmode", 0, eCmdHdlrGetWord, NULL, &pszStrmDrvrAuthMode, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdrivercertfingerprint", 0, eCmdHdlrGetWord, setFingerprint, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriverpermittedpeer", 0, eCmdHdlrGetWord, setPermittedPeer, NULL, NULL)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit -- cgit v1.2.3 From b4baf2bda0370c8727c8bd2d20aa89d30f91448f Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Fri, 23 May 2008 11:28:31 +0200 Subject: updated TLS documentation with HOWTO on certificate generation --- doc/rsyslog_tls.html | 124 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 113 insertions(+), 11 deletions(-) diff --git a/doc/rsyslog_tls.html b/doc/rsyslog_tls.html index c0ebb9c8..e1729feb 100644 --- a/doc/rsyslog_tls.html +++ b/doc/rsyslog_tls.html @@ -13,11 +13,12 @@ messages on the network. Encryption is vital to keep the confidiental content of syslog messages secure. I describe the overall approach and provide an HOWTO do it with rsyslog's TLS -features. 

    Please +features. 

    +

    Please note that TLS is the more secure successor of SSL. While people often talk about "SSL encryption" they actually mean "TLS encryption". So don't look any further if you look for how to SSL-encrypt syslog. You -have found the right spot.

    +have found the right spot.

    Background

    Traditional syslog is a clear-text protocol. That means anyone with a sniffer can have a peek at your data. In @@ -36,17 +37,20 @@ of TCP syslog). GSSAPI since long to overcome these limitatinos. However, syslog via GSSAPI is a rsyslog-exclusive transfer mode and it requires a proper Kerberos environment. As such, it isn't a really universal -solution. The IETF has begun standardizing syslog over plain tcp over +solution. The IETF +has begun standardizing syslog over plain tcp over TLS for a while now. While I am not fully satisfied with the results so far, this obviously has the  potential to become the long-term solution. The Internet Draft in question, syslog-transport-tls has been dormant for some time but is now (May of 2008) again being worked on. I expect it to turn into a RFC within the next 12 month (but don't take this for granted ;)). I didn't want to wait for it, because there -obviously is need for TLS syslog right now (and, honestly, I have waited long enough...). Consequently, I have +obviously is need for TLS syslog right now (and, honestly, I have +waited long enough...). Consequently, I have implemented the current draft, with some interpretations I made (there will be a compliance doc soon). So in essence, a TLS-protected syslog -transfer mode is available right now. As a side-note, Rsyslog is the world's first +transfer mode is available right now. As a side-note, Rsyslog +is the world's first implementation of syslog-transport-tls.

    Please note that in theory it should be compatible with other, non IETF syslog-transport-tls implementations. If you would like to run @@ -129,8 +133,10 @@ following these steps, you should have a working secure syslog forwarding system. To verify, you can type "logger test" or a similar "smart" command on the client. It should show up in the respective server log file. If you dig out your sniffer, you should see -that the traffic on the wire is actually protected.

    Limitations

    -

    The current implementation has a number of limitations. These are +that the traffic on the wire is actually protected.

    +

    Limitations

    +

    The current implementation has a number of limitations. These +are being worked on. Most importantly, neither the client nor the server are authenticated. So while the message transfer is encrypted, you can not be sure which peer you are talking to. Please note that this is a @@ -138,14 +144,109 @@ limitation found in most real-world SSL syslog systems. Of course, that is not an excuse for not yet providing this feature - but it tells you that it is acceptable and can be worked around by proper firewalling, ACLs and other organizational measures. Mutual authentication will be -added shortly to rsyslog.

    Secondly, the plain tcp syslog listener +added shortly to rsyslog.

    +

    Secondly, the plain tcp syslog listener can currently listen to a single port, in a single mode. So if you use a TLS-based listener, you can not run unencrypted syslog on the same instance at the same time. A work-around is to run a second rsyslogd -instance. This limitation, too, is scheduled to be removed soon.

    The +instance. This limitation, too, is scheduled to be removed soon.

    +

    The RELP transport can currently not be protected by TLS. A work-around is to use stunnel. TLS support for RELP will be added once plain TCP -syslog has sufficiently matured.

    Conclusion

    +syslog has sufficiently matured.

    +

    Certificates

    +

    In order to be really secure, certificates are needed. This is +a short summary on how to generate the necessary certificates with +GnuTLS' certtool. You can also generate certificates via other tools, +but as we currently support GnuTLS as the only TLS library, we thought +it is a good idea to use their tools.

    +

    Note that this section aims at people who are not involved +with PKI at all. The main goal is to get them going in a reasonable +secure way. 

    +

    CA Certificate

    +

    This is used to sign all of your other certificates. The CA +cert must be trusted by all clients and servers. The private key must +be well-protected and not given to any third parties. The certificate +itself can (and must) be distributed. To generate it, do the following:

    +
      +
    1. generate the private key: +
      certtool --generate-privkey --outfile ca-key.pem
      +
      +This takes a short while. Be sure to do some work on your workstation, +it waits for radom input. Switching between windows is sufficient +;)  +
    2. +
    3. now create the (self-signed) CA certificate itself:
      +
      certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca.pem
      +This generates the CA certificate. This command queries you for a +number of things. Use appropriate responses. When it comes to +certificate validity, keep in mind that you need to recreate all +certificates when this one expires. So it may be a good idea to use a +long period, eg. 3650 days (roughly 10 years). You need to specify that +the certificates belongs to an authrity. The certificate is used to +sign other certificates.
      +
    4. +
    5. You need to distribute this certificate +to all peers and you need to point to it via the +$DefaultNetstreamDriverCAFile config directive. All other certificates +will be issued by this CA.
      +Important: do only distribute the ca.pem, NOT ca-key.pem (the private +key). Distributing the CA private key would totally breach security as +everybody could issue new certificates on the behalf of this CA. +
    6. +
    +

    Individual Peer Certificate

    +

    Each peer (be it client, server or both), needs a certificate +that conveys its identity. Access control is based on these +certificates. You can, for example, configure a server to accept +connections only from configured clients. The client ID is taken from +the client instances certificate. So as a general rule of thumb, you +need to create a certificate for each instance of rsyslogd that you +run. That instance also needs the private key, so that it can properly +decrypt the traffic. Safeguard the peer's private key file. If somebody +gets hold of it, it can malicously pretend to be the compromised host. +If such happens, regenerate the certificate and make sure you use a +different name instead of the compromised one (if you use name-based +authentication). 

    +

    These are the steps to generate the indivudual certificates +(repeat: you need to do this for every instance, do NOT share the +certificates created in this step):

    +
      +
    1. generate a private key (do NOT mistake this with the CA's +private key - this one is different):
      +
      certtool --generate-privkey --outfile key.pem
      +Again, this takes a short while.
    2. +
    3. generate a certificate request:
      +
      certtool --generate-request --load-privkey key.pem --outfile request.pem
      +If you do not have the CA's private key (because you are not authorized +for this), you can send the certificate request to the responsible +person. If you do this, you can skip the remaining steps, as the CA +will provide you with the final certificate. If you submit the request +to the CA, you need to tell the CA the answers that you would normally +provide in step 3 below. +
    4. +
    5. Sign (validate, authorize) the certificate request and +generate the instances certificate. You need to have the CA's +certificate and private key for this:
      +
      certtool --generate-certificate --load-request request.pem --outfile cert.pem \
      --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem
      +Answer questions as follows: Cert does not belogn to an authority; it +is a TLS web server and client certificate; the dnsName MUST be the +name of the peer in question (e.g. centralserver.example.net) - this is +the name used for authenticating the peers. Please note that you may +use an IP address in dnsName. This is a good idea if you would like to +use default server authentication and you use selector lines with IP +addresses (e.g. "*.* @@192.168.0.1") - in that case you need to select +a dnsName of 192.168.0.1. But, of course, changing the server IP then +requires generating a new certificate.
    6. +
    After you have generated the certificate, you need to place it +onto the local machine running rsyslogd. Specify the certificate and +key via the $DefaultNetstreamDriverCertFile /path/to/cert.pem and +$DefaultNetstreamDriverKeyFile /path/to/key.pem configuration +directives. Make sure that nobody has access to key.pem, as that would +breach security. And, once again: do NOT use these files on more than +one instance. Doing so would prevent you from distinguising between the +instances and thus would disable useful authentication. +

    Conclusion

    With minumal effort, you can set up a secure logging infrastructure employing TLS encrypted syslog message transmission.

    Feedback requested

    @@ -156,7 +257,8 @@ please

    Revision History

    +Gerhards * Initial Version created +

    Copyright

    Copyright (c) 2008 Rainer Gerhards and -- cgit v1.2.3 From 3b5c252784fcd73c1f7c75301c3ef058a9a15397 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Fri, 23 May 2008 11:39:37 +0200 Subject: checking if client provided a cert and complain if not --- runtime/nsd_gtls.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index 59cd398c..aec3f0c5 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -638,7 +638,9 @@ gtlsChkPeerCertValidity(nsd_gtls_t *pThis) ISOBJ_TYPE_assert(pThis, nsd_gtls); gnuRet = gnutls_certificate_verify_peers(pThis->sess); - if(gnuRet < 1) + if(gnuRet == GNUTLS_E_NO_CERTIFICATE_FOUND) { + errmsg.LogError(NO_ERRCODE, "peer did not provide a certificate, not permitted to talk to it"); + } else if(gnuRet < 1) CHKgnutls(gnuRet); if(gnuRet & GNUTLS_CERT_INVALID) { -- cgit v1.2.3 From f31a0537c649b0ecf40986e5dc8fea6386e6bcb0 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 10:15:49 +0200 Subject: improved gtls error reporting --- doc/rsyslog_tls.html | 20 +++++++++++++++----- runtime/nsd_gtls.c | 13 +++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/doc/rsyslog_tls.html b/doc/rsyslog_tls.html index e1729feb..2d5fd8e9 100644 --- a/doc/rsyslog_tls.html +++ b/doc/rsyslog_tls.html @@ -159,7 +159,8 @@ syslog has sufficiently matured.

    a short summary on how to generate the necessary certificates with GnuTLS' certtool. You can also generate certificates via other tools, but as we currently support GnuTLS as the only TLS library, we thought -it is a good idea to use their tools.

    +it is a good idea to use their tools.
    +

    Note that this section aims at people who are not involved with PKI at all. The main goal is to get them going in a reasonable secure way. 

    @@ -238,14 +239,22 @@ use default server authentication and you use selector lines with IP addresses (e.g. "*.* @@192.168.0.1") - in that case you need to select a dnsName of 192.168.0.1. But, of course, changing the server IP then requires generating a new certificate. -After you have generated the certificate, you need to place it -onto the local machine running rsyslogd. Specify the certificate and -key via the $DefaultNetstreamDriverCertFile /path/to/cert.pem and + +After you have generated the certificate, you need to place it onto the +local machine running rsyslogd. Specify the certificate and key via the +$DefaultNetstreamDriverCertFile /path/to/cert.pem and $DefaultNetstreamDriverKeyFile /path/to/key.pem configuration directives. Make sure that nobody has access to key.pem, as that would breach security. And, once again: do NOT use these files on more than one instance. Doing so would prevent you from distinguising between the instances and thus would disable useful authentication. +

    Troubleshooting Certificates

    +

    If you experience trouble with your certificate setup, it may +be +useful to get some information on what is contained in a specific +certificate (file). To obtain that information, do 

    +
    $ certtool --certificate-info --infile cert.pem
    +

    where "cert.pem" can be replaced by the various certificate pem files (but it does not work with the key files).

    Conclusion

    With minumal effort, you can set up a secure logging infrastructure employing TLS encrypted syslog message transmission.

    @@ -257,7 +266,8 @@ please

    Revision History

    Copyright

    Copyright (c) 2008 Rainer diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index aec3f0c5..54fbecd2 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -335,6 +335,7 @@ gtlsAddOurCert(void) int gnuRet; uchar *keyFile; uchar *certFile; + uchar *pGnuErr; /* for GnuTLS error reporting */ DEFiRet; certFile = glbl.GetDfltNetstrmDrvrCertFile(); @@ -344,6 +345,13 @@ gtlsAddOurCert(void) 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(NO_ERRCODE, "error adding our certificate. GnuTLS error %d, message: '%s', " + "key: '%s', cert: '%s'\n", gnuRet, pGnuErr, certFile, keyFile); + free(pGnuErr); + } RETiRet; } @@ -435,7 +443,6 @@ gtlsGlblInitLstn(void) * considered legacy. -- rgerhards, 2008-05-05 */ /*CHKgnutls(gnutls_certificate_set_x509_crl_file(xcred, CRLFILE, GNUTLS_X509_FMT_PEM));*/ - //CHKiRet(gtlsAddOurCert()); CHKiRet(generate_dh_params()); gnutls_certificate_set_dh_params(xcred, dh_params); /* this is void */ bGlblSrvrInitDone = 1; /* we are all set now */ @@ -535,6 +542,7 @@ gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) */ pPeer = pThis->pPermPeers; while(pPeer != NULL && !bFoundPositiveMatch) { +RUNLOG_VAR("%s", pPeer->pszID); if(!strcmp(szAltName, (char*)pPeer->pszID)) { bFoundPositiveMatch = 1; } else { @@ -640,6 +648,7 @@ gtlsChkPeerCertValidity(nsd_gtls_t *pThis) gnuRet = gnutls_certificate_verify_peers(pThis->sess); if(gnuRet == GNUTLS_E_NO_CERTIFICATE_FOUND) { errmsg.LogError(NO_ERRCODE, "peer did not provide a certificate, not permitted to talk to it"); + ABORT_FINALIZE(RS_RET_TLS_NO_CERT); } else if(gnuRet < 1) CHKgnutls(gnuRet); @@ -757,7 +766,7 @@ gtlsSetTransportPtr(nsd_gtls_t *pThis, int sock) BEGINobjConstruct(nsd_gtls) /* be sure to specify the object type also in END macro! */ iRet = nsd_ptcp.Construct(&pThis->pTcp); pThis->bReportAuthErr = 1; -CHKiRet(gtlsAddOurCert()); + CHKiRet(gtlsAddOurCert()); finalize_it: ENDobjConstruct(nsd_gtls) -- cgit v1.2.3 From 7b604269c725eaa6120ddbece6a1ec0b67d9cf82 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 11:01:42 +0200 Subject: added capability to auto-configure tls auth rule for client connecting to server must match hostname in send action --- runtime/nsd_gtls.c | 64 +++++++++++++++++++++++++++++++++++++++++++----------- runtime/nsd_gtls.h | 1 + 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index 54fbecd2..fab400fc 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -504,6 +504,45 @@ finalize_it: } +/* 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) + * 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 && !*pbFoundPositiveMatch) { + if(!strcmp((char*)pszPeerID, (char*)pPeer->pszID)) { + *pbFoundPositiveMatch = 1; + } else { + pPeer = pPeer->pNext; + } + } + } else { + /* we do not have configured peer IDs, so we use defaults */ +RUNLOG_VAR("%s", pThis->pszConnectHost); + if( pThis->pszConnectHost + && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) { + *pbFoundPositiveMatch = 1; + } + } + + RETiRet; +} + + /* Check the peer's ID in name auth mode. * rgerhards, 2008-05-22 */ @@ -515,7 +554,6 @@ gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) int iAltName; size_t szAltNameLen; int bFoundPositiveMatch; - permittedPeers_t *pPeer; cstr_t *pStr = NULL; int gnuRet; DEFiRet; @@ -537,18 +575,7 @@ gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) dbgprintf("subject alt dnsName: '%s'\n", szAltName); snprintf((char*)lnBuf, sizeof(lnBuf), "DNSname: %s; ", szAltName); CHKiRet(rsCStrAppendStr(pStr, lnBuf)); - /* we found it - now we need to loop through the list of permitted - * peer IDs. As soon as we have a positive match, we are all set. - */ - pPeer = pThis->pPermPeers; - while(pPeer != NULL && !bFoundPositiveMatch) { -RUNLOG_VAR("%s", pPeer->pszID); - if(!strcmp(szAltName, (char*)pPeer->pszID)) { - bFoundPositiveMatch = 1; - } else { - pPeer = pPeer->pNext; - } - } + CHKiRet(gtlsChkOnePeerName(pThis, (uchar*)szAltName, &bFoundPositiveMatch)); /* do NOT break, because there may be multiple dNSName's! */ } ++iAltName; @@ -647,6 +674,7 @@ gtlsChkPeerCertValidity(nsd_gtls_t *pThis) ISOBJ_TYPE_assert(pThis, nsd_gtls); gnuRet = gnutls_certificate_verify_peers(pThis->sess); if(gnuRet == GNUTLS_E_NO_CERTIFICATE_FOUND) { + errno = 0; errmsg.LogError(NO_ERRCODE, "peer did not provide a certificate, not permitted to talk to it"); ABORT_FINALIZE(RS_RET_TLS_NO_CERT); } else if(gnuRet < 1) @@ -781,6 +809,10 @@ CODESTARTobjDestruct(nsd_gtls) if(pThis->pTcp != NULL) { nsd_ptcp.Destruct(&pThis->pTcp); } + + if(pThis->pszConnectHost != NULL) { + free(pThis->pszConnectHost); + } ENDobjDestruct(nsd_gtls) @@ -1131,6 +1163,12 @@ Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) 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"); diff --git a/runtime/nsd_gtls.h b/runtime/nsd_gtls.h index 59109e68..a88e34fc 100644 --- a/runtime/nsd_gtls.h +++ b/runtime/nsd_gtls.h @@ -37,6 +37,7 @@ typedef nsd_if_t nsd_gtls_if_t; /* we just *implement* this interface */ struct nsd_gtls_s { BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ nsd_t *pTcp; /**< our aggregated nsd_ptcp data */ + uchar *pszConnectHost; /**< hostname used for connect - may be used to authenticate peer if no other name given */ int iMode; /* 0 - plain tcp, 1 - TLS */ int bAbortConn; /* if set, abort conncection (fatal error had happened) */ enum { -- cgit v1.2.3 From b674dd69bd35ee4da511ae73e70946ce4040b439 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 12:53:49 +0200 Subject: added gtls name authentication based on common name (inside DN) also changed fingerprint gtls auth mode to new format fingerprint --- ChangeLog | 1 + runtime/nsd_gtls.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++---- runtime/rsyslog.h | 1 + 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 032d7b29..4a65e0c5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ - implemented x509/certvalid gtls auth mode - implemented x509/name gtls auth mode +- changed fingerprint gtls auth mode to new format fingerprint - changed config directive name to reflect different use $ActionSendStreamDriverCertFingerprint is now $ActionSendStreamDriverPermittedPeer and can be used both for diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index fab400fc..f3c0d983 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -282,17 +282,13 @@ GenFingerprintStr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr) cstr_t *pStr = NULL; uchar buf[4]; size_t i; - int bAddColon = 0; /* do we need to add a colon to the fingerprint string? */ DEFiRet; CHKiRet(rsCStrConstruct(&pStr)); + CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4)); for(i = 0 ; i < sizeFingerprint ; ++i) { - if(bAddColon) { - CHKiRet(rsCStrAppendChar(pStr, ':')); - } else { - bAddColon = 1; /* all but the first need a colon added */ - } - snprintf((char*)buf, sizeof(buf), "%2.2X", pFingerprint[i]); + CHKiRet(rsCStrAppendChar(pStr, ':')); + snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]); CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 2)); } CHKiRet(rsCStrFinish(pStr)); @@ -453,6 +449,88 @@ finalize_it: } +/* 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 */ @@ -555,6 +633,7 @@ gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) size_t szAltNameLen; int bFoundPositiveMatch; cstr_t *pStr = NULL; + cstr_t *pstrCN = NULL; int gnuRet; DEFiRet; @@ -581,6 +660,17 @@ gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) ++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 noch 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) { @@ -597,6 +687,8 @@ gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) finalize_it: if(pStr != NULL) rsCStrDestruct(&pStr); + if(pstrCN != NULL) + rsCStrDestruct(&pstrCN); RETiRet; } diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index dfa14f35..4f858928 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -230,6 +230,7 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_INVALID_FINGERPRINT = -2088, /**< a fingerprint is not valid for this use case */ RS_RET_CONNECTION_ABORTREQ = -2089, /**< connection was abort requested due to previous error */ RS_RET_CERT_INVALID = -2090, /**< a x509 certificate failed validation */ + RS_RET_CERT_INVALID_DN = -2091, /**< distinguised name in x509 certificate is invalid (e.g. wrong escaping) */ /* RainerScript error messages (range 1000.. 1999) */ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ -- cgit v1.2.3 From bc5eb93e40534f55b240d33dd605ed3e52bae555 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 14:47:36 +0200 Subject: added certificate validity date check (gtls) --- runtime/nsd_gtls.c | 65 +++++++++++++++++++++++++++++++++++++++++++++--------- runtime/rsyslog.h | 3 +++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index f3c0d983..5c82d082 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -113,7 +113,7 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) if(cert_list_size > 0) { /* we only print information about the first certificate */ - gnutls_x509_crt_init( &cert); + CHKgnutls(gnutls_x509_crt_init(&cert)); CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); @@ -762,25 +762,35 @@ gtlsChkPeerCertValidity(nsd_gtls_t *pThis) 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); - gnuRet = gnutls_certificate_verify_peers(pThis->sess); - if(gnuRet == GNUTLS_E_NO_CERTIFICATE_FOUND) { + + /* 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(NO_ERRCODE, "peer did not provide a certificate, not permitted to talk to it"); ABORT_FINALIZE(RS_RET_TLS_NO_CERT); - } else if(gnuRet < 1) - CHKgnutls(gnuRet); + } + + CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert)); - if(gnuRet & GNUTLS_CERT_INVALID) { + if(stateCert & GNUTLS_CERT_INVALID) { /* provide error details if we have them */ - if(gnuRet & GNUTLS_CERT_SIGNER_NOT_FOUND) { + if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) { pszErrCause = "signer not found"; - } else if(gnuRet & GNUTLS_CERT_SIGNER_NOT_FOUND) { + } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) { pszErrCause = "signer is not a CA"; - } else if(gnuRet & GNUTLS_CERT_SIGNER_NOT_CA) { + } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) { pszErrCause = "insecure algorithm"; - } else if(gnuRet & GNUTLS_CERT_REVOKED) { + } else if(stateCert & GNUTLS_CERT_REVOKED) { pszErrCause = "certificate revoked"; } else { pszErrCause = "no specific reason"; @@ -793,6 +803,41 @@ gtlsChkPeerCertValidity(nsd_gtls_t *pThis) 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(NO_ERRCODE, "not permitted to talk to peer: certificate %d not yet active", i); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(NO_ERRCODE, "info on invalid cert: %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(NO_ERRCODE, "not permitted to talk to peer: certificate %d expired", i); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(NO_ERRCODE, "info on invalid cert: %s", rsCStrGetSzStr(pStr)); + rsCStrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_EXPIRED); + } + gnutls_x509_crt_deinit(cert); + } + finalize_it: RETiRet; } diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 4f858928..5f39c3d8 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -231,6 +231,9 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_CONNECTION_ABORTREQ = -2089, /**< connection was abort requested due to previous error */ RS_RET_CERT_INVALID = -2090, /**< a x509 certificate failed validation */ RS_RET_CERT_INVALID_DN = -2091, /**< distinguised name in x509 certificate is invalid (e.g. wrong escaping) */ + RS_RET_CERT_EXPIRED = -2092, /**< we are past a x.509 cert's expiration time */ + RS_RET_CERT_NOT_YET_ACTIVE = -2094, /**< x.509 cert's activation time not yet reached */ + RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */ /* RainerScript error messages (range 1000.. 1999) */ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ -- cgit v1.2.3 From 7918bbe7fc4c704ef79ebd2fb58871cb3fa8c3f6 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 15:11:00 +0200 Subject: fixed wrong cert expiration date check --- runtime/nsd_gtls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index 5c82d082..76f37c94 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -828,7 +828,7 @@ gtlsChkPeerCertValidity(nsd_gtls_t *pThis) ttCert = gnutls_x509_crt_get_expiration_time(cert); if(ttCert == -1) ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); - else if(ttCert > ttNow) { + else if(ttCert < ttNow) { errmsg.LogError(NO_ERRCODE, "not permitted to talk to peer: certificate %d expired", i); gtlsGetCertInfo(pThis, &pStr); errmsg.LogError(NO_ERRCODE, "info on invalid cert: %s", rsCStrGetSzStr(pStr)); -- cgit v1.2.3 From fce6ddc99fe4894bbacf2271653d558292183d62 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 15:31:41 +0200 Subject: fixed fingerprint generator fixed problem introduced earlier today --- runtime/nsd_gtls.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index 76f37c94..aaa3159c 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -287,9 +287,8 @@ GenFingerprintStr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr) CHKiRet(rsCStrConstruct(&pStr)); CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4)); for(i = 0 ; i < sizeFingerprint ; ++i) { - CHKiRet(rsCStrAppendChar(pStr, ':')); snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]); - CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 2)); + CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3)); } CHKiRet(rsCStrFinish(pStr)); -- cgit v1.2.3 From 331a6442021405ecc0704fc11adb42178c917e67 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Mon, 26 May 2008 15:49:32 +0200 Subject: protected gtls error string function by a mutex. Without it, we could have a race condition in extreme cases. This was very remote, but now can no longer happen. --- ChangeLog | 3 +++ runtime/nsd_gtls.c | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 4a65e0c5..6691ac70 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,9 @@ - implemented x509/certvalid gtls auth mode - implemented x509/name gtls auth mode - changed fingerprint gtls auth mode to new format fingerprint +- protected gtls error string function by a mutex. Without it, we + could have a race condition in extreme cases. This was very remote, + but now can no longer happen. - changed config directive name to reflect different use $ActionSendStreamDriverCertFingerprint is now $ActionSendStreamDriverPermittedPeer and can be used both for diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index aaa3159c..4f1a82e3 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "rsyslog.h" #include "syslogd-types.h" @@ -60,6 +61,8 @@ 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) { \ @@ -311,8 +314,9 @@ uchar *gtlsStrerror(int error) { uchar *pErr; - // TODO: guard by mutex! + pthread_mutex_lock(&mutGtlsStrerror); pErr = (uchar*) strdup(gnutls_strerror(error)); + pthread_mutex_unlock(&mutGtlsStrerror); return pErr; } @@ -1389,6 +1393,7 @@ BEGINmodExit CODESTARTmodExit nsdsel_gtlsClassExit(); nsd_gtlsClassExit(); + pthread_mutex_destroy(&mutGtlsStrerror); ENDmodExit @@ -1406,6 +1411,7 @@ CODESTARTmodInit 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: */ -- cgit v1.2.3 From ae387d6900c02ba655bd970c8053103a1b3f1dcd Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 27 May 2008 09:46:30 +0200 Subject: client now provides cert even if it is not signed by one of the server's trusted CAs (gtls) --- runtime/nsd_gtls.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++--- runtime/nsd_gtls.h | 6 +- runtime/rsyslog.h | 2 + 3 files changed, 170 insertions(+), 10 deletions(-) diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index 4f1a82e3..d1f87e90 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -31,6 +31,9 @@ #include #include #include +#include +#include +#include #include #include "rsyslog.h" @@ -77,6 +80,141 @@ static pthread_mutex_t mutGtlsStrerror; /**< a mutex protecting the potentially static gnutls_certificate_credentials xcred; static gnutls_dh_params dh_params; +/* 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(NO_ERRCODE, "can not read file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + + } + + if(fstat(fd, &stat_st) == -1) { + errmsg.LogError(NO_ERRCODE, "can not stat file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_FILE_NO_STAT); + } + + /* 1MB limit */ + if(stat_st.st_size > 1024 * 1024) { + errmsg.LogError(NO_ERRCODE, "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(NO_ERRCODE, "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(); + + /* 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. @@ -98,8 +236,8 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) int gnuRet; DEFiRet; unsigned iAltName; - char szAltName[1024]; /* this is sufficient for the DNSNAME... */ size_t szAltNameLen; + char szAltName[1024]; /* this is sufficient for the DNSNAME... */ assert(ppStr != NULL); ISOBJ_TYPE_assert(pThis, nsd_gtls); @@ -111,20 +249,18 @@ gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) CHKiRet(rsCStrConstruct(&pStr)); - snprintf((char*)lnBuf, sizeof(lnBuf), "Peer provided %d certificate(s). ", cert_list_size); + 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); @@ -306,7 +442,7 @@ finalize_it: } -/* a thread-safe variant of gnutls_strerror - TODO: implement it! +/* a thread-safe variant of gnutls_strerror * The caller must free the returned string. * rgerhards, 2008-04-30 */ @@ -589,7 +725,8 @@ finalize_it: * 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) + * called multiple times). + * TODO: implemet wildcards? * rgerhards, 2008-05-26 */ static rsRetVal @@ -613,7 +750,6 @@ gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatc } } else { /* we do not have configured peer IDs, so we use defaults */ -RUNLOG_VAR("%s", pThis->pszConnectHost); if( pThis->pszConnectHost && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) { *pbFoundPositiveMatch = 1; @@ -953,6 +1089,11 @@ CODESTARTobjDestruct(nsd_gtls) if(pThis->pszConnectHost != NULL) { free(pThis->pszConnectHost); } + + if(pThis->bOurCertIsInit) + gnutls_x509_crt_deinit(pThis->ourCert); + if(pThis->bOurKeyIsInit) + gnutls_x509_privkey_deinit(pThis->ourKey); ENDobjDestruct(nsd_gtls) @@ -1275,7 +1416,8 @@ Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; int sock; int gnuRet; - static const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 }; + /* 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); @@ -1292,6 +1434,15 @@ Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) 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); + CHKiRet(gtlsLoadOurCertKey(pThis)); /* first load .pem files */ + gnutls_certificate_client_set_retrieve_function(xcred, gtlsClientCertCallback); + /* Use default priorities */ CHKgnutls(gnutls_set_default_priority(pThis->sess)); CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority)); @@ -1313,7 +1464,10 @@ Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) CHKgnutls(gnutls_handshake(pThis->sess)); dbgprintf("GnuTLS handshake succeeded\n"); - /* now check if the remote peer is permitted to talk to us */ + /* 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: diff --git a/runtime/nsd_gtls.h b/runtime/nsd_gtls.h index a88e34fc..bbd650a2 100644 --- a/runtime/nsd_gtls.h +++ b/runtime/nsd_gtls.h @@ -54,7 +54,11 @@ struct nsd_gtls_s { int bReportAuthErr; /* only the first auth error is to be reported, this var triggers it. Initially, it is * set to 1 and changed to 0 after the first report. It is changed back to 1 after * one successful authentication. */ - permittedPeers_t *pPermPeers; /* permitted senders */ + permittedPeers_t *pPermPeers; /* permitted peers */ + gnutls_x509_crt ourCert; /**< our certificate, if in client mode (unused in server mode) */ + gnutls_x509_privkey ourKey; /**< our private key, if in client mode (unused in server mode) */ + short bOurCertIsInit; /**< 1 if our certificate is initialized and must be deinit on destruction */ + short bOurKeyIsInit; /**< 1 if our private key is initialized and must be deinit on destruction */ }; /* interface is defined in nsd.h, we just implement it! */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 5f39c3d8..7b6d08ff 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -234,6 +234,8 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_CERT_EXPIRED = -2092, /**< we are past a x.509 cert's expiration time */ RS_RET_CERT_NOT_YET_ACTIVE = -2094, /**< x.509 cert's activation time not yet reached */ RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */ + RS_RET_FILE_NO_STAT = -2096, /**< can not stat() a file */ + RS_RET_FILE_TOO_LARGE = -2097, /**< a file is larger than permitted */ /* RainerScript error messages (range 1000.. 1999) */ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ -- cgit v1.2.3 From 531f27a8c3d6c988650e09bb126f792b8bac5421 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 27 May 2008 14:49:45 +0200 Subject: implemented wildcards inside certificate name check authentication --- runtime/net.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++ runtime/net.h | 27 +++++- runtime/nsd_gtls.c | 15 +-- runtime/rsyslog.h | 2 + 4 files changed, 311 insertions(+), 7 deletions(-) diff --git a/runtime/net.c b/runtime/net.c index cbff1003..43da9fe5 100644 --- a/runtime/net.c +++ b/runtime/net.c @@ -92,6 +92,114 @@ int ACLDontResolve = 0; /* add hostname to acl instead of resolving it /* ------------------------------ begin permitted peers code ------------------------------ */ +/* add a wildcard entry to this permitted peer. Entries are always + * added at the tail of the list. pszStr and lenStr identify the wildcard + * entry to be added. Note that the string is NOT \0 terminated, so + * we must rely on lenStr for when it is finished. + * rgerhards, 2008-05-27 + */ +static rsRetVal +AddPermittedPeerWildcard(permittedPeers_t *pPeer, uchar* pszStr, size_t lenStr) +{ + permittedPeerWildcard_t *pNew = NULL; + size_t iSrc; + size_t iDst; + DEFiRet; + + assert(pPeer != NULL); + assert(pszStr != NULL); + + CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); + + if(lenStr == 0) { /* empty domain components are permitted */ + pNew->wildcardType = PEER_WILDCARD_EMPTY_COMPONENT; + FINALIZE; + } else { + /* alloc memory for the domain component. We may waste a byte or + * two, but that's ok. + */ + CHKmalloc(pNew->pszDomainPart = malloc(lenStr +1 )); + } + + if(pszStr[0] == '*') { + pNew->wildcardType = PEER_WILDCARD_AT_START; + iSrc = 1; /* skip '*' */ + } else { + iSrc = 0; + } + + for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) { + pNew->pszDomainPart[iDst] = pszStr[iSrc]; + } + + if(iSrc < lenStr) { + if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') { + if(pNew->wildcardType == PEER_WILDCARD_AT_START) { + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } else { + pNew->wildcardType = PEER_WILDCARD_AT_END; + } + } else { + /* we have an invalid wildcard, something follows the asterisk! */ + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } + } + + if(lenStr == 1 && pNew->wildcardType == PEER_WILDCARD_AT_START) { + pNew->wildcardType = PEER_WILDCARD_MATCH_ALL; + } + + /* if we reach this point, we had a valid wildcard. We now need to + * properly terminate the domain component string. + */ + pNew->pszDomainPart[iDst] = '\0'; + pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) { + if(pNew->pszDomainPart != NULL) + free(pNew->pszDomainPart); + free(pNew); + } + } else { + /* enqueue the element */ + if(pPeer->pWildcardRoot == NULL) { + pPeer->pWildcardRoot = pNew; + } else { + pPeer->pWildcardLast->pNext = pNew; + } + pPeer->pWildcardLast = pNew; + } + + RETiRet; +} + + +/* Destruct a permitted peer's wildcard list -- rgerhards, 2008-05-27 */ +static rsRetVal +DestructPermittedPeerWildcards(permittedPeers_t *pPeer) +{ + permittedPeerWildcard_t *pCurr; + permittedPeerWildcard_t *pDel; + DEFiRet; + + assert(pPeer != NULL); + + for(pCurr = pPeer->pWildcardRoot ; pCurr != NULL ; /*EMPTY*/) { + pDel = pCurr; + pCurr = pCurr->pNext; + free(pDel->pszDomainPart); + free(pDel); + } + + pPeer->pWildcardRoot = NULL; + pPeer->pWildcardLast = NULL; + + RETiRet; +} + + /* add a permitted peer. PermittedPeers is an interim solution until we can provide * access control via enhanced RainerScript methods. * Note: the provided string is handed over to this function, caller must @@ -137,6 +245,7 @@ DestructPermittedPeers(permittedPeers_t **ppRootPeer) for(pCurr = *ppRootPeer ; pCurr != NULL ; /*EMPTY*/) { pDel = pCurr; pCurr = pCurr->pNext; + DestructPermittedPeerWildcards(pDel); free(pDel->pszID); free(pDel); } @@ -147,6 +256,170 @@ DestructPermittedPeers(permittedPeers_t **ppRootPeer) } +/* Compile a wildcard. The function first checks if there is a wildcard + * present and compiles it only if so ;) It sets the etryType status + * accordingly. + * rgerhards, 2008-05-27 + */ +static rsRetVal +PermittedPeerWildcardCompile(permittedPeers_t *pPeer) +{ + uchar *pC; + uchar *pStart; + DEFiRet; + + assert(pPeer != NULL); + assert(pPeer->pszID != NULL); + + /* first check if we have a wildcard */ + for(pC = pPeer->pszID ; *pC != '\0' && *pC != '*' ; ++pC) + /*EMPTY, just skip*/; + + if(*pC == '\0') { + /* no wildcard found, we are mostly done */ + pPeer->etryType = PERM_PEER_TYPE_PLAIN; + FINALIZE; + } + + /* if we reach this point, the string contains wildcards. So let's + * compile the structure. To do so, we must parse from dot to dot + * and create a wildcard entry for each domain component we find. + * We must also flag problems if we have an asterisk in the middle + * of the text (it is supported at the start or end only). + */ + pPeer->etryType = PERM_PEER_TYPE_WILDCARD; + + for(pC = pPeer->pszID ; *pC != '\0' ; ++pC) { + pStart = pC; + /* find end of domain component */ + for( ; *pC != '\0' && *pC != '.' ; ++pC) + /*EMPTY, just skip*/; + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, pC - pStart)); + /* now check if we have an empty component at end of string */ + if(*pC == '.' && *(pC + 1) == '\0') { + /* pStart is a dummy, it is not used if length is 0 */ + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, 0)); + } + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "error compiling wildcard expression '%s'", + pPeer->pszID); + } + RETiRet; +} + + +/* Do a (potential) wildcard match. The function first checks if the wildcard + * has already been compiled and, if not, compiles it. If the peer entry in + * question does NOT contain a wildcard, a simple strcmp() is done. + * *pbIsMatching is set to 0 if there is no match and something else otherwise. + * rgerhards, 2008-05-27 */ +static rsRetVal +PermittedPeerWildcardMatch(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching) +{ + permittedPeerWildcard_t *pWildcard; + uchar *pC; + uchar *pStart; /* start of current domain component */ + size_t iWildcard, iName; /* work indexes for backward comparisons */ + DEFiRet; + + assert(pPeer != NULL); + assert(pszNameToMatch != NULL); + assert(pbIsMatching != NULL); + + if(pPeer->etryType == PERM_PEER_TYPE_UNDECIDED) { + PermittedPeerWildcardCompile(pPeer); + } + + if(pPeer->etryType == PERM_PEER_TYPE_PLAIN) { + *pbIsMatching = !strcmp((char*)pPeer->pszID, (char*)pszNameToMatch); + FINALIZE; + } + + /* we have a wildcard, so we need to extract the domain components and + * check then against the provided wildcards. + */ + pWildcard = pPeer->pWildcardRoot; + pC = pszNameToMatch; + while(*pC != '\0') { + if(pWildcard == NULL) { + /* we have more domain components than we have wildcards --> no match */ + *pbIsMatching = 0; + FINALIZE; + } + pStart = pC; + while(*pC != '\0' && *pC != '.') { + ++pC; + } + + /* got the component, now do the match */ + switch(pWildcard->wildcardType) { + case PEER_WILDCARD_NONE: + if( pWildcard->lenDomainPart != (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_AT_START: + /* we need to do the backwards-matching manually */ + if(pWildcard->lenDomainPart > (size_t) (pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart; + iWildcard = 0; + while(iWildcard < pWildcard->lenDomainPart) { + if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) { + *pbIsMatching = 0; + FINALIZE; + } + ++iName; + ++iWildcard; + } + break; + case PEER_WILDCARD_AT_END: + if( pWildcard->lenDomainPart > (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pWildcard->lenDomainPart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_MATCH_ALL: + /* everything is OK, just continue */ + break; + case PEER_WILDCARD_EMPTY_COMPONENT: + if(pC - pStart > 0) { + /* if it is not empty, it is no match... */ + *pbIsMatching = 0; + FINALIZE; + } + break; + } + pWildcard = pWildcard->pNext; /* we processed this entry */ + + /* skip '.' if we had it and so prepare for next iteration */ + if(*pC == '.') + ++pC; + } + + if(pWildcard != NULL) { + /* we have more domain components than in the name to be + * checked. So this is no match. + */ + *pbIsMatching = 0; + FINALIZE; + } + + *pbIsMatching = 1; /* finally... it matches ;) */ + +finalize_it: + RETiRet; +} + + /* ------------------------------ end permitted peers code ------------------------------ */ @@ -1159,6 +1432,7 @@ CODESTARTobjQueryInterface(net) pIf->getLocalHostname = getLocalHostname; pIf->AddPermittedPeer = AddPermittedPeer; pIf->DestructPermittedPeers = DestructPermittedPeers; + pIf->PermittedPeerWildcardMatch = PermittedPeerWildcardMatch; finalize_it: ENDobjQueryInterface(net) diff --git a/runtime/net.h b/runtime/net.h index 673f45a9..0d36e824 100644 --- a/runtime/net.h +++ b/runtime/net.h @@ -91,6 +91,23 @@ struct AllowedSenders { }; +/* this structure is a helper to implement wildcards in permittedPeers_t. It specifies + * the domain component and the matching mode. + * rgerhards, 2008-05-27 + */ +struct permittedPeerWildcard_s { + uchar *pszDomainPart; + size_t lenDomainPart; + enum { + PEER_WILDCARD_NONE = 0, /**< no wildcard in this entry */ + PEER_WILDCARD_AT_START = 1, /**< wildcard at start of entry (*name) */ + PEER_WILDCARD_AT_END = 2, /**< wildcard at end of entry (name*) */ + PEER_WILDCARD_MATCH_ALL = 3, /**< only * wildcard, matches all values */ + PEER_WILDCARD_EMPTY_COMPONENT = 4/**< special case: domain component empty (e.g. "..") */ + } wildcardType; + permittedPeerWildcard_t *pNext; +}; + /* for fingerprints and hostnames, we need to have a temporary linked list of * permitted values. Unforutnately, we must also duplicate this in the netstream * drivers. However, this is the best interim solution (with the least effort). @@ -101,7 +118,14 @@ struct AllowedSenders { */ struct permittedPeers_s { uchar *pszID; + enum { + PERM_PEER_TYPE_UNDECIDED = 0, /**< we have not yet decided the type (fine in some auth modes) */ + PERM_PEER_TYPE_PLAIN = 1, /**< just plain text contained */ + PERM_PEER_TYPE_WILDCARD = 2, /**< wildcards are contained, wildcard struture is filled */ + } etryType; permittedPeers_t *pNext; + permittedPeerWildcard_t *pWildcardRoot; /**< root of the wildcard, NULL if not initialized */ + permittedPeerWildcard_t *pWildcardLast; /**< end of the wildcard list, NULL if not initialized */ }; @@ -121,6 +145,7 @@ BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ /* permitted peer handling should be replaced by something better (see comments above) */ rsRetVal (*AddPermittedPeer)(permittedPeers_t **ppRootPeer, uchar *pszID); rsRetVal (*DestructPermittedPeers)(permittedPeers_t **ppRootPeer); + rsRetVal (*PermittedPeerWildcardMatch)(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching); /* data members - these should go away over time... TODO */ int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */ int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */ @@ -128,7 +153,7 @@ BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ struct AllowedSenders *pAllowedSenders_TCP; struct AllowedSenders *pAllowedSenders_GSS; ENDinterface(net) -#define netCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ +#define netCURR_IF_VERSION 4 /* increment whenever you change the interface structure! */ /* prototypes */ PROTOTYPEObj(net); diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index d1f87e90..e3ff3477 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -60,6 +60,7 @@ MODULE_TYPE_LIB DEFobjStaticHelpers DEFobjCurrIf(errmsg) DEFobjCurrIf(glbl) +DEFobjCurrIf(net) DEFobjCurrIf(nsd_ptcp) static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - already done */ @@ -741,12 +742,11 @@ gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatc if(pThis->pPermPeers) { /* do we have configured peer IDs? */ pPeer = pThis->pPermPeers; - while(pPeer != NULL && !*pbFoundPositiveMatch) { - if(!strcmp((char*)pszPeerID, (char*)pPeer->pszID)) { - *pbFoundPositiveMatch = 1; - } else { - pPeer = pPeer->pNext; - } + 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 */ @@ -756,6 +756,7 @@ gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatc } } +finalize_it: RETiRet; } @@ -1520,6 +1521,7 @@ CODESTARTObjClassExit(nsd_gtls) /* 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) @@ -1533,6 +1535,7 @@ 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 */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 7b6d08ff..f296a608 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -82,6 +82,7 @@ typedef struct objInfo_s objInfo_t; typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ typedef rsRetVal (*errLogFunc_t)(uchar*); /* this is a trick to store a function ptr to a function returning a function ptr... */ typedef struct permittedPeers_s permittedPeers_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ +typedef struct permittedPeerWildcard_s permittedPeerWildcard_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ typedef struct tcpsrv_s tcpsrv_t; /* some universal 64 bit define... */ @@ -236,6 +237,7 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */ RS_RET_FILE_NO_STAT = -2096, /**< can not stat() a file */ RS_RET_FILE_TOO_LARGE = -2097, /**< a file is larger than permitted */ + RS_RET_INVALID_WILDCARD = -2098, /**< a wildcard entry is invalid */ /* RainerScript error messages (range 1000.. 1999) */ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ -- cgit v1.2.3