X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=libraries%2Flibldap%2Ftls_m.c;h=c85d322014fa838341f3fefdea9a5f693fadc079;hb=7164c4c66215da39f03e353a9c72eba408abddbf;hp=7d5e37e20f873c16695f5d4aeb03210e82269c9e;hpb=16b7df8397829493aa0b02518f237f9ef7cf09b5;p=openldap diff --git a/libraries/libldap/tls_m.c b/libraries/libldap/tls_m.c index 7d5e37e20f..c85d322014 100644 --- a/libraries/libldap/tls_m.c +++ b/libraries/libldap/tls_m.c @@ -2,7 +2,7 @@ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * - * Copyright 2008-2010 The OpenLDAP Foundation. + * Copyright 2008-2011 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,14 +59,22 @@ #include #include #include +#include + +#undef NSS_VERSION_INT +#define NSS_VERSION_INT ((NSS_VMAJOR << 24) | (NSS_VMINOR << 16) | \ + (NSS_VPATCH << 8) | NSS_VBUILD) /* NSS 3.12.5 and later have NSS_InitContext */ -#if NSS_VMAJOR <= 3 && NSS_VMINOR <= 12 && NSS_VPATCH < 5 -/* do nothing */ -#else +#if NSS_VERSION_INT >= 0x030c0500 #define HAVE_NSS_INITCONTEXT 1 #endif +/* NSS 3.12.9 and later have SECMOD_RestartModules */ +#if NSS_VERSION_INT >= 0x030c0900 +#define HAVE_SECMOD_RESTARTMODULES 1 +#endif + /* InitContext does not currently work in server mode */ /* #define INITCONTEXT_HACK 1 */ @@ -88,6 +96,7 @@ typedef struct tlsm_ctx { #endif PK11GenericObject **tc_pem_objs; /* array of objects to free */ int tc_n_pem_objs; /* number of objects */ + PRBool tc_warn_only; /* only warn of errors in validation */ #ifdef LDAP_R_COMPILE ldap_pvt_thread_mutex_t tc_refmutex; #endif @@ -121,9 +130,29 @@ static int tlsm_init( void ); #ifdef LDAP_R_COMPILE +/* it doesn't seem guaranteed that a client will call + tlsm_thr_init in a non-threaded context - so we have + to wrap the mutex creation in a prcallonce +*/ +static ldap_pvt_thread_mutex_t tlsm_init_mutex; +static PRCallOnceType tlsm_init_mutex_callonce = {0,0}; + +static PRStatus PR_CALLBACK +tlsm_thr_init_callonce( void ) +{ + if ( ldap_pvt_thread_mutex_init( &tlsm_init_mutex ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not create mutex for moznss initialization: %d\n", errno, 0, 0 ); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + static void tlsm_thr_init( void ) { + ( void )PR_CallOnce( &tlsm_init_mutex_callonce, tlsm_thr_init_callonce ); } #endif /* LDAP_R_COMPILE */ @@ -209,7 +238,7 @@ static cipher_properties ciphers_def[] = { /* SSL3 ciphers */ {"RC4-MD5", SSL_RSA_WITH_RC4_128_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL3, 128, 128, SSL_MEDIUM, SSL_ALLOWED}, - {"RC4-SHA", SSL_RSA_WITH_RC4_128_SHA, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_SHA1, SSL3, 128, 128, SSL_MEDIUM, SSL_NOT_ALLOWED}, + {"RC4-SHA", SSL_RSA_WITH_RC4_128_SHA, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_SHA1, SSL3, 128, 128, SSL_MEDIUM, SSL_ALLOWED}, {"DES-CBC3-SHA", SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_3DES|SSL_SHA1, SSL3, 168, 168, SSL_HIGH, SSL_ALLOWED}, {"DES-CBC-SHA", SSL_RSA_WITH_DES_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_DES|SSL_SHA1, SSL3, 56, 56, SSL_LOW, SSL_ALLOWED}, {"EXP-RC4-MD5", SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL3, 40, 128, SSL_EXPORT40, SSL_ALLOWED}, @@ -220,8 +249,8 @@ static cipher_properties ciphers_def[] = { /* TLSv1 ciphers */ {"EXP1024-DES-CBC-SHA", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_DES|SSL_SHA, TLS1, 56, 56, SSL_EXPORT56, SSL_ALLOWED}, {"EXP1024-RC4-SHA", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_SHA, TLS1, 56, 56, SSL_EXPORT56, SSL_ALLOWED}, - {"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_AES|SSL_SHA, TLS1, 128, 128, SSL_HIGH, SSL_NOT_ALLOWED}, - {"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_AES|SSL_SHA, TLS1, 256, 256, SSL_HIGH, SSL_NOT_ALLOWED}, + {"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_AES|SSL_SHA, TLS1, 128, 128, SSL_HIGH, SSL_ALLOWED}, + {"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA, SSL_kRSA|SSL_aRSA|SSL_AES|SSL_SHA, TLS1, 256, 256, SSL_HIGH, SSL_ALLOWED}, }; #define ciphernum (sizeof(ciphers_def)/sizeof(cipher_properties)) @@ -663,6 +692,7 @@ tlsm_bad_cert_handler(void *arg, PRFileDesc *ssl) case SEC_ERROR_UNTRUSTED_ISSUER: case SEC_ERROR_UNKNOWN_ISSUER: case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: if (ctx->tc_verify_cert) { success = SECFailure; } @@ -896,32 +926,154 @@ tlsm_pin_prompt(PK11SlotInfo *slot, PRBool retry, void *arg) } static SECStatus -tlsm_auth_cert_handler(void *arg, PRFileDesc *fd, - PRBool checksig, PRBool isServer) +tlsm_get_basic_constraint_extension( CERTCertificate *cert, + CERTBasicConstraints *cbcval ) { - SECStatus ret = SSL_AuthCertificate(arg, fd, checksig, isServer); + SECItem encodedVal = { 0, NULL }; + SECStatus rc; - if ( ret != SECSuccess ) { - PRErrorCode errcode = PORT_GetError(); - /* we bypass NSS's hostname checks and do our own - tlsm_session_chkhost will handle it */ - if ( errcode == SSL_ERROR_BAD_CERT_DOMAIN ) { - Debug( LDAP_DEBUG_TRACE, - "TLS certificate verification: defer\n", - 0, 0, 0 ); - } else { - Debug( LDAP_DEBUG_ANY, - "TLS certificate verification: Error, %d: %s\n", - errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ) ; + rc = CERT_FindCertExtension( cert, SEC_OID_X509_BASIC_CONSTRAINTS, + &encodedVal); + if ( rc != SECSuccess ) { + return rc; + } + + rc = CERT_DecodeBasicConstraintValue( cbcval, &encodedVal ); + + /* free the raw extension data */ + PORT_Free( encodedVal.data ); + + return rc; +} + +static PRBool +tlsm_cert_is_self_issued( CERTCertificate *cert ) +{ + /* A cert is self-issued if its subject and issuer are equal and + * both are of non-zero length. + */ + PRBool is_self_issued = cert && + (PRBool)SECITEM_ItemsAreEqual( &cert->derIssuer, + &cert->derSubject ) && + cert->derSubject.len > 0; + return is_self_issued; +} + +static SECStatus +tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg, + PRBool checksig, SECCertificateUsage certUsage, int errorToIgnore ) +{ + CERTVerifyLog verifylog; + SECStatus ret = SECSuccess; + const char *name; + int debug_level = LDAP_DEBUG_ANY; + + if ( errorToIgnore == -1 ) { + debug_level = LDAP_DEBUG_TRACE; + } + + /* the log captures information about every cert in the chain, so we can tell + which cert caused the problem and what the problem was */ + memset( &verifylog, 0, sizeof( verifylog ) ); + verifylog.arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE ); + if ( verifylog.arena == NULL ) { + Debug( LDAP_DEBUG_ANY, + "TLS certificate verification: Out of memory for certificate verification logger\n", + 0, 0, 0 ); + return SECFailure; + } + ret = CERT_VerifyCertificate( handle, cert, checksig, certUsage, PR_Now(), pinarg, &verifylog, + NULL ); + if ( ( name = cert->subjectName ) == NULL ) { + name = cert->nickname; + } + if ( verifylog.head == NULL ) { + /* it is possible for CERT_VerifyCertificate return with an error with no logging */ + if ( ret != SECSuccess ) { + PRErrorCode errcode = PR_GetError(); + Debug( debug_level, + "TLS: certificate [%s] is not valid - error %d:%s.\n", + name ? name : "(unknown)", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); } } else { + const char *name; + CERTVerifyLogNode *node; + + ret = SECSuccess; /* reset */ + node = verifylog.head; + while ( node ) { + if ( ( name = node->cert->subjectName ) == NULL ) { + name = node->cert->nickname; + } + if ( node->error ) { + /* NSS does not like CA certs that have the basic constraints extension + with the CA flag set to FALSE - openssl doesn't check if the cert + is self issued */ + if ( ( node->error == SEC_ERROR_CA_CERT_INVALID ) && + tlsm_cert_is_self_issued( node->cert ) ) { + CERTBasicConstraints basicConstraint; + SECStatus rv = tlsm_get_basic_constraint_extension( node->cert, &basicConstraint ); + if ( ( rv == SECSuccess ) && ( basicConstraint.isCA == PR_FALSE ) ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: certificate [%s] is not correct because it is a CA cert and the " + "BasicConstraint CA flag is set to FALSE - allowing for now, but " + "please fix your certs if possible\n", name, 0, 0 ); + } else { /* does not have basicconstraint, or some other error */ + ret = SECFailure; + Debug( debug_level, + "TLS: certificate [%s] is not valid - CA cert is not valid\n", + name, 0, 0 ); + } + } else if ( errorToIgnore && ( node->error == errorToIgnore ) ) { + Debug( debug_level, + "TLS: Warning: ignoring error for certificate [%s] - error %ld:%s.\n", + name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) ); + } else { + ret = SECFailure; + Debug( debug_level, + "TLS: certificate [%s] is not valid - error %ld:%s.\n", + name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) ); + } + } + CERT_DestroyCertificate( node->cert ); + node = node->next; + } + } + + PORT_FreeArena( verifylog.arena, PR_FALSE ); + + if ( ret == SECSuccess ) { Debug( LDAP_DEBUG_TRACE, - "TLS certificate verification: ok\n", - 0, 0, 0 ); + "TLS: certificate [%s] is valid\n", name, 0, 0 ); + } else if ( errorToIgnore == -1 ) { + ret = SECSuccess; } return ret; } +static SECStatus +tlsm_auth_cert_handler(void *arg, PRFileDesc *fd, + PRBool checksig, PRBool isServer) +{ + SECCertificateUsage certUsage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; + SECStatus ret = SECSuccess; + CERTCertificate *peercert = SSL_PeerCertificate( fd ); + int errorToIgnore = 0; + tlsm_ctx *ctx = (tlsm_ctx *)arg; + + if (ctx && ctx->tc_warn_only ) + errorToIgnore = -1; + + ret = tlsm_verify_cert( ctx->tc_certdb, peercert, + SSL_RevealPinArg( fd ), + checksig, certUsage, errorToIgnore ); + CERT_DestroyCertificate( peercert ); + + return ret; +} + static int tlsm_authenticate_to_slot( tlsm_ctx *ctx, PK11SlotInfo *slot ) { @@ -947,7 +1099,6 @@ tlsm_nss_shutdown_cb( void *appData, void *nssData ) SECStatus rc = SECSuccess; SSL_ShutdownServerSessionIDCache(); - SSL_ClearSessionCache(); if ( pem_module ) { SECMOD_UnloadUserModule( pem_module ); @@ -957,6 +1108,24 @@ tlsm_nss_shutdown_cb( void *appData, void *nssData ) return rc; } +static PRCallOnceType tlsm_register_shutdown_callonce = {0,0}; +static PRStatus PR_CALLBACK +tlsm_register_nss_shutdown_cb( void ) +{ + if ( SECSuccess == NSS_RegisterShutdown( tlsm_nss_shutdown_cb, + NULL ) ) { + return PR_SUCCESS; + } + return PR_FAILURE; +} + +static PRStatus +tlsm_register_nss_shutdown( void ) +{ + return PR_CallOnce( &tlsm_register_shutdown_callonce, + tlsm_register_nss_shutdown_cb ); +} + static int tlsm_init_pem_module( void ) { @@ -1013,7 +1182,7 @@ tlsm_free_pem_objs( tlsm_ctx *ctx ) } static int -tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) +tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca, PRBool istrusted ) { CK_SLOT_ID slotID; PK11SlotInfo *slot = NULL; @@ -1055,9 +1224,14 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) slotID = 0; /* CA and trust objects use slot 0 */ PR_snprintf( tmpslotname, sizeof(tmpslotname), TLSM_PEM_TOKEN_FMT, slotID ); slotname = tmpslotname; + istrusted = PR_TRUE; } else { if ( ctx->tc_slotname == NULL ) { /* need new slot */ - slotID = ++tlsm_slot_count; + if ( istrusted ) { + slotID = 0; + } else { + slotID = ++tlsm_slot_count; + } ctx->tc_slotname = PR_smprintf( TLSM_PEM_TOKEN_FMT, slotID ); } slotname = ctx->tc_slotname; @@ -1065,7 +1239,15 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) if ( ( ptr = PL_strrchr( filename, sep ) ) ) { PL_strfree( ctx->tc_certname ); ++ptr; - ctx->tc_certname = PR_smprintf( "%s:%s", slotname, ptr ); + if ( istrusted ) { + /* pemnss conflates trusted certs with CA certs - since there can + be more than one CA cert in a file (e.g. ca-bundle.crt) pemnss + numbers each trusted cert - in the case of a server cert, there will be + only one, so it will be number 0 */ + ctx->tc_certname = PR_smprintf( "%s:%s - 0", slotname, ptr ); + } else { + ctx->tc_certname = PR_smprintf( "%s:%s", slotname, ptr ); + } } } @@ -1083,7 +1265,7 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) PK11_SETATTRS( attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++; PK11_SETATTRS( attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++; PK11_SETATTRS( attrs, CKA_LABEL, (unsigned char *)filename, strlen(filename)+1 ); attrs++; - if ( isca ) { + if ( istrusted ) { PK11_SETATTRS( attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) ); attrs++; } else { PK11_SETATTRS( attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) ); attrs++; @@ -1191,7 +1373,7 @@ static int tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir ) { PRBool isca = PR_TRUE; - PRStatus status = PR_FAILURE; + PRStatus status = PR_SUCCESS; PRErrorCode errcode = PR_SUCCESS; if ( !cacertfile && !cacertdir ) { @@ -1200,21 +1382,31 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir } if ( cacertfile ) { - int rc = tlsm_add_cert_from_file( ctx, cacertfile, isca ); + int rc = tlsm_add_cert_from_file( ctx, cacertfile, isca, PR_TRUE ); if ( rc ) { errcode = PR_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: %s is not a valid CA certificate file - error %d:%s.\n", cacertfile, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + /* failure with cacertfile is a hard failure even if cacertdir is + also specified and contains valid CA cert files */ + status = PR_FAILURE; } else { Debug( LDAP_DEBUG_TRACE, "TLS: loaded CA certificate file %s.\n", cacertfile, 0, 0 ); - status = PR_SUCCESS; /* have at least one good CA - we can proceed */ } } + /* if cacertfile above failed, we will return failure, even + if there is a valid CA cert in cacertdir - but we still + process cacertdir in case the user has enabled trace level + debugging so they can see the processing for cacertdir too */ + /* any cacertdir failures are "soft" failures - if the user specifies + no cert checking, then we allow the tls/ssl to continue, no matter + what was specified for cacertdir, or the contents of the directory + - this is different behavior than that of cacertfile */ if ( cacertdir ) { PRFileInfo fi; PRDir *dir; @@ -1264,11 +1456,10 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir continue; } fullpath = PR_smprintf( "%s/%s", cacertdir, entry->name ); - if ( !tlsm_add_cert_from_file( ctx, fullpath, isca ) ) { + if ( !tlsm_add_cert_from_file( ctx, fullpath, isca, PR_TRUE ) ) { Debug( LDAP_DEBUG_TRACE, "TLS: loaded CA certificate file %s from CA certificate directory %s.\n", fullpath, cacertdir, 0 ); - status = PR_SUCCESS; /* found at least 1 valid CA file in the dir */ } else { errcode = PR_GetError(); Debug( LDAP_DEBUG_TRACE, @@ -1283,14 +1474,6 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir } done: if ( status != PR_SUCCESS ) { - const char *fmtstr = NULL; - if ( cacertfile && cacertdir ) { - fmtstr = "TLS: did not find any valid CA certificates in %s or %s\n"; - } else { - fmtstr = "TLS: did not find any valid CA certificates in %s%s\n"; - } - Debug( LDAP_DEBUG_ANY, fmtstr, cacertdir ? cacertdir : "", - cacertfile ? cacertfile : "", 0 ); return -1; } @@ -1360,11 +1543,35 @@ tlsm_deferred_init( void *arg ) SECStatus rc; int done = 0; +#ifdef HAVE_SECMOD_RESTARTMODULES + /* NSS enforces the pkcs11 requirement that modules should be unloaded after + a fork() - since there is no portable way to determine if NSS has been + already initialized in a parent process, we just call SECMOD_RestartModules + with force == FALSE - if the module has been unloaded due to a fork, it will + be reloaded, otherwise, it is a no-op */ + if ( SECFailure == ( rc = SECMOD_RestartModules(PR_FALSE /* do not force */) ) ) { + errcode = PORT_GetError(); + if ( errcode != SEC_ERROR_NOT_INITIALIZED ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: could not restart the security modules: %d:%s\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + } else { + errcode = 1; + } + } +#endif + #ifdef HAVE_NSS_INITCONTEXT memset( &initParams, 0, sizeof( initParams ) ); initParams.length = sizeof( initParams ); #endif /* HAVE_NSS_INITCONTEXT */ +#ifdef LDAP_R_COMPILE + if ( PR_CallOnce( &tlsm_init_mutex_callonce, tlsm_thr_init_callonce ) ) { + return -1; + } +#endif /* LDAP_R_COMPILE */ + #ifndef HAVE_NSS_INITCONTEXT if ( !NSS_IsInitialized() ) { #endif /* HAVE_NSS_INITCONTEXT */ @@ -1392,6 +1599,8 @@ tlsm_deferred_init( void *arg ) } tlsm_get_certdb_prefix( securitydir, &realcertdir, &prefix ); + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); + #ifdef HAVE_NSS_INITCONTEXT #ifdef INITCONTEXT_HACK if ( !NSS_IsInitialized() && ctx->tc_is_server ) { @@ -1410,6 +1619,8 @@ tlsm_deferred_init( void *arg ) rc = NSS_Initialize( realcertdir, prefix, prefix, SECMOD_DB, NSS_INIT_READONLY ); #endif + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); + if ( rc != SECSuccess ) { errcode = PORT_GetError(); if ( securitydirs[ii] != lt->lt_cacertdir) { @@ -1433,6 +1644,7 @@ tlsm_deferred_init( void *arg ) } if ( errcode ) { /* no moznss db found, or not using moznss db */ + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); #ifdef HAVE_NSS_INITCONTEXT int flags = NSS_INIT_READONLY|NSS_INIT_NOCERTDB|NSS_INIT_NOMODDB; #ifdef INITCONTEXT_HACK @@ -1451,6 +1663,7 @@ tlsm_deferred_init( void *arg ) #else rc = NSS_NoDB_Init( NULL ); #endif + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); if ( rc != SECSuccess ) { errcode = PORT_GetError(); Debug( LDAP_DEBUG_ANY, @@ -1464,13 +1677,16 @@ tlsm_deferred_init( void *arg ) #endif /* initialize the PEM module */ + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); if ( tlsm_init_pem_module() ) { + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); errcode = PORT_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: could not initialize moznss PEM module - error %d:%s.\n", errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); return -1; } + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); if ( tlsm_init_ca_certs( ctx, lt->lt_cacertfile, lt->lt_cacertdir ) ) { /* if we tried to use lt->lt_cacertdir as an NSS key/cert db, errcode @@ -1505,10 +1721,13 @@ tlsm_deferred_init( void *arg ) PK11_SetPasswordFunc( tlsm_pin_prompt ); /* register cleanup function */ - /* delete the old one, if any */ - NSS_UnregisterShutdown( tlsm_nss_shutdown_cb, NULL ); - NSS_RegisterShutdown( tlsm_nss_shutdown_cb, NULL ); - + if ( tlsm_register_nss_shutdown() ) { + errcode = PORT_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not register NSS shutdown function: %d:%s\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + return -1; + } #ifndef HAVE_NSS_INITCONTEXT } #endif /* HAVE_NSS_INITCONTEXT */ @@ -1607,6 +1826,8 @@ tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certnam SECCertificateUsage certUsage; PRBool checkSig = PR_TRUE; SECStatus status; + /* may not have a CA cert - ok - ignore SEC_ERROR_UNKNOWN_ISSUER */ + int errorToIgnore = SEC_ERROR_UNKNOWN_ISSUER; if ( pRetKey ) { *pRetKey = key; /* caller will deal with this */ @@ -1623,43 +1844,13 @@ tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certnam } else { checkSig = PR_FALSE; } - status = CERT_VerifyCertificateNow( ctx->tc_certdb, cert, - checkSig, certUsage, - pin_arg, NULL ); - if ( status != SECSuccess ) { - /* NSS doesn't like self-signed CA certs that are also used for - TLS/SSL server certs (such as generated by openssl req -x509) - CERT_VerifyCertificateNow returns SEC_ERROR_UNTRUSTED_ISSUER in that case - so, see if the cert and issuer are the same cert - */ - PRErrorCode errcode = PR_GetError(); - - if ( errcode == SEC_ERROR_UNTRUSTED_ISSUER ) { - CERTCertificate *issuer = CERT_FindCertIssuer( cert, PR_Now(), certUsageSSLServer ); - if ( NULL == issuer ) { - /* no issuer - fail */ - Debug( LDAP_DEBUG_ANY, - "TLS: error: the server certificate %s has no issuer - " - "please check this certificate for validity\n", - certname, 0, 0 ); - } else if ( CERT_CompareCerts( cert, issuer ) ) { - /* self signed - warn and allow */ - status = SECSuccess; - rc = 0; - Debug( LDAP_DEBUG_ANY, - "TLS: warning: using self-signed server certificate %s\n", - certname, 0, 0 ); - } - CERT_DestroyCertificate( issuer ); - } - - if ( status != SECSuccess ) { - Debug( LDAP_DEBUG_ANY, - "TLS: error: the certificate %s is not valid - error %d:%s\n", - certname, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); - } - } else { - rc = 0; /* success */ + if ( ctx->tc_warn_only ) { + errorToIgnore = -1; + } + status = tlsm_verify_cert( ctx->tc_certdb, cert, pin_arg, + checkSig, certUsage, errorToIgnore ); + if ( status == SECSuccess ) { + rc = 0; } } else { PRErrorCode errcode = PR_GetError(); @@ -1684,14 +1875,26 @@ tlsm_get_client_auth_data( void *arg, PRFileDesc *fd, { tlsm_ctx *ctx = (tlsm_ctx *)arg; int rc; + PRBool saveval; /* don't need caNames - this function will call CERT_VerifyCertificateNow which will verify the cert against the known CAs */ + saveval = ctx->tc_warn_only; + ctx->tc_warn_only = PR_TRUE; rc = tlsm_find_and_verify_cert_key( ctx, fd, ctx->tc_certname, 0, pRetCert, pRetKey ); + ctx->tc_warn_only = saveval; if ( rc ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to perform client certificate authentication for " "certificate named %s\n", ctx->tc_certname, 0, 0 ); + if ( pRetKey && *pRetKey ) { + SECKEY_DestroyPrivateKey( *pRetKey ); + *pRetKey = NULL; + } + if ( pRetCert && *pRetCert ) { + CERT_DestroyCertificate( *pRetCert ); + *pRetCert = NULL; + } return SECFailure; } @@ -1710,8 +1913,12 @@ tlsm_clientauth_init( tlsm_ctx *ctx ) { SECStatus status = SECFailure; int rc; + PRBool saveval; + saveval = ctx->tc_warn_only; + ctx->tc_warn_only = PR_TRUE; rc = tlsm_find_and_verify_cert_key( ctx, ctx->tc_model, ctx->tc_certname, 0, NULL, NULL ); + ctx->tc_warn_only = saveval; if ( rc ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to set up client certificate authentication for " @@ -1732,6 +1939,9 @@ tlsm_clientauth_init( tlsm_ctx *ctx ) static void tlsm_destroy( void ) { +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_destroy( &tlsm_init_mutex ); +#endif } static tls_ctx * @@ -1760,6 +1970,7 @@ tlsm_ctx_new ( struct ldapoptions *lo ) #endif /* HAVE_NSS_INITCONTEXT */ ctx->tc_pem_objs = NULL; ctx->tc_n_pem_objs = 0; + ctx->tc_warn_only = PR_FALSE; } return (tls_ctx *)ctx; } @@ -1796,8 +2007,16 @@ tlsm_ctx_free ( tls_ctx *ctx ) PL_strfree( c->tc_slotname ); tlsm_free_pem_objs( c ); #ifdef HAVE_NSS_INITCONTEXT - if (c->tc_initctx) - NSS_ShutdownContext( c->tc_initctx ); + if ( c->tc_initctx ) { + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); + if ( NSS_ShutdownContext( c->tc_initctx ) ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not shutdown NSS - error %d:%s.\n", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + } + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); + } c->tc_initctx = NULL; #endif /* HAVE_NSS_INITCONTEXT */ #ifdef LDAP_R_COMPILE @@ -1914,9 +2133,16 @@ tlsm_deferred_ctx_init( void *arg ) "TLS: could not set cipher list %s.\n", lt->lt_ciphersuite, 0, 0 ); return -1; - } + } else if ( tlsm_parse_ciphers( ctx, "DEFAULT" ) ) { + Debug( LDAP_DEBUG_ANY, + "TLS: could not set cipher list DEFAULT.\n", + 0, 0, 0 ); + return -1; + } - if ( ctx->tc_require_cert ) { + if ( !ctx->tc_require_cert ) { + ctx->tc_verify_cert = PR_FALSE; + } else if ( !ctx->tc_is_server ) { request_cert = PR_TRUE; require_cert = SSL_REQUIRE_NO_ERROR; if ( ctx->tc_require_cert == LDAP_OPT_X_TLS_DEMAND || @@ -1925,8 +2151,22 @@ tlsm_deferred_ctx_init( void *arg ) } if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW ) ctx->tc_verify_cert = PR_TRUE; - } else { - ctx->tc_verify_cert = PR_FALSE; + } else { /* server */ + /* server does not request certs by default */ + /* if allow - client may send cert, server will ignore if errors */ + /* if try - client may send cert, server will error if bad cert */ + /* if hard or demand - client must send cert, server will error if bad cert */ + request_cert = PR_TRUE; + require_cert = SSL_REQUIRE_NO_ERROR; + if ( ctx->tc_require_cert == LDAP_OPT_X_TLS_DEMAND || + ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) { + require_cert = SSL_REQUIRE_ALWAYS; + } + if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW ) { + ctx->tc_verify_cert = PR_TRUE; + } else { + ctx->tc_warn_only = PR_TRUE; + } } if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_REQUEST_CERTIFICATE, request_cert ) ) { @@ -1949,7 +2189,7 @@ tlsm_deferred_ctx_init( void *arg ) /* otherwise, assume this is the name of a cert already in the db */ if ( ctx->tc_using_pem ) { /* this sets ctx->tc_certname to the correct value */ - int rc = tlsm_add_cert_from_file( ctx, lt->lt_certfile, PR_FALSE /* not a ca */ ); + int rc = tlsm_add_cert_from_file( ctx, lt->lt_certfile, PR_FALSE, PR_TRUE ); if ( rc ) { return rc; } @@ -2038,6 +2278,8 @@ tlsm_deferred_ctx_init( void *arg ) Debug( LDAP_DEBUG_ANY, "TLS: error: unable to find and verify server's cert and key for certificate %s\n", ctx->tc_certname, 0, 0 ); + CERT_DestroyCertificate( serverCert ); + SECKEY_DestroyPrivateKey( serverKey ); return -1; } @@ -2059,7 +2301,7 @@ tlsm_deferred_ctx_init( void *arg ) /* Callback for authenticating certificate */ if ( SSL_AuthCertificateHook( ctx->tc_model, tlsm_auth_cert_handler, - ctx->tc_certdb ) != SECSuccess ) { + ctx ) != SECSuccess ) { PRErrorCode err = PR_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: error: could not set auth cert handler for moznss - error %d:%s\n", @@ -2091,49 +2333,74 @@ struct tls_data { we will just see if the IO op returns EAGAIN or EWOULDBLOCK, and just set this flag */ PRBool nonblock; + /* + * NSS tries hard to be backwards compatible with SSLv2 clients, or + * clients that send an SSLv2 client hello. This message is not + * tagged in any way, so NSS has no way to know if the incoming + * message is a valid SSLv2 client hello or just some bogus data + * (or cleartext LDAP). We store the first byte read from the + * client here. The most common case will be a client sending + * LDAP data instead of SSL encrypted LDAP data. This can happen, + * for example, if using ldapsearch -Z - if the starttls fails, + * the client will fallback to plain cleartext LDAP. So if we + * see that the firstbyte is a valid LDAP tag, we can be + * pretty sure this is happening. + */ + ber_tag_t firsttag; + /* + * NSS doesn't return SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, etc. + * when it is blocked, so we have to set a flag in the wrapped send + * and recv calls that tells us what operation NSS was last blocked + * on + */ +#define TLSM_READ 1 +#define TLSM_WRITE 2 + int io_flag; }; -static int -tlsm_is_io_ready( PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags ) +static struct tls_data * +tlsm_get_pvt_tls_data( PRFileDesc *fd ) { struct tls_data *p; - PRFileDesc *pollfd = NULL; PRFileDesc *myfd; - PRPollDesc polldesc; - int rc; + + if ( !fd ) { + return NULL; + } myfd = PR_GetIdentitiesLayer( fd, tlsm_layer_id ); if ( !myfd ) { - return 0; + return NULL; } p = (struct tls_data *)myfd->secret; - if ( p == NULL || p->sbiod == NULL ) { - return 0; - } + return p; +} - /* wrap the sockbuf fd with a NSPR FD created especially - for use with polling, and only with polling */ - pollfd = PR_CreateSocketPollFd( p->sbiod->sbiod_sb->sb_fd ); - polldesc.fd = pollfd; - polldesc.in_flags = in_flags; - polldesc.out_flags = 0; +static int +tlsm_is_non_ssl_message( PRFileDesc *fd, ber_tag_t *thebyte ) +{ + struct tls_data *p; - /* do the poll - no waiting, no blocking */ - rc = PR_Poll( &polldesc, 1, PR_INTERVAL_NO_WAIT ); + if ( thebyte ) { + *thebyte = LBER_DEFAULT; + } - /* unwrap the socket */ - PR_DestroySocketPollFd( pollfd ); + p = tlsm_get_pvt_tls_data( fd ); + if ( p == NULL || p->sbiod == NULL ) { + return 0; + } - /* rc will be either 1 if IO is ready, 0 if IO is not - ready, or -1 if there was some error (and the caller - should use PR_GetError() to figure out what */ - if (out_flags) { - *out_flags = polldesc.out_flags; + if ( p->firsttag == LBER_SEQUENCE ) { + if ( thebyte ) { + *thebyte = p->firsttag; + } + return 1; } - return rc; + + return 0; } static tls_session * @@ -2143,6 +2410,7 @@ tlsm_session_new ( tls_ctx * ctx, int is_server ) tlsm_session *session; PRFileDesc *fd; PRStatus status; + int rc; c->tc_is_server = is_server; status = PR_CallOnceWithArg( &c->tc_callonce, tlsm_deferred_ctx_init, c ); @@ -2170,121 +2438,80 @@ tlsm_session_new ( tls_ctx * ctx, int is_server ) SSL_ConfigServerSessionIDCache( 0, 0, 0, NULL ); } + rc = SSL_ResetHandshake( session, is_server ); + if ( rc ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_TRACE, + "TLS: error: new session - reset handshake failure %d - error %d:%s\n", + rc, err, + err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); + PR_DELETE( fd ); + PR_Close( session ); + session = NULL; + } + return (tls_session *)session; } static int -tlsm_session_accept( tls_session *session ) +tlsm_session_accept_or_connect( tls_session *session, int is_accept ) { tlsm_session *s = (tlsm_session *)session; - int rc; - PRErrorCode err; - int waitcounter = 0; - - rc = SSL_ResetHandshake( s, PR_TRUE /* server */ ); - if (rc) { - err = PR_GetError(); - Debug( LDAP_DEBUG_TRACE, - "TLS: error: accept - reset handshake failure %d - error %d:%s\n", - rc, err, - err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); - } - - do { - PRInt32 filesReady; - PRInt16 in_flags; - PRInt16 out_flags; + int rc = SSL_ForceHandshake( s ); + const char *op = is_accept ? "accept" : "connect"; - errno = 0; - rc = SSL_ForceHandshake( s ); - if (rc == SECSuccess) { - rc = 0; - break; /* done */ - } - err = PR_GetError(); - if ( errno == EAGAIN || errno == EWOULDBLOCK ) { - waitcounter++; - in_flags = PR_POLL_READ | PR_POLL_EXCEPT; - out_flags = 0; - errno = 0; - filesReady = tlsm_is_io_ready( s, in_flags, &out_flags ); - if ( filesReady < 0 ) { - err = PR_GetError(); - Debug( LDAP_DEBUG_ANY, - "TLS: error: accept - error waiting for socket to be ready: %d - error %d:%s\n", - errno, err, - err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); - rc = -1; - break; /* hard error */ - } else if ( out_flags & PR_POLL_NVAL ) { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); - Debug( LDAP_DEBUG_ANY, - "TLS: error: accept failure - invalid socket\n", - NULL, NULL, NULL ); - rc = -1; - break; - } else if ( out_flags & PR_POLL_EXCEPT ) { - err = PR_GetError(); + if ( rc ) { + PRErrorCode err = PR_GetError(); + rc = -1; + if ( err == PR_WOULD_BLOCK_ERROR ) { + ber_tag_t thetag = LBER_DEFAULT; + /* see if we are blocked because of a bogus packet */ + if ( tlsm_is_non_ssl_message( s, &thetag ) ) { /* see if we received a non-SSL message */ Debug( LDAP_DEBUG_ANY, - "TLS: error: accept - error waiting for socket to be ready: %d - error %d:%s\n", - errno, err, - err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); - rc = -1; - break; /* hard error */ + "TLS: error: %s - error - received non-SSL message [0x%x]\n", + op, (unsigned int)thetag, 0 ); + /* reset error to something more descriptive */ + PR_SetError( SSL_ERROR_RX_MALFORMED_HELLO_REQUEST, EPROTO ); } - } else { /* hard error */ - err = PR_GetError(); + } else { Debug( LDAP_DEBUG_ANY, - "TLS: error: accept - force handshake failure: %d - error %d:%s\n", - errno, err, - err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); - rc = -1; - break; /* hard error */ + "TLS: error: %s - force handshake failure: errno %d - moznss error %d\n", + op, errno, err ); } - } while (rc == SECFailure); - - Debug( LDAP_DEBUG_TRACE, - "TLS: accept completed after %d waits\n", waitcounter, NULL, NULL ); + } return rc; } +static int +tlsm_session_accept( tls_session *session ) +{ + return tlsm_session_accept_or_connect( session, 1 ); +} static int tlsm_session_connect( LDAP *ld, tls_session *session ) { - tlsm_session *s = (tlsm_session *)session; - int rc; - PRErrorCode err; - - rc = SSL_ResetHandshake( s, PR_FALSE /* server */ ); - if (rc) { - err = PR_GetError(); - Debug( LDAP_DEBUG_TRACE, - "TLS: error: connect - reset handshake failure %d - error %d:%s\n", - rc, err, - err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); - } - - rc = SSL_ForceHandshake( s ); - if (rc) { - err = PR_GetError(); - Debug( LDAP_DEBUG_TRACE, - "TLS: error: connect - force handshake failure %d - error %d:%s\n", - rc, err, - err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" ); - } - - return rc; + return tlsm_session_accept_or_connect( session, 0 ); } static int tlsm_session_upflags( Sockbuf *sb, tls_session *session, int rc ) { - /* Should never happen */ - rc = PR_GetError(); + int prerror = PR_GetError(); + + if ( ( prerror == PR_PENDING_INTERRUPT_ERROR ) || ( prerror == PR_WOULD_BLOCK_ERROR ) ) { + tlsm_session *s = (tlsm_session *)session; + struct tls_data *p = tlsm_get_pvt_tls_data( s ); + + if ( p && ( p->io_flag == TLSM_READ ) ) { + sb->sb_trans_needs_read = 1; + return 1; + } else if ( p && ( p->io_flag == TLSM_WRITE ) ) { + sb->sb_trans_needs_write = 1; + return 1; + } + } - if ( rc != PR_PENDING_INTERRUPT_ERROR && rc != PR_WOULD_BLOCK_ERROR ) - return 0; return 0; } @@ -2292,8 +2519,8 @@ static char * tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len ) { int i; + int prerror = PR_GetError(); - rc = PR_GetError(); i = PR_GetErrorTextLength(); if ( i > len ) { char *msg = LDAP_MALLOC( i+1 ); @@ -2302,9 +2529,12 @@ tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len ) LDAP_FREE( msg ); } else if ( i ) { PR_GetErrorText( buf ); + } else if ( prerror ) { + i = PR_snprintf( buf, len, "TLS error %d:%s", + prerror, PR_ErrorToString( prerror, PR_LANGUAGE_I_DEFAULT ) ); } - return i ? buf : NULL; + return ( i > 0 ) ? buf : NULL; } static int @@ -2573,7 +2803,7 @@ tlsm_PR_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags, if ( buf == NULL || len <= 0 ) return 0; - p = (struct tls_data *)fd->secret; + p = tlsm_get_pvt_tls_data( fd ); if ( p == NULL || p->sbiod == NULL ) { return 0; @@ -2589,7 +2819,10 @@ tlsm_PR_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags, "TLS: error: tlsm_PR_Recv returned %d - error %d:%s\n", rc, errno, STRERROR(errno) ); } + } else if ( ( rc > 0 ) && ( len > 0 ) && ( p->firsttag == LBER_DEFAULT ) ) { + p->firsttag = (ber_tag_t)*((char *)buf); } + p->io_flag = TLSM_READ; return rc; } @@ -2603,7 +2836,7 @@ tlsm_PR_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags, if ( buf == NULL || len <= 0 ) return 0; - p = (struct tls_data *)fd->secret; + p = tlsm_get_pvt_tls_data( fd ); if ( p == NULL || p->sbiod == NULL ) { return 0; @@ -2620,6 +2853,7 @@ tlsm_PR_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags, rc, errno, STRERROR(errno) ); } } + p->io_flag = TLSM_WRITE; return rc; } @@ -2642,7 +2876,7 @@ tlsm_PR_GetPeerName(PRFileDesc *fd, PRNetAddr *addr) struct tls_data *p; ber_socklen_t len; - p = (struct tls_data *)fd->secret; + p = tlsm_get_pvt_tls_data( fd ); if ( p == NULL || p->sbiod == NULL ) { return PR_FAILURE; @@ -2655,9 +2889,9 @@ static PRStatus PR_CALLBACK tlsm_PR_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data) { struct tls_data *p; - p = (struct tls_data *)fd->secret; + p = tlsm_get_pvt_tls_data( fd ); - if ( !data ) { + if ( p == NULL || data == NULL ) { return PR_FAILURE; } @@ -2760,10 +2994,29 @@ static const PRIOMethods tlsm_PR_methods = { static int tlsm_init( void ) { + char *nofork = PR_GetEnv( "NSS_STRICT_NOFORK" ); + PR_Init(0, 0, 0); tlsm_layer_id = PR_GetUniqueIdentity( "OpenLDAP" ); + /* + * There are some applications that acquire a crypto context in the parent process + * and expect that crypto context to work after a fork(). This does not work + * with NSS using strict PKCS11 compliance mode. We set this environment + * variable here to tell the software encryption module/token to allow crypto + * contexts to persist across a fork(). However, if you are using some other + * module or encryption device that supports and expects full PKCS11 semantics, + * the only recourse is to rewrite the application with atfork() handlers to save + * the crypto context in the parent and restore (and SECMOD_RestartModules) the + * context in the child. + */ + if ( !nofork ) { + /* will leak one time */ + char *noforkenvvar = PL_strdup( "NSS_STRICT_NOFORK=DISABLED" ); + PR_SetEnv( noforkenvvar ); + } + return 0; } @@ -2790,6 +3043,7 @@ tlsm_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) fd->secret = (PRFilePrivate *)p; p->session = session; p->sbiod = sbiod; + p->firsttag = LBER_DEFAULT; sbiod->sbiod_pvt = p; return 0; } @@ -2837,7 +3091,7 @@ tlsm_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg ) return 1; } else if ( opt == LBER_SB_OPT_DATA_READY ) { - if ( tlsm_is_io_ready( p->session, PR_POLL_READ, NULL ) > 0 ) { + if ( p && ( SSL_DataPending( p->session ) > 0 ) ) { return 1; }