X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=libraries%2Flibldap%2Ftls_m.c;h=c85d322014fa838341f3fefdea9a5f693fadc079;hb=7164c4c66215da39f03e353a9c72eba408abddbf;hp=b62aa0e7462ae6d7318dc877970bb19fa929ccdc;hpb=2ba0e44ad11643a27fd28dbcd75e5630bfa902ea;p=openldap diff --git a/libraries/libldap/tls_m.c b/libraries/libldap/tls_m.c index b62aa0e746..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-2009 The OpenLDAP Foundation. + * Copyright 2008-2011 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,10 +42,6 @@ #include "ldap-int.h" #include "ldap-tls.h" -#ifdef LDAP_R_COMPILE -#include -#endif - #define READ_PASSWORD_FROM_STDIN #define READ_PASSWORD_FROM_FILE @@ -63,6 +59,24 @@ #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_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 */ typedef struct tlsm_ctx { PRFileDesc *tc_model; @@ -77,6 +91,12 @@ typedef struct tlsm_ctx { PRCallOnceType tc_callonce; PRBool tc_using_pem; char *tc_slotname; /* if using pem */ +#ifdef HAVE_NSS_INITCONTEXT + NSSInitContext *tc_initctx; /* the NSS context */ +#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 @@ -88,10 +108,13 @@ static PRDescIdentity tlsm_layer_id; static const PRIOMethods tlsm_PR_methods; -static int tlsm_did_init; +#define PEM_LIBRARY "nsspem" +#define PEM_MODULE "PEM" +/* hash files for use with cacertdir have this file name suffix */ +#define PEM_CA_HASH_FILE_SUFFIX ".0" +#define PEM_CA_HASH_FILE_SUFFIX_LEN 2 -static const char* pem_library = "nsspem"; -static SECMODModule* pemMod = NULL; +static SECMODModule *pem_module; #define DEFAULT_TOKEN_NAME "default" /* sprintf format used to create token name */ @@ -107,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 */ @@ -195,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}, @@ -206,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)) @@ -649,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; } @@ -696,6 +740,12 @@ tlsm_dump_security_status(PRFileDesc *fd) return ""; } +static void +tlsm_handshake_complete_cb( PRFileDesc *fd, void *client_data ) +{ + tlsm_dump_security_status( fd ); +} + #ifdef READ_PASSWORD_FROM_FILE static char * tlsm_get_pin_from_file(const char *token_name, tlsm_ctx *ctx) @@ -876,23 +926,151 @@ 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; - tlsm_dump_security_status( fd ); - Debug( LDAP_DEBUG_TRACE, - "TLS certificate verification: %s\n", - ret == SECSuccess ? "ok" : "bad", 0, 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; + } - if ( ret != SECSuccess ) { - PRErrorCode errcode = PORT_GetError(); + /* 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: Error, %d: %s\n", - errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ) ; + "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 [%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; } @@ -915,25 +1093,39 @@ tlsm_authenticate_to_slot( tlsm_ctx *ctx, PK11SlotInfo *slot ) return rc; } -static int -tlsm_init_tokens( tlsm_ctx *ctx ) +static SECStatus +tlsm_nss_shutdown_cb( void *appData, void *nssData ) { - PK11SlotList *slotList; - PK11SlotListElement *listEntry; - int rc = 0; + SECStatus rc = SECSuccess; - slotList = PK11_GetAllTokens( CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, NULL ); + SSL_ShutdownServerSessionIDCache(); - for ( listEntry = PK11_GetFirstSafe( slotList ); !rc && listEntry; - listEntry = listEntry->next) { - PK11SlotInfo *slot = listEntry->slot; - rc = tlsm_authenticate_to_slot( ctx, slot ); - PK11_FreeSlot(slot); + if ( pem_module ) { + SECMOD_UnloadUserModule( pem_module ); + SECMOD_DestroyModule( pem_module ); + pem_module = NULL; } - 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 ) { @@ -941,19 +1133,24 @@ tlsm_init_pem_module( void ) char *fullname = NULL; char *configstring = NULL; + if ( pem_module ) { + return rc; + } + + /* not loaded - load it */ /* get the system dependent library name */ - fullname = PR_GetLibraryName( NULL, pem_library ); + fullname = PR_GetLibraryName( NULL, PEM_LIBRARY ); /* Load our PKCS#11 module */ - configstring = PR_smprintf( "library=%s name=PEM parameters=\"\"", fullname ); - PR_smprintf_free( fullname ); + configstring = PR_smprintf( "library=%s name=" PEM_MODULE " parameters=\"\"", fullname ); + PL_strfree( fullname ); - pemMod = SECMOD_LoadUserModule( configstring, NULL, PR_FALSE ); + pem_module = SECMOD_LoadUserModule( configstring, NULL, PR_FALSE ); PR_smprintf_free( configstring ); - if ( !pemMod || !pemMod->loaded ) { - if ( pemMod ) { - SECMOD_DestroyModule( pemMod ); - pemMod = NULL; + if ( !pem_module || !pem_module->loaded ) { + if ( pem_module ) { + SECMOD_DestroyModule( pem_module ); + pem_module = NULL; } rc = -1; } @@ -961,8 +1158,31 @@ tlsm_init_pem_module( void ) return rc; } +static void +tlsm_add_pem_obj( tlsm_ctx *ctx, PK11GenericObject *obj ) +{ + int idx = ctx->tc_n_pem_objs; + ctx->tc_n_pem_objs++; + ctx->tc_pem_objs = (PK11GenericObject **) + PORT_Realloc( ctx->tc_pem_objs, ctx->tc_n_pem_objs * sizeof( PK11GenericObject * ) ); + ctx->tc_pem_objs[idx] = obj; +} + +static void +tlsm_free_pem_objs( tlsm_ctx *ctx ) +{ + /* free in reverse order of allocation */ + while ( ctx->tc_n_pem_objs-- ) { + PK11_DestroyGenericObject( ctx->tc_pem_objs[ctx->tc_n_pem_objs] ); + ctx->tc_pem_objs[ctx->tc_n_pem_objs] = NULL; + } + PORT_Free(ctx->tc_pem_objs); + ctx->tc_pem_objs = NULL; + ctx->tc_n_pem_objs = 0; +} + 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; @@ -976,6 +1196,27 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) char *slotname = NULL; const char *ptr = NULL; char sep = PR_GetDirectorySeparator(); + PRFileInfo fi; + PRStatus status; + + memset( &fi, 0, sizeof(fi) ); + status = PR_GetFileInfo( filename, &fi ); + if ( PR_SUCCESS != status) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not read certificate file %s - error %d:%s.\n", + filename, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + if ( fi.type != PR_FILE_FILE ) { + PR_SetError(PR_IS_DIRECTORY_ERROR, 0); + Debug( LDAP_DEBUG_ANY, + "TLS: error: the certificate file %s is not a file.\n", + filename, 0 ,0 ); + return -1; + } attrs = theTemplate; @@ -983,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; @@ -993,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 ); + } } } @@ -1011,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++; @@ -1032,6 +1286,8 @@ tlsm_add_cert_from_file( tlsm_ctx *ctx, const char *filename, PRBool isca ) return -1; } + tlsm_add_pem_obj( ctx, rv ); + return 0; } @@ -1046,6 +1302,27 @@ tlsm_add_key_from_file( tlsm_ctx *ctx, const char *filename ) CK_BBOOL cktrue = CK_TRUE; CK_OBJECT_CLASS objClass = CKO_PRIVATE_KEY; int retcode = 0; + PRFileInfo fi; + PRStatus status; + + memset( &fi, 0, sizeof(fi) ); + status = PR_GetFileInfo( filename, &fi ); + if ( PR_SUCCESS != status) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not read key file %s - error %d:%s.\n", + filename, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + if ( fi.type != PR_FILE_FILE ) { + PR_SetError(PR_IS_DIRECTORY_ERROR, 0); + Debug( LDAP_DEBUG_ANY, + "TLS: error: the key file %s is not a file.\n", + filename, 0 ,0 ); + return -1; + } attrs = theTemplate; @@ -1079,13 +1356,16 @@ tlsm_add_key_from_file( tlsm_ctx *ctx, const char *filename ) } else { /* When adding an encrypted key the PKCS#11 will be set as removed */ /* This will force the token to be seen as re-inserted */ - SECMOD_WaitForAnyTokenEvent( pemMod, 0, 0 ); + SECMOD_WaitForAnyTokenEvent( pem_module, 0, 0 ); PK11_IsPresent( slot ); retcode = 0; } PK11_FreeSlot( slot ); + if ( !retcode ) { + tlsm_add_pem_obj( ctx, rv ); + } return retcode; } @@ -1093,74 +1373,152 @@ static int tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir ) { PRBool isca = PR_TRUE; + PRStatus status = PR_SUCCESS; + PRErrorCode errcode = PR_SUCCESS; + + if ( !cacertfile && !cacertdir ) { + /* no checking - not good, but allowed */ + return 0; + } 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 ) { - return 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 ); } } + /* 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; - PRStatus status; PRDir *dir; PRDirEntry *entry; + PRStatus fistatus = PR_FAILURE; memset( &fi, 0, sizeof(fi) ); - status = PR_GetFileInfo( cacertdir, &fi ); - if ( PR_SUCCESS != status) { - PRErrorCode errcode = PR_GetError(); + fistatus = PR_GetFileInfo( cacertdir, &fi ); + if ( PR_SUCCESS != fistatus) { + errcode = PR_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: could not get info about the CA certificate directory %s - error %d:%s.\n", cacertdir, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); - return -1; + goto done; } if ( fi.type != PR_FILE_DIRECTORY ) { Debug( LDAP_DEBUG_ANY, "TLS: error: the CA certificate directory %s is not a directory.\n", cacertdir, 0 ,0 ); - return -1; + goto done; } dir = PR_OpenDir( cacertdir ); if ( NULL == dir ) { - PRErrorCode errcode = PR_GetError(); + errcode = PR_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: could not open the CA certificate directory %s - error %d:%s.\n", cacertdir, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); - return -1; + goto done; } - status = -1; do { entry = PR_ReadDir( dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN ); - if ( NULL != entry ) { - char *fullpath = PR_smprintf( "%s/%s", cacertdir, entry->name ); - if ( !tlsm_add_cert_from_file( ctx, fullpath, isca ) ) { - status = 0; /* found at least 1 valid CA file in the dir */ + if ( ( NULL != entry ) && ( NULL != entry->name ) ) { + char *fullpath = NULL; + char *ptr; + + ptr = PL_strrstr( entry->name, PEM_CA_HASH_FILE_SUFFIX ); + if ( ( ptr == NULL ) || ( *(ptr + PEM_CA_HASH_FILE_SUFFIX_LEN) != '\0' ) ) { + Debug( LDAP_DEBUG_TRACE, + "TLS: file %s does not end in [%s] - does not appear to be a CA certificate " + "directory file with a properly hashed file name - skipping.\n", + entry->name, PEM_CA_HASH_FILE_SUFFIX, 0 ); + continue; + } + fullpath = PR_smprintf( "%s/%s", cacertdir, entry->name ); + 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 ); + } else { + errcode = PR_GetError(); + Debug( LDAP_DEBUG_TRACE, + "TLS: %s is not a valid CA certificate file - error %d:%s.\n", + fullpath, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); } PR_smprintf_free( fullpath ); } } while ( NULL != entry ); PR_CloseDir( dir ); - - if ( status ) { - PRErrorCode errcode = PR_GetError(); - Debug( LDAP_DEBUG_ANY, - "TLS: did not find any valid CA certificate files in the CA certificate directory %s - error %d:%s.\n", - cacertdir, errcode, - PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); - return -1; - } + } +done: + if ( status != PR_SUCCESS ) { + return -1; } return 0; } +/* + * NSS supports having multiple cert/key databases in the same + * directory, each one having a unique string prefix e.g. + * slapd-01-cert8.db - the prefix here is "slapd-01-" + * this function examines the given certdir - if it looks like + * /path/to/directory/prefix it will return the + * /path/to/directory part in realcertdir, and the prefix in prefix + */ +static void +tlsm_get_certdb_prefix( const char *certdir, char **realcertdir, char **prefix ) +{ + char sep = PR_GetDirectorySeparator(); + char *ptr = NULL; + struct PRFileInfo prfi; + PRStatus prc; + + *realcertdir = (char *)certdir; /* default is the one passed in */ + + /* if certdir is not given, just return */ + if ( !certdir ) { + return; + } + + prc = PR_GetFileInfo( certdir, &prfi ); + /* if certdir exists (file or directory) then it cannot specify a prefix */ + if ( prc == PR_SUCCESS ) { + return; + } + + /* if certdir was given, and there is a '/' in certdir, see if there + is anything after the last '/' - if so, assume it is the prefix */ + if ( ( ( ptr = strrchr( certdir, sep ) ) ) && *(ptr+1) ) { + *realcertdir = PL_strndup( certdir, ptr-certdir ); + *prefix = PL_strdup( ptr+1 ); + } + + return; +} + /* * This is the part of the init we defer until we get the * actual security configuration information. This is @@ -1178,11 +1536,45 @@ tlsm_deferred_init( void *arg ) int ii; int nn; PRErrorCode errcode = 1; +#ifdef HAVE_NSS_INITCONTEXT + NSSInitParameters initParams; + NSSInitContext *initctx = NULL; +#endif + 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 */ - /* NSS support for multi-init is coming */ -#ifndef NSS_MULTI_INIT +#ifndef HAVE_NSS_INITCONTEXT if ( !NSS_IsInitialized() ) { -#endif /* NSS_MULTI_INIT */ +#endif /* HAVE_NSS_INITCONTEXT */ /* MOZNSS_DIR will override everything else - you can always set MOZNSS_DIR to force the use of this @@ -1197,27 +1589,82 @@ tlsm_deferred_init( void *arg ) securitydirs[nn++] = PR_GetEnv( "MOZNSS_DIR" ); securitydirs[nn++] = lt->lt_cacertdir; securitydirs[nn++] = PR_GetEnv( "DEFAULT_MOZNSS_DIR" ); - for (ii = 0; ii < nn; ++ii) { + for ( ii = 0; !done && ( ii < nn ); ++ii ) { + char *realcertdir = NULL; + const char *defprefix = ""; + char *prefix = (char *)defprefix; const char *securitydir = securitydirs[ii]; if ( NULL == securitydir ) { continue; } - if ( NSS_Initialize( securitydir, "", "", SECMOD_DB, NSS_INIT_READONLY ) ) { + + 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 ) { + rc = NSS_Initialize( realcertdir, prefix, prefix, SECMOD_DB, NSS_INIT_READONLY ); + } else { + initctx = NSS_InitContext( realcertdir, prefix, prefix, SECMOD_DB, + &initParams, NSS_INIT_READONLY ); + rc = (initctx == NULL) ? SECFailure : SECSuccess; + } +#else + initctx = NSS_InitContext( realcertdir, prefix, prefix, SECMOD_DB, + &initParams, NSS_INIT_READONLY ); + rc = (initctx == NULL) ? SECFailure : SECSuccess; +#endif +#else + rc = NSS_Initialize( realcertdir, prefix, prefix, SECMOD_DB, NSS_INIT_READONLY ); +#endif + + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); + + if ( rc != SECSuccess ) { errcode = PORT_GetError(); - Debug( LDAP_DEBUG_TRACE, - "TLS: could not initialize moznss using security dir %s - error %d:%s.\n", - securitydir, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + if ( securitydirs[ii] != lt->lt_cacertdir) { + Debug( LDAP_DEBUG_TRACE, + "TLS: could not initialize moznss using security dir %s prefix %s - error %d.\n", + realcertdir, prefix, errcode ); + } } else { /* success */ - Debug( LDAP_DEBUG_TRACE, "TLS: using moznss security dir %s.\n", - securitydir, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "TLS: using moznss security dir %s prefix %s.\n", + realcertdir, prefix, 0 ); errcode = 0; - break; + done = 1; + } + if ( realcertdir != securitydir ) { + PL_strfree( realcertdir ); + } + if ( prefix != defprefix ) { + PL_strfree( prefix ); } } if ( errcode ) { /* no moznss db found, or not using moznss db */ - if ( NSS_NoDB_Init( NULL ) ) { + LDAP_MUTEX_LOCK( &tlsm_init_mutex ); +#ifdef HAVE_NSS_INITCONTEXT + int flags = NSS_INIT_READONLY|NSS_INIT_NOCERTDB|NSS_INIT_NOMODDB; +#ifdef INITCONTEXT_HACK + if ( !NSS_IsInitialized() && ctx->tc_is_server ) { + rc = NSS_NoDB_Init( NULL ); + } else { + initctx = NSS_InitContext( "", "", "", SECMOD_DB, + &initParams, flags ); + rc = (initctx == NULL) ? SECFailure : SECSuccess; + } +#else + initctx = NSS_InitContext( "", "", "", SECMOD_DB, + &initParams, flags ); + rc = (initctx == NULL) ? SECFailure : SECSuccess; +#endif +#else + rc = NSS_NoDB_Init( NULL ); +#endif + LDAP_MUTEX_UNLOCK( &tlsm_init_mutex ); + if ( rc != SECSuccess ) { errcode = PORT_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: could not initialize moznss - error %d:%s.\n", @@ -1225,34 +1672,65 @@ tlsm_deferred_init( void *arg ) return -1; } +#ifdef HAVE_NSS_INITCONTEXT + ctx->tc_initctx = initctx; +#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 + will be a value other than 1 - print an error message so that the + user will know that failed too */ + if ( ( errcode != 1 ) && ( lt->lt_cacertdir ) ) { + char *realcertdir = NULL; + char *prefix = NULL; + tlsm_get_certdb_prefix( lt->lt_cacertdir, &realcertdir, &prefix ); + Debug( LDAP_DEBUG_TRACE, + "TLS: could not initialize moznss using security dir %s prefix %s - error %d.\n", + realcertdir, prefix ? prefix : "", errcode ); + if ( realcertdir != lt->lt_cacertdir ) { + PL_strfree( realcertdir ); + } + PL_strfree( prefix ); + } return -1; } ctx->tc_using_pem = PR_TRUE; } +#ifdef HAVE_NSS_INITCONTEXT + if ( !ctx->tc_initctx ) { + ctx->tc_initctx = initctx; + } +#endif + NSS_SetDomesticPolicy(); PK11_SetPasswordFunc( tlsm_pin_prompt ); - if ( tlsm_init_tokens( ctx ) ) { + /* register cleanup function */ + 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; } - - tlsm_did_init = 1; /* we did the init - we should also clean up */ -#ifndef NSS_MULTI_INIT +#ifndef HAVE_NSS_INITCONTEXT } -#endif /* NSS_MULTI_INIT */ +#endif /* HAVE_NSS_INITCONTEXT */ return 0; } @@ -1348,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 */ @@ -1364,16 +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) { - PRErrorCode errcode = PR_GetError(); - 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(); @@ -1398,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; } @@ -1420,22 +1909,23 @@ tlsm_get_client_auth_data( void *arg, PRFileDesc *fd, * the database */ static int -tlsm_clientauth_init( tlsm_ctx *ctx, const char *certname ) +tlsm_clientauth_init( tlsm_ctx *ctx ) { SECStatus status = SECFailure; int rc; + PRBool saveval; - PL_strfree( ctx->tc_certname ); - rc = tlsm_find_and_verify_cert_key( ctx, ctx->tc_model, certname, 0, NULL, NULL ); + 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 " - "certificate named %s\n", certname, 0, 0 ); + "certificate named %s\n", ctx->tc_certname, 0, 0 ); return -1; } - ctx->tc_certname = PL_strdup( certname ); - status = SSL_GetClientAuthDataHook( ctx->tc_model, tlsm_get_client_auth_data, (void *)ctx ); @@ -1449,21 +1939,9 @@ tlsm_clientauth_init( tlsm_ctx *ctx, const char *certname ) static void tlsm_destroy( void ) { - if (pemMod) { - SECMOD_DestroyModule(pemMod); - pemMod = NULL; - } - - /* Only if we did the actual initialization */ - if ( tlsm_did_init ) { - tlsm_did_init = 0; - - SSL_ShutdownServerSessionIDCache(); - SSL_ClearSessionCache(); - NSS_Shutdown(); - } - - PR_Cleanup(); +#ifdef LDAP_R_COMPILE + ldap_pvt_thread_mutex_destroy( &tlsm_init_mutex ); +#endif } static tls_ctx * @@ -1487,6 +1965,12 @@ tlsm_ctx_new ( struct ldapoptions *lo ) ctx->tc_verify_cert = PR_FALSE; ctx->tc_using_pem = PR_FALSE; ctx->tc_slotname = NULL; +#ifdef HAVE_NSS_INITCONTEXT + ctx->tc_initctx = NULL; +#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; } @@ -1495,13 +1979,9 @@ static void tlsm_ctx_ref( tls_ctx *ctx ) { tlsm_ctx *c = (tlsm_ctx *)ctx; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_lock( &c->tc_refmutex ); -#endif + LDAP_MUTEX_LOCK( &c->tc_refmutex ); c->tc_refcnt++; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &c->tc_refmutex ); -#endif + LDAP_MUTEX_UNLOCK( &c->tc_refmutex ); } static void @@ -1512,13 +1992,9 @@ tlsm_ctx_free ( tls_ctx *ctx ) if ( !c ) return; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_lock( &c->tc_refmutex ); -#endif + LDAP_MUTEX_LOCK( &c->tc_refmutex ); refcount = --c->tc_refcnt; -#ifdef LDAP_R_COMPILE - ldap_pvt_thread_mutex_unlock( &c->tc_refmutex ); -#endif + LDAP_MUTEX_UNLOCK( &c->tc_refmutex ); if ( refcount ) return; if ( c->tc_model ) @@ -1528,7 +2004,21 @@ tlsm_ctx_free ( tls_ctx *ctx ) c->tc_certname = NULL; PL_strfree( c->tc_pin_file ); c->tc_pin_file = NULL; - PL_strfree( c->tc_slotname ); + PL_strfree( c->tc_slotname ); + tlsm_free_pem_objs( c ); +#ifdef HAVE_NSS_INITCONTEXT + 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 ldap_pvt_thread_mutex_destroy( &c->tc_refmutex ); #endif @@ -1566,7 +2056,7 @@ tlsm_deferred_ctx_init( void *arg ) return -1; } - ctx->tc_certdb = CERT_GetDefaultCertDB(); /* replace with multi-init db call */ + ctx->tc_certdb = CERT_GetDefaultCertDB(); /* If there is ever a per-context db, change this */ fd = PR_CreateIOLayerStub( tlsm_layer_id, &tlsm_PR_methods ); if ( fd ) { @@ -1643,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 || @@ -1654,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 ) ) { @@ -1678,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; } @@ -1731,7 +2242,7 @@ tlsm_deferred_ctx_init( void *arg ) ctx->tc_certname, 0, 0 ); return -1; } - if ( tlsm_clientauth_init( ctx, ctx->tc_certname ) ) { + if ( tlsm_clientauth_init( ctx ) ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to set up client certificate authentication using %s\n", ctx->tc_certname, 0, 0 ); @@ -1767,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; } @@ -1788,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", @@ -1796,6 +2309,14 @@ tlsm_deferred_ctx_init( void *arg ) return -1; } + if ( SSL_HandshakeCallback( ctx->tc_model, tlsm_handshake_complete_cb, ctx ) ) { + PRErrorCode err = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: error: could not set handshake callback for moznss - error %d:%s\n", + err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL ); + return -1; + } + return 0; } @@ -1812,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 * @@ -1864,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 ); @@ -1891,133 +2438,89 @@ 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(); - Debug( LDAP_DEBUG_TRACE, - "TLS: error: accept - force handshake failure %d - error %d waitcounter %d\n", - errno, err, waitcounter ); - 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; } static char * -tlsm_session_errmsg( int rc, char *buf, size_t len ) +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 ); @@ -2026,9 +2529,12 @@ tlsm_session_errmsg( 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 @@ -2040,7 +2546,7 @@ tlsm_session_my_dn( tls_session *session, struct berval *der_dn ) cert = SSL_LocalCertificate( s ); if (!cert) return LDAP_INVALID_CREDENTIALS; - der_dn->bv_val = cert->derSubject.data; + der_dn->bv_val = (char *)cert->derSubject.data; der_dn->bv_len = cert->derSubject.len; CERT_DestroyCertificate( cert ); return 0; @@ -2055,7 +2561,7 @@ tlsm_session_peer_dn( tls_session *session, struct berval *der_dn ) cert = SSL_PeerCertificate( s ); if (!cert) return LDAP_INVALID_CREDENTIALS; - der_dn->bv_val = cert->derSubject.data; + der_dn->bv_val = (char *)cert->derSubject.data; der_dn->bv_len = cert->derSubject.len; CERT_DestroyCertificate( cert ); return 0; @@ -2072,7 +2578,7 @@ tlsm_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) tlsm_session *s = (tlsm_session *)session; CERTCertificate *cert; const char *name, *domain = NULL, *ptr; - int i, ret, ntype = IS_DNS, nlen, dlen; + int ret, ntype = IS_DNS, nlen, dlen; #ifdef LDAP_PF_INET6 struct in6_addr addr; #else @@ -2144,7 +2650,7 @@ tlsm_session_chkhost( LDAP *ld, tls_session *session, const char *name_in ) /* ignore empty */ if ( !cur->name.other.len ) continue; - host = cur->name.other.data; + host = (char *)cur->name.other.data; hlen = cur->name.other.len; if ( cur->type == certDNSName ) { @@ -2202,11 +2708,11 @@ altfail: if ( lastava ) { SECItem *av = CERT_DecodeAVAValue( &lastava->value ); if ( av ) { - if ( av->len == nlen && !strncasecmp( name, av->data, nlen )) { + if ( av->len == nlen && !strncasecmp( name, (char *)av->data, nlen )) { ret = LDAP_SUCCESS; } else if ( av->data[0] == '*' && av->data[1] == '.' && domain && dlen == av->len - 1 && !strncasecmp( name, - av->data+1, dlen )) { + (char *)(av->data+1), dlen )) { ret = LDAP_SUCCESS; } else { int len = av->len; @@ -2297,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; @@ -2313,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; } @@ -2327,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; @@ -2344,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; } @@ -2364,10 +2874,9 @@ static PRStatus PR_CALLBACK tlsm_PR_GetPeerName(PRFileDesc *fd, PRNetAddr *addr) { struct tls_data *p; - int rc; 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; @@ -2380,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; } @@ -2485,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; } @@ -2515,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; } @@ -2562,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; }