mirror of
https://git.openldap.org/openldap/openldap.git
synced 2024-12-27 03:20:22 +08:00
4e9926ca97
Deferred TLS initialization is used with Mozilla NSS. The real initialization takes place when the TLS context is needed for the first time. If the initialization parameters were freed immediately after tlsm_ctx_init was called, they were not available at the time of deferred initialization which caused segmentation fault. With this patch, initialization parameters are copied and stored until the deferred initialization is finished. The parameters are freed afterwards. Red Hat Bugzilla: #783431
3290 lines
87 KiB
C
3290 lines
87 KiB
C
/* tls_m.c - Handle tls/ssl using Mozilla NSS. */
|
|
/* $OpenLDAP$ */
|
|
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
|
*
|
|
* Copyright 2008-2012 The OpenLDAP Foundation.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted only as authorized by the OpenLDAP
|
|
* Public License.
|
|
*
|
|
* A copy of this license is available in the file LICENSE in the
|
|
* top-level directory of the distribution or, alternatively, at
|
|
* <http://www.OpenLDAP.org/license.html>.
|
|
*/
|
|
/* ACKNOWLEDGEMENTS: Initial version written by Howard Chu.
|
|
* Additional support by Rich Megginson.
|
|
*/
|
|
|
|
#include "portable.h"
|
|
|
|
#ifdef HAVE_MOZNSS
|
|
|
|
#include "ldap_config.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#if defined( HAVE_FCNTL_H )
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include <ac/stdlib.h>
|
|
#include <ac/errno.h>
|
|
#include <ac/socket.h>
|
|
#include <ac/string.h>
|
|
#include <ac/ctype.h>
|
|
#include <ac/time.h>
|
|
#include <ac/unistd.h>
|
|
#include <ac/param.h>
|
|
#include <ac/dirent.h>
|
|
|
|
#include "ldap-int.h"
|
|
#include "ldap-tls.h"
|
|
|
|
#define READ_PASSWORD_FROM_STDIN
|
|
#define READ_PASSWORD_FROM_FILE
|
|
|
|
#ifdef READ_PASSWORD_FROM_STDIN
|
|
#include <termios.h> /* for echo on/off */
|
|
#endif
|
|
|
|
#include <nspr/nspr.h>
|
|
#include <nspr/private/pprio.h>
|
|
#include <nss/nss.h>
|
|
#include <nss/ssl.h>
|
|
#include <nss/sslerr.h>
|
|
#include <nss/sslproto.h>
|
|
#include <nss/pk11pub.h>
|
|
#include <nss/secerr.h>
|
|
#include <nss/keyhi.h>
|
|
#include <nss/secmod.h>
|
|
#include <nss/cert.h>
|
|
|
|
#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;
|
|
int tc_refcnt;
|
|
PRBool tc_verify_cert;
|
|
CERTCertDBHandle *tc_certdb;
|
|
char *tc_certname;
|
|
char *tc_pin_file;
|
|
struct ldaptls *tc_config;
|
|
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
|
|
} tlsm_ctx;
|
|
|
|
typedef PRFileDesc tlsm_session;
|
|
|
|
static PRDescIdentity tlsm_layer_id;
|
|
|
|
static const PRIOMethods tlsm_PR_methods;
|
|
|
|
#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 ldap_pvt_thread_mutex_t tlsm_pem_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;
|
|
}
|
|
|
|
if ( ldap_pvt_thread_mutex_init( &tlsm_pem_mutex ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not create mutex for PEM module: %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 */
|
|
|
|
static const char *
|
|
tlsm_dump_cipher_info(PRFileDesc *fd)
|
|
{
|
|
PRUint16 ii;
|
|
|
|
for (ii = 0; ii < SSL_NumImplementedCiphers; ++ii) {
|
|
PRInt32 cipher = (PRInt32)SSL_ImplementedCiphers[ii];
|
|
PRBool enabled = PR_FALSE;
|
|
PRInt32 policy = 0;
|
|
SSLCipherSuiteInfo info;
|
|
|
|
if (fd) {
|
|
SSL_CipherPrefGet(fd, cipher, &enabled);
|
|
} else {
|
|
SSL_CipherPrefGetDefault(cipher, &enabled);
|
|
}
|
|
SSL_CipherPolicyGet(cipher, &policy);
|
|
SSL_GetCipherSuiteInfo(cipher, &info, (PRUintn)sizeof(info));
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"TLS: cipher: %d - %s, enabled: %d, ",
|
|
info.cipherSuite, info.cipherSuiteName, enabled );
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"policy: %d\n", policy, 0, 0 );
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/* Cipher definitions */
|
|
typedef struct {
|
|
char *ossl_name; /* The OpenSSL cipher name */
|
|
int num; /* The cipher id */
|
|
int attr; /* cipher attributes: algorithms, etc */
|
|
int version; /* protocol version valid for this cipher */
|
|
int bits; /* bits of strength */
|
|
int alg_bits; /* bits of the algorithm */
|
|
int strength; /* LOW, MEDIUM, HIGH */
|
|
int enabled; /* Enabled by default? */
|
|
} cipher_properties;
|
|
|
|
/* cipher attributes */
|
|
#define SSL_kRSA 0x00000001L
|
|
#define SSL_aRSA 0x00000002L
|
|
#define SSL_aDSS 0x00000004L
|
|
#define SSL_DSS SSL_aDSS
|
|
#define SSL_eNULL 0x00000008L
|
|
#define SSL_DES 0x00000010L
|
|
#define SSL_3DES 0x00000020L
|
|
#define SSL_RC4 0x00000040L
|
|
#define SSL_RC2 0x00000080L
|
|
#define SSL_AES 0x00000100L
|
|
#define SSL_MD5 0x00000200L
|
|
#define SSL_SHA1 0x00000400L
|
|
#define SSL_SHA SSL_SHA1
|
|
#define SSL_RSA (SSL_kRSA|SSL_aRSA)
|
|
|
|
/* cipher strength */
|
|
#define SSL_NULL 0x00000001L
|
|
#define SSL_EXPORT40 0x00000002L
|
|
#define SSL_EXPORT56 0x00000004L
|
|
#define SSL_LOW 0x00000008L
|
|
#define SSL_MEDIUM 0x00000010L
|
|
#define SSL_HIGH 0x00000020L
|
|
|
|
#define SSL2 0x00000001L
|
|
#define SSL3 0x00000002L
|
|
/* OpenSSL treats SSL3 and TLSv1 the same */
|
|
#define TLS1 SSL3
|
|
|
|
/* Cipher translation */
|
|
static cipher_properties ciphers_def[] = {
|
|
/* SSL 2 ciphers */
|
|
{"DES-CBC3-MD5", SSL_EN_DES_192_EDE3_CBC_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_3DES|SSL_MD5, SSL2, 168, 168, SSL_HIGH, SSL_ALLOWED},
|
|
{"RC2-CBC-MD5", SSL_EN_RC2_128_CBC_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC2|SSL_MD5, SSL2, 128, 128, SSL_MEDIUM, SSL_ALLOWED},
|
|
{"RC4-MD5", SSL_EN_RC4_128_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL2, 128, 128, SSL_MEDIUM, SSL_ALLOWED},
|
|
{"DES-CBC-MD5", SSL_EN_DES_64_CBC_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_DES|SSL_MD5, SSL2, 56, 56, SSL_LOW, SSL_ALLOWED},
|
|
{"EXP-RC2-CBC-MD5", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC2|SSL_MD5, SSL2, 40, 128, SSL_EXPORT40, SSL_ALLOWED},
|
|
{"EXP-RC4-MD5", SSL_EN_RC4_128_EXPORT40_WITH_MD5, SSL_kRSA|SSL_aRSA|SSL_RC4|SSL_MD5, SSL2, 40, 128, SSL_EXPORT40, SSL_ALLOWED},
|
|
|
|
/* 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_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},
|
|
{"EXP-RC2-CBC-MD5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, SSL_kRSA|SSL_aRSA|SSL_RC2|SSL_MD5, SSL3, 0, 0, SSL_EXPORT40, SSL_ALLOWED},
|
|
{"NULL-MD5", SSL_RSA_WITH_NULL_MD5, SSL_kRSA|SSL_aRSA|SSL_eNULL|SSL_MD5, SSL3, 0, 0, SSL_NULL, SSL_NOT_ALLOWED},
|
|
{"NULL-SHA", SSL_RSA_WITH_NULL_SHA, SSL_kRSA|SSL_aRSA|SSL_eNULL|SSL_SHA1, SSL3, 0, 0, SSL_NULL, SSL_NOT_ALLOWED},
|
|
|
|
/* 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_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))
|
|
|
|
/* given err which is the current errno, calls PR_SetError with
|
|
the corresponding NSPR error code */
|
|
static void
|
|
tlsm_map_error(int err)
|
|
{
|
|
PRErrorCode prError;
|
|
|
|
switch ( err ) {
|
|
case EACCES:
|
|
prError = PR_NO_ACCESS_RIGHTS_ERROR;
|
|
break;
|
|
case EADDRINUSE:
|
|
prError = PR_ADDRESS_IN_USE_ERROR;
|
|
break;
|
|
case EADDRNOTAVAIL:
|
|
prError = PR_ADDRESS_NOT_AVAILABLE_ERROR;
|
|
break;
|
|
case EAFNOSUPPORT:
|
|
prError = PR_ADDRESS_NOT_SUPPORTED_ERROR;
|
|
break;
|
|
case EAGAIN:
|
|
prError = PR_WOULD_BLOCK_ERROR;
|
|
break;
|
|
/*
|
|
* On QNX and Neutrino, EALREADY is defined as EBUSY.
|
|
*/
|
|
#if EALREADY != EBUSY
|
|
case EALREADY:
|
|
prError = PR_ALREADY_INITIATED_ERROR;
|
|
break;
|
|
#endif
|
|
case EBADF:
|
|
prError = PR_BAD_DESCRIPTOR_ERROR;
|
|
break;
|
|
#ifdef EBADMSG
|
|
case EBADMSG:
|
|
prError = PR_IO_ERROR;
|
|
break;
|
|
#endif
|
|
case EBUSY:
|
|
prError = PR_FILESYSTEM_MOUNTED_ERROR;
|
|
break;
|
|
case ECONNABORTED:
|
|
prError = PR_CONNECT_ABORTED_ERROR;
|
|
break;
|
|
case ECONNREFUSED:
|
|
prError = PR_CONNECT_REFUSED_ERROR;
|
|
break;
|
|
case ECONNRESET:
|
|
prError = PR_CONNECT_RESET_ERROR;
|
|
break;
|
|
case EDEADLK:
|
|
prError = PR_DEADLOCK_ERROR;
|
|
break;
|
|
#ifdef EDIRCORRUPTED
|
|
case EDIRCORRUPTED:
|
|
prError = PR_DIRECTORY_CORRUPTED_ERROR;
|
|
break;
|
|
#endif
|
|
#ifdef EDQUOT
|
|
case EDQUOT:
|
|
prError = PR_NO_DEVICE_SPACE_ERROR;
|
|
break;
|
|
#endif
|
|
case EEXIST:
|
|
prError = PR_FILE_EXISTS_ERROR;
|
|
break;
|
|
case EFAULT:
|
|
prError = PR_ACCESS_FAULT_ERROR;
|
|
break;
|
|
case EFBIG:
|
|
prError = PR_FILE_TOO_BIG_ERROR;
|
|
break;
|
|
case EHOSTUNREACH:
|
|
prError = PR_HOST_UNREACHABLE_ERROR;
|
|
break;
|
|
case EINPROGRESS:
|
|
prError = PR_IN_PROGRESS_ERROR;
|
|
break;
|
|
case EINTR:
|
|
prError = PR_PENDING_INTERRUPT_ERROR;
|
|
break;
|
|
case EINVAL:
|
|
prError = PR_INVALID_ARGUMENT_ERROR;
|
|
break;
|
|
case EIO:
|
|
prError = PR_IO_ERROR;
|
|
break;
|
|
case EISCONN:
|
|
prError = PR_IS_CONNECTED_ERROR;
|
|
break;
|
|
case EISDIR:
|
|
prError = PR_IS_DIRECTORY_ERROR;
|
|
break;
|
|
case ELOOP:
|
|
prError = PR_LOOP_ERROR;
|
|
break;
|
|
case EMFILE:
|
|
prError = PR_PROC_DESC_TABLE_FULL_ERROR;
|
|
break;
|
|
case EMLINK:
|
|
prError = PR_MAX_DIRECTORY_ENTRIES_ERROR;
|
|
break;
|
|
case EMSGSIZE:
|
|
prError = PR_INVALID_ARGUMENT_ERROR;
|
|
break;
|
|
#ifdef EMULTIHOP
|
|
case EMULTIHOP:
|
|
prError = PR_REMOTE_FILE_ERROR;
|
|
break;
|
|
#endif
|
|
case ENAMETOOLONG:
|
|
prError = PR_NAME_TOO_LONG_ERROR;
|
|
break;
|
|
case ENETUNREACH:
|
|
prError = PR_NETWORK_UNREACHABLE_ERROR;
|
|
break;
|
|
case ENFILE:
|
|
prError = PR_SYS_DESC_TABLE_FULL_ERROR;
|
|
break;
|
|
/*
|
|
* On SCO OpenServer 5, ENOBUFS is defined as ENOSR.
|
|
*/
|
|
#if defined(ENOBUFS) && (ENOBUFS != ENOSR)
|
|
case ENOBUFS:
|
|
prError = PR_INSUFFICIENT_RESOURCES_ERROR;
|
|
break;
|
|
#endif
|
|
case ENODEV:
|
|
prError = PR_FILE_NOT_FOUND_ERROR;
|
|
break;
|
|
case ENOENT:
|
|
prError = PR_FILE_NOT_FOUND_ERROR;
|
|
break;
|
|
case ENOLCK:
|
|
prError = PR_FILE_IS_LOCKED_ERROR;
|
|
break;
|
|
#ifdef ENOLINK
|
|
case ENOLINK:
|
|
prError = PR_REMOTE_FILE_ERROR;
|
|
break;
|
|
#endif
|
|
case ENOMEM:
|
|
prError = PR_OUT_OF_MEMORY_ERROR;
|
|
break;
|
|
case ENOPROTOOPT:
|
|
prError = PR_INVALID_ARGUMENT_ERROR;
|
|
break;
|
|
case ENOSPC:
|
|
prError = PR_NO_DEVICE_SPACE_ERROR;
|
|
break;
|
|
#ifdef ENOSR
|
|
case ENOSR:
|
|
prError = PR_INSUFFICIENT_RESOURCES_ERROR;
|
|
break;
|
|
#endif
|
|
case ENOTCONN:
|
|
prError = PR_NOT_CONNECTED_ERROR;
|
|
break;
|
|
case ENOTDIR:
|
|
prError = PR_NOT_DIRECTORY_ERROR;
|
|
break;
|
|
case ENOTSOCK:
|
|
prError = PR_NOT_SOCKET_ERROR;
|
|
break;
|
|
case ENXIO:
|
|
prError = PR_FILE_NOT_FOUND_ERROR;
|
|
break;
|
|
case EOPNOTSUPP:
|
|
prError = PR_NOT_TCP_SOCKET_ERROR;
|
|
break;
|
|
#ifdef EOVERFLOW
|
|
case EOVERFLOW:
|
|
prError = PR_BUFFER_OVERFLOW_ERROR;
|
|
break;
|
|
#endif
|
|
case EPERM:
|
|
prError = PR_NO_ACCESS_RIGHTS_ERROR;
|
|
break;
|
|
case EPIPE:
|
|
prError = PR_CONNECT_RESET_ERROR;
|
|
break;
|
|
#ifdef EPROTO
|
|
case EPROTO:
|
|
prError = PR_IO_ERROR;
|
|
break;
|
|
#endif
|
|
case EPROTONOSUPPORT:
|
|
prError = PR_PROTOCOL_NOT_SUPPORTED_ERROR;
|
|
break;
|
|
case EPROTOTYPE:
|
|
prError = PR_ADDRESS_NOT_SUPPORTED_ERROR;
|
|
break;
|
|
case ERANGE:
|
|
prError = PR_INVALID_METHOD_ERROR;
|
|
break;
|
|
case EROFS:
|
|
prError = PR_READ_ONLY_FILESYSTEM_ERROR;
|
|
break;
|
|
case ESPIPE:
|
|
prError = PR_INVALID_METHOD_ERROR;
|
|
break;
|
|
case ETIMEDOUT:
|
|
prError = PR_IO_TIMEOUT_ERROR;
|
|
break;
|
|
#if EWOULDBLOCK != EAGAIN
|
|
case EWOULDBLOCK:
|
|
prError = PR_WOULD_BLOCK_ERROR;
|
|
break;
|
|
#endif
|
|
case EXDEV:
|
|
prError = PR_NOT_SAME_DEVICE_ERROR;
|
|
break;
|
|
default:
|
|
prError = PR_UNKNOWN_ERROR;
|
|
break;
|
|
}
|
|
PR_SetError( prError, err );
|
|
}
|
|
|
|
/*
|
|
* cipher_list is an integer array with the following values:
|
|
* -1: never enable this cipher
|
|
* 0: cipher disabled
|
|
* 1: cipher enabled
|
|
*/
|
|
static int
|
|
nss_parse_ciphers(const char *cipherstr, int cipher_list[ciphernum])
|
|
{
|
|
int i;
|
|
char *cipher;
|
|
char *ciphers;
|
|
char *ciphertip;
|
|
int action;
|
|
int rv;
|
|
|
|
/* All disabled to start */
|
|
for (i=0; i<ciphernum; i++)
|
|
cipher_list[i] = 0;
|
|
|
|
ciphertip = strdup(cipherstr);
|
|
cipher = ciphers = ciphertip;
|
|
|
|
while (ciphers && (strlen(ciphers))) {
|
|
while ((*cipher) && (isspace(*cipher)))
|
|
++cipher;
|
|
|
|
action = 1;
|
|
switch(*cipher) {
|
|
case '+': /* Add something */
|
|
action = 1;
|
|
cipher++;
|
|
break;
|
|
case '-': /* Subtract something */
|
|
action = 0;
|
|
cipher++;
|
|
break;
|
|
case '!': /* Disable something */
|
|
action = -1;
|
|
cipher++;
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
|
|
if ((ciphers = strchr(cipher, ':'))) {
|
|
*ciphers++ = '\0';
|
|
}
|
|
|
|
/* Do the easy one first */
|
|
if (!strcmp(cipher, "ALL")) {
|
|
for (i=0; i<ciphernum; i++) {
|
|
if (!(ciphers_def[i].attr & SSL_eNULL))
|
|
cipher_list[i] = action;
|
|
}
|
|
} else if (!strcmp(cipher, "COMPLEMENTOFALL")) {
|
|
for (i=0; i<ciphernum; i++) {
|
|
if ((ciphers_def[i].attr & SSL_eNULL))
|
|
cipher_list[i] = action;
|
|
}
|
|
} else if (!strcmp(cipher, "DEFAULT")) {
|
|
for (i=0; i<ciphernum; i++) {
|
|
cipher_list[i] = ciphers_def[i].enabled == SSL_ALLOWED ? 1 : 0;
|
|
}
|
|
} else {
|
|
int mask = 0;
|
|
int strength = 0;
|
|
int protocol = 0;
|
|
char *c;
|
|
|
|
c = cipher;
|
|
while (c && (strlen(c))) {
|
|
|
|
if ((c = strchr(cipher, '+'))) {
|
|
*c++ = '\0';
|
|
}
|
|
|
|
if (!strcmp(cipher, "RSA")) {
|
|
mask |= SSL_RSA;
|
|
} else if ((!strcmp(cipher, "NULL")) || (!strcmp(cipher, "eNULL"))) {
|
|
mask |= SSL_eNULL;
|
|
} else if (!strcmp(cipher, "AES")) {
|
|
mask |= SSL_AES;
|
|
} else if (!strcmp(cipher, "3DES")) {
|
|
mask |= SSL_3DES;
|
|
} else if (!strcmp(cipher, "DES")) {
|
|
mask |= SSL_DES;
|
|
} else if (!strcmp(cipher, "RC4")) {
|
|
mask |= SSL_RC4;
|
|
} else if (!strcmp(cipher, "RC2")) {
|
|
mask |= SSL_RC2;
|
|
} else if (!strcmp(cipher, "MD5")) {
|
|
mask |= SSL_MD5;
|
|
} else if ((!strcmp(cipher, "SHA")) || (!strcmp(cipher, "SHA1"))) {
|
|
mask |= SSL_SHA1;
|
|
} else if (!strcmp(cipher, "SSLv2")) {
|
|
protocol |= SSL2;
|
|
} else if (!strcmp(cipher, "SSLv3")) {
|
|
protocol |= SSL3;
|
|
} else if (!strcmp(cipher, "TLSv1")) {
|
|
protocol |= TLS1;
|
|
} else if (!strcmp(cipher, "HIGH")) {
|
|
strength |= SSL_HIGH;
|
|
} else if (!strcmp(cipher, "MEDIUM")) {
|
|
strength |= SSL_MEDIUM;
|
|
} else if (!strcmp(cipher, "LOW")) {
|
|
strength |= SSL_LOW;
|
|
} else if ((!strcmp(cipher, "EXPORT")) || (!strcmp(cipher, "EXP"))) {
|
|
strength |= SSL_EXPORT40|SSL_EXPORT56;
|
|
} else if (!strcmp(cipher, "EXPORT40")) {
|
|
strength |= SSL_EXPORT40;
|
|
} else if (!strcmp(cipher, "EXPORT56")) {
|
|
strength |= SSL_EXPORT56;
|
|
}
|
|
|
|
if (c)
|
|
cipher = c;
|
|
|
|
} /* while */
|
|
|
|
/* If we have a mask, apply it. If not then perhaps they provided
|
|
* a specific cipher to enable.
|
|
*/
|
|
if (mask || strength || protocol) {
|
|
for (i=0; i<ciphernum; i++) {
|
|
if (((ciphers_def[i].attr & mask) ||
|
|
(ciphers_def[i].strength & strength) ||
|
|
(ciphers_def[i].version & protocol)) &&
|
|
(cipher_list[i] != -1)) {
|
|
/* Enable the NULL ciphers only if explicity
|
|
* requested */
|
|
if (ciphers_def[i].attr & SSL_eNULL) {
|
|
if (mask & SSL_eNULL)
|
|
cipher_list[i] = action;
|
|
} else
|
|
cipher_list[i] = action;
|
|
}
|
|
}
|
|
} else {
|
|
for (i=0; i<ciphernum; i++) {
|
|
if (!strcmp(ciphers_def[i].ossl_name, cipher) &&
|
|
cipher_list[1] != -1)
|
|
cipher_list[i] = action;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ciphers)
|
|
cipher = ciphers;
|
|
}
|
|
|
|
/* See if any ciphers were enabled */
|
|
rv = 0;
|
|
for (i=0; i<ciphernum; i++) {
|
|
if (cipher_list[i] == 1)
|
|
rv = 1;
|
|
}
|
|
|
|
free(ciphertip);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
tlsm_parse_ciphers(tlsm_ctx *ctx, const char *str)
|
|
{
|
|
int cipher_state[ciphernum];
|
|
int rv, i;
|
|
|
|
if (!ctx)
|
|
return 0;
|
|
|
|
rv = nss_parse_ciphers(str, cipher_state);
|
|
|
|
if (rv) {
|
|
/* First disable everything */
|
|
for (i = 0; i < SSL_NumImplementedCiphers; i++)
|
|
SSL_CipherPrefSet(ctx->tc_model, SSL_ImplementedCiphers[i], SSL_NOT_ALLOWED);
|
|
|
|
/* Now enable what was requested */
|
|
for (i=0; i<ciphernum; i++) {
|
|
SSLCipherSuiteInfo suite;
|
|
PRBool enabled;
|
|
|
|
if (SSL_GetCipherSuiteInfo(ciphers_def[i].num, &suite, sizeof suite)
|
|
== SECSuccess) {
|
|
enabled = cipher_state[i] < 0 ? 0 : cipher_state[i];
|
|
if (enabled == SSL_ALLOWED) {
|
|
if (PK11_IsFIPS() && !suite.isFIPS)
|
|
enabled = SSL_NOT_ALLOWED;
|
|
}
|
|
SSL_CipherPrefSet(ctx->tc_model, ciphers_def[i].num, enabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv == 1 ? 0 : -1;
|
|
}
|
|
|
|
static SECStatus
|
|
tlsm_bad_cert_handler(void *arg, PRFileDesc *ssl)
|
|
{
|
|
SECStatus success = SECSuccess;
|
|
PRErrorCode err;
|
|
tlsm_ctx *ctx = (tlsm_ctx *)arg;
|
|
|
|
if (!ssl || !ctx) {
|
|
return SECFailure;
|
|
}
|
|
|
|
err = PORT_GetError();
|
|
|
|
switch (err) {
|
|
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;
|
|
}
|
|
break;
|
|
/* we bypass NSS's hostname checks and do our own */
|
|
case SSL_ERROR_BAD_CERT_DOMAIN:
|
|
break;
|
|
default:
|
|
success = SECFailure;
|
|
break;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static const char *
|
|
tlsm_dump_security_status(PRFileDesc *fd)
|
|
{
|
|
char * cp; /* bulk cipher name */
|
|
char * ip; /* cert issuer DN */
|
|
char * sp; /* cert subject DN */
|
|
int op; /* High, Low, Off */
|
|
int kp0; /* total key bits */
|
|
int kp1; /* secret key bits */
|
|
SSL3Statistics * ssl3stats = SSL_GetStatistics();
|
|
|
|
SSL_SecurityStatus( fd, &op, &cp, &kp0, &kp1, &ip, &sp );
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"TLS certificate verification: subject: %s, issuer: %s, cipher: %s, ",
|
|
sp ? sp : "-unknown-", ip ? ip : "-unknown-", cp ? cp : "-unknown-" );
|
|
PR_Free(cp);
|
|
PR_Free(ip);
|
|
PR_Free(sp);
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"security level: %s, secret key bits: %d, total key bits: %d, ",
|
|
((op == SSL_SECURITY_STATUS_ON_HIGH) ? "high" :
|
|
((op == SSL_SECURITY_STATUS_ON_LOW) ? "low" : "off")),
|
|
kp1, kp0 );
|
|
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"cache hits: %ld, cache misses: %ld, cache not reusable: %ld\n",
|
|
ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses,
|
|
ssl3stats->hch_sid_cache_not_ok );
|
|
|
|
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)
|
|
{
|
|
char *pwdstr = NULL;
|
|
char *contents = NULL;
|
|
char *lasts = NULL;
|
|
char *line = NULL;
|
|
char *candidate = NULL;
|
|
PRFileInfo file_info;
|
|
PRFileDesc *pwd_fileptr = PR_Open( ctx->tc_pin_file, PR_RDONLY, 00400 );
|
|
|
|
/* open the password file */
|
|
if ( !pwd_fileptr ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not open security pin file %s - error %d:%s.\n",
|
|
ctx->tc_pin_file, errcode,
|
|
PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
goto done;
|
|
}
|
|
|
|
/* get the file size */
|
|
if ( PR_SUCCESS != PR_GetFileInfo( ctx->tc_pin_file, &file_info ) ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not get file info from pin file %s - error %d:%s.\n",
|
|
ctx->tc_pin_file, errcode,
|
|
PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
goto done;
|
|
}
|
|
|
|
/* create a buffer to hold the file contents */
|
|
if ( !( contents = PR_MALLOC( file_info.size + 1 ) ) ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not alloc a buffer for contents of pin file %s - error %d:%s.\n",
|
|
ctx->tc_pin_file, errcode,
|
|
PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
goto done;
|
|
}
|
|
|
|
/* read file into the buffer */
|
|
if( PR_Read( pwd_fileptr, contents, file_info.size ) <= 0 ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not read the file contents from pin file %s - error %d:%s.\n",
|
|
ctx->tc_pin_file, errcode,
|
|
PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
goto done;
|
|
}
|
|
|
|
/* format is [tokenname:]password EOL [tokenname:]password EOL ... */
|
|
/* if you want to use a password containing a colon character, use
|
|
the special tokenname "default" */
|
|
for ( line = PL_strtok_r( contents, "\r\n", &lasts ); line;
|
|
line = PL_strtok_r( NULL, "\r\n", &lasts ) ) {
|
|
char *colon;
|
|
|
|
if ( !*line ) {
|
|
continue; /* skip blank lines */
|
|
}
|
|
colon = PL_strchr( line, ':' );
|
|
if ( colon ) {
|
|
if ( *(colon + 1) && token_name &&
|
|
!PL_strncmp( token_name, line, colon-line ) ) {
|
|
candidate = colon + 1; /* found a definite match */
|
|
break;
|
|
} else if ( !PL_strncmp( DEFAULT_TOKEN_NAME, line, colon-line ) ) {
|
|
candidate = colon + 1; /* found possible match */
|
|
}
|
|
} else { /* no token name */
|
|
candidate = line;
|
|
}
|
|
}
|
|
done:
|
|
if ( pwd_fileptr ) {
|
|
PR_Close( pwd_fileptr );
|
|
}
|
|
if ( candidate ) {
|
|
pwdstr = PL_strdup( candidate );
|
|
}
|
|
PL_strfree( contents );
|
|
|
|
return pwdstr;
|
|
}
|
|
#endif /* READ_PASSWORD_FROM_FILE */
|
|
|
|
#ifdef READ_PASSWORD_FROM_STDIN
|
|
/*
|
|
* Turn the echoing off on a tty.
|
|
*/
|
|
static void
|
|
echoOff(int fd)
|
|
{
|
|
if ( isatty( fd ) ) {
|
|
struct termios tio;
|
|
tcgetattr( fd, &tio );
|
|
tio.c_lflag &= ~ECHO;
|
|
tcsetattr( fd, TCSAFLUSH, &tio );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
|
|
/* 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 );
|
|
|
|
/* 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;
|
|
}
|
|
|
|
if ( ctx->tc_is_server ) {
|
|
LDAP_MUTEX_LOCK( &tlsm_init_mutex );
|
|
/* 0 means use the defaults here */
|
|
SSL_ConfigServerSessionIDCache( 0, 0, 0, NULL );
|
|
LDAP_MUTEX_UNLOCK( &tlsm_init_mutex );
|
|
}
|
|
|
|
#ifndef HAVE_NSS_INITCONTEXT
|
|
}
|
|
#endif /* HAVE_NSS_INITCONTEXT */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tlsm_authenticate( tlsm_ctx *ctx, const char *certname, const char *pininfo )
|
|
{
|
|
const char *colon = NULL;
|
|
char *token_name = NULL;
|
|
PK11SlotInfo *slot = NULL;
|
|
int rc = -1;
|
|
|
|
if ( !certname || !*certname ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( ( colon = PL_strchr( certname, ':' ) ) ) {
|
|
token_name = PL_strndup( certname, colon-certname );
|
|
}
|
|
|
|
if ( token_name ) {
|
|
slot = PK11_FindSlotByName( token_name );
|
|
} else {
|
|
slot = PK11_GetInternalKeySlot();
|
|
}
|
|
|
|
if ( !slot ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not find the slot for security token %s - error %d:%s.\n",
|
|
token_name ? token_name : DEFAULT_TOKEN_NAME, errcode,
|
|
PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
goto done;
|
|
}
|
|
|
|
rc = tlsm_authenticate_to_slot( ctx, slot );
|
|
|
|
done:
|
|
PL_strfree( token_name );
|
|
if ( slot ) {
|
|
PK11_FreeSlot( slot );
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Find and verify the certificate.
|
|
* Either fd is given, in which case the cert will be taken from it via SSL_PeerCertificate
|
|
* or certname is given, and it will be searched for by name
|
|
*/
|
|
static int
|
|
tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certname, int isServer, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
|
|
{
|
|
CERTCertificate *cert = NULL;
|
|
int rc = -1;
|
|
void *pin_arg = NULL;
|
|
SECKEYPrivateKey *key = NULL;
|
|
|
|
pin_arg = SSL_RevealPinArg( fd );
|
|
if ( certname ) {
|
|
cert = PK11_FindCertFromNickname( certname, pin_arg );
|
|
if ( !cert ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: the certificate %s could not be found in the database - error %d:%s\n",
|
|
certname, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* we are verifying the peer cert
|
|
we also need to swap the isServer meaning */
|
|
cert = SSL_PeerCertificate( fd );
|
|
if ( !cert ) {
|
|
PRErrorCode errcode = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: could not get the certificate from the peer connection - error %d:%s\n",
|
|
errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), NULL );
|
|
return -1;
|
|
}
|
|
isServer = !isServer; /* verify the peer's cert instead */
|
|
}
|
|
|
|
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 */
|
|
} else {
|
|
SECKEY_DestroyPrivateKey( key );
|
|
}
|
|
if ( isServer ) {
|
|
certUsage = certificateUsageSSLServer;
|
|
} else {
|
|
certUsage = certificateUsageSSLClient;
|
|
}
|
|
if ( ctx->tc_verify_cert ) {
|
|
checkSig = PR_TRUE;
|
|
} else {
|
|
checkSig = PR_FALSE;
|
|
}
|
|
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();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: could not find the private key for certificate %s - error %d:%s\n",
|
|
certname, errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
|
|
}
|
|
|
|
if ( pRetCert ) {
|
|
*pRetCert = cert; /* caller will deal with this */
|
|
} else {
|
|
CERT_DestroyCertificate( cert );
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
tlsm_get_client_auth_data( void *arg, PRFileDesc *fd,
|
|
CERTDistNames *caNames, CERTCertificate **pRetCert,
|
|
SECKEYPrivateKey **pRetKey )
|
|
{
|
|
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;
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
/*
|
|
* ctx must have a tc_model that is valid
|
|
* certname is in the form [<tokenname>:]<certnickname>
|
|
* where <tokenname> is the name of the PKCS11 token
|
|
* and <certnickname> is the nickname of the cert/key in
|
|
* the database
|
|
*/
|
|
static int
|
|
tlsm_clientauth_init( tlsm_ctx *ctx )
|
|
{
|
|
SECStatus status = SECFailure;
|
|
int rc;
|
|
PRBool saveval;
|
|
|
|
saveval = ctx->tc_warn_only;
|
|
ctx->tc_warn_only = PR_TRUE;
|
|
rc = tlsm_find_and_verify_cert_key( ctx, ctx->tc_model, ctx->tc_certname, 0, NULL, NULL );
|
|
ctx->tc_warn_only = saveval;
|
|
if ( rc ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: unable to set up client certificate authentication for "
|
|
"certificate named %s\n", ctx->tc_certname, 0, 0 );
|
|
return -1;
|
|
}
|
|
|
|
status = SSL_GetClientAuthDataHook( ctx->tc_model,
|
|
tlsm_get_client_auth_data,
|
|
(void *)ctx );
|
|
|
|
return ( status == SECSuccess ? 0 : -1 );
|
|
}
|
|
|
|
/*
|
|
* Tear down the TLS subsystem. Should only be called once.
|
|
*/
|
|
static void
|
|
tlsm_destroy( void )
|
|
{
|
|
#ifdef LDAP_R_COMPILE
|
|
ldap_pvt_thread_mutex_destroy( &tlsm_init_mutex );
|
|
ldap_pvt_thread_mutex_destroy( &tlsm_pem_mutex );
|
|
#endif
|
|
}
|
|
|
|
static struct ldaptls *
|
|
tlsm_copy_config ( const struct ldaptls *config )
|
|
{
|
|
struct ldaptls *copy;
|
|
|
|
assert(config);
|
|
|
|
copy = LDAP_MALLOC(sizeof(*copy));
|
|
if (!copy)
|
|
return NULL;
|
|
|
|
memset(copy, 0, sizeof(*copy));
|
|
|
|
if (config->lt_certfile)
|
|
copy->lt_certfile = LDAP_STRDUP(config->lt_certfile);
|
|
if (config->lt_keyfile)
|
|
copy->lt_keyfile = LDAP_STRDUP(config->lt_keyfile);
|
|
if (config->lt_dhfile)
|
|
copy->lt_dhfile = LDAP_STRDUP(config->lt_dhfile);
|
|
if (config->lt_cacertfile)
|
|
copy->lt_cacertfile = LDAP_STRDUP(config->lt_cacertfile);
|
|
if (config->lt_cacertdir)
|
|
copy->lt_cacertdir = LDAP_STRDUP(config->lt_cacertdir);
|
|
if (config->lt_ciphersuite)
|
|
copy->lt_ciphersuite = LDAP_STRDUP(config->lt_ciphersuite);
|
|
if (config->lt_crlfile)
|
|
copy->lt_crlfile = LDAP_STRDUP(config->lt_crlfile);
|
|
if (config->lt_randfile)
|
|
copy->lt_randfile = LDAP_STRDUP(config->lt_randfile);
|
|
|
|
copy->lt_protocol_min = config->lt_protocol_min;
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
tlsm_free_config ( struct ldaptls *config )
|
|
{
|
|
assert(config);
|
|
|
|
if (config->lt_certfile)
|
|
LDAP_FREE(config->lt_certfile);
|
|
if (config->lt_keyfile)
|
|
LDAP_FREE(config->lt_keyfile);
|
|
if (config->lt_dhfile)
|
|
LDAP_FREE(config->lt_dhfile);
|
|
if (config->lt_cacertfile)
|
|
LDAP_FREE(config->lt_cacertfile);
|
|
if (config->lt_cacertdir)
|
|
LDAP_FREE(config->lt_cacertdir);
|
|
if (config->lt_ciphersuite)
|
|
LDAP_FREE(config->lt_ciphersuite);
|
|
if (config->lt_crlfile)
|
|
LDAP_FREE(config->lt_crlfile);
|
|
if (config->lt_randfile)
|
|
LDAP_FREE(config->lt_randfile);
|
|
|
|
LDAP_FREE(config);
|
|
}
|
|
|
|
static tls_ctx *
|
|
tlsm_ctx_new ( struct ldapoptions *lo )
|
|
{
|
|
tlsm_ctx *ctx;
|
|
|
|
ctx = LDAP_MALLOC( sizeof (*ctx) );
|
|
if ( ctx ) {
|
|
ctx->tc_refcnt = 1;
|
|
#ifdef LDAP_R_COMPILE
|
|
ldap_pvt_thread_mutex_init( &ctx->tc_refmutex );
|
|
#endif
|
|
ctx->tc_config = NULL; /* populated later by tlsm_ctx_init */
|
|
ctx->tc_certdb = NULL;
|
|
ctx->tc_certname = NULL;
|
|
ctx->tc_pin_file = NULL;
|
|
ctx->tc_model = NULL;
|
|
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;
|
|
}
|
|
|
|
static void
|
|
tlsm_ctx_ref( tls_ctx *ctx )
|
|
{
|
|
tlsm_ctx *c = (tlsm_ctx *)ctx;
|
|
LDAP_MUTEX_LOCK( &c->tc_refmutex );
|
|
c->tc_refcnt++;
|
|
LDAP_MUTEX_UNLOCK( &c->tc_refmutex );
|
|
}
|
|
|
|
static void
|
|
tlsm_ctx_free ( tls_ctx *ctx )
|
|
{
|
|
tlsm_ctx *c = (tlsm_ctx *)ctx;
|
|
int refcount;
|
|
|
|
if ( !c ) return;
|
|
|
|
LDAP_MUTEX_LOCK( &c->tc_refmutex );
|
|
refcount = --c->tc_refcnt;
|
|
LDAP_MUTEX_UNLOCK( &c->tc_refmutex );
|
|
if ( refcount )
|
|
return;
|
|
if ( c->tc_model )
|
|
PR_Close( c->tc_model );
|
|
c->tc_certdb = NULL; /* if not the default, may have to clean up */
|
|
PL_strfree( c->tc_certname );
|
|
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
|
|
|
|
if ( c->tc_config )
|
|
tlsm_free_config( c->tc_config );
|
|
|
|
LDAP_FREE( c );
|
|
}
|
|
|
|
/*
|
|
* initialize a new TLS context
|
|
*/
|
|
static int
|
|
tlsm_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server )
|
|
{
|
|
tlsm_ctx *ctx = (tlsm_ctx *)lo->ldo_tls_ctx;
|
|
ctx->tc_config = tlsm_copy_config(lt);
|
|
ctx->tc_is_server = is_server;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tlsm_deferred_ctx_init( void *arg )
|
|
{
|
|
tlsm_ctx *ctx = (tlsm_ctx *)arg;
|
|
PRBool sslv2 = PR_FALSE;
|
|
PRBool sslv3 = PR_TRUE;
|
|
PRBool tlsv1 = PR_TRUE;
|
|
PRBool request_cert = PR_FALSE;
|
|
PRInt32 require_cert = PR_FALSE;
|
|
PRFileDesc *fd;
|
|
struct ldaptls *lt;
|
|
|
|
if ( tlsm_deferred_init( ctx ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not perform TLS system initialization.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
|
|
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 ) {
|
|
ctx->tc_model = SSL_ImportFD( NULL, fd );
|
|
}
|
|
|
|
if ( !ctx->tc_model ) {
|
|
PRErrorCode err = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could perform TLS socket I/O layer initialization - error %d:%s.\n",
|
|
err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL );
|
|
|
|
if ( fd ) {
|
|
PR_Close( fd );
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_SECURITY, PR_TRUE ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set secure mode on.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
|
|
lt = ctx->tc_config;
|
|
|
|
/* default is sslv3 and tlsv1 */
|
|
if ( lt->lt_protocol_min ) {
|
|
if ( lt->lt_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL3 ) {
|
|
sslv3 = PR_FALSE;
|
|
} else if ( lt->lt_protocol_min <= LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) {
|
|
sslv2 = PR_TRUE;
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: warning: minimum TLS protocol level set to "
|
|
"include SSLv2 - SSLv2 is insecure - do not use\n", 0, 0, 0 );
|
|
}
|
|
}
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_ENABLE_SSL2, sslv2 ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set SSLv2 mode on.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_ENABLE_SSL3, sslv3 ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set SSLv3 mode on.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_ENABLE_TLS, tlsv1 ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set TLSv1 mode on.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_HANDSHAKE_AS_CLIENT, !ctx->tc_is_server ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set handshake as client.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_HANDSHAKE_AS_SERVER, ctx->tc_is_server ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set handshake as server.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
|
|
if ( lt->lt_ciphersuite &&
|
|
tlsm_parse_ciphers( ctx, lt->lt_ciphersuite )) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"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 ) {
|
|
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 ||
|
|
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 { /* 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 ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set request certificate mode.\n",
|
|
0, 0, 0 );
|
|
return -1;
|
|
}
|
|
|
|
if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_REQUIRE_CERTIFICATE, require_cert ) ) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: could not set require certificate mode.\n",
|
|
0, 0, 0 );
|
|
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 ) {
|
|
PRErrorCode err = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: could not set nocache option for moznss - error %d:%s\n",
|
|
err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL );
|
|
return -1;
|
|
}
|
|
|
|
if ( SSL_BadCertHook( ctx->tc_model, tlsm_bad_cert_handler, ctx ) != SECSuccess ) {
|
|
PRErrorCode err = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: could not set bad cert handler for moznss - error %d:%s\n",
|
|
err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL );
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
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",
|
|
ctx->tc_certname, 0, 0 );
|
|
return -1;
|
|
}
|
|
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 );
|
|
return -1;
|
|
}
|
|
}
|
|
} else { /* set up secure server */
|
|
SSLKEAType certKEA;
|
|
CERTCertificate *serverCert = NULL;
|
|
SECKEYPrivateKey *serverKey = NULL;
|
|
SECStatus status;
|
|
|
|
/* must have a certificate for the server to use */
|
|
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 );
|
|
return -1;
|
|
}
|
|
|
|
/* authenticate to the server's token - this will do nothing
|
|
if the key/cert db is not password protected */
|
|
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",
|
|
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, 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",
|
|
ctx->tc_certname, 0, 0 );
|
|
CERT_DestroyCertificate( serverCert );
|
|
SECKEY_DestroyPrivateKey( serverKey );
|
|
return -1;
|
|
}
|
|
|
|
certKEA = NSS_FindCertKEAType( serverCert );
|
|
/* configure the socket to be a secure server socket */
|
|
status = SSL_ConfigSecureServer( ctx->tc_model, serverCert, serverKey, certKEA );
|
|
/* SSL_ConfigSecureServer copies these */
|
|
CERT_DestroyCertificate( serverCert );
|
|
SECKEY_DestroyPrivateKey( serverKey );
|
|
|
|
if ( SECSuccess != status ) {
|
|
PRErrorCode err = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: unable to configure secure server using certificate %s - error %d:%s\n",
|
|
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 ) != SECSuccess ) {
|
|
PRErrorCode err = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: could not set auth cert handler for moznss - error %d:%s\n",
|
|
err, PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ), NULL );
|
|
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;
|
|
}
|
|
|
|
tlsm_free_config( ctx->tc_config );
|
|
ctx->tc_config = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct tls_data {
|
|
tlsm_session *session;
|
|
Sockbuf_IO_Desc *sbiod;
|
|
/* there seems to be no portable way to determine if the
|
|
sockbuf sd has been set to nonblocking mode - the
|
|
call to ber_pvt_socket_set_nonblock() takes place
|
|
before the tls socket is set up, so we cannot
|
|
intercept that call either.
|
|
On systems where fcntl is available, we can just
|
|
F_GETFL and test for O_NONBLOCK. On other systems,
|
|
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 struct tls_data *
|
|
tlsm_get_pvt_tls_data( PRFileDesc *fd )
|
|
{
|
|
struct tls_data *p;
|
|
PRFileDesc *myfd;
|
|
|
|
if ( !fd ) {
|
|
return NULL;
|
|
}
|
|
|
|
myfd = PR_GetIdentitiesLayer( fd, tlsm_layer_id );
|
|
|
|
if ( !myfd ) {
|
|
return NULL;
|
|
}
|
|
|
|
p = (struct tls_data *)myfd->secret;
|
|
|
|
return p;
|
|
}
|
|
|
|
static int
|
|
tlsm_is_non_ssl_message( PRFileDesc *fd, ber_tag_t *thebyte )
|
|
{
|
|
struct tls_data *p;
|
|
|
|
if ( thebyte ) {
|
|
*thebyte = LBER_DEFAULT;
|
|
}
|
|
|
|
p = tlsm_get_pvt_tls_data( fd );
|
|
if ( p == NULL || p->sbiod == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( p->firsttag == LBER_SEQUENCE ) {
|
|
if ( thebyte ) {
|
|
*thebyte = p->firsttag;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static tls_session *
|
|
tlsm_session_new ( tls_ctx * ctx, int is_server )
|
|
{
|
|
tlsm_ctx *c = (tlsm_ctx *)ctx;
|
|
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 );
|
|
if ( PR_SUCCESS != status ) {
|
|
PRErrorCode err = PR_GetError();
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: could not initialize moznss security context - error %d:%s\n",
|
|
err, PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT), NULL );
|
|
return NULL;
|
|
}
|
|
|
|
fd = PR_CreateIOLayerStub( tlsm_layer_id, &tlsm_PR_methods );
|
|
if ( !fd ) {
|
|
return NULL;
|
|
}
|
|
|
|
session = SSL_ImportFD( c->tc_model, fd );
|
|
if ( !session ) {
|
|
PR_DELETE( fd );
|
|
return 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_or_connect( tls_session *session, int is_accept )
|
|
{
|
|
tlsm_session *s = (tlsm_session *)session;
|
|
int rc;
|
|
const char *op = is_accept ? "accept" : "connect";
|
|
|
|
if ( pem_module ) {
|
|
LDAP_MUTEX_LOCK( &tlsm_pem_mutex );
|
|
}
|
|
rc = SSL_ForceHandshake( s );
|
|
if ( pem_module ) {
|
|
LDAP_MUTEX_UNLOCK( &tlsm_pem_mutex );
|
|
}
|
|
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: %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 {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: error: %s - force handshake failure: errno %d - moznss error %d\n",
|
|
op, errno, err );
|
|
}
|
|
}
|
|
|
|
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 )
|
|
{
|
|
return tlsm_session_accept_or_connect( session, 0 );
|
|
}
|
|
|
|
static int
|
|
tlsm_session_upflags( Sockbuf *sb, tls_session *session, int rc )
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *
|
|
tlsm_session_errmsg( tls_session *sess, int rc, char *buf, size_t len )
|
|
{
|
|
int i;
|
|
int prerror = PR_GetError();
|
|
|
|
i = PR_GetErrorTextLength();
|
|
if ( i > len ) {
|
|
char *msg = LDAP_MALLOC( i+1 );
|
|
PR_GetErrorText( msg );
|
|
memcpy( buf, msg, 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 > 0 ) ? buf : NULL;
|
|
}
|
|
|
|
static int
|
|
tlsm_session_my_dn( tls_session *session, struct berval *der_dn )
|
|
{
|
|
tlsm_session *s = (tlsm_session *)session;
|
|
CERTCertificate *cert;
|
|
|
|
cert = SSL_LocalCertificate( s );
|
|
if (!cert) return LDAP_INVALID_CREDENTIALS;
|
|
|
|
der_dn->bv_val = (char *)cert->derSubject.data;
|
|
der_dn->bv_len = cert->derSubject.len;
|
|
CERT_DestroyCertificate( cert );
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tlsm_session_peer_dn( tls_session *session, struct berval *der_dn )
|
|
{
|
|
tlsm_session *s = (tlsm_session *)session;
|
|
CERTCertificate *cert;
|
|
|
|
cert = SSL_PeerCertificate( s );
|
|
if (!cert) return LDAP_INVALID_CREDENTIALS;
|
|
|
|
der_dn->bv_val = (char *)cert->derSubject.data;
|
|
der_dn->bv_len = cert->derSubject.len;
|
|
CERT_DestroyCertificate( cert );
|
|
return 0;
|
|
}
|
|
|
|
/* what kind of hostname were we given? */
|
|
#define IS_DNS 0
|
|
#define IS_IP4 1
|
|
#define IS_IP6 2
|
|
|
|
static int
|
|
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 ret, ntype = IS_DNS, nlen, dlen;
|
|
#ifdef LDAP_PF_INET6
|
|
struct in6_addr addr;
|
|
#else
|
|
struct in_addr addr;
|
|
#endif
|
|
SECItem altname;
|
|
SECStatus rv;
|
|
|
|
if( ldap_int_hostname &&
|
|
( !name_in || !strcasecmp( name_in, "localhost" ) ) )
|
|
{
|
|
name = ldap_int_hostname;
|
|
} else {
|
|
name = name_in;
|
|
}
|
|
nlen = strlen( name );
|
|
|
|
cert = SSL_PeerCertificate( s );
|
|
if (!cert) {
|
|
Debug( LDAP_DEBUG_ANY,
|
|
"TLS: unable to get peer certificate.\n",
|
|
0, 0, 0 );
|
|
/* if this was a fatal condition, things would have
|
|
* aborted long before now.
|
|
*/
|
|
return LDAP_SUCCESS;
|
|
}
|
|
|
|
#ifdef LDAP_PF_INET6
|
|
if (name[0] == '[' && strchr(name, ']')) {
|
|
char *n2 = ldap_strdup(name+1);
|
|
*strchr(n2, ']') = 0;
|
|
if (inet_pton(AF_INET6, n2, &addr))
|
|
ntype = IS_IP6;
|
|
LDAP_FREE(n2);
|
|
} else
|
|
#endif
|
|
if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) {
|
|
if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4;
|
|
}
|
|
if (ntype == IS_DNS ) {
|
|
domain = strchr( name, '.' );
|
|
if ( domain )
|
|
dlen = nlen - ( domain - name );
|
|
}
|
|
|
|
ret = LDAP_LOCAL_ERROR;
|
|
|
|
rv = CERT_FindCertExtension( cert, SEC_OID_X509_SUBJECT_ALT_NAME,
|
|
&altname );
|
|
if ( rv == SECSuccess && altname.data ) {
|
|
PRArenaPool *arena;
|
|
CERTGeneralName *names, *cur;
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if ( !arena ) {
|
|
ret = LDAP_NO_MEMORY;
|
|
goto fail;
|
|
}
|
|
|
|
names = cur = CERT_DecodeAltNameExtension(arena, &altname);
|
|
if ( !cur )
|
|
goto altfail;
|
|
|
|
do {
|
|
char *host;
|
|
int hlen;
|
|
|
|
/* ignore empty */
|
|
if ( !cur->name.other.len ) continue;
|
|
|
|
host = (char *)cur->name.other.data;
|
|
hlen = cur->name.other.len;
|
|
|
|
if ( cur->type == certDNSName ) {
|
|
if ( ntype != IS_DNS ) continue;
|
|
|
|
/* is this an exact match? */
|
|
if ( nlen == hlen && !strncasecmp( name, host, nlen )) {
|
|
ret = LDAP_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
/* is this a wildcard match? */
|
|
if ( domain && host[0] == '*' && host[1] == '.' &&
|
|
dlen == hlen-1 && !strncasecmp( domain, host+1, dlen )) {
|
|
ret = LDAP_SUCCESS;
|
|
break;
|
|
}
|
|
} else if ( cur->type == certIPAddress ) {
|
|
if ( ntype == IS_DNS ) continue;
|
|
|
|
#ifdef LDAP_PF_INET6
|
|
if (ntype == IS_IP6 && hlen != sizeof(struct in6_addr)) {
|
|
continue;
|
|
} else
|
|
#endif
|
|
if (ntype == IS_IP4 && hlen != sizeof(struct in_addr)) {
|
|
continue;
|
|
}
|
|
if (!memcmp(host, &addr, hlen)) {
|
|
ret = LDAP_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
} while (( cur = CERT_GetNextGeneralName( cur )) != names );
|
|
altfail:
|
|
PORT_FreeArena( arena, PR_FALSE );
|
|
SECITEM_FreeItem( &altname, PR_FALSE );
|
|
}
|
|
/* no altnames matched, try the CN */
|
|
if ( ret != LDAP_SUCCESS ) {
|
|
/* find the last CN */
|
|
CERTRDN *rdn, **rdns;
|
|
CERTAVA *lastava = NULL;
|
|
char buf[2048];
|
|
|
|
buf[0] = '\0';
|
|
rdns = cert->subject.rdns;
|
|
while ( rdns && ( rdn = *rdns++ )) {
|
|
CERTAVA *ava, **avas = rdn->avas;
|
|
while ( avas && ( ava = *avas++ )) {
|
|
if ( CERT_GetAVATag( ava ) == SEC_OID_AVA_COMMON_NAME )
|
|
lastava = ava;
|
|
}
|
|
}
|
|
if ( lastava ) {
|
|
SECItem *av = CERT_DecodeAVAValue( &lastava->value );
|
|
if ( av ) {
|
|
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( domain,
|
|
(char *)(av->data+1), dlen )) {
|
|
ret = LDAP_SUCCESS;
|
|
} else {
|
|
int len = av->len;
|
|
if ( len >= sizeof(buf) )
|
|
len = sizeof(buf)-1;
|
|
memcpy( buf, av->data, len );
|
|
buf[len] = '\0';
|
|
}
|
|
SECITEM_FreeItem( av, PR_TRUE );
|
|
}
|
|
}
|
|
if ( ret != LDAP_SUCCESS ) {
|
|
Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
|
|
"common name in certificate (%s).\n",
|
|
name, buf, 0 );
|
|
ret = LDAP_CONNECT_ERROR;
|
|
if ( ld->ld_error ) {
|
|
LDAP_FREE( ld->ld_error );
|
|
}
|
|
ld->ld_error = LDAP_STRDUP(
|
|
_("TLS: hostname does not match CN in peer certificate"));
|
|
}
|
|
}
|
|
|
|
fail:
|
|
CERT_DestroyCertificate( cert );
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
tlsm_session_strength( tls_session *session )
|
|
{
|
|
tlsm_session *s = (tlsm_session *)session;
|
|
int rc, keySize;
|
|
|
|
rc = SSL_SecurityStatus( s, NULL, NULL, NULL, &keySize,
|
|
NULL, NULL );
|
|
return rc ? 0 : keySize;
|
|
}
|
|
|
|
/*
|
|
* TLS support for LBER Sockbufs
|
|
*/
|
|
|
|
static PRStatus PR_CALLBACK
|
|
tlsm_PR_Close(PRFileDesc *fd)
|
|
{
|
|
int rc = PR_SUCCESS;
|
|
|
|
/* we don't need to actually close anything here, just
|
|
pop our io layer off the stack */
|
|
fd->secret = NULL; /* must have been freed before calling PR_Close */
|
|
if ( fd->lower ) {
|
|
fd = PR_PopIOLayer( fd, tlsm_layer_id );
|
|
/* if we are not the last layer, pass the close along */
|
|
if ( fd ) {
|
|
if ( fd->dtor ) {
|
|
fd->dtor( fd );
|
|
}
|
|
rc = fd->methods->close( fd );
|
|
}
|
|
} else {
|
|
/* we are the last layer - just call our dtor */
|
|
fd->dtor(fd);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static PRStatus PR_CALLBACK
|
|
tlsm_PR_Shutdown(PRFileDesc *fd, PRShutdownHow how)
|
|
{
|
|
int rc = PR_SUCCESS;
|
|
|
|
if ( fd->lower ) {
|
|
rc = PR_Shutdown( fd->lower, how );
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int PR_CALLBACK
|
|
tlsm_PR_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags,
|
|
PRIntervalTime timeout)
|
|
{
|
|
struct tls_data *p;
|
|
int rc;
|
|
|
|
if ( buf == NULL || len <= 0 ) return 0;
|
|
|
|
p = tlsm_get_pvt_tls_data( fd );
|
|
|
|
if ( p == NULL || p->sbiod == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
rc = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len );
|
|
if (rc <= 0) {
|
|
tlsm_map_error( errno );
|
|
if ( errno == EAGAIN || errno == EWOULDBLOCK ) {
|
|
p->nonblock = PR_TRUE; /* fd is using non-blocking io */
|
|
} else if ( errno ) { /* real error */
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"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;
|
|
}
|
|
|
|
static int PR_CALLBACK
|
|
tlsm_PR_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags,
|
|
PRIntervalTime timeout)
|
|
{
|
|
struct tls_data *p;
|
|
int rc;
|
|
|
|
if ( buf == NULL || len <= 0 ) return 0;
|
|
|
|
p = tlsm_get_pvt_tls_data( fd );
|
|
|
|
if ( p == NULL || p->sbiod == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
rc = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len );
|
|
if (rc <= 0) {
|
|
tlsm_map_error( errno );
|
|
if ( errno == EAGAIN || errno == EWOULDBLOCK ) {
|
|
p->nonblock = PR_TRUE;
|
|
} else if ( errno ) { /* real error */
|
|
Debug( LDAP_DEBUG_TRACE,
|
|
"TLS: error: tlsm_PR_Send returned %d - error %d:%s\n",
|
|
rc, errno, STRERROR(errno) );
|
|
}
|
|
}
|
|
p->io_flag = TLSM_WRITE;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int PR_CALLBACK
|
|
tlsm_PR_Read(PRFileDesc *fd, void *buf, PRInt32 len)
|
|
{
|
|
return tlsm_PR_Recv( fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT );
|
|
}
|
|
|
|
static int PR_CALLBACK
|
|
tlsm_PR_Write(PRFileDesc *fd, const void *buf, PRInt32 len)
|
|
{
|
|
return tlsm_PR_Send( fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT );
|
|
}
|
|
|
|
static PRStatus PR_CALLBACK
|
|
tlsm_PR_GetPeerName(PRFileDesc *fd, PRNetAddr *addr)
|
|
{
|
|
struct tls_data *p;
|
|
ber_socklen_t len;
|
|
|
|
p = tlsm_get_pvt_tls_data( fd );
|
|
|
|
if ( p == NULL || p->sbiod == NULL ) {
|
|
return PR_FAILURE;
|
|
}
|
|
len = sizeof(PRNetAddr);
|
|
return getpeername( p->sbiod->sbiod_sb->sb_fd, (struct sockaddr *)addr, &len );
|
|
}
|
|
|
|
static PRStatus PR_CALLBACK
|
|
tlsm_PR_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data)
|
|
{
|
|
struct tls_data *p;
|
|
p = tlsm_get_pvt_tls_data( fd );
|
|
|
|
if ( p == NULL || data == NULL ) {
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
/* only the nonblocking option is supported at this time
|
|
MozNSS SSL code needs it */
|
|
if ( data->option != PR_SockOpt_Nonblocking ) {
|
|
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
|
|
return PR_FAILURE;
|
|
}
|
|
#ifdef HAVE_FCNTL
|
|
int flags = fcntl( p->sbiod->sbiod_sb->sb_fd, F_GETFL );
|
|
data->value.non_blocking = (flags & O_NONBLOCK) ? PR_TRUE : PR_FALSE;
|
|
#else /* punt :P */
|
|
data->value.non_blocking = p->nonblock;
|
|
#endif
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
static PRStatus PR_CALLBACK
|
|
tlsm_PR_prs_unimp()
|
|
{
|
|
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
static PRFileDesc * PR_CALLBACK
|
|
tlsm_PR_pfd_unimp()
|
|
{
|
|
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
|
|
return NULL;
|
|
}
|
|
|
|
static PRInt16 PR_CALLBACK
|
|
tlsm_PR_i16_unimp()
|
|
{
|
|
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
static PRInt32 PR_CALLBACK
|
|
tlsm_PR_i32_unimp()
|
|
{
|
|
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
static PRInt64 PR_CALLBACK
|
|
tlsm_PR_i64_unimp()
|
|
{
|
|
PRInt64 res;
|
|
|
|
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
|
|
LL_I2L(res, -1L);
|
|
return res;
|
|
}
|
|
|
|
static const PRIOMethods tlsm_PR_methods = {
|
|
PR_DESC_LAYERED,
|
|
tlsm_PR_Close, /* close */
|
|
tlsm_PR_Read, /* read */
|
|
tlsm_PR_Write, /* write */
|
|
tlsm_PR_i32_unimp, /* available */
|
|
tlsm_PR_i64_unimp, /* available64 */
|
|
tlsm_PR_prs_unimp, /* fsync */
|
|
tlsm_PR_i32_unimp, /* seek */
|
|
tlsm_PR_i64_unimp, /* seek64 */
|
|
tlsm_PR_prs_unimp, /* fileInfo */
|
|
tlsm_PR_prs_unimp, /* fileInfo64 */
|
|
tlsm_PR_i32_unimp, /* writev */
|
|
tlsm_PR_prs_unimp, /* connect */
|
|
tlsm_PR_pfd_unimp, /* accept */
|
|
tlsm_PR_prs_unimp, /* bind */
|
|
tlsm_PR_prs_unimp, /* listen */
|
|
(PRShutdownFN)tlsm_PR_Shutdown, /* shutdown */
|
|
tlsm_PR_Recv, /* recv */
|
|
tlsm_PR_Send, /* send */
|
|
tlsm_PR_i32_unimp, /* recvfrom */
|
|
tlsm_PR_i32_unimp, /* sendto */
|
|
(PRPollFN)tlsm_PR_i16_unimp, /* poll */
|
|
tlsm_PR_i32_unimp, /* acceptread */
|
|
tlsm_PR_i32_unimp, /* transmitfile */
|
|
tlsm_PR_prs_unimp, /* getsockname */
|
|
tlsm_PR_GetPeerName, /* getpeername */
|
|
tlsm_PR_i32_unimp, /* getsockopt OBSOLETE */
|
|
tlsm_PR_i32_unimp, /* setsockopt OBSOLETE */
|
|
tlsm_PR_GetSocketOption, /* getsocketoption */
|
|
tlsm_PR_i32_unimp, /* setsocketoption */
|
|
tlsm_PR_i32_unimp, /* Send a (partial) file with header/trailer*/
|
|
(PRConnectcontinueFN)tlsm_PR_prs_unimp, /* connectcontinue */
|
|
tlsm_PR_i32_unimp, /* reserved for future use */
|
|
tlsm_PR_i32_unimp, /* reserved for future use */
|
|
tlsm_PR_i32_unimp, /* reserved for future use */
|
|
tlsm_PR_i32_unimp /* reserved for future use */
|
|
};
|
|
|
|
/*
|
|
* 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" );
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
static int
|
|
tlsm_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg )
|
|
{
|
|
struct tls_data *p;
|
|
tlsm_session *session = arg;
|
|
PRFileDesc *fd;
|
|
|
|
assert( sbiod != NULL );
|
|
|
|
p = LBER_MALLOC( sizeof( *p ) );
|
|
if ( p == NULL ) {
|
|
return -1;
|
|
}
|
|
|
|
fd = PR_GetIdentitiesLayer( session, tlsm_layer_id );
|
|
if ( !fd ) {
|
|
LBER_FREE( p );
|
|
return -1;
|
|
}
|
|
|
|
fd->secret = (PRFilePrivate *)p;
|
|
p->session = session;
|
|
p->sbiod = sbiod;
|
|
p->firsttag = LBER_DEFAULT;
|
|
sbiod->sbiod_pvt = p;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tlsm_sb_remove( Sockbuf_IO_Desc *sbiod )
|
|
{
|
|
struct tls_data *p;
|
|
|
|
assert( sbiod != NULL );
|
|
assert( sbiod->sbiod_pvt != NULL );
|
|
|
|
p = (struct tls_data *)sbiod->sbiod_pvt;
|
|
PR_Close( p->session );
|
|
LBER_FREE( sbiod->sbiod_pvt );
|
|
sbiod->sbiod_pvt = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tlsm_sb_close( Sockbuf_IO_Desc *sbiod )
|
|
{
|
|
struct tls_data *p;
|
|
|
|
assert( sbiod != NULL );
|
|
assert( sbiod->sbiod_pvt != NULL );
|
|
|
|
p = (struct tls_data *)sbiod->sbiod_pvt;
|
|
PR_Shutdown( p->session, PR_SHUTDOWN_BOTH );
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tlsm_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg )
|
|
{
|
|
struct tls_data *p;
|
|
|
|
assert( sbiod != NULL );
|
|
assert( sbiod->sbiod_pvt != NULL );
|
|
|
|
p = (struct tls_data *)sbiod->sbiod_pvt;
|
|
|
|
if ( opt == LBER_SB_OPT_GET_SSL ) {
|
|
*((tlsm_session **)arg) = p->session;
|
|
return 1;
|
|
|
|
} else if ( opt == LBER_SB_OPT_DATA_READY ) {
|
|
if ( p && ( SSL_DataPending( p->session ) > 0 ) ) {
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|
|
return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg );
|
|
}
|
|
|
|
static ber_slen_t
|
|
tlsm_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
|
|
{
|
|
struct tls_data *p;
|
|
ber_slen_t ret;
|
|
int err;
|
|
|
|
assert( sbiod != NULL );
|
|
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
|
|
|
|
p = (struct tls_data *)sbiod->sbiod_pvt;
|
|
|
|
ret = PR_Recv( p->session, buf, len, 0, PR_INTERVAL_NO_TIMEOUT );
|
|
if ( ret < 0 ) {
|
|
err = PR_GetError();
|
|
if ( err == PR_PENDING_INTERRUPT_ERROR || err == PR_WOULD_BLOCK_ERROR ) {
|
|
sbiod->sbiod_sb->sb_trans_needs_read = 1;
|
|
sock_errset(EWOULDBLOCK);
|
|
}
|
|
} else {
|
|
sbiod->sbiod_sb->sb_trans_needs_read = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ber_slen_t
|
|
tlsm_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
|
|
{
|
|
struct tls_data *p;
|
|
ber_slen_t ret;
|
|
int err;
|
|
|
|
assert( sbiod != NULL );
|
|
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
|
|
|
|
p = (struct tls_data *)sbiod->sbiod_pvt;
|
|
|
|
ret = PR_Send( p->session, (char *)buf, len, 0, PR_INTERVAL_NO_TIMEOUT );
|
|
if ( ret < 0 ) {
|
|
err = PR_GetError();
|
|
if ( err == PR_PENDING_INTERRUPT_ERROR || err == PR_WOULD_BLOCK_ERROR ) {
|
|
sbiod->sbiod_sb->sb_trans_needs_write = 1;
|
|
sock_errset(EWOULDBLOCK);
|
|
ret = 0;
|
|
}
|
|
} else {
|
|
sbiod->sbiod_sb->sb_trans_needs_write = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static Sockbuf_IO tlsm_sbio =
|
|
{
|
|
tlsm_sb_setup, /* sbi_setup */
|
|
tlsm_sb_remove, /* sbi_remove */
|
|
tlsm_sb_ctrl, /* sbi_ctrl */
|
|
tlsm_sb_read, /* sbi_read */
|
|
tlsm_sb_write, /* sbi_write */
|
|
tlsm_sb_close /* sbi_close */
|
|
};
|
|
|
|
tls_impl ldap_int_tls_impl = {
|
|
"MozNSS",
|
|
|
|
tlsm_init,
|
|
tlsm_destroy,
|
|
|
|
tlsm_ctx_new,
|
|
tlsm_ctx_ref,
|
|
tlsm_ctx_free,
|
|
tlsm_ctx_init,
|
|
|
|
tlsm_session_new,
|
|
tlsm_session_connect,
|
|
tlsm_session_accept,
|
|
tlsm_session_upflags,
|
|
tlsm_session_errmsg,
|
|
tlsm_session_my_dn,
|
|
tlsm_session_peer_dn,
|
|
tlsm_session_chkhost,
|
|
tlsm_session_strength,
|
|
|
|
&tlsm_sbio,
|
|
|
|
#ifdef LDAP_R_COMPILE
|
|
tlsm_thr_init,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
|
|
0
|
|
};
|
|
|
|
#endif /* HAVE_MOZNSS */
|
|
/*
|
|
emacs settings
|
|
Local Variables:
|
|
indent-tabs-mode: t
|
|
tab-width: 4
|
|
End:
|
|
*/
|