X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=libraries%2Flibldap%2Ftls_m.c;h=c85d322014fa838341f3fefdea9a5f693fadc079;hb=7164c4c66215da39f03e353a9c72eba408abddbf;hp=55b35ec7e7b9f723d231c09a75ef1fa43bda8e48;hpb=d479db53e0a09b6b8006a07b176cea2f9a00eee0;p=openldap diff --git a/libraries/libldap/tls_m.c b/libraries/libldap/tls_m.c index 55b35ec7e7..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 @@ -53,15 +49,34 @@ #include /* for echo on/off */ #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#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; @@ -74,6 +89,14 @@ typedef struct tlsm_ctx { int tc_is_server; int tc_require_cert; 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 @@ -85,18 +108,51 @@ 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 SECMODModule *pem_module; #define DEFAULT_TOKEN_NAME "default" +/* sprintf format used to create token name */ +#define TLSM_PEM_TOKEN_FMT "PEM Token #%ld" + +static int tlsm_slot_count; + +#define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \ + (x)->pValue=(v); (x)->ulValueLen = (l); /* forward declaration */ 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 */ @@ -182,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}, @@ -193,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)) @@ -636,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; } @@ -683,114 +740,10 @@ tlsm_dump_security_status(PRFileDesc *fd) return ""; } -static SECStatus -tlsm_auth_cert_handler(void *arg, PRFileDesc *fd, - PRBool checksig, PRBool isServer) +static void +tlsm_handshake_complete_cb( PRFileDesc *fd, void *client_data ) { - SECStatus ret = SSL_AuthCertificate(arg, fd, checksig, isServer); - tlsm_dump_security_status( fd ); - Debug( LDAP_DEBUG_TRACE, - "TLS certificate verification: %s\n", - ret == SECSuccess ? "ok" : "bad", 0, 0 ); - - if ( ret != SECSuccess ) { - PRErrorCode errcode = PORT_GetError(); - Debug( LDAP_DEBUG_ANY, - "TLS certificate verification: Error, %d: %s\n", - errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ) ; - } - - return ret; -} - -static char * -tlsm_dirname(const char *pathname) -{ - char *ret = NULL; - char *p = NULL; - char sep = PR_GetDirectorySeparator(); - - if (pathname && (p = PL_strrchr(pathname, sep)) && (p > pathname)) { - ret = PL_strndup(pathname, (p - pathname)); - } - - return ret; -} - -/* - * This is the part of the init we defer until we get the - * actual security configuration information. This is - * only called once, protected by a PRCallOnce - * NOTE: This must be done before the first call to SSL_ImportFD, - * especially the setting of the policy - * NOTE: This must be called after fork() - */ -static int -tlsm_deferred_init( void *arg ) -{ - tlsm_ctx *ctx = (tlsm_ctx *)arg; - struct ldaptls *lt = ctx->tc_config; - char *securitydir = NULL; - int needfree = 0; - char *val; - PRErrorCode errcode; - - /* NSS support for multi-init is coming */ -#ifndef NSS_MULTI_INIT - if ( !NSS_IsInitialized() ) { -#endif /* NSS_MULTI_INIT */ - /* - MOZNSS_DIR will override everything else - you can - always set MOZNSS_DIR to force the use of this - directory - DEFAULT_MOZNSS_DIR will only be used if the code cannot - find a security dir to use based on the current - settings - */ - if ( (val = PR_GetEnv( "MOZNSS_DIR" ) ) && (*val) ) { - securitydir = PL_strdup( val ); - needfree = 1; - } else if ( lt->lt_cacertdir ) { - securitydir = lt->lt_cacertdir; - } else if ( lt->lt_cacertfile ) { - securitydir = tlsm_dirname( lt->lt_cacertfile ); - needfree = 1; - } else if ( lt->lt_certfile ) { - securitydir = tlsm_dirname( lt->lt_certfile ); - needfree = 1; - } else if ( lt->lt_keyfile ) { - securitydir = tlsm_dirname( lt->lt_keyfile ); - needfree = 1; - } else if ( (val = PR_GetEnv( "DEFAULT_MOZNSS_DIR" ) ) && (*val) ) { - securitydir = PL_strdup( val ); - needfree = 1; - } else { - securitydir = "/etc/pki/nssdb"; - } - - if ( NSS_Initialize( securitydir, "", "", SECMOD_DB, NSS_INIT_READONLY ) ) { - errcode = PORT_GetError(); - Debug( LDAP_DEBUG_ANY, - "TLS: could not initialize moznss using security dir %s - error %d:%s.\n", - securitydir, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); - if ( needfree ) { - PL_strfree( securitydir ); - } - return -1; - } - - if ( needfree ) { - PL_strfree( securitydir ); - } - - NSS_SetDomesticPolicy(); - tlsm_did_init = 1; /* we did the init - we should also clean up */ -#ifndef NSS_MULTI_INIT - } -#endif /* NSS_MULTI_INIT */ - - return 0; } #ifdef READ_PASSWORD_FROM_FILE @@ -896,80 +849,890 @@ echoOff(int fd) } } -/* - * Turn the echoing on on a tty. - */ -static void -echoOn(int fd) -{ - if ( isatty( fd ) ) { - struct termios tio; - tcgetattr( fd, &tio ); - tio.c_lflag |= ECHO; - tcsetattr( fd, TCSAFLUSH, &tio ); - tcsetattr( fd, TCSAFLUSH, &tio ); - } -} -#endif /* READ_PASSWORD_FROM_STDIN */ +/* + * Turn the echoing on on a tty. + */ +static void +echoOn(int fd) +{ + if ( isatty( fd ) ) { + struct termios tio; + tcgetattr( fd, &tio ); + tio.c_lflag |= ECHO; + tcsetattr( fd, TCSAFLUSH, &tio ); + tcsetattr( fd, TCSAFLUSH, &tio ); + } +} +#endif /* READ_PASSWORD_FROM_STDIN */ + +/* + * This does the actual work of reading the pin/password/pass phrase + */ +static char * +tlsm_get_pin(PK11SlotInfo *slot, PRBool retry, tlsm_ctx *ctx) +{ + char *token_name = NULL; + char *pwdstr = NULL; + + token_name = PK11_GetTokenName( slot ); +#ifdef READ_PASSWORD_FROM_FILE + /* Try to get the passwords from the password file if it exists. + * THIS IS UNSAFE and is provided for convenience only. Without this + * capability the server would have to be started in foreground mode + * if using an encrypted key. + */ + if ( ctx->tc_pin_file ) { + pwdstr = tlsm_get_pin_from_file( token_name, ctx ); + } +#endif /* RETRIEVE_PASSWORD_FROM_FILE */ +#ifdef READ_PASSWORD_FROM_STDIN + if ( !pwdstr ) { + int infd = PR_FileDesc2NativeHandle( PR_STDIN ); + int isTTY = isatty( infd ); + unsigned char phrase[200]; + /* Prompt for password */ + if ( isTTY ) { + fprintf( stdout, + "Please enter pin, password, or pass phrase for security token '%s': ", + token_name ? token_name : DEFAULT_TOKEN_NAME ); + echoOff( infd ); + } + fgets( (char*)phrase, sizeof(phrase), stdin ); + if ( isTTY ) { + fprintf( stdout, "\n" ); + echoOn( infd ); + } + /* stomp on newline */ + phrase[strlen((char*)phrase)-1] = 0; + + pwdstr = PL_strdup( (char*)phrase ); + } + +#endif /* READ_PASSWORD_FROM_STDIN */ + return pwdstr; +} + +/* + * PKCS11 devices (including the internal softokn cert/key database) + * may be protected by a pin or password or even pass phrase + * MozNSS needs a way for the user to provide that + */ +static char * +tlsm_pin_prompt(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + tlsm_ctx *ctx = (tlsm_ctx *)arg; + + return tlsm_get_pin( slot, retry, ctx ); +} + +static SECStatus +tlsm_get_basic_constraint_extension( CERTCertificate *cert, + CERTBasicConstraints *cbcval ) +{ + SECItem encodedVal = { 0, NULL }; + SECStatus rc; + + 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 [%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 ) +{ + int rc = -1; + + if ( SECSuccess != PK11_Authenticate( slot, PR_FALSE, ctx ) ) { + char *token_name = PK11_GetTokenName( slot ); + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not authenticate to the security token %s - error %d:%s.\n", + token_name ? token_name : DEFAULT_TOKEN_NAME, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + } else { + rc = 0; /* success */ + } + + return rc; +} + +static SECStatus +tlsm_nss_shutdown_cb( void *appData, void *nssData ) +{ + SECStatus rc = SECSuccess; + + SSL_ShutdownServerSessionIDCache(); + + 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 ) +{ + int rc = 0; + 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 ); + /* Load our PKCS#11 module */ + configstring = PR_smprintf( "library=%s name=" PEM_MODULE " parameters=\"\"", fullname ); + PL_strfree( fullname ); + + pem_module = SECMOD_LoadUserModule( configstring, NULL, PR_FALSE ); + PR_smprintf_free( configstring ); + + if ( !pem_module || !pem_module->loaded ) { + if ( pem_module ) { + SECMOD_DestroyModule( pem_module ); + pem_module = NULL; + } + rc = -1; + } + + 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, PRBool istrusted ) +{ + CK_SLOT_ID slotID; + PK11SlotInfo *slot = NULL; + PK11GenericObject *rv; + CK_ATTRIBUTE *attrs; + CK_ATTRIBUTE theTemplate[20]; + CK_BBOOL cktrue = CK_TRUE; + CK_BBOOL ckfalse = CK_FALSE; + CK_OBJECT_CLASS objClass = CKO_CERTIFICATE; + char tmpslotname[64]; + 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; + + if ( 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 */ + if ( istrusted ) { + slotID = 0; + } else { + slotID = ++tlsm_slot_count; + } + ctx->tc_slotname = PR_smprintf( TLSM_PEM_TOKEN_FMT, slotID ); + } + slotname = ctx->tc_slotname; + + if ( ( ptr = PL_strrchr( filename, sep ) ) ) { + PL_strfree( ctx->tc_certname ); + ++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 ); + } + } + } + + slot = PK11_FindSlotByName( slotname ); + + if ( !slot ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not find the slot for certificate %s - error %d:%s.\n", + ctx->tc_certname, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + 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 ( istrusted ) { + PK11_SETATTRS( attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) ); attrs++; + } else { + PK11_SETATTRS( attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) ); attrs++; + } + /* This loads the certificate in our PEM module into the appropriate + * slot. + */ + rv = PK11_CreateGenericObject( slot, theTemplate, 4, PR_FALSE /* isPerm */ ); + + PK11_FreeSlot( slot ); + + if ( !rv ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not add the certificate %s - error %d:%s.\n", + ctx->tc_certname, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + tlsm_add_pem_obj( ctx, rv ); + + return 0; +} + +static int +tlsm_add_key_from_file( tlsm_ctx *ctx, const char *filename ) +{ + CK_SLOT_ID slotID; + PK11SlotInfo * slot = NULL; + PK11GenericObject *rv; + CK_ATTRIBUTE *attrs; + CK_ATTRIBUTE theTemplate[20]; + 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; + + if ( ctx->tc_slotname == NULL ) { /* need new slot */ + slotID = ++tlsm_slot_count; + ctx->tc_slotname = PR_smprintf( TLSM_PEM_TOKEN_FMT, slotID ); + } + slot = PK11_FindSlotByName( ctx->tc_slotname ); + + if ( !slot ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not find the slot %s for the private key - error %d:%s.\n", + ctx->tc_slotname, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + return -1; + } + + 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++; + rv = PK11_CreateGenericObject( slot, theTemplate, 3, PR_FALSE /* isPerm */ ); + + if ( !rv ) { + PRErrorCode errcode = PR_GetError(); + Debug( LDAP_DEBUG_ANY, + "TLS: could not add the certificate %s - error %d:%s.\n", + ctx->tc_certname, errcode, + PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); + retcode = -1; + } 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( pem_module, 0, 0 ); + PK11_IsPresent( slot ); + retcode = 0; + } + + PK11_FreeSlot( slot ); + + if ( !retcode ) { + tlsm_add_pem_obj( ctx, rv ); + } + return retcode; +} + +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, 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 ); + } + } + + /* 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; + PRDirEntry *entry; + PRStatus fistatus = PR_FAILURE; + + memset( &fi, 0, sizeof(fi) ); + 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 ) ); + 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 ); + goto done; + } + + dir = PR_OpenDir( cacertdir ); + if ( NULL == dir ) { + 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 ) ); + goto done; + } + + do { + entry = PR_ReadDir( dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN ); + 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 ); + } +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 + * only called once, protected by a PRCallOnce + * NOTE: This must be done before the first call to SSL_ImportFD, + * especially the setting of the policy + * NOTE: This must be called after fork() + */ +static int +tlsm_deferred_init( void *arg ) +{ + tlsm_ctx *ctx = (tlsm_ctx *)arg; + struct ldaptls *lt = ctx->tc_config; + const char *securitydirs[3]; + 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 */ + +#ifndef HAVE_NSS_INITCONTEXT + if ( !NSS_IsInitialized() ) { +#endif /* HAVE_NSS_INITCONTEXT */ + /* + MOZNSS_DIR will override everything else - you can + always set MOZNSS_DIR to force the use of this + directory + If using MOZNSS, specify the location of the moznss db dir + in the cacertdir directive of the OpenLDAP configuration. + DEFAULT_MOZNSS_DIR will only be used if the code cannot + find a security dir to use based on the current + settings + */ + nn = 0; + securitydirs[nn++] = PR_GetEnv( "MOZNSS_DIR" ); + securitydirs[nn++] = lt->lt_cacertdir; + securitydirs[nn++] = PR_GetEnv( "DEFAULT_MOZNSS_DIR" ); + 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; + } + + 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(); + 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 prefix %s.\n", + realcertdir, prefix, 0 ); + errcode = 0; + 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 */ + 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", + errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 ); + return -1; + } + +#ifdef HAVE_NSS_INITCONTEXT + ctx->tc_initctx = initctx; +#endif -/* - * This does the actual work of reading the pin/password/pass phrase - */ -static char * -tlsm_get_pin(PK11SlotInfo *slot, PRBool retry, tlsm_ctx *ctx) -{ - char *token_name = NULL; - char *pwdstr = NULL; + /* 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; + } - token_name = PK11_GetTokenName( slot ); -#ifdef READ_PASSWORD_FROM_FILE - /* Try to get the passwords from the password file if it exists. - * THIS IS UNSAFE and is provided for convenience only. Without this - * capability the server would have to be started in foreground mode - * if using an encrypted key. - */ - if ( ctx->tc_pin_file ) { - pwdstr = tlsm_get_pin_from_file( token_name, ctx ); - } -#endif /* RETRIEVE_PASSWORD_FROM_FILE */ -#ifdef READ_PASSWORD_FROM_STDIN - if ( !pwdstr ) { - int infd = PR_FileDesc2NativeHandle( PR_STDIN ); - int isTTY = isatty( infd ); - unsigned char phrase[200]; - /* Prompt for password */ - if ( isTTY ) { - fprintf( stdout, - "Please enter pin, password, or pass phrase for security token '%s': ", - token_name ? token_name : DEFAULT_TOKEN_NAME ); - echoOff( infd ); + ctx->tc_using_pem = PR_TRUE; } - fgets( (char*)phrase, sizeof(phrase), stdin ); - if ( isTTY ) { - fprintf( stdout, "\n" ); - echoOn( infd ); + +#ifdef HAVE_NSS_INITCONTEXT + if ( !ctx->tc_initctx ) { + ctx->tc_initctx = initctx; } - /* stomp on newline */ - phrase[strlen((char*)phrase)-1] = 0; +#endif - pwdstr = PL_strdup( (char*)phrase ); - } + NSS_SetDomesticPolicy(); -#endif /* READ_PASSWORD_FROM_STDIN */ - return pwdstr; -} + PK11_SetPasswordFunc( tlsm_pin_prompt ); -/* - * PKCS11 devices (including the internal softokn cert/key database) - * may be protected by a pin or password or even pass phrase - * MozNSS needs a way for the user to provide that - */ -static char * -tlsm_pin_prompt(PK11SlotInfo *slot, PRBool retry, void *arg) -{ - tlsm_ctx *ctx = (tlsm_ctx *)arg; + /* 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; + } +#ifndef HAVE_NSS_INITCONTEXT + } +#endif /* HAVE_NSS_INITCONTEXT */ - return tlsm_get_pin( slot, retry, ctx ); + return 0; } static int @@ -984,8 +1747,6 @@ tlsm_authenticate( tlsm_ctx *ctx, const char *certname, const char *pininfo ) return 0; } - PK11_SetPasswordFunc( tlsm_pin_prompt ); - if ( ( colon = PL_strchr( certname, ':' ) ) ) { token_name = PL_strndup( certname, colon-certname ); } @@ -1005,22 +1766,7 @@ tlsm_authenticate( tlsm_ctx *ctx, const char *certname, const char *pininfo ) goto done; } - if ( pininfo ) { - PL_strfree( ctx->tc_pin_file ); - ctx->tc_pin_file = PL_strdup( pininfo ); - } - - if ( PK11_NeedLogin( slot ) && - ( SECSuccess != PK11_Authenticate( slot, PR_FALSE, ctx ) ) ) { - PRErrorCode errcode = PR_GetError(); - Debug( LDAP_DEBUG_ANY, - "TLS: could not authenticate to the security token %s - error %d:%s.\n", - token_name ? token_name : DEFAULT_TOKEN_NAME, errcode, - PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) ); - goto done; - } else { - rc = 0; /* success */ - } + rc = tlsm_authenticate_to_slot( ctx, slot ); done: PL_strfree( token_name ); @@ -1068,11 +1814,20 @@ tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certnam isServer = !isServer; /* verify the peer's cert instead */ } - key = PK11_FindKeyByAnyCert( cert, pin_arg ); + if ( ctx->tc_slotname ) { + PK11SlotInfo *slot = PK11_FindSlotByName( ctx->tc_slotname ); + key = PK11_FindPrivateKeyFromCert( slot, cert, NULL ); + PK11_FreeSlot( slot ); + } else { + key = PK11_FindKeyByAnyCert( cert, pin_arg ); + } + if (key) { 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 */ @@ -1089,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(); @@ -1123,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; } @@ -1145,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 ); @@ -1174,16 +1939,9 @@ tlsm_clientauth_init( tlsm_ctx *ctx, const char *certname ) static void tlsm_destroy( void ) { - /* 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 * @@ -1205,6 +1963,14 @@ tlsm_ctx_new ( struct ldapoptions *lo ) memset(&ctx->tc_callonce, 0, sizeof(ctx->tc_callonce)); ctx->tc_require_cert = lo->ldo_tls_require_cert; 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; } @@ -1213,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 @@ -1230,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 ) @@ -1246,6 +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 ); + 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 @@ -1283,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 ) { @@ -1360,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 || @@ -1371,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 ) ) { @@ -1389,6 +2183,37 @@ tlsm_deferred_ctx_init( void *arg ) return -1; } + /* set up our cert and key, if any */ + if ( lt->lt_certfile ) { + /* if using the PEM module, load the PEM file specified by lt_certfile */ + /* 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, PR_TRUE ); + if ( rc ) { + return rc; + } + } else { + PL_strfree( ctx->tc_certname ); + ctx->tc_certname = PL_strdup( lt->lt_certfile ); + } + } + + if ( lt->lt_keyfile ) { + /* if using the PEM module, load the PEM file specified by lt_keyfile */ + /* otherwise, assume this is the pininfo for the key */ + if ( ctx->tc_using_pem ) { + /* this sets ctx->tc_certname to the correct value */ + int rc = tlsm_add_key_from_file( ctx, lt->lt_keyfile ); + if ( rc ) { + return rc; + } + } else { + PL_strfree( ctx->tc_pin_file ); + ctx->tc_pin_file = PL_strdup( lt->lt_keyfile ); + } + } + /* Set up callbacks for use by clients */ if ( !ctx->tc_is_server ) { if ( SSL_OptionSet( ctx->tc_model, SSL_NO_CACHE, PR_TRUE ) != SECSuccess ) { @@ -1407,21 +2232,20 @@ tlsm_deferred_ctx_init( void *arg ) return -1; } - /* we don't currently support import of cert/key pair - assume the certfile - is really the name of a cert/key in the database in the form of - tokenname:certname - also assume since this is specified, the caller - wants to attempt client cert auth */ - if ( lt->lt_certfile ) { - if ( tlsm_authenticate( ctx, lt->lt_certfile, lt->lt_keyfile ) ) { + /* + since a cert has been specified, assume the client wants to do cert auth + */ + if ( ctx->tc_certname ) { + if ( tlsm_authenticate( ctx, ctx->tc_certname, ctx->tc_pin_file ) ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to authenticate to the security device for certificate %s\n", - lt->lt_certfile, 0, 0 ); + ctx->tc_certname, 0, 0 ); return -1; } - if ( tlsm_clientauth_init( ctx, lt->lt_certfile ) ) { + if ( tlsm_clientauth_init( ctx ) ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to set up client certificate authentication using %s\n", - lt->lt_certfile, 0, 0 ); + ctx->tc_certname, 0, 0 ); return -1; } } @@ -1432,7 +2256,7 @@ tlsm_deferred_ctx_init( void *arg ) SECStatus status; /* must have a certificate for the server to use */ - if ( !lt->lt_certfile ) { + if ( !ctx->tc_certname ) { Debug( LDAP_DEBUG_ANY, "TLS: error: no server certificate: must specify a certificate for the server to use\n", 0, 0, 0 ); @@ -1441,19 +2265,21 @@ tlsm_deferred_ctx_init( void *arg ) /* authenticate to the server's token - this will do nothing if the key/cert db is not password protected */ - if ( tlsm_authenticate( ctx, lt->lt_certfile, lt->lt_keyfile ) ) { + if ( tlsm_authenticate( ctx, ctx->tc_certname, ctx->tc_pin_file ) ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to authenticate to the security device for certificate %s\n", - lt->lt_certfile, 0, 0 ); + ctx->tc_certname, 0, 0 ); return -1; } /* get the server's key and cert */ - if ( tlsm_find_and_verify_cert_key( ctx, ctx->tc_model, lt->lt_certfile, ctx->tc_is_server, + if ( tlsm_find_and_verify_cert_key( ctx, ctx->tc_model, ctx->tc_certname, ctx->tc_is_server, &serverCert, &serverKey ) ) { Debug( LDAP_DEBUG_ANY, "TLS: error: unable to find and verify server's cert and key for certificate %s\n", - lt->lt_certfile, 0, 0 ); + ctx->tc_certname, 0, 0 ); + CERT_DestroyCertificate( serverCert ); + SECKEY_DestroyPrivateKey( serverKey ); return -1; } @@ -1468,14 +2294,14 @@ tlsm_deferred_ctx_init( void *arg ) PRErrorCode err = PR_GetError(); Debug( LDAP_DEBUG_ANY, "TLS: error: unable to configure secure server using certificate %s - error %d:%s\n", - lt->lt_certfile, err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) ); + ctx->tc_certname, err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) ); return -1; } } /* 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", @@ -1483,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; } @@ -1499,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 * @@ -1551,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 ); @@ -1578,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; + int rc = SSL_ForceHandshake( s ); + const char *op = is_accept ? "accept" : "connect"; - 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; - - 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 ); @@ -1713,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 @@ -1727,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; @@ -1742,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; @@ -1759,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 @@ -1831,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 ) { @@ -1889,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; @@ -1984,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; @@ -2000,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; } @@ -2014,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; @@ -2031,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; } @@ -2051,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; @@ -2067,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; } @@ -2167,15 +2989,34 @@ static const PRIOMethods tlsm_PR_methods = { /* * Initialize TLS subsystem. Should be called only once. + * See tlsm_deferred_init for the bulk of the init process */ static int tlsm_init( void ) { + char *nofork = PR_GetEnv( "NSS_STRICT_NOFORK" ); + PR_Init(0, 0, 0); tlsm_layer_id = PR_GetUniqueIdentity( "OpenLDAP" ); - /* see deferred init */ + /* + * 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; } @@ -2202,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; } @@ -2249,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; } @@ -2352,3 +3194,10 @@ tls_impl ldap_int_tls_impl = { }; #endif /* HAVE_MOZNSS */ +/* + emacs settings + Local Variables: + indent-tabs-mode: t + tab-width: 4 + End: +*/