/* tls_o.c - Handle tls/ssl using OpenSSL */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2008-2011 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 * . */ /* ACKNOWLEDGEMENTS: Rewritten by Howard Chu */ #include "portable.h" #ifdef HAVE_OPENSSL #include "ldap_config.h" #include #include #include #include #include #include #include #include #include #include #include "ldap-int.h" #include "ldap-tls.h" #ifdef HAVE_OPENSSL_SSL_H #include #include #include #include #include #elif defined( HAVE_SSL_H ) #include #endif #ifdef __APPLE__ #include #include static int tlso_use_cert_from_keychain( struct ldapoptions *lo ); static X509 *tslo_copy_cert_to_x509(SecCertificateRef inRef); static OSStatus tslo_create_ca_chain(SSL_CTX* ctx, SecCertificateRef sslCertRef); static EVP_PKEY *tslo_copy_key_to_evpkey(SecKeyRef inKey); #endif typedef SSL_CTX tlso_ctx; typedef SSL tlso_session; static int tlso_opt_trace = 1; static void tlso_report_error( void ); static void tlso_info_cb( const SSL *ssl, int where, int ret ); static int tlso_verify_cb( int ok, X509_STORE_CTX *ctx ); static int tlso_pphrase_cb( char *buf, int bufsize, int verify ); static int tlso_verify_ok( int ok, X509_STORE_CTX *ctx ); static RSA * tlso_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ); EVP_PKEY *SSL_read_PrivateKey(FILE *fp, EVP_PKEY **key, int (*cb)()); static DH * tlso_tmp_dh_cb( SSL *ssl, int is_export, int key_length ); typedef struct dhplist { struct dhplist *next; int keylength; DH *param; } dhplist; static dhplist *tlso_dhparams; static int tlso_seed_PRNG( const char *randfile ); #ifdef LDAP_R_COMPILE /* * provide mutexes for the OpenSSL library. */ static ldap_pvt_thread_mutex_t tlso_mutexes[CRYPTO_NUM_LOCKS]; static ldap_pvt_thread_mutex_t tlso_dh_mutex; static void tlso_locking_cb( int mode, int type, const char *file, int line ) { if ( mode & CRYPTO_LOCK ) { ldap_pvt_thread_mutex_lock( &tlso_mutexes[type] ); } else { ldap_pvt_thread_mutex_unlock( &tlso_mutexes[type] ); } } static unsigned long tlso_thread_self( void ) { /* FIXME: CRYPTO_set_id_callback only works when ldap_pvt_thread_t * is an integral type that fits in an unsigned long */ /* force an error if the ldap_pvt_thread_t type is too large */ enum { ok = sizeof( ldap_pvt_thread_t ) <= sizeof( unsigned long ) }; typedef struct { int dummy: ok ? 1 : -1; } Check[ok ? 1 : -1]; return (unsigned long) ldap_pvt_thread_self(); } static void tlso_thr_init( void ) { int i; for( i=0; i< CRYPTO_NUM_LOCKS ; i++ ) { ldap_pvt_thread_mutex_init( &tlso_mutexes[i] ); } ldap_pvt_thread_mutex_init( &tlso_dh_mutex ); CRYPTO_set_locking_callback( tlso_locking_cb ); CRYPTO_set_id_callback( tlso_thread_self ); } #endif /* LDAP_R_COMPILE */ /*Apple Specific code*/ EVP_PKEY *SSL_read_PrivateKey(FILE *fp, EVP_PKEY **key, int (*cb)()) { EVP_PKEY *rc; BIO *bioS; BIO *bioF; /* 1. try PEM (= DER+Base64+headers) */ rc = PEM_read_PrivateKey(fp, key, cb, NULL); if (rc == NULL) { /* 2. try DER+Base64 */ fseek(fp, 0L, SEEK_SET); if ((bioS = BIO_new(BIO_s_fd())) == NULL) return NULL; BIO_set_fd(bioS, fileno(fp), BIO_NOCLOSE); if ((bioF = BIO_new(BIO_f_base64())) == NULL) { BIO_free(bioS); return NULL; } bioS = BIO_push(bioF, bioS); rc = d2i_PrivateKey_bio(bioS, NULL); BIO_free_all(bioS); if (rc == NULL) { /* 3. try plain DER */ fseek(fp, 0L, SEEK_SET); if ((bioS = BIO_new(BIO_s_fd())) == NULL) return NULL; BIO_set_fd(bioS, fileno(fp), BIO_NOCLOSE); rc = d2i_PrivateKey_bio(bioS, NULL); BIO_free(bioS); } } if (rc != NULL && key != NULL) { if (*key != NULL) EVP_PKEY_free(*key); *key = rc; } return rc; } #define SYSTEM_KEYCHAIN_PATH "/Library/Keychains/System.keychain" int tlso_pphrase_cb( char *buf, int bufsize, int verify ) { int len = -1; struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); /* * When a remembered passphrase is available, use it */ if (lo->ldo_tls_passphrase != NULL) { SecKeychainRef keychainRef = NULL; OSStatus status = SecKeychainOpen( SYSTEM_KEYCHAIN_PATH, &keychainRef ); if ( status != errSecSuccess ) { Debug( LDAP_DEBUG_ANY, "TLS: SecKeychainOpen failed for keychain %s: %d", SYSTEM_KEYCHAIN_PATH, (int)status, 0 ); syslog( LOG_ERR, "TLS: SecKeychainOpen failed for keychain %s: %d", SYSTEM_KEYCHAIN_PATH, (int)status, 0 ); cssmPerror( "SecKeychainOpen", status ); return len; } char *keychain_identifier = LDAP_STRDUP(lo->ldo_tls_passphrase); CFStringRef keychainCFName = CFStringCreateWithCString(NULL, keychain_identifier, kCFStringEncodingUTF8); if(keychainCFName) { CFRange foundRange; if(CFStringFindWithOptions(keychainCFName, CFSTR("."), CFRangeMake(0, CFStringGetLength(keychainCFName)), kCFCompareCaseInsensitive, &foundRange) == true) { CFStringRef itemName = CFStringCreateWithSubstring(NULL, keychainCFName, CFRangeMake(0, foundRange.location)); CFStringRef accountName = CFStringCreateWithSubstring(NULL, keychainCFName, CFRangeMake(foundRange.location + 1, CFStringGetLength(keychainCFName) - foundRange.location)); char *item_name, *account_name; void *passphraseBytes = nil; UInt32 passphraseLength = 0; if(itemName) { int length = CFStringGetLength(itemName); item_name = (char*)calloc(1, length + 1); CFStringGetCString(itemName, item_name, length +1, kCFStringEncodingUTF8); } if(accountName) { int length = CFStringGetLength(accountName); account_name = (char*)calloc(1, length + 1); CFStringGetCString(accountName, account_name, length +1, kCFStringEncodingUTF8); } status = SecKeychainFindGenericPassword(keychainRef, strlen(item_name),item_name , strlen(account_name), account_name, &passphraseLength, &passphraseBytes, nil); if(status == 0) { if(passphraseLength < bufsize) { memcpy(buf, passphraseBytes, passphraseLength); buf[passphraseLength] ='\0'; len = strlen( buf ); } SecKeychainItemFreeContent(nil, passphraseBytes); } if(item_name) free(item_name); if(account_name) free(account_name); if(keychain_identifier) LDAP_FREE(keychain_identifier); } } } /* * Ok, we now have the pass phrase * so return its length to OpenSSL... */ return len; } /* Apple Specific code end*/ static STACK_OF(X509_NAME) * tlso_ca_list( char * bundle, char * dir ) { STACK_OF(X509_NAME) *ca_list = NULL; if ( bundle ) { ca_list = SSL_load_client_CA_file( bundle ); } #if defined(HAVE_DIRENT_H) || defined(dirent) if ( dir ) { int freeit = 0; if ( !ca_list ) { ca_list = sk_X509_NAME_new_null(); freeit = 1; } if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && freeit ) { sk_X509_NAME_free( ca_list ); ca_list = NULL; } } #endif return ca_list; } /* * Initialize TLS subsystem. Should be called only once. */ static int tlso_init( void ) { struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); #ifdef HAVE_EBCDIC { char *file = LDAP_STRDUP( lo->ldo_tls_randfile ); if ( file ) __atoe( file ); (void) tlso_seed_PRNG( file ); LDAP_FREE( file ); } #else (void) tlso_seed_PRNG( lo->ldo_tls_randfile ); #endif SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_digests(); /* FIXME: mod_ssl does this */ X509V3_add_standard_extensions(); return 0; } /* * Tear down the TLS subsystem. Should only be called once. */ static void tlso_destroy( void ) { struct ldapoptions *lo = LDAP_INT_GLOBAL_OPT(); EVP_cleanup(); ERR_remove_state(0); ERR_free_strings(); if ( lo->ldo_tls_randfile ) { LDAP_FREE( lo->ldo_tls_randfile ); lo->ldo_tls_randfile = NULL; } if ( lo->ldo_tls_passphrase ) { memset( lo->ldo_tls_passphrase, 0, strlen(lo->ldo_tls_passphrase)); LDAP_FREE( lo->ldo_tls_passphrase ); lo->ldo_tls_passphrase = NULL; } } static tls_ctx * tlso_ctx_new( struct ldapoptions *lo ) { return (tls_ctx *) SSL_CTX_new( SSLv23_method() ); } static void tlso_ctx_ref( tls_ctx *ctx ) { tlso_ctx *c = (tlso_ctx *)ctx; CRYPTO_add( &c->references, 1, CRYPTO_LOCK_SSL_CTX ); } static void tlso_ctx_free ( tls_ctx *ctx ) { tlso_ctx *c = (tlso_ctx *)ctx; SSL_CTX_free( c ); } /* * initialize a new TLS context */ static int tlso_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) { tlso_ctx *ctx = (tlso_ctx *)lo->ldo_tls_ctx; int i; if ( is_server ) { SSL_CTX_set_session_id_context( ctx, (const unsigned char *) "OpenLDAP", sizeof("OpenLDAP")-1 ); } if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL3 ) SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 ); else if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 ); if ( lo->ldo_tls_ciphersuite && !SSL_CTX_set_cipher_list( ctx, lt->lt_ciphersuite ) ) { Debug( LDAP_DEBUG_ANY, "TLS: could not set cipher list %s.\n", lo->ldo_tls_ciphersuite, 0, 0 ); tlso_report_error(); return -1; } #ifdef __APPLE__ /* ldo_tls_server_ident_ref_name should only be set for servers. */ /* Since this is server-only, using syslog() instead of Debug(). */ if(lo->ldo_tls_server_ident_ref_name != NULL) { OSStatus status = errSecSuccess; SecIdentityRef sslIdentityRef = NULL; X509 *x509_cert = NULL; SecCertificateRef certRef = NULL; SecKeyRef keyRef = NULL; EVP_PKEY *pvtKey = NULL; SecKeychainSetUserInteractionAllowed(false); SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); sslIdentityRef = SecIdentityCopyPreferred(CFSTR("OPENDIRECTORY_SSL_IDENTITY"), 0, NULL); if (sslIdentityRef) { syslog(LOG_DEBUG, "TLS: found identity in keychain using identity preference."); } else { syslog(LOG_DEBUG, "TLS: failed to find identity in keychain using identity preference. Trying to find identity by name '%s'.", lo->ldo_tls_server_ident_ref_name); /* The identity name may be preceeded by "APPLE:". If so, strip that part. */ char *sep = strchr(lo->ldo_tls_server_ident_ref_name, ':'); char *cIdentityName = sep ? sep + 1 : lo->ldo_tls_server_ident_ref_name; CFStringRef identityName = CFStringCreateWithCString(NULL, cIdentityName, kCFStringEncodingUTF8); if (!identityName) { syslog(LOG_NOTICE, "TLS: failed to create CFString version of identity name %s.", lo->ldo_tls_server_ident_ref_name); } else { CFMutableDictionaryRef query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(query, kSecClass, kSecClassIdentity); CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue); CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne); CFDictionaryAddValue(query, kSecMatchSubjectContains, identityName); /* Always want to specify the system keychain to protect ourselves from * damage done to the keychain search list by Server.app & friends. */ SecKeychainRef keychainRef = NULL; status = SecKeychainOpen(SYSTEM_KEYCHAIN_PATH, &keychainRef); if (status == errSecSuccess) { CFArrayRef searchList = CFArrayCreate(kCFAllocatorDefault, (const void**)&keychainRef, 1, &kCFTypeArrayCallBacks); if (searchList) { CFDictionaryAddValue(query, kSecMatchSearchList, searchList); CFRelease(searchList); } } CFTypeRef results = NULL; status = SecItemCopyMatching(query, &results); if(status != noErr) { syslog(LOG_NOTICE, "TLS: SecItemCopyMatching returned error %d when searching for identity %s.", status, lo->ldo_tls_server_ident_ref_name); } else if (!results) { syslog(LOG_NOTICE, "TLS: failed to find identity using name %s: no results returned.", lo->ldo_tls_server_ident_ref_name); } else { if (CFGetTypeID(results) == SecIdentityGetTypeID()) { sslIdentityRef = (SecIdentityRef)results; CFRetain(sslIdentityRef); } else if (CFGetTypeID(results) == CFArrayGetTypeID()) { if (CFArrayGetCount(results) != 0) { sslIdentityRef = (SecIdentityRef)CFArrayGetValueAtIndex(results, 0); CFRetain(sslIdentityRef); } else { syslog(LOG_NOTICE, "TLS: failed to find identity using name %s: empty array returned.", lo->ldo_tls_server_ident_ref_name); } } else { syslog(LOG_DEBUG, "TLS: SecItemCopyMatching returned unhandled type."); } } CFRelease(identityName); CFRelease(query); } if (sslIdentityRef) { syslog(LOG_DEBUG, "TLS: found identity in keychain by name '%s'.", lo->ldo_tls_server_ident_ref_name); } else { syslog(LOG_NOTICE, "TLS: failed to find ssl identity in keychain."); return LDAP_TLS_KEYCHAIN_CERT_NOTFOUND; } } status = SecIdentityCopyCertificate(sslIdentityRef, &certRef); if(status) { syslog(LOG_NOTICE, "TLS: could not copy certificate from keychain identity '%s'.", lo->ldo_tls_server_ident_ref_name); goto done; } x509_cert = tslo_copy_cert_to_x509(certRef); status = tslo_create_ca_chain(ctx, certRef); if(status != noErr) { syslog(LOG_NOTICE, "TLS: failed to create ca chain for '%s'.", lo->ldo_tls_server_ident_ref_name); goto done; } if(!SSL_CTX_use_certificate(ctx, x509_cert)) { syslog(LOG_NOTICE, "TLS: could not use certificate '%s'.", lo->ldo_tls_server_ident_ref_name); tlso_report_error(); status = -1; goto done; } status = SecIdentityCopyPrivateKey(sslIdentityRef, &keyRef); if(status != noErr) { syslog(LOG_NOTICE, "TLS: could not retrieve pvt key for '%s' from system keychain.", lo->ldo_tls_server_ident_ref_name); goto done; } pvtKey = tslo_copy_key_to_evpkey(keyRef); if(!pvtKey) { syslog(LOG_NOTICE, "TLS: could not convert keychain item '%s' to EVP_PKEY.", lo->ldo_tls_server_ident_ref_name); tlso_report_error(); status = -1; goto done; } if (!SSL_CTX_use_PrivateKey( lo->ldo_tls_ctx, pvtKey ) ) { syslog(LOG_NOTICE, "TLS: could not use pvt key '%s'.", lo->ldo_tls_server_ident_ref_name); tlso_report_error(); status = -1; goto done; } if (!SSL_CTX_check_private_key(lo->ldo_tls_ctx) ) { syslog(LOG_NOTICE, "TLS: pvt key doesn't match public key '%s'.", lo->ldo_tls_server_ident_ref_name); tlso_report_error(); status = -1; } done: if(pvtKey) EVP_PKEY_free(pvtKey); if(keyRef) CFRelease(keyRef); if(x509_cert) X509_free(x509_cert); if(certRef) CFRelease(certRef); if(sslIdentityRef) CFRelease(sslIdentityRef); if(status != noErr) return -1; } else { #endif if (lo->ldo_tls_cacertfile != NULL || lo->ldo_tls_cacertdir != NULL) { if ( !SSL_CTX_load_verify_locations( ctx, lt->lt_cacertfile, lt->lt_cacertdir ) || !SSL_CTX_set_default_verify_paths( ctx ) ) { Debug( LDAP_DEBUG_ANY, "TLS: " "could not load verify locations (file:`%s',dir:`%s').\n", lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", 0 ); tlso_report_error(); return -1; } if ( is_server ) { STACK_OF(X509_NAME) *calist; /* List of CA names to send to a client */ calist = tlso_ca_list( lt->lt_cacertfile, lt->lt_cacertdir ); if ( !calist ) { Debug( LDAP_DEBUG_ANY, "TLS: " "could not load client CA list (file:`%s',dir:`%s').\n", lo->ldo_tls_cacertfile ? lo->ldo_tls_cacertfile : "", lo->ldo_tls_cacertdir ? lo->ldo_tls_cacertdir : "", 0 ); tlso_report_error(); return -1; } SSL_CTX_set_client_CA_list( ctx, calist ); } } if ( lo->ldo_tls_certfile && !SSL_CTX_use_certificate_file( ctx, lt->lt_certfile, SSL_FILETYPE_PEM ) ) { Debug( LDAP_DEBUG_ANY, "TLS: could not use certificate `%s'.\n", lo->ldo_tls_certfile,0,0); tlso_report_error(); return -1; } /* Key validity is checked automatically if cert has already been set */ if ( lo->ldo_tls_keyfile ) { FILE *fp; EVP_PKEY *privatekey; int success; Debug( LDAP_DEBUG_ANY, "TLS: attempting to read `%s'.\n", lo->ldo_tls_keyfile,0,0); /* * Try to read the private key file with the help of * the callback function which serves the pass * phrases to OpenSSL */ if ((fp = fopen(lo->ldo_tls_keyfile, "r")) == NULL) { success = 0; } else { privatekey = SSL_read_PrivateKey(fp, NULL, tlso_pphrase_cb); success = (privatekey != NULL ? 1 : 0); fclose(fp); } if (!success || !SSL_CTX_use_PrivateKey( lo->ldo_tls_ctx, privatekey ) ) { Debug( LDAP_DEBUG_ANY, "TLS: could not use key file `%s'.\n", lo->ldo_tls_keyfile,0,0); tlso_report_error(); return -1; } } #ifdef __APPLE__ } /* Client path only. If the server's certificate was previously found * in the keychain, try to use. */ if ( lo->ldo_tls_cert_ref ) { if ( !tlso_use_cert_from_keychain( lo ) ) { return -1; } } #endif if ( lo->ldo_tls_dhfile ) { DH *dh = NULL; BIO *bio; dhplist *p; if (( bio=BIO_new_file( lt->lt_dhfile,"r" )) == NULL ) { Debug( LDAP_DEBUG_ANY, "TLS: could not use DH parameters file `%s'.\n", lo->ldo_tls_dhfile,0,0); tlso_report_error(); return -1; } while (( dh=PEM_read_bio_DHparams( bio, NULL, NULL, NULL ))) { p = LDAP_MALLOC( sizeof(dhplist) ); if ( p != NULL ) { p->keylength = DH_size( dh ) * 8; p->param = dh; p->next = tlso_dhparams; tlso_dhparams = p; } } BIO_free( bio ); } if ( tlso_opt_trace ) { SSL_CTX_set_info_callback( ctx, tlso_info_cb ); } i = SSL_VERIFY_NONE; if ( lo->ldo_tls_require_cert ) { i = SSL_VERIFY_PEER; if ( lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_DEMAND || lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_HARD ) { i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; } } SSL_CTX_set_verify( ctx, i, lo->ldo_tls_require_cert == LDAP_OPT_X_TLS_ALLOW ? tlso_verify_ok : tlso_verify_cb ); SSL_CTX_set_tmp_rsa_callback( ctx, tlso_tmp_rsa_cb ); if ( lo->ldo_tls_dhfile ) { SSL_CTX_set_tmp_dh_callback( ctx, tlso_tmp_dh_cb ); } #ifdef HAVE_OPENSSL_CRL if ( lo->ldo_tls_crlcheck ) { X509_STORE *x509_s = SSL_CTX_get_cert_store( ctx ); if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_PEER ) { X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK ); } else if ( lo->ldo_tls_crlcheck == LDAP_OPT_X_TLS_CRL_ALL ) { X509_STORE_set_flags( x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); } } #endif return 0; } static tls_session * tlso_session_new( tls_ctx *ctx, int is_server ) { tlso_ctx *c = (tlso_ctx *)ctx; return (tls_session *)SSL_new( c ); } static int tlso_session_connect( LDAP *ld, tls_session *sess ) { tlso_session *s = (tlso_session *)sess; /* Caller expects 0 = success, OpenSSL returns 1 = success */ return SSL_connect( s ) - 1; } static int tlso_session_accept( tls_session *sess ) { tlso_session *s = (tlso_session *)sess; /* Caller expects 0 = success, OpenSSL returns 1 = success */ return SSL_accept( s ) - 1; } static int tlso_session_upflags( Sockbuf *sb, tls_session *sess, int rc ) { tlso_session *s = (tlso_session *)sess; /* 1 was subtracted above, offset it back now */ rc = SSL_get_error(s, rc+1); if (rc == SSL_ERROR_WANT_READ) { sb->sb_trans_needs_read = 1; return 1; } else if (rc == SSL_ERROR_WANT_WRITE) { sb->sb_trans_needs_write = 1; return 1; } else if (rc == SSL_ERROR_WANT_CONNECT) { return 1; } return 0; } static char * tlso_session_errmsg( tls_session *sess, int rc, char *buf, size_t len ) { char err[256] = ""; const char *certerr=NULL; tlso_session *s = (tlso_session *)sess; rc = ERR_peek_error(); if ( rc ) { ERR_error_string_n( rc, err, sizeof(err) ); if ( ( ERR_GET_LIB(rc) == ERR_LIB_SSL ) && ( ERR_GET_REASON(rc) == SSL_R_CERTIFICATE_VERIFY_FAILED ) ) { int certrc = SSL_get_verify_result(s); certerr = (char *)X509_verify_cert_error_string(certrc); } snprintf(buf, len, "%s%s%s%s", err, certerr ? " (" :"", certerr ? certerr : "", certerr ? ")" : "" ); return buf; } return NULL; } static int tlso_session_my_dn( tls_session *sess, struct berval *der_dn ) { tlso_session *s = (tlso_session *)sess; X509 *x; X509_NAME *xn; x = SSL_get_certificate( s ); if (!x) return LDAP_INVALID_CREDENTIALS; xn = X509_get_subject_name(x); der_dn->bv_len = i2d_X509_NAME( xn, NULL ); der_dn->bv_val = xn->bytes->data; /* Don't X509_free, the session is still using it */ return 0; } static X509 * tlso_get_cert( SSL *s ) { /* If peer cert was bad, treat as if no cert was given */ if (SSL_get_verify_result(s)) { return NULL; } return SSL_get_peer_certificate(s); } static int tlso_session_peer_dn( tls_session *sess, struct berval *der_dn ) { tlso_session *s = (tlso_session *)sess; X509 *x = tlso_get_cert( s ); X509_NAME *xn; if ( !x ) return LDAP_INVALID_CREDENTIALS; xn = X509_get_subject_name(x); der_dn->bv_len = i2d_X509_NAME( xn, NULL ); der_dn->bv_val = xn->bytes->data; X509_free(x); return 0; } /* what kind of hostname were we given? */ #define IS_DNS 0 #define IS_IP4 1 #define IS_IP6 2 static int tlso_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in ) { tlso_session *s = (tlso_session *)sess; int i, ret = LDAP_LOCAL_ERROR; X509 *x; const char *name; char *ptr; int ntype = IS_DNS, nlen; #ifdef LDAP_PF_INET6 struct in6_addr addr; #else struct in_addr addr; #endif if( ldap_int_hostname && ( !name_in || !strcasecmp( name_in, "localhost" ) ) ) { name = ldap_int_hostname; } else { name = name_in; } nlen = strlen(name); x = tlso_get_cert(s); if (!x) { 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; } i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); if (i >= 0) { X509_EXTENSION *ex; STACK_OF(GENERAL_NAME) *alt; ex = X509_get_ext(x, i); alt = X509V3_EXT_d2i(ex); if (alt) { int n, len2 = 0; char *domain = NULL; GENERAL_NAME *gn; if (ntype == IS_DNS) { domain = strchr(name, '.'); if (domain) { len2 = nlen - (domain-name); } } n = sk_GENERAL_NAME_num(alt); for (i=0; itype == GEN_DNS) { if (ntype != IS_DNS) continue; sn = (char *) ASN1_STRING_data(gn->d.ia5); sl = ASN1_STRING_length(gn->d.ia5); /* ignore empty */ if (sl == 0) continue; /* Is this an exact match? */ if ((nlen == sl) && !strncasecmp(name, sn, nlen)) { break; } /* Is this a wildcard match? */ if (domain && (sn[0] == '*') && (sn[1] == '.') && (len2 == sl-1) && !strncasecmp(domain, &sn[1], len2)) { break; } } else if (gn->type == GEN_IPADD) { if (ntype == IS_DNS) continue; sn = (char *) ASN1_STRING_data(gn->d.ia5); sl = ASN1_STRING_length(gn->d.ia5); #ifdef LDAP_PF_INET6 if (ntype == IS_IP6 && sl != sizeof(struct in6_addr)) { continue; } else #endif if (ntype == IS_IP4 && sl != sizeof(struct in_addr)) { continue; } if (!memcmp(sn, &addr, sl)) { break; } } } GENERAL_NAMES_free(alt); if (i < n) { /* Found a match */ ret = LDAP_SUCCESS; } } } if (ret != LDAP_SUCCESS) { X509_NAME *xn; X509_NAME_ENTRY *ne; ASN1_OBJECT *obj; ASN1_STRING *cn = NULL; int navas; /* find the last CN */ obj = OBJ_nid2obj( NID_commonName ); if ( !obj ) goto no_cn; /* should never happen */ xn = X509_get_subject_name(x); navas = X509_NAME_entry_count( xn ); for ( i=navas-1; i>=0; i-- ) { ne = X509_NAME_get_entry( xn, i ); if ( !OBJ_cmp( ne->object, obj )) { cn = X509_NAME_ENTRY_get_data( ne ); break; } } if( !cn ) { no_cn: Debug( LDAP_DEBUG_ANY, "TLS: unable to get common name from peer certificate.\n", 0, 0, 0 ); ret = LDAP_CONNECT_ERROR; if ( ld->ld_error ) { LDAP_FREE( ld->ld_error ); } ld->ld_error = LDAP_STRDUP( _("TLS: unable to get CN from peer certificate")); } else if ( cn->length == nlen && strncasecmp( name, (char *) cn->data, nlen ) == 0 ) { ret = LDAP_SUCCESS; } else if (( cn->data[0] == '*' ) && ( cn->data[1] == '.' )) { char *domain = strchr(name, '.'); if( domain ) { int dlen; dlen = nlen - (domain-name); /* Is this a wildcard match? */ if ((dlen == cn->length-1) && !strncasecmp(domain, (char *) &cn->data[1], dlen)) { ret = LDAP_SUCCESS; } } } if( ret == LDAP_LOCAL_ERROR ) { Debug( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match " "common name in certificate (%.*s).\n", name, cn->length, cn->data ); 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")); } } X509_free(x); return ret; } static int tlso_session_strength( tls_session *sess ) { tlso_session *s = (tlso_session *)sess; SSL_CIPHER *c; c = SSL_get_current_cipher(s); return SSL_CIPHER_get_bits(c, NULL); } /* * TLS support for LBER Sockbufs */ struct tls_data { tlso_session *session; Sockbuf_IO_Desc *sbiod; }; static int tlso_bio_create( BIO *b ) { b->init = 1; b->num = 0; b->ptr = NULL; b->flags = 0; return 1; } static int tlso_bio_destroy( BIO *b ) { if ( b == NULL ) return 0; b->ptr = NULL; /* sb_tls_remove() will free it */ b->init = 0; b->flags = 0; return 1; } static int tlso_bio_read( BIO *b, char *buf, int len ) { struct tls_data *p; int ret; if ( buf == NULL || len <= 0 ) return 0; p = (struct tls_data *)b->ptr; if ( p == NULL || p->sbiod == NULL ) { return 0; } ret = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len ); BIO_clear_retry_flags( b ); if ( ret < 0 ) { int err = sock_errno(); if ( err == EAGAIN || err == EWOULDBLOCK ) { BIO_set_retry_read( b ); } } return ret; } static int tlso_bio_write( BIO *b, const char *buf, int len ) { struct tls_data *p; int ret; if ( buf == NULL || len <= 0 ) return 0; p = (struct tls_data *)b->ptr; if ( p == NULL || p->sbiod == NULL ) { return 0; } ret = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len ); BIO_clear_retry_flags( b ); if ( ret < 0 ) { int err = sock_errno(); if ( err == EAGAIN || err == EWOULDBLOCK ) { BIO_set_retry_write( b ); } } return ret; } static long tlso_bio_ctrl( BIO *b, int cmd, long num, void *ptr ) { if ( cmd == BIO_CTRL_FLUSH ) { /* The OpenSSL library needs this */ return 1; } return 0; } static int tlso_bio_gets( BIO *b, char *buf, int len ) { return -1; } static int tlso_bio_puts( BIO *b, const char *str ) { return tlso_bio_write( b, str, strlen( str ) ); } static BIO_METHOD tlso_bio_method = { ( 100 | 0x400 ), /* it's a source/sink BIO */ "sockbuf glue", tlso_bio_write, tlso_bio_read, tlso_bio_puts, tlso_bio_gets, tlso_bio_ctrl, tlso_bio_create, tlso_bio_destroy }; static int tlso_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg ) { struct tls_data *p; BIO *bio; assert( sbiod != NULL ); p = LBER_MALLOC( sizeof( *p ) ); if ( p == NULL ) { return -1; } p->session = arg; p->sbiod = sbiod; bio = BIO_new( &tlso_bio_method ); bio->ptr = (void *)p; SSL_set_bio( p->session, bio, bio ); sbiod->sbiod_pvt = p; return 0; } static int tlso_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; SSL_free( p->session ); LBER_FREE( sbiod->sbiod_pvt ); sbiod->sbiod_pvt = NULL; return 0; } static int tlso_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; SSL_shutdown( p->session ); return 0; } static int tlso_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 ) { *((tlso_session **)arg) = p->session; return 1; } else if ( opt == LBER_SB_OPT_DATA_READY ) { if( SSL_pending( p->session ) > 0 ) { return 1; } } return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg ); } static ber_slen_t tlso_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 = SSL_read( p->session, (char *)buf, len ); #ifdef HAVE_WINSOCK errno = WSAGetLastError(); #endif err = SSL_get_error( p->session, ret ); if (err == SSL_ERROR_WANT_READ ) { 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 tlso_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 = SSL_write( p->session, (char *)buf, len ); #ifdef HAVE_WINSOCK errno = WSAGetLastError(); #endif err = SSL_get_error( p->session, ret ); if (err == SSL_ERROR_WANT_WRITE ) { sbiod->sbiod_sb->sb_trans_needs_write = 1; sock_errset(EWOULDBLOCK); } else { sbiod->sbiod_sb->sb_trans_needs_write = 0; } return ret; } static Sockbuf_IO tlso_sbio = { tlso_sb_setup, /* sbi_setup */ tlso_sb_remove, /* sbi_remove */ tlso_sb_ctrl, /* sbi_ctrl */ tlso_sb_read, /* sbi_read */ tlso_sb_write, /* sbi_write */ tlso_sb_close /* sbi_close */ }; /* Derived from openssl/apps/s_cb.c */ static void tlso_info_cb( const SSL *ssl, int where, int ret ) { int w; char *op; char *state = (char *) SSL_state_string_long( (SSL *)ssl ); w = where & ~SSL_ST_MASK; if ( w & SSL_ST_CONNECT ) { op = "SSL_connect"; } else if ( w & SSL_ST_ACCEPT ) { op = "SSL_accept"; } else { op = "undefined"; } #ifdef HAVE_EBCDIC if ( state ) { state = LDAP_STRDUP( state ); __etoa( state ); } #endif if ( where & SSL_CB_LOOP ) { Debug( LDAP_DEBUG_TRACE, "TLS trace: %s:%s\n", op, state, 0 ); } else if ( where & SSL_CB_ALERT ) { char *atype = (char *) SSL_alert_type_string_long( ret ); char *adesc = (char *) SSL_alert_desc_string_long( ret ); op = ( where & SSL_CB_READ ) ? "read" : "write"; #ifdef HAVE_EBCDIC if ( atype ) { atype = LDAP_STRDUP( atype ); __etoa( atype ); } if ( adesc ) { adesc = LDAP_STRDUP( adesc ); __etoa( adesc ); } #endif Debug( LDAP_DEBUG_TRACE, "TLS trace: SSL3 alert %s:%s:%s\n", op, atype, adesc ); #ifdef HAVE_EBCDIC if ( atype ) LDAP_FREE( atype ); if ( adesc ) LDAP_FREE( adesc ); #endif } else if ( where & SSL_CB_EXIT ) { if ( ret == 0 ) { Debug( LDAP_DEBUG_TRACE, "TLS trace: %s:failed in %s\n", op, state, 0 ); } else if ( ret < 0 ) { Debug( LDAP_DEBUG_TRACE, "TLS trace: %s:error in %s\n", op, state, 0 ); } } #ifdef HAVE_EBCDIC if ( state ) LDAP_FREE( state ); #endif } static int tlso_verify_cb( int ok, X509_STORE_CTX *ctx ) { X509 *cert; int errnum; int errdepth; X509_NAME *subject; X509_NAME *issuer; char *sname; char *iname; char *certerr = NULL; cert = X509_STORE_CTX_get_current_cert( ctx ); errnum = X509_STORE_CTX_get_error( ctx ); errdepth = X509_STORE_CTX_get_error_depth( ctx ); /* * X509_get_*_name return pointers to the internal copies of * those things requested. So do not free them. */ subject = X509_get_subject_name( cert ); issuer = X509_get_issuer_name( cert ); /* X509_NAME_oneline, if passed a NULL buf, allocate memomry */ sname = X509_NAME_oneline( subject, NULL, 0 ); iname = X509_NAME_oneline( issuer, NULL, 0 ); if ( !ok ) certerr = (char *)X509_verify_cert_error_string( errnum ); #ifdef HAVE_EBCDIC if ( sname ) __etoa( sname ); if ( iname ) __etoa( iname ); if ( certerr ) { certerr = LDAP_STRDUP( certerr ); __etoa( certerr ); } #endif Debug( LDAP_DEBUG_TRACE, "TLS certificate verification: depth: %d, err: %d, subject: %s,", errdepth, errnum, sname ? sname : "-unknown-" ); Debug( LDAP_DEBUG_TRACE, " issuer: %s\n", iname ? iname : "-unknown-", 0, 0 ); if ( !ok ) { Debug( LDAP_DEBUG_ANY, "TLS certificate verification: Error, %s\n", certerr, 0, 0 ); } if ( sname ) CRYPTO_free ( sname ); if ( iname ) CRYPTO_free ( iname ); #ifdef HAVE_EBCDIC if ( certerr ) LDAP_FREE( certerr ); #endif return ok; } static int tlso_verify_ok( int ok, X509_STORE_CTX *ctx ) { (void) tlso_verify_cb( ok, ctx ); return 1; } /* Inspired by ERR_print_errors in OpenSSL */ static void tlso_report_error( void ) { unsigned long l; char buf[200]; const char *file; int line; while ( ( l = ERR_get_error_line( &file, &line ) ) != 0 ) { ERR_error_string_n( l, buf, sizeof( buf ) ); #ifdef HAVE_EBCDIC if ( file ) { file = LDAP_STRDUP( file ); __etoa( (char *)file ); } __etoa( buf ); #endif Debug( LDAP_DEBUG_ANY, "TLS: %s %s:%d\n", buf, file, line ); #ifdef HAVE_EBCDIC if ( file ) LDAP_FREE( (void *)file ); #endif } } static RSA * tlso_tmp_rsa_cb( SSL *ssl, int is_export, int key_length ) { RSA *tmp_rsa; /* FIXME: Pregenerate the key on startup */ /* FIXME: Who frees the key? */ #if OPENSSL_VERSION_NUMBER >= 0x00908000 BIGNUM *bn = BN_new(); tmp_rsa = NULL; if ( bn ) { if ( BN_set_word( bn, RSA_F4 )) { tmp_rsa = RSA_new(); if ( tmp_rsa && !RSA_generate_key_ex( tmp_rsa, key_length, bn, NULL )) { RSA_free( tmp_rsa ); tmp_rsa = NULL; } } BN_free( bn ); } #else tmp_rsa = RSA_generate_key( key_length, RSA_F4, NULL, NULL ); #endif if ( !tmp_rsa ) { Debug( LDAP_DEBUG_ANY, "TLS: Failed to generate temporary %d-bit %s RSA key\n", key_length, is_export ? "export" : "domestic", 0 ); } return tmp_rsa; } static int tlso_seed_PRNG( const char *randfile ) { #ifndef URANDOM_DEVICE /* no /dev/urandom (or equiv) */ long total=0; char buffer[MAXPATHLEN]; if (randfile == NULL) { /* The seed file is $RANDFILE if defined, otherwise $HOME/.rnd. * If $HOME is not set or buffer too small to hold the pathname, * an error occurs. - From RAND_file_name() man page. * The fact is that when $HOME is NULL, .rnd is used. */ randfile = RAND_file_name( buffer, sizeof( buffer ) ); } else if (RAND_egd(randfile) > 0) { /* EGD socket */ return 0; } if (randfile == NULL) { Debug( LDAP_DEBUG_ANY, "TLS: Use configuration file or $RANDFILE to define seed PRNG\n", 0, 0, 0); return -1; } total = RAND_load_file(randfile, -1); if (RAND_status() == 0) { Debug( LDAP_DEBUG_ANY, "TLS: PRNG not been seeded with enough data\n", 0, 0, 0); return -1; } /* assume if there was enough bits to seed that it's okay * to write derived bits to the file */ RAND_write_file(randfile); #endif return 0; } struct dhinfo { int keylength; const char *pem; size_t size; }; /* From the OpenSSL 0.9.7 distro */ static const char tlso_dhpem512[] = "-----BEGIN DH PARAMETERS-----\n\ MEYCQQDaWDwW2YUiidDkr3VvTMqS3UvlM7gE+w/tlO+cikQD7VdGUNNpmdsp13Yn\n\ a6LT1BLiGPTdHghM9tgAPnxHdOgzAgEC\n\ -----END DH PARAMETERS-----\n"; static const char tlso_dhpem1024[] = "-----BEGIN DH PARAMETERS-----\n\ MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq\n\ /Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx\n\ /mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC\n\ -----END DH PARAMETERS-----\n"; static const char tlso_dhpem2048[] = "-----BEGIN DH PARAMETERS-----\n\ MIIBCAKCAQEA7ZKJNYJFVcs7+6J2WmkEYb8h86tT0s0h2v94GRFS8Q7B4lW9aG9o\n\ AFO5Imov5Jo0H2XMWTKKvbHbSe3fpxJmw/0hBHAY8H/W91hRGXKCeyKpNBgdL8sh\n\ z22SrkO2qCnHJ6PLAMXy5fsKpFmFor2tRfCzrfnggTXu2YOzzK7q62bmqVdmufEo\n\ pT8igNcLpvZxk5uBDvhakObMym9mX3rAEBoe8PwttggMYiiw7NuJKO4MqD1llGkW\n\ aVM8U2ATsCun1IKHrRxynkE1/MJ86VHeYYX8GZt2YA8z+GuzylIOKcMH6JAWzMwA\n\ Gbatw6QwizOhr9iMjZ0B26TE3X8LvW84wwIBAg==\n\ -----END DH PARAMETERS-----\n"; static const char tlso_dhpem4096[] = "-----BEGIN DH PARAMETERS-----\n\ MIICCAKCAgEA/urRnb6vkPYc/KEGXWnbCIOaKitq7ySIq9dTH7s+Ri59zs77zty7\n\ vfVlSe6VFTBWgYjD2XKUFmtqq6CqXMhVX5ElUDoYDpAyTH85xqNFLzFC7nKrff/H\n\ TFKNttp22cZE9V0IPpzedPfnQkE7aUdmF9JnDyv21Z/818O93u1B4r0szdnmEvEF\n\ bKuIxEHX+bp0ZR7RqE1AeifXGJX3d6tsd2PMAObxwwsv55RGkn50vHO4QxtTARr1\n\ rRUV5j3B3oPMgC7Offxx+98Xn45B1/G0Prp11anDsR1PGwtaCYipqsvMwQUSJtyE\n\ EOQWk+yFkeMe4vWv367eEi0Sd/wnC+TSXBE3pYvpYerJ8n1MceI5GQTdarJ77OW9\n\ bGTHmxRsLSCM1jpLdPja5jjb4siAa6EHc4qN9c/iFKS3PQPJEnX7pXKBRs5f7AF3\n\ W3RIGt+G9IVNZfXaS7Z/iCpgzgvKCs0VeqN38QsJGtC1aIkwOeyjPNy2G6jJ4yqH\n\ ovXYt/0mc00vCWeSNS1wren0pR2EiLxX0ypjjgsU1mk/Z3b/+zVf7fZSIB+nDLjb\n\ NPtUlJCVGnAeBK1J1nG3TQicqowOXoM6ISkdaXj5GPJdXHab2+S7cqhKGv5qC7rR\n\ jT6sx7RUr0CNTxzLI7muV2/a4tGmj0PSdXQdsZ7tw7gbXlaWT1+MM2MCAQI=\n\ -----END DH PARAMETERS-----\n"; static const struct dhinfo tlso_dhpem[] = { { 512, tlso_dhpem512, sizeof(tlso_dhpem512) }, { 1024, tlso_dhpem1024, sizeof(tlso_dhpem1024) }, { 2048, tlso_dhpem2048, sizeof(tlso_dhpem2048) }, { 4096, tlso_dhpem4096, sizeof(tlso_dhpem4096) }, { 0, NULL, 0 } }; static DH * tlso_tmp_dh_cb( SSL *ssl, int is_export, int key_length ) { struct dhplist *p = NULL; BIO *b = NULL; DH *dh = NULL; int i; /* Do we have params of this length already? */ LDAP_MUTEX_LOCK( &tlso_dh_mutex ); for ( p = tlso_dhparams; p; p=p->next ) { if ( p->keylength == key_length ) { LDAP_MUTEX_UNLOCK( &tlso_dh_mutex ); return p->param; } } /* No - check for hardcoded params */ for (i=0; tlso_dhpem[i].keylength; i++) { if ( tlso_dhpem[i].keylength == key_length ) { b = BIO_new_mem_buf( (char *)tlso_dhpem[i].pem, tlso_dhpem[i].size ); break; } } if ( b ) { dh = PEM_read_bio_DHparams( b, NULL, NULL, NULL ); BIO_free( b ); } /* Generating on the fly is expensive/slow... */ if ( !dh ) { dh = DH_generate_parameters( key_length, DH_GENERATOR_2, NULL, NULL ); } if ( dh ) { p = LDAP_MALLOC( sizeof(struct dhplist) ); if ( p != NULL ) { p->keylength = key_length; p->param = dh; p->next = tlso_dhparams; tlso_dhparams = p; } } LDAP_MUTEX_UNLOCK( &tlso_dh_mutex ); return dh; } tls_impl ldap_int_tls_impl = { "OpenSSL", tlso_init, tlso_destroy, tlso_ctx_new, tlso_ctx_ref, tlso_ctx_free, tlso_ctx_init, tlso_session_new, tlso_session_connect, tlso_session_accept, tlso_session_upflags, tlso_session_errmsg, tlso_session_my_dn, tlso_session_peer_dn, tlso_session_chkhost, tlso_session_strength, &tlso_sbio, #ifdef LDAP_R_COMPILE tlso_thr_init, #else NULL, #endif 0 }; #ifdef __APPLE__ static int tlso_use_cert_from_keychain( struct ldapoptions *lo ) { if ( lo == NULL || lo->ldo_tls_cert_ref == NULL ) { return 0; } CFDataRef certificateCFData = NULL; OSStatus status = SecKeychainItemExport( lo->ldo_tls_cert_ref, kSecFormatX509Cert, 0, NULL, &certificateCFData ); if ( status != noErr ) { Debug( LDAP_DEBUG_ANY, "%s: SecKeychainItemExport(certificateRef) failed: %d\n", __PRETTY_FUNCTION__, (int)status, 0 ); syslog( LOG_ERR, "%s: SecKeychainItemExport(certificateRef): %d", __PRETTY_FUNCTION__, (int)status, 0 ); cssmPerror( "SecKeychainItemExport(certificateRef)", status ); return 0; } /* Put the data into a C array so openssl can use it. */ int rc = 0; unsigned char *certificateData = NULL; int certificateDataLen = CFDataGetLength( certificateCFData ); if ( certificateDataLen ) { certificateData = LDAP_MALLOC( certificateDataLen * sizeof(*certificateData) ); if ( certificateData ) { CFDataGetBytes( certificateCFData, CFRangeMake(0, certificateDataLen), certificateData ); rc = SSL_CTX_use_certificate_ASN1( lo->ldo_tls_ctx, certificateDataLen, certificateData ); if ( !rc ) { Debug( LDAP_DEBUG_ANY, "TLS: could not use keychain certificate data in SSL_CTX_use_certificate_ASN1\n", 0,0,0); tlso_report_error(); } else { Debug( LDAP_DEBUG_ANY, "TLS: using keychain certificate data\n", 0, 0, 0); } LDAP_FREE( certificateData ); } } CFRelease( certificateCFData ); return rc; } static X509 *tslo_copy_cert_to_x509(SecCertificateRef inRef) { X509 *result = NULL; const unsigned char *buf = NULL; CFDataRef DERCertData = SecCertificateCopyData(inRef); CFIndex len = 0; if(DERCertData == NULL) return NULL; len = CFDataGetLength(DERCertData); buf = (const unsigned char *)CFDataGetBytePtr(DERCertData); result = d2i_X509(NULL, &buf, len); CFRelease(DERCertData); return result; } static EVP_PKEY *tslo_copy_key_to_evpkey(SecKeyRef inKey) { OSStatus ortn = 0; EVP_PKEY *result = NULL; SecItemImportExportKeyParameters params; CFDataRef intermediateData = NULL; BIO *intermediateBIO = NULL; char* cExportPass = ";aweCrugf99;uvPvh8229fliaYnx``1"; CFStringRef cfExportPass = NULL; cfExportPass = CFStringCreateWithCString(NULL, cExportPass, kCFStringEncodingUTF8); if (!cfExportPass) { syslog(LOG_DEBUG, "TLS: Unable to create CFString private key passphrase."); return NULL; } memset(¶ms, 0, sizeof(params)); params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; params.flags = 0; params.passphrase = cfExportPass; ortn = SecItemExport(inKey, kSecFormatWrappedPKCS8, 0, ¶ms, (CFDataRef*)&intermediateData); if(ortn != 0) { syslog( LOG_DEBUG, "TLS: Unable to export private key."); CFRelease(cfExportPass); return NULL; } intermediateBIO = BIO_new_mem_buf((void *)CFDataGetBytePtr(intermediateData), CFDataGetLength(intermediateData)); if(intermediateBIO == NULL) { syslog( LOG_DEBUG, "TLS: Unable to create BIO buffer for private key data."); CFRelease(cfExportPass); CFRelease(intermediateData); return NULL; } result = d2i_PKCS8PrivateKey_bio(intermediateBIO, NULL, NULL, cExportPass); if(result == NULL) { char errbuf[512]; ERR_error_string(ERR_get_error(), errbuf); syslog( LOG_DEBUG, "TLS: Unable to use passphrase for private key: %s.", errbuf); } BIO_free_all(intermediateBIO); CFRelease(intermediateData); CFRelease(cfExportPass); return result; } static OSStatus tslo_create_ca_chain(SSL_CTX* ctx, SecCertificateRef sslCertRef) { OSStatus status = noErr; CFMutableArrayRef certChain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); SecPolicyRef policy = SecPolicyCreateBasicX509(); SecTrustRef trustRef = NULL; SecTrustResultType trustResult = NULL; X509* x509_cert = NULL; SSL_CTX_set_mode(ctx, SSL_MODE_NO_AUTO_CHAIN); do { if (sslCertRef) { CFArrayAppendValue(certChain, sslCertRef); status = SecTrustCreateWithCertificates(certChain, policy, &trustRef); if (status == noErr) { status = SecTrustEvaluate(trustRef, &trustResult); if (status == noErr) { CFIndex count = SecTrustGetCertificateCount(trustRef); if (count) { int i; for (i = 0; i < count; i++) { SecCertificateRef certInChainRef = SecTrustGetCertificateAtIndex(trustRef, i); if (certInChainRef) { x509_cert = tslo_copy_cert_to_x509(certInChainRef); if(SSL_CTX_add_client_CA(ctx, x509_cert) < 1) { syslog( LOG_DEBUG, "[%s] TLS: could not load client CA name using SSL_CTX_add_client_CA ", __func__); tlso_report_error(); status = -1; X509_free(x509_cert); break; } if(SSL_CTX_add_extra_chain_cert(ctx, x509_cert) < 1) { syslog( LOG_DEBUG, "[%s] TLS: could not load client CA name using SSL_CTX_add_extra_chain_cert ", __func__); tlso_report_error(); status = -1; X509_free(x509_cert); break; } } } } else { syslog( LOG_DEBUG, "[%s] SecTrustGetCertificateCount - No Certificates returned", __func__); status = -1; break; } } else { syslog( LOG_DEBUG, "[%s] SecTrustEvaluate (ssl) err (%d) trustResult(%d)", __func__, status, trustResult); status = -1; break; } } else { syslog( LOG_DEBUG, "[%s] SecTrustCreateWithCertificates (ssl) err (%d)", __func__, status); status = -1; break; } } } while (0); if (certChain) CFRelease(certChain); if (policy) CFRelease(policy); if (trustRef) CFRelease(trustRef); return status; } #endif /*__APPLE__*/ #endif /* HAVE_OPENSSL */