On the multiple_ssl_impls branch: Merge trunk up to r2024.
Revs 2022 and 2023 were already merged manually, so this revision only records
the merged revisions.
git-svn-id: https://svn.apache.org/repos/asf/serf/branches/multiple_ssl_impls@1699562 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/SConstruct b/SConstruct
index 1e6dab4..111a666 100644
--- a/SConstruct
+++ b/SConstruct
@@ -18,6 +18,7 @@
import sys
import os
import re
+import platform
HEADER_FILES = ['serf.h',
'serf_bucket_types.h',
@@ -41,10 +42,6 @@
"Path to apu-1-config, or to APR's install area",
'/usr',
PathVariable.PathAccept),
- PathVariable('OPENSSL',
- "Path to OpenSSL's install area",
- '/usr',
- PathVariable.PathIsDir),
PathVariable('ZLIB',
"Path to zlib's install area",
'/usr',
@@ -58,6 +55,19 @@
False),
)
+if sys.platform == 'darwin':
+ opts.AddVariables(
+ PathVariable('OPENSSL',
+ "Path to OpenSSL's install area",
+ None,
+ None))
+else:
+ opts.AddVariables(
+ PathVariable('OPENSSL',
+ "Path to OpenSSL's install area",
+ '/usr',
+ PathVariable.PathIsDir))
+
env = Environment(variables=opts,
tools=('default', 'textfile',),
CPPPATH=['.', ],
@@ -95,6 +105,11 @@
gssapi = krb5_config
env['GSSAPI'] = krb5_config
+openssl = env.get('OPENSSL', None)
+if openssl and os.path.isdir(str(openssl)):
+ env.Append(CPPPATH='$OPENSSL/include')
+ env.Append(LIBPATH='$OPENSSL/lib')
+ env.Append(CFLAGS='-DSERF_HAVE_OPENSSL')
debug = env.get('DEBUG', None)
Help(opts.GenerateHelpText(env))
@@ -134,6 +149,14 @@
linkflags.append('-Wl,-compatibility_version,%d' % (MINOR+1,))
linkflags.append('-Wl,-current_version,%d.%d' % (MINOR+1, PATCH,))
+ # enable macosxssl_buckets only on Mac OS X 10.7+
+ macosxssl = False
+ ver, _, _ = platform.mac_ver()
+ ver = float('.'.join(ver.split('.')[:2]))
+ if ver >= 10.7:
+ macosxssl = True
+ env.Append(CFLAGS='-DSERF_HAVE_MACOSXSSL')
+
if sys.platform == 'win32':
### we should create serf.def for Windows DLLs and add it into the link
### step somehow.
@@ -168,6 +191,11 @@
### works for Mac OS. probably needs to change
libs = ['ssl', 'crypto', 'z', ]
+ if macosxssl:
+ env.Append(FRAMEWORKS=['Security', 'SecurityInterface', 'CoreFoundation',
+ 'AppKit'])
+ libs.append('objc')
+
if sys.platform == 'sunos5':
libs.append('m')
@@ -180,6 +208,8 @@
# PLAN THE BUILD
SOURCES = Glob('*.c') + Glob('buckets/*.c') + Glob('auth/*.c')
+if macosxssl:
+ SOURCES += ['buckets/macosxssl_helper.m']
lib_static = env.StaticLibrary(LIBNAMESTATIC, SOURCES)
lib_shared = env.SharedLibrary(LIBNAME, SOURCES)
diff --git a/buckets/bucket_private.h b/buckets/bucket_private.h
new file mode 100644
index 0000000..b77608d
--- /dev/null
+++ b/buckets/bucket_private.h
@@ -0,0 +1,273 @@
+/* Copyright 2013 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BUCKET_PRIVATE_H_
+#define _BUCKET_PRIVATE_H_
+
+typedef struct serf_ssl_bucket_type_t serf_ssl_bucket_type_t;
+
+struct serf_ssl_bucket_type_t {
+ void * (*decrypt_create)(serf_bucket_t *bucket,
+ serf_bucket_t *stream,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator);
+
+ void * (*decrypt_context_get)(serf_bucket_t *bucket);
+
+ void * (*encrypt_create)(serf_bucket_t *bucket,
+ serf_bucket_t *stream,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator);
+
+ void * (*encrypt_context_get)(serf_bucket_t *bucket);
+
+ /**
+ * Allow SNI indicators to be sent to the server.
+ */
+ apr_status_t (*set_hostname)(void *impl_ctx, const char *hostname);
+
+ void (*client_cert_provider_set)(void *impl_ctx,
+ serf_ssl_need_client_cert_t callback,
+ void *data,
+ void *cache_pool);
+
+ void (*identity_provider_set)(void *impl_ctx,
+ serf_ssl_need_identity_t callback,
+ void *data,
+ void *cache_pool);
+
+ void (*client_cert_password_set)(void *impl_ctx,
+ serf_ssl_need_cert_password_t callback,
+ void *data,
+ void *cache_pool);
+ /**
+ * Set a callback to override the default SSL server certificate validation
+ * algorithm.
+ */
+ void (*server_cert_callback_set)(void *impl_ctx,
+ serf_ssl_need_server_cert_t callback,
+ void *data);
+ /**
+ * Set callbacks to override the default SSL server certificate validation
+ * algorithm for the current certificate or the entire certificate chain.
+ */
+ void (*server_cert_chain_callback_set)(void *impl_ctx,
+ serf_ssl_need_server_cert_t cert_callback,
+ serf_ssl_server_cert_chain_cb_t cert_chain_callback,
+ void *data);
+ /**
+ * Use the default root CA certificates as included with the OpenSSL library.
+ * TODO: fix comment!
+ */
+ apr_status_t (*use_default_certificates)(void *impl_ctx);
+
+ /**
+ * Load a CA certificate file from a path @a file_path. If the file was
+ * loaded and parsed correctly, a certificate @a cert will be created and
+ * returned.
+ * This certificate object will be alloced in @a pool.
+ */
+ apr_status_t (*load_CA_cert_from_file)(serf_ssl_certificate_t **cert,
+ const char *file_path,
+ apr_pool_t *pool);
+
+ apr_status_t (*load_identity_from_file)(void *impl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path,
+ apr_pool_t *pool);
+
+ /**
+ * Adds the certificate @a cert to the list of trusted certificates in
+ * @a ssl_ctx that will be used for verification.
+ * See also @a serf_ssl_load_cert_file.
+ */
+ apr_status_t (*trust_cert)(void *impl_ctx,
+ serf_ssl_certificate_t *cert);
+
+ /**
+ * Extract the fields of the issuer in a table with keys (E, CN, OU, O, L,
+ * ST and C). The returned table will be allocated in @a pool.
+ */
+ apr_hash_t * (*cert_issuer)(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool);
+
+ /**
+ * Extract the fields of the subject in a table with keys (E, CN, OU, O, L,
+ * ST and C). The returned table will be allocated in @a pool.
+ */
+ apr_hash_t * (*cert_subject)(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool);
+
+ /**
+ * Extract the fields of the certificate in a table with keys (sha1,
+ * notBefore, notAfter, array of subjectAltName's). The returned table will
+ * be allocated in @a pool.
+ */
+ apr_hash_t * (*cert_certificate)(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool);
+
+ /**
+ * Export a certificate to base64-encoded, zero-terminated string.
+ * The returned string is allocated in @a pool. Returns NULL on failure.
+ */
+ const char * (*cert_export)(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool);
+ /**
+ * Enable or disable SSL compression on a SSL session.
+ * @a enabled = 1 to enable compression, 0 to disable compression.
+ * Default = disabled.
+ */
+ apr_status_t (*use_compression)(void *impl_ctx,
+ int enabled);
+
+ /**
+ * Exports @a session to continous memory block.
+ */
+ apr_status_t (*session_export)(void **data,
+ apr_size_t *len,
+ const serf_ssl_session_t *session,
+ apr_pool_t *pool);
+
+ /**
+ * Restores previously saved session from continuous memory block @a data
+ * with @a len length.
+ */
+ apr_status_t (*session_import)(const serf_ssl_session_t **session,
+ void *data,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+ /**
+ * TODO: comment
+ */
+ void (*new_session_callback_set)(void *impl_ctx,
+ serf_ssl_new_session_t new_session_cb,
+ void *baton);
+
+ /* Configure @a ssl_ctx to attempt resume exisiting @a ssl_session. */
+ apr_status_t (*resume_session)(void *impl_ctx,
+ const serf_ssl_session_t *ssl_session,
+ apr_pool_t *pool);
+};
+
+/* Implementation independent certificate object. */
+struct serf_ssl_certificate_t {
+ /** bucket implementation that can parse this certificate. */
+ const serf_ssl_bucket_type_t *type;
+
+ /** implementation specific certificate data */
+ /* Note: non-const, as required by OpenSSL. */
+ void *impl_cert;
+
+ /** Depth in the chain where an error was found. */
+ int depth_of_error;
+};
+
+/* Loads a certificate from a memory buffer.
+ Defined here so it can be used by the test suite. */
+apr_status_t
+load_CA_cert_from_buffer(serf_ssl_certificate_t **cert,
+ const char *buf,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+/* Creates a serf_ssl_certificate_t object, caller takes ownership. */
+serf_ssl_certificate_t *
+serf__create_certificate(serf_bucket_alloc_t *allocator,
+ const serf_ssl_bucket_type_t *type,
+ void *impl_cert,
+ int depth);
+
+/* Implementation independent identity object. An identity is a combination
+ of a certificate and a private key, typically stored in a .p12 file. */
+struct serf_ssl_identity_t {
+ /** bucket implementation that can parse this identity. */
+ const serf_ssl_bucket_type_t *type;
+
+ /** implementation specific client certificate */
+ void *impl_cert;
+
+ /** implementation specific private key */
+ void *impl_pkey;
+};
+
+/* Creates a serf_ssl_identity_t object, caller takes ownership. */
+serf_ssl_identity_t *
+serf__create_identity(const serf_ssl_bucket_type_t *type,
+ void *impl_cert,
+ void *impl_pkey,
+ apr_pool_t *pool);
+
+void *serf__ssl_get_impl_context(serf_ssl_context_t *ssl_ctx);
+
+/* Implementation independent serialized SSL session object */
+struct serf_ssl_session_t {
+#if 0
+ /* Not needed as long as each API takes a serf_ssl_context_t. */
+ /** bucket implementation that can parse this session object. */
+ const serf_ssl_bucket_type_t *type;
+#endif
+
+ /** implementation specific serialized SSL session object. */
+ void *impl_session_obj;
+};
+
+
+/* macosxssl_bucket internal functions */
+#ifdef SERF_HAVE_MACOSXSSL
+
+#include <Security/SecCertificate.h>
+
+/* macosxssl_bucket private certificate structure. Wrapper around the
+ SecCertificateRef ptr, with content the cached parsed information from the
+ certificate. */
+typedef struct macosxssl_certificate_t {
+ SecCertificateRef certref;
+
+ apr_hash_t *content;
+} macosxssl_certificate_t;
+
+apr_status_t
+serf__macosxssl_read_X509_DER_DN(apr_hash_t **o, CFDataRef ptr,
+ apr_pool_t *pool);
+
+apr_status_t
+serf__macosxssl_read_X509_DER_certificate(apr_hash_t **o,
+ const macosxssl_certificate_t *cert,
+ apr_pool_t *pool);
+
+#endif
+
+/* ==================================================================== */
+
+#ifdef SERF_HAVE_OPENSSL
+
+extern const serf_ssl_bucket_type_t serf_ssl_bucket_type_openssl;
+
+#endif /* SERF_HAVE_OPENSSL */
+
+/* ==================================================================== */
+
+#if SERF_HAVE_MACOSXSSL
+
+extern const serf_ssl_bucket_type_t serf_ssl_bucket_type_macosxssl;
+
+#endif /* SERF_HAVE_MACOSXSSL */
+
+/* ==================================================================== */
+
+
+
+#endif
\ No newline at end of file
diff --git a/buckets/buckets.c b/buckets/buckets.c
index a64e9bd..28d64ba 100644
--- a/buckets/buckets.c
+++ b/buckets/buckets.c
@@ -14,10 +14,77 @@
*/
#include <apr_pools.h>
+#include <apr_version.h>
+#include <apr_atomic.h>
#include "serf.h"
#include "serf_bucket_util.h"
#include "serf_private.h"
+#include "serf_bucket_types.h"
+
+/* ==================================================================== */
+
+/* Code to select specific implementations for buckets (currently only
+ for SSL buckets).
+ */
+
+static apr_uint32_t available_bucket_impls;
+static apr_uint32_t bucket_impls;
+static apr_uint32_t have_init_bucket_impls = 0;
+
+/* Initializes the list of chosen bucket implementation libraries with those
+ included at compile time. */
+static void init_bucket_impls()
+{
+ apr_uint32_t val;
+#if APR_VERSION_AT_LEAST(1,0,0)
+ val = apr_atomic_xchg32(&have_init_bucket_impls, 1);
+#else
+ val = apr_atomic_cas(&have_init_bucket_impls, 1, 0);
+#endif
+
+ if (!val)
+ {
+ available_bucket_impls = 0;
+#ifdef SERF_HAVE_OPENSSL
+ available_bucket_impls |= SERF_IMPL_SSL_OPENSSL;
+#endif
+#ifdef SERF_HAVE_MACOSXSSL
+ available_bucket_impls |= SERF_IMPL_SSL_MACOSXSSL;
+#endif
+
+ bucket_impls = available_bucket_impls;
+ }
+}
+
+apr_uint32_t serf_config_enable_bucket_impls(apr_uint32_t selected)
+{
+ init_bucket_impls();
+
+ bucket_impls |= selected;
+ bucket_impls &= available_bucket_impls;
+
+ return bucket_impls;
+}
+
+apr_uint32_t serf_config_disable_bucket_impls(apr_uint32_t selected)
+{
+ init_bucket_impls();
+
+ bucket_impls &= ~selected;
+ bucket_impls &= available_bucket_impls;
+
+ return bucket_impls;
+}
+
+apr_uint32_t serf_config_get_bucket_impls(void)
+{
+ init_bucket_impls();
+
+ return bucket_impls;
+}
+
+/* ==================================================================== */
serf_bucket_t *serf_bucket_create(
const serf_bucket_type_t *type,
diff --git a/buckets/macosxssl_buckets.c b/buckets/macosxssl_buckets.c
new file mode 100644
index 0000000..96be161
--- /dev/null
+++ b/buckets/macosxssl_buckets.c
@@ -0,0 +1,2471 @@
+/* Copyright 2013 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This code implements the ssl bucket API using the services provided by Apple
+ on Mac OS X 10.7+:
+ - Secure Transport
+ - Keychain Services
+ - Certificate, Key and Trust Services.
+
+ Reference documentation can be found on http://developer.apple.com .
+
+ Source code can be found on http://www.opensource.apple.com . Search for the
+ Security-xxxxxx package, where xxxxxx is a version number.
+
+ Note: unfortunately, the reference documentation seems to be a specification
+ more than a correct representation of the actual implementation. So
+ analysis of the source code of the services is needed to understand its exact
+ behavior.
+
+ Furthermore, the implementation of the services has some bugs that needed
+ workarounds. This page is very helpful in identifying these bugs:
+ https://github.com/lorentey/LKSecurity/blob/master/Framework%20Bugs.markdown
+
+
+ A macosxssl_bucket implements following API's
+ - a serf_bucket_t for encryption
+ - a serf_bucket_t for decryption
+ - a serf_ssl_bucket_type_t for certificate handling, general SSL/TLS stuff.
+ - extra serf_macosxssl_ functions to integrate with Keychain.
+ */
+
+#ifdef SERF_HAVE_MACOSXSSL
+
+#include "serf.h"
+#include "serf_private.h"
+#include "serf_bucket_util.h"
+#include "bucket_private.h"
+
+#include <apr_strings.h>
+#include <apr_base64.h>
+#include <apr_file_io.h>
+
+#include <Security/SecureTransport.h>
+#include <Security/SecPolicy.h>
+#include <Security/SecImportExport.h>
+#include <Security/SecIdentity.h>
+#include <Security/SecItem.h>
+#include <objc/runtime.h>
+#include <objc/message.h>
+
+/* The minimum amount of data we try to read and decrypt in one pass. */
+#define READ_BUFSIZE 8096
+
+typedef struct macosxssl_ssl_stream_t {
+ /* For an encrypt stream: data encrypted & not yet written to the network.
+ For a decrypt stream: data decrypted & not yet read by the application.*/
+ serf_bucket_t *pending;
+
+ /* For an encrypt stream: the outgoing data provided by the application.
+ For a decrypt stream: encrypted data read from the network. */
+ serf_bucket_t *stream;
+} macosxssl_ssl_stream_t;
+
+
+/* States for the different stages in the lifecyle of an SSL session. */
+typedef enum {
+ SERF_MACOSXSSL_INIT, /* no SSL handshake yet */
+ SERF_MACOSXSSL_HANDSHAKE, /* SSL handshake in progress */
+ SERF_MACOSXSSL_CONNECTED, /* SSL handshake successfully finished */
+ SERF_MACOSXSSL_CLOSING, /* SSL session closing */
+} macosxssl_session_state_t;
+
+typedef struct macosxssl_context_t {
+ /* How many open buckets refer to this context. */
+ int refcount;
+
+ /* Pool that stays alive during the whole ssl session */
+ apr_pool_t *pool;
+
+ /* Pool that is alive only during the hanshake phase. */
+ apr_pool_t *handshake_pool;
+
+ serf_bucket_alloc_t *allocator;
+
+ SSLContextRef st_ctxr;
+
+ /* Temporary keychain created when importing a client certificate. */
+ SecKeychainRef tempKeyChainRef;
+ char *keychain_temp_file;
+
+ /* Trust object created during validation of the server certificate. */
+ SecTrustRef trust;
+
+ /* stream of (to be) encrypted data, outgoing to the network. */
+ macosxssl_ssl_stream_t encrypt;
+
+ /* stream of (to be) decrypted data, read from the network. */
+ macosxssl_ssl_stream_t decrypt;
+
+ macosxssl_session_state_t state;
+
+ /* name of the peer, used with TLS's Server Name Indication extension. */
+ char *hostname;
+
+ /* Client cert callbacks */
+ serf_ssl_need_client_cert_t client_cert_callback;
+ serf_ssl_need_identity_t identity_callback;
+ void *identity_userdata;
+#if 0
+ apr_pool_t *client_cert_cache_pool;
+ const char *cert_file_success;
+#endif
+
+ /* Client cert PW callbacks */
+ serf_ssl_need_cert_password_t identity_pw_callback;
+ void *identity_pw_userdata;
+#if 0
+ apr_pool_t *cert_pw_cache_pool;
+ const char *cert_pw_success;
+#endif
+
+ /* Server cert callbacks */
+ serf_ssl_need_server_cert_t server_cert_callback;
+ serf_ssl_server_cert_chain_cb_t server_cert_chain_callback;
+ void *server_cert_userdata;
+
+ /* Flag, if set use the root certificates stored in the System keychain
+ when evaluating server certificates. */
+ int use_system_roots;
+
+ /* cache of the trusted certificates, added via serf_ssl_trust_cert(). */
+ apr_array_header_t *anchor_certs;
+ CFArrayRef anchor_certrefs;
+
+ /* Flag set when an asynchronous evaluation of the server certificate chain
+ is ongoing. */
+ int evaluate_in_progress;
+
+ /* Result of the evaluation of the server certificate chain. */
+ SecTrustResultType result;
+} macosxssl_context_t;
+
+/* Some forward declarations */
+static apr_status_t
+serf__macosxssl_load_identity_from_file(void *impl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path,
+ apr_pool_t *pool);
+
+
+/* Copies the unicode string from a CFStringRef to a new buffer allocated
+ from pool. */
+static const char *
+CFStringToChar(CFStringRef str, apr_pool_t *pool)
+{
+ const char *ptr = CFStringGetCStringPtr(str, kCFStringEncodingMacRoman);
+
+ if (ptr == NULL) {
+ const int strlen = CFStringGetLength(str) * 2;
+ char *buf = apr_pcalloc(pool, strlen);
+ if (CFStringGetCString(str, buf, strlen, kCFStringEncodingMacRoman))
+ return buf;
+ } else {
+ return apr_pstrdup(pool, ptr);
+ }
+
+ return NULL;
+}
+
+static apr_status_t
+translate_macosxssl_status(OSStatus osstatus)
+{
+ apr_status_t status;
+
+ switch (osstatus)
+ {
+ case noErr:
+ return APR_SUCCESS;
+ case errSSLWouldBlock:
+ return APR_EAGAIN;
+ case errSSLClosedGraceful:
+ /* Server sent a */
+ status = APR_EOF;
+ break;
+ case errSSLClosedAbort:
+ status = APR_ECONNABORTED;
+ break;
+ default:
+ status = APR_EGENERAL;
+ }
+
+#if SSL_VERBOSE
+ {
+ apr_pool_t *temppool;
+ CFStringRef errref = SecCopyErrorMessageString(osstatus, NULL);
+ apr_pool_create(&temppool, NULL);
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "Unknown Secure Transport error: %d,%s.\n",
+ osstatus, CFStringToChar(errref, temppool));
+ apr_pool_destroy(temppool);
+ }
+#endif
+
+ return status;
+}
+
+static apr_status_t cfrelease_ref(void *data)
+{
+ CFTypeRef tr = data;
+
+ if (tr)
+ CFRelease(tr);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t cfrelease_trust(void *data)
+{
+ macosxssl_context_t *ssl_ctx = data;
+
+ if (ssl_ctx->trust)
+ CFRelease(ssl_ctx->trust);
+ ssl_ctx->trust = NULL;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Note for both read and write callback functions, from SecureTransport.h:
+ * "Data's memory is allocated by caller; on entry to these two functions
+ * the *length argument indicates both the size of the available data and the
+ * requested byte count. Number of bytes actually transferred is returned in
+ * *length."
+ **/
+
+/** Secure Transport callback function.
+ Reads encrypted data from the network. **/
+static OSStatus
+macosxssl_read_cb(SSLConnectionRef connection,
+ void *data,
+ size_t *dataLength)
+{
+ const macosxssl_context_t *ssl_ctx = connection;
+ apr_status_t status = 0;
+ const char *buf;
+ char *outbuf = data;
+ size_t requested = *dataLength, buflen = 0;
+
+ serf__log(SSL_VERBOSE, __FILE__, "macosxssl_read_cb called for "
+ "%d bytes.\n", requested);
+
+ *dataLength = 0;
+ while (!status && requested) {
+ status = serf_bucket_read(ssl_ctx->decrypt.stream, requested,
+ &buf, &buflen);
+
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ serf__log(SSL_VERBOSE, __FILE__, "Returned status %d.\n", status);
+ return -1;
+ }
+
+ serf__log(SSL_VERBOSE, __FILE__, "Read %d bytes with status %d.\n",
+ buflen, status);
+
+ if (buflen) {
+ /* Copy the data in the buffer provided by the caller. */
+ memcpy(outbuf, buf, buflen);
+ outbuf += buflen;
+ requested -= buflen;
+ (*dataLength) += buflen;
+ }
+ }
+
+ if (APR_STATUS_IS_EAGAIN(status))
+ return errSSLWouldBlock;
+
+ if (!status)
+ return noErr;
+
+ /* TODO: map apr status to Mac OS X error codes(??) */
+ return -1;
+}
+
+/** Secure Transport callback function.
+ Writes encrypted data to the network. **/
+static OSStatus
+macosxssl_write_cb(SSLConnectionRef connection,
+ const void *data,
+ size_t *dataLength)
+{
+ serf_bucket_t *tmp;
+ const macosxssl_context_t *ctx = connection;
+
+ serf__log(SSL_VERBOSE, __FILE__, "macosxssl_write_cb called for "
+ "%d bytes.\n", *dataLength);
+
+ tmp = serf_bucket_simple_copy_create(data, *dataLength,
+ ctx->encrypt.pending->allocator);
+
+ serf_bucket_aggregate_append(ctx->encrypt.pending, tmp);
+
+ return noErr;
+}
+
+#pragma mark VALIDATE SERVER CERTIFICATES
+#pragma mark -
+
+/* Creates a macosxssl_certificate_t allocated on pool. */
+static apr_status_t
+create_macosxssl_certificate(macosxssl_certificate_t **out_macosxssl_cert,
+ SecCertificateRef certref,
+ int parse_content,
+ apr_pool_t *pool)
+{
+ macosxssl_certificate_t *macosxssl_cert;
+ apr_status_t status = APR_SUCCESS;
+
+ macosxssl_cert = apr_pcalloc(pool, sizeof(macosxssl_certificate_t));
+ macosxssl_cert->certref = certref;
+
+ if (parse_content)
+ status = serf__macosxssl_read_X509_DER_certificate(
+ &macosxssl_cert->content,
+ macosxssl_cert,
+ pool);
+ *out_macosxssl_cert = macosxssl_cert;
+
+ return status;
+}
+
+/* Creates a serf_ssl_certificate_t at depth allocated on pool. */
+static serf_ssl_certificate_t *
+create_ssl_certificate(SecCertificateRef certref,
+ int depth,
+ apr_pool_t *pool)
+{
+ macosxssl_certificate_t *macosxssl_cert;
+ serf_bucket_alloc_t *allocator;
+
+ /* Since we're not asking to parse the content we can ignore the status. */
+ (void) create_macosxssl_certificate(&macosxssl_cert, certref, 0, pool);
+
+ allocator = serf_bucket_allocator_create(pool, NULL, NULL);
+ return serf__create_certificate(allocator,
+ &serf_ssl_bucket_type_macosxssl,
+ macosxssl_cert,
+ depth);
+}
+
+/* Read the contents of a file in memory in a CFDataRef buffer. */
+static apr_status_t
+load_data_from_file(const char *file_path, CFDataRef *databuf, apr_pool_t *pool)
+{
+ apr_file_t *fp;
+ apr_finfo_t file_info;
+ apr_size_t len;
+ char *buf;
+ apr_status_t status;
+
+ status = apr_file_open(&fp, file_path,
+ APR_FOPEN_READ | APR_FOPEN_BINARY,
+ APR_FPROT_OS_DEFAULT, pool);
+ if (status)
+ return status;
+
+ /* Read the file in memory */
+ apr_file_info_get(&file_info, APR_FINFO_SIZE, fp);
+ buf = apr_palloc(pool, file_info.size);
+
+ status = apr_file_read_full(fp, buf, file_info.size, &len);
+ if (status)
+ return status;
+
+ *databuf = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
+ (unsigned char *)buf,
+ file_info.size,
+ kCFAllocatorNull);
+
+ apr_file_close(fp);
+
+ return APR_SUCCESS;
+}
+
+/* Use Keychain Services to extract one or multiple SecCertificateRef's from
+ a data buffer.
+ */
+static apr_status_t
+load_certificate_from_databuf(CFDataRef databuf,
+ CFArrayRef *items,
+ apr_pool_t *pool)
+{
+ SecExternalItemType itemType;
+ OSStatus osstatus;
+ apr_status_t status = APR_SUCCESS;
+
+ osstatus = SecItemImport(databuf, NULL,
+ kSecFormatUnknown,
+ &itemType,
+ 0, /* SecItemImportExportFlags */
+ NULL, /* SecItemImportExportKeyParameters */
+ NULL, /* SecKeychainRef */
+ items);
+ if (osstatus != errSecSuccess)
+ {
+ /* TODO: should be handled in translate_... */
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ }
+
+ return status;
+}
+
+/* Logs the issuer and subject of cert. */
+static void
+log_certificate(macosxssl_certificate_t *cert, const char *msg)
+{
+#if SSL_VERBOSE
+ apr_hash_t *subject, *issuer;
+ apr_pool_t *tmppool;
+
+ apr_pool_create(&tmppool, NULL);
+ if (!cert->content) {
+ apr_status_t status;
+ status = serf__macosxssl_read_X509_DER_certificate(&cert->content,
+ cert,
+ tmppool);
+ if (status)
+ goto cleanup;
+ }
+
+ subject = (apr_hash_t *)apr_hash_get(cert->content,
+ "subject", APR_HASH_KEY_STRING);
+
+ serf__log(SSL_VERBOSE, __FILE__, msg);
+ serf__log(SSL_VERBOSE, __FILE__, "Subject:\n");
+ serf__log(SSL_VERBOSE, __FILE__, " CN:%s,",
+ apr_hash_get(subject, "CN", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " OU:%s,",
+ apr_hash_get(subject, "OU", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " O:%s,",
+ apr_hash_get(subject, "O", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " L:%s,",
+ apr_hash_get(subject, "L", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " ST:%s,",
+ apr_hash_get(subject, "ST", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " C:%s,",
+ apr_hash_get(subject, "C", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " E:%s\n",
+ apr_hash_get(subject, "E", APR_HASH_KEY_STRING));
+
+ issuer = (apr_hash_t *)apr_hash_get(cert->content,
+ "issuer", APR_HASH_KEY_STRING);
+
+ serf__log(SSL_VERBOSE, __FILE__, "Issuer:\n");
+ serf__log(SSL_VERBOSE, __FILE__, " CN:%s,",
+ apr_hash_get(issuer, "CN", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " OU:%s,",
+ apr_hash_get(issuer, "OU", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " O:%s,",
+ apr_hash_get(issuer, "O", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " L:%s,",
+ apr_hash_get(issuer, "L", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " ST:%s,",
+ apr_hash_get(issuer, "ST", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " C:%s,",
+ apr_hash_get(issuer, "C", APR_HASH_KEY_STRING));
+ serf__log_nopref(SSL_VERBOSE, " E:%s\n",
+ apr_hash_get(issuer, "E", APR_HASH_KEY_STRING));
+
+cleanup:
+ apr_pool_destroy(tmppool);
+#endif
+}
+
+/* Finds the issuer certificate of cert in the provided list of
+ SecCertificateRef's. *outcert is allocated in pool. */
+static apr_status_t
+find_issuer_cert_in_array(serf_ssl_certificate_t **outcert,
+ macosxssl_certificate_t *cert,
+ CFArrayRef certref_list,
+ apr_pool_t *pool)
+{
+ CFDataRef issuer;
+ apr_pool_t *tmppool;
+ serf_bucket_alloc_t *tmpalloc;
+ int i;
+ apr_status_t status;
+
+ apr_pool_create(&tmppool, pool);
+ tmpalloc = serf_bucket_allocator_create(tmppool, NULL, NULL);
+
+ /* Get the issuer DER encoded data buffer of the provided certificate. */
+ log_certificate(cert, "Search for issuer of this cert:\n");
+ issuer = apr_hash_get(cert->content, "_issuer_der", APR_HASH_KEY_STRING);
+
+ /* Get the subject DER encoded data buffer of each cert in the list and
+ compare it with the issuer data buffer. */
+ for (i = 0; i < CFArrayGetCount(certref_list); i++)
+ {
+ macosxssl_certificate_t *list_cert;
+ CFDataRef subject;
+ SecCertificateRef certref;
+
+ certref = (SecCertificateRef)CFArrayGetValueAtIndex(certref_list, i);
+ status = create_macosxssl_certificate(&list_cert, certref, 1,
+ tmppool);
+ if (status)
+ goto cleanup;
+
+ subject = apr_hash_get(list_cert->content, "_subject_der",
+ APR_HASH_KEY_STRING);
+
+ if (CFEqual(subject, issuer))
+ {
+ CFTypeRef outcertref;
+
+ /* This is the one. */
+ outcertref = CFArrayGetValueAtIndex(certref_list, i);
+
+ *outcert = create_ssl_certificate((SecCertificateRef)outcertref,
+ i,
+ pool);
+ status = APR_SUCCESS;
+ goto cleanup;
+ }
+ }
+
+ /* Nothing found. */
+ status = SERF_ERROR_SSL_CERT_FAILED;
+
+cleanup:
+ apr_pool_destroy(tmppool);
+ return status;
+}
+
+/* Certificate validation errors are only available as string. Convert them
+ to serf's failure codes. */
+static int
+convert_certerr_to_failure(const char *errstr)
+{
+ if (strcmp(errstr, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0)
+ return SERF_SSL_CERT_SELF_SIGNED;
+ if (strcmp(errstr, "CSSMERR_TP_CERT_EXPIRED") == 0)
+ return SERF_SSL_CERT_EXPIRED;
+ if (strcmp(errstr, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ return SERF_SSL_CERT_NOTYETVALID;
+ if ((strcmp(errstr, "CSSMERR_TP_NOT_TRUSTED") == 0) ||
+ (strcmp(errstr, "CSSMERR_TP_VERIFICATION_FAILURE") == 0))
+ return SERF_SSL_CERT_UNKNOWNCA;
+
+ return SERF_SSL_CERT_UNKNOWN_FAILURE;
+}
+
+/* Validate a server certificate. Call back to the application if needed.
+ Returns APR_SUCCESS if the server certificate is accepted.
+ Otherwise returns an error.
+ */
+static int
+validate_server_certificate(macosxssl_context_t *ssl_ctx)
+{
+ CFArrayRef anchor_certrefs = NULL;
+ size_t depth_of_error, chain_depth;
+ int failures = 0;
+ OSStatus osstatus;
+ apr_status_t status;
+
+ serf__log(SSL_VERBOSE, __FILE__, "validate_server_certificate called.\n");
+ osstatus = SSLCopyPeerTrust(ssl_ctx->st_ctxr, &ssl_ctx->trust);
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+ apr_pool_cleanup_register(ssl_ctx->handshake_pool, ssl_ctx,
+ cfrelease_trust, cfrelease_trust);
+
+ if (!ssl_ctx->evaluate_in_progress)
+ {
+ CFArrayRef root_certrefs = NULL;
+ void *macosxssl_cls;
+ id tmp;
+
+ /* If the application provided certificates to trust, use them here. */
+ if (ssl_ctx->anchor_certs)
+ {
+ int anchor_certs = ssl_ctx->anchor_certs->nelts;
+ int i;
+ SecCertificateRef certs[anchor_certs];
+
+ for (i = 0; i < anchor_certs; i++)
+ certs[i] = APR_ARRAY_IDX(ssl_ctx->anchor_certs, i,
+ SecCertificateRef);
+
+ anchor_certrefs = CFArrayCreate(kCFAllocatorDefault,
+ (void *)certs,
+ anchor_certs,
+ NULL);
+ ssl_ctx->anchor_certrefs = anchor_certrefs;
+ }
+
+ /* If we can use the system's default root certificates, copy them
+ here. */
+ if (ssl_ctx->use_system_roots)
+ {
+ osstatus = SecTrustCopyAnchorCertificates(&root_certrefs);
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+ }
+
+ /* Make one list of custom anchor and system root certificates and
+ add them to the trust object to use them during evaluation of the
+ server certificate chain. */
+ if (anchor_certrefs || root_certrefs)
+ {
+ CFIndex anchors, roots;
+ CFMutableArrayRef all_certrefs;
+
+ anchors = anchor_certrefs ? CFArrayGetCount(anchor_certrefs) : 0;
+ roots = root_certrefs ? CFArrayGetCount(root_certrefs) : 0;
+
+ all_certrefs = CFArrayCreateMutable(kCFAllocatorDefault,
+ anchors + roots, NULL);
+ if (anchor_certrefs)
+ CFArrayAppendArray(all_certrefs, anchor_certrefs,
+ CFRangeMake(0, anchors));
+ if (root_certrefs)
+ CFArrayAppendArray(all_certrefs, root_certrefs,
+ CFRangeMake(0, roots));
+
+ osstatus = SecTrustSetAnchorCertificates(ssl_ctx->trust,
+ all_certrefs);
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+ }
+
+ ssl_ctx->evaluate_in_progress = 1;
+
+ macosxssl_cls = objc_getClass("macosxssl_Buckets");
+ tmp = objc_msgSend(macosxssl_cls,
+ sel_getUid("evaluate:trustResult:"),
+ ssl_ctx->trust,
+ &ssl_ctx->result);
+ osstatus = (OSStatus)(SInt64)tmp;
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+ }
+
+ if (!ssl_ctx->result) {
+ /* No evaluation results received yet. */
+ return APR_EAGAIN;
+ }
+
+ /* Based on the contents of the user's Keychain, Secure Transport will make
+ a first validation of this certificate chain.
+ The status set here can in some cases be overridden by the application.*/
+ switch (ssl_ctx->result)
+ {
+ /* kSecTrustResultProceed Indicates you may proceed.
+ User trusts this certificate, so continue without calling back to
+ the application. */
+ case kSecTrustResultProceed:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultProceed.\n");
+ status = APR_SUCCESS;
+ goto cleanup;
+
+ /* Non-fatal errors, application decides. */
+
+ /* kSecTrustResultUnspecified Indicates user intent is unknown.
+ (certificate is valid, but user didn't specify trust) */
+ case kSecTrustResultUnspecified:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultUnspecified.\n");
+ status = APR_SUCCESS;
+ failures = 0;
+ break;
+
+ /* kSecTrustResultConfirm Indicates confirmation with the user is
+ required before proceeding. */
+ case kSecTrustResultConfirm:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultConfirm.\n");
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ failures = SERF_SSL_CERT_CONFIRM_NEEDED;
+ break;
+
+ /* kSecTrustResultRecoverableTrustFailure Indicates a trust framework
+ failure; retry after fixing inputs. */
+ case kSecTrustResultRecoverableTrustFailure:
+ serf__log(SSL_VERBOSE, __FILE__,
+ "kSecTrustResultRecoverableTrustFailure.\n");
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ failures = 0; /* Failure info will be added later */
+ break;
+
+ /* Fatal errors. Since they cannot be overridden, don't call back to the
+ application but return with an error. */
+
+ /* kSecTrustResultInvalid Indicates an invalid setting or result. */
+ case kSecTrustResultInvalid:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultInvalid.\n");
+ status = SERF_ERROR_SSL_FATAL_CERT_INVALID;
+ goto cleanup;
+
+ /* kSecTrustResultDeny Indicates a user-configured deny; do not
+ proceed. */
+ case kSecTrustResultDeny:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultDeny.\n");
+ status = SERF_ERROR_SSL_FATAL_CERT_DENIED_IN_KEYCHAIN;
+ goto cleanup;
+
+ /* kSecTrustResultFatalTrustFailure Indicates a trust framework failure;
+ no "easy" fix. */
+ case kSecTrustResultFatalTrustFailure:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultFatalTrustFailure.\n");
+ status = SERF_ERROR_SSL_FATAL_CERT_TRUST_FAILURE;
+ goto cleanup;
+
+ /* kSecTrustResultOtherError Indicates a failure other than that of
+ trust evaluation. */
+ case kSecTrustResultOtherError:
+ serf__log(SSL_VERBOSE, __FILE__, "kSecTrustResultOtherError.\n");
+ status = SERF_ERROR_SSL_FATAL_CERT_FAILED;
+ goto cleanup;
+ default:
+ serf__log(SSL_VERBOSE, __FILE__, "unknown result from trust "
+ "evaluation.\n");
+ status = SERF_ERROR_SSL_FATAL_CERT_FAILED;
+ goto cleanup;
+ }
+
+ /* Secure Transport only reports one error per evaluation. This is stored
+ at depth 0 in the result array, so we don't even know at what depth
+ the error occurred.
+ Get the total chain length (incuding anchor) from
+ SecTrustCopyProperties. */
+ {
+ CFArrayRef props = SecTrustCopyProperties(ssl_ctx->trust);
+ CFDictionaryRef dict = CFArrayGetValueAtIndex(props, 0);
+ CFStringRef errref = CFDictionaryGetValue(dict, kSecPropertyTypeError);
+
+ chain_depth = CFArrayGetCount(props); /* length of the full chain,
+ including anchor cert. */
+
+ if (errref) {
+ apr_pool_t *tmppool;
+ const char *errstr;
+
+ apr_pool_create(&tmppool, NULL);
+ errstr = CFStringToChar(errref, tmppool);
+
+ failures |= convert_certerr_to_failure(errstr);
+ serf__log(SSL_VERBOSE, __FILE__,
+ "Certificate ERROR: %s in chain of length %d.\n", errstr,
+ chain_depth);
+ apr_pool_destroy(tmppool);
+ } else {
+ serf__log(SSL_VERBOSE, __FILE__, "No certificate validation errors "
+ "found.\n");
+ }
+
+ CFRelease(props);
+ }
+
+ /* TODO: 0, oh really? How can we know where the error occurred? */
+ depth_of_error = 0;
+
+ /* Ask the application to validate the server certificate at depth 0.
+ TODO: any certificate at other depths with failures. */
+ if (ssl_ctx->server_cert_callback)
+ {
+ serf_ssl_certificate_t *cert;
+ SecCertificateRef certref;
+
+ serf__log(SSL_VERBOSE, __FILE__, "Call application for server cert "
+ "validation.\n");
+
+ certref = SecTrustGetCertificateAtIndex(ssl_ctx->trust, 0);
+ cert = create_ssl_certificate(certref, 0, ssl_ctx->handshake_pool);
+
+ /* Callback for further verification. */
+ status = ssl_ctx->server_cert_callback(ssl_ctx->server_cert_userdata,
+ failures, cert);
+ serf__log(SSL_VERBOSE, __FILE__, "Application returned status %d.\n",
+ status);
+ }
+
+ /* We need to get the full certificate chain and provide it to the
+ application.
+
+ There are 4 scenario's:
+ 1. The server provided all certificates including the root CA.
+ 2. The server provided all certificates except the anchor certificate.
+ The anchor certificate is stored in a Keychain. (a root CA provided
+ by Apple or a certificate imported in a Keychain by the user).
+ 3. The server provided all certificates except the anchor certificate.
+ The anchor certificate was explicitly trusted by the application via
+ serf_ssl_trust_cert.
+ 4. The server provided some certificates but not the root CA. This cert
+ is not available in the Keychain nor in the trusted list set by the
+ application.
+
+ The Keychain API gives us multiple options to get the full chain in
+ scenario 1, 2 and 4. However, when the anchor certificate was provided
+ by the application, it's not included in the chain returned by the
+ Keychain API.
+
+ We get the total chain length from SecTrustCopyProperties. We get the
+ certificate chain from the trust object via SecTrustGetCertificateCount
+ and SecTrustGetCertificateAtIndex. If the length of the certificate
+ chain is one shorter than the expected total chain length, we know we're
+ in scenario 3.
+ */
+ if (ssl_ctx->server_cert_chain_callback)
+ {
+ serf_ssl_certificate_t **certs;
+ int certs_len, actual_len, i;
+
+ serf__log(SSL_VERBOSE, __FILE__, "Call application for server cert "
+ "chain validation.\n");
+
+ /* Room for the total chain length and a trailing NULL. */
+ certs = apr_palloc(ssl_ctx->handshake_pool,
+ sizeof(*certs) * (chain_depth + 1));
+
+ /* Copy the certificates as provided by the server + Keychain. */
+ certs_len = SecTrustGetCertificateCount(ssl_ctx->trust);
+ for (i = 0; i < certs_len; ++i)
+ {
+ SecCertificateRef certref;
+
+ certref = SecTrustGetCertificateAtIndex(ssl_ctx->trust, i);
+ certs[i] = create_ssl_certificate(certref, i,
+ ssl_ctx->handshake_pool);
+ }
+
+ actual_len = certs_len;
+ if (chain_depth > certs_len)
+ {
+ /* The chain relies on (root) CA certificates not provided by the
+ server or a Keychain (scenario 3). We have to find them in the
+ list of trusted anchor certificates.
+ */
+ SecCertificateRef certref;
+ macosxssl_certificate_t *cert;
+
+ serf__log(SSL_VERBOSE, __FILE__, "Chain length (%d) is longer than "
+ "what we received from the server (%d). Search the "
+ "remaining anchor certificate.\n",
+ chain_depth, certs_len);
+
+ /* Take the last known certificate and search its issuer in the
+ list of trusted anchor certificates. */
+ certref = SecTrustGetCertificateAtIndex(ssl_ctx->trust,
+ certs_len - 1);
+ status = create_macosxssl_certificate(&cert, certref, 1,
+ ssl_ctx->handshake_pool);
+
+ status = find_issuer_cert_in_array(&certs[certs_len],
+ cert,
+ ssl_ctx->anchor_certrefs,
+ ssl_ctx->handshake_pool);
+ if (!status)
+ actual_len++;
+ }
+
+ status =
+ ssl_ctx->server_cert_chain_callback(ssl_ctx->server_cert_userdata,
+ failures, 0, /*depth_of_error,*/
+ (const serf_ssl_certificate_t * const *)certs,
+ actual_len);
+ }
+
+ /* Return a specific error if the server certificate is not accepted by
+ S.T./Keychain and the application has not set callbacks to override
+ this. */
+ if (failures &&
+ !ssl_ctx->server_cert_chain_callback &&
+ !ssl_ctx->server_cert_callback)
+ {
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ }
+
+cleanup:
+ if (anchor_certrefs)
+ CFRelease(anchor_certrefs);
+
+ return status;
+}
+
+/**** Client identity related code ****/
+/**************************************/
+#pragma mark NEED CLIENT IDENTITY
+#pragma mark -
+
+static apr_status_t delete_temp_keychain(void *data)
+{
+ macosxssl_context_t *ssl_ctx = data;
+ apr_status_t status = APR_SUCCESS;
+ OSStatus osstatus;
+
+ if (!ssl_ctx->tempKeyChainRef)
+ return APR_SUCCESS;
+
+ osstatus = SecKeychainDelete(ssl_ctx->tempKeyChainRef);
+ if (osstatus != errSecSuccess) {
+ status = translate_macosxssl_status(osstatus);
+ }
+ ssl_ctx->tempKeyChainRef = NULL;
+
+ return status;
+}
+
+static apr_status_t create_temp_keychain(macosxssl_context_t *ssl_ctx,
+ apr_pool_t *pool)
+{
+ apr_file_t *tmpfile;
+ const char *temp_dir;
+ apr_status_t status;
+ OSStatus osstatus;
+
+ if (ssl_ctx->tempKeyChainRef)
+ return APR_SUCCESS;
+
+ /* The Keychain API only allows us to load an identity (private key +
+ certificate) for use in the SetCertificate call in a keychain.
+ We don't want to load this identity in the login or system keychain,
+ so we need to create a temporary keychain.
+
+ For the duration of the SSL handshake, this keychain will be visible to
+ the user in the Keychain Access tool.
+ */
+
+ /* We need a unique filename for a temporary file. So create an empty
+ file using APR and close it immediately. */
+ status = apr_temp_dir_get(&temp_dir, pool);
+ if (status)
+ return status;
+
+ status = apr_filepath_merge(&ssl_ctx->keychain_temp_file,
+ temp_dir,
+ "tempfile_XXXXXX",
+ APR_FILEPATH_NATIVE | APR_FILEPATH_NOTRELATIVE,
+ pool);
+ if (status)
+ return status;
+
+ status = apr_file_mktemp(&tmpfile, ssl_ctx->keychain_temp_file,
+ APR_READ | APR_WRITE | APR_CREATE | APR_EXCL |
+ APR_DELONCLOSE | APR_BINARY,
+ pool);
+ if (status)
+ return status;
+
+ status = apr_file_close(tmpfile);
+ if (status)
+ return status;
+
+ serf__log(SSL_VERBOSE, __FILE__, "Creating temporary keychain in %s.\n",
+ ssl_ctx->keychain_temp_file);
+
+ /* TODO: random password */
+ /* TODO: standard access rights gives unlimited access to the keychain for
+ this application. Other applications can also access the keychain,
+ but require confirmation from the user (no pwd needed). Probably better
+ if the keychain is locked down so that e.g. searches for identity
+ objects don't return any from this keychain. */
+ osstatus = SecKeychainCreate(ssl_ctx->keychain_temp_file,
+ 4,
+ "serf",
+ FALSE,
+ NULL, /* Standard access rights */
+ &ssl_ctx->tempKeyChainRef);
+ if (osstatus != errSecSuccess) {
+ return translate_macosxssl_status(osstatus);
+ }
+ apr_pool_cleanup_register(pool, ssl_ctx,
+ delete_temp_keychain, delete_temp_keychain);
+
+ return APR_SUCCESS;
+}
+
+/* Find the certificate of the issuer of certref in the keychains. */
+static apr_status_t
+find_issuer_certificate_in_keychain(macosxssl_certificate_t **out_cert,
+ SecCertificateRef certref,
+ apr_pool_t *pool)
+{
+ CFErrorRef error = NULL;
+ CFDataRef issuer;
+ apr_pool_t *tmppool;
+ apr_status_t status;
+
+ apr_pool_create(&tmppool, pool);
+
+ issuer = SecCertificateCopyNormalizedIssuerContent(certref,
+ &error);
+ if (error) {
+ CFStringRef errstr = CFErrorCopyDescription(error);
+
+ serf__log(SSL_VERBOSE, __FILE__, "Can't get issuer DER buffer from "
+ "certificate, reason: %s.\n",
+ CFStringToChar(errstr, tmppool));
+ CFRelease(error);
+
+ return SERF_ERROR_SSL_CERT_FAILED;
+ }
+ else
+ {
+ CFDictionaryRef query;
+ macosxssl_certificate_t *cert, *issuer_cert;
+ SecCertificateRef issuer_certref;
+ CFDataRef cert_issuer, issuer_subject;
+ OSStatus osstatus;
+
+ const void *keys[] = { kSecClass, kSecAttrSubject,
+ kSecMatchLimit, kSecReturnRef };
+ const void *values[] = { kSecClassCertificate, issuer,
+ kSecMatchLimitOne, kCFBooleanTrue };
+
+ /* Find a certificate with issuer in the keychains. */
+ query = CFDictionaryCreate(kCFAllocatorDefault,
+ (const void **)keys, (const void **)values,
+ 4L, NULL, NULL);
+ osstatus = SecItemCopyMatching(query, (CFTypeRef*)&issuer_certref);
+ CFRelease(query);
+ CFRelease(issuer);
+ if (osstatus != errSecSuccess) {
+ return translate_macosxssl_status(osstatus);
+ }
+
+ /* if SecItemCopyMatching doesn't find a matching certificate, it is
+ known that it returns another (no kidding), so check that we received
+ the right certificate.
+ */
+ status = create_macosxssl_certificate(&cert,
+ certref,
+ 1,
+ pool);
+ if (status)
+ return status;
+ status = create_macosxssl_certificate(&issuer_cert,
+ issuer_certref,
+ 1,
+ pool);
+ if (status)
+ return status;
+
+ cert_issuer = apr_hash_get(cert->content, "_issuer_der",
+ APR_HASH_KEY_STRING);
+ issuer_subject = apr_hash_get(issuer_cert->content, "_subject_der",
+ APR_HASH_KEY_STRING);
+
+ if (CFEqual(cert_issuer, issuer_subject))
+ {
+ *out_cert = issuer_cert;
+ return APR_SUCCESS;
+ }
+
+ *out_cert = NULL;
+
+ return APR_SUCCESS;
+ }
+}
+
+/* Create a list of all certificates in between certref and any anchor
+ certificate in the list of anchor_certrefs.
+ Caller is responsible for cleanin up *intermediate_ca_certrefs. */
+static apr_status_t
+find_intermediate_cas(CFArrayRef *intermediate_ca_certrefs,
+ SecCertificateRef certref,
+ CFArrayRef peer_certrefs,
+ apr_pool_t *pool)
+{
+ macosxssl_certificate_t *prevcert;
+ CFMutableArrayRef ca_certrefs;
+ apr_pool_t *tmppool;
+ apr_status_t status;
+
+ ca_certrefs = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+
+ if (peer_certrefs == NULL ||
+ CFArrayGetCount(peer_certrefs) == 0)
+ {
+ /* The server didn't provide any certificate at all?? */
+ *intermediate_ca_certrefs = ca_certrefs;
+
+ return APR_SUCCESS;
+ }
+
+ apr_pool_create(&tmppool, pool);
+
+ status = create_macosxssl_certificate(&prevcert, certref, 1, tmppool);
+ if (status)
+ goto cleanup;
+
+ /* Get the issuer DER encoded data buffer of the provided certificate. */
+ while (1)
+ {
+ macosxssl_certificate_t *issuer_cert;
+ CFDataRef issuer, subject;
+ serf_ssl_certificate_t *dummy_cert;
+
+ /* Find issuer in the list of certificates sent by the server. */
+ status = find_issuer_cert_in_array(&dummy_cert,
+ prevcert,
+ peer_certrefs,
+ tmppool);
+ if (status == APR_SUCCESS)
+ goto cleanup;
+#if 0
+ /* We have to send the certificate to the server. Find issuer in the
+ list of anchor certificates set by the application. */
+ status = find_issuer_cert_in_array(&cert,
+ prevcert,
+ ssl_ctx->anchor_certs,
+ tmppool)
+ if (status == APR_SUCCESS)
+ goto cleanup;
+#endif
+
+ /* Issuer certificate was not found in peer_certs, add it to the
+ output list. */
+ status = find_issuer_certificate_in_keychain(&issuer_cert,
+ prevcert->certref,
+ pool);
+ if (status)
+ goto cleanup;
+
+ if (!issuer_cert)
+ {
+ /* The issuer's certificate was not found in the keychain.
+ Send what we have to the server. */
+ status = APR_SUCCESS;
+ goto cleanup;
+ }
+
+ CFArrayAppendValue(ca_certrefs, issuer_cert->certref);
+
+ prevcert = issuer_cert;
+
+ /* break if selfsigned */
+ subject = apr_hash_get(issuer_cert->content, "_subject_der",
+ APR_HASH_KEY_STRING);
+ issuer = apr_hash_get(issuer_cert->content, "_issuer_der",
+ APR_HASH_KEY_STRING);
+ if (CFEqual(subject, issuer))
+ {
+ status = APR_SUCCESS;
+ goto cleanup;
+ }
+ }
+
+ /* Nothing found. */
+ status = SERF_ERROR_SSL_CERT_FAILED;
+
+cleanup:
+ *intermediate_ca_certrefs = ca_certrefs;
+ apr_pool_destroy(tmppool);
+ return status;
+}
+
+static apr_status_t
+callback_for_identity_password(macosxssl_context_t *ssl_ctx,
+ const char *cert_path,
+ const char **passphrase)
+{
+ if (ssl_ctx->identity_pw_callback)
+ {
+ apr_status_t status;
+
+ status = ssl_ctx->identity_pw_callback(ssl_ctx->identity_pw_userdata,
+ cert_path,
+ passphrase);
+ return status;
+ }
+
+ return SERF_ERROR_SSL_CLIENT_CERT_PW_FAILED;
+}
+
+static apr_status_t
+callback_for_identity(macosxssl_context_t *ssl_ctx,
+ apr_hash_t **dnlist,
+ apr_size_t dnlen,
+ SecIdentityRef *identityref,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const serf_ssl_identity_t *identity;
+
+ if (ssl_ctx->client_cert_callback)
+ {
+ const char *cert_path;
+
+ status = ssl_ctx->client_cert_callback(ssl_ctx->identity_userdata,
+ &cert_path);
+ if (status)
+ return status;
+
+ status = serf__macosxssl_load_identity_from_file(ssl_ctx, &identity,
+ cert_path, pool);
+ if (status)
+ return status;
+ } else if (ssl_ctx->identity_callback)
+ {
+ status = ssl_ctx->identity_callback(ssl_ctx->identity_userdata,
+ dnlist, dnlen, &identity, pool);
+ } else
+ status = APR_EGENERAL;
+
+ if (status == APR_SUCCESS)
+ *identityref = (SecIdentityRef)identity->impl_cert;
+
+ return status;
+}
+
+/* Get a client certificate for this server from the application. */
+static apr_status_t
+provide_client_certificate(macosxssl_context_t *ssl_ctx)
+{
+ SecIdentityRef identityref = NULL;
+ CFArrayRef dnlistrefs = NULL;
+ apr_hash_t **dnlist = NULL;
+ apr_size_t dnlen = 0;
+ apr_pool_t *tmppool;
+ apr_status_t status;
+ OSStatus osstatus;
+
+ serf__log(SSL_VERBOSE, __FILE__, "provide_client_certificate called.\n");
+
+ /* The server asked for a client certificate but we can't ask the
+ application. Consider this a success, the server decides if the
+ request was optional or mandatory. */
+ if (!ssl_ctx->client_cert_callback &&
+ !ssl_ctx->identity_callback)
+ {
+ serf__log(SSL_VERBOSE, __FILE__, "Server asked for client "
+ "certificate, but the application didn't set the "
+ "necessary callback.\n");
+ return APR_SUCCESS;
+ }
+
+ /* The server will send us a list of Distinguished Names, indicating that
+ client certificates issued by one of these DN's are acceptable. */
+ osstatus = SSLCopyDistinguishedNames(ssl_ctx->st_ctxr, &dnlistrefs);
+ if (osstatus != noErr) {
+ return translate_macosxssl_status(osstatus);
+ }
+
+ apr_pool_create(&tmppool, ssl_ctx->pool);
+ if (dnlistrefs && CFArrayGetCount(dnlistrefs) > 0)
+ {
+ int i;
+
+ dnlen = CFArrayGetCount(dnlistrefs);
+ dnlist = apr_palloc(tmppool, dnlen * sizeof(apr_hash_t*));
+
+ for (i = 0; i < dnlen; i++) {
+ CFDataRef cader = CFArrayGetValueAtIndex(dnlistrefs, i);
+ apr_hash_t *ca;
+
+ status = serf__macosxssl_read_X509_DER_DN(&ca, cader, tmppool);
+ if (status)
+ goto cleanup;
+
+ dnlist[i] = ca;
+ }
+ }
+
+ /* Call the application to find an identity trusted by one of the DN's
+ provided by the server. */
+ status = callback_for_identity(ssl_ctx, dnlist, dnlen, &identityref,
+ ssl_ctx->pool);
+ if (status)
+ goto cleanup;
+
+ /* If the issuer of the client certificate is not in the list
+ of certificates the server provided, we need to send it along.
+ Otherwise the server can complain that it doesn't trust the
+ client identity.
+ Note: this is what happens with the Belgian Personal ID Card on
+ site https://https://test.eid.belgium.be, where the "Citizen CA"
+ certificate is the issuer of the client certificate, but is not
+ sent by the server. */
+ if (identityref)
+ {
+ CFArrayRef intermediate_certrefs, peer_certrefs;
+ SecCertificateRef cert;
+ CFMutableArrayRef items;
+
+ /* Secure Transport assumes the following:
+ The certificate references remain valid for the lifetime of the
+ session.
+ The identity specified in certRefs[0] is capable of signing. */
+ items = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+ CFArrayAppendValue(items, identityref);
+ apr_pool_cleanup_register(ssl_ctx->pool, items,
+ cfrelease_ref, cfrelease_ref);
+
+ osstatus = SecIdentityCopyCertificate(identityref, &cert);
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+
+ osstatus = SSLCopyPeerCertificates(ssl_ctx->st_ctxr,
+ &peer_certrefs);
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+
+ status = find_intermediate_cas(&intermediate_certrefs,
+ cert,
+ peer_certrefs,
+ ssl_ctx->pool);
+ CFRelease(peer_certrefs);
+ if (status)
+ goto cleanup;
+
+ /* Add the intermediate certificates to the list to send to the
+ server. */
+ CFArrayAppendArray(items, intermediate_certrefs,
+ CFRangeMake(0,
+ CFArrayGetCount(intermediate_certrefs)));
+
+ /* This can show a popup to ask the user if the application is
+ allowed to use the signing key. */
+ osstatus = SSLSetCertificate(ssl_ctx->st_ctxr, items);
+ if (osstatus != noErr) {
+ status = translate_macosxssl_status(osstatus);
+ goto cleanup;
+ }
+
+ status = APR_SUCCESS;
+ }
+
+cleanup:
+ apr_pool_destroy(tmppool);
+ if (dnlistrefs)
+ CFRelease(dnlistrefs);
+
+ return status;
+}
+
+/* Use Keychain Services to extract a SecIndentityRef (client private key +
+ certificate) from a data buffer. Databuf needs to be in PKCS12 format.
+ Caller is responsible to clean up items.
+ */
+static apr_status_t
+load_identity_from_databuf(macosxssl_context_t *ssl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *label,
+ CFDataRef databuf,
+ apr_pool_t *pool)
+{
+ SecExternalFormat format;
+ SecItemImportExportKeyParameters keyParams;
+ SecExternalItemType itemType;
+
+ /* SecItemImport will crash if keyUsage member is not set to NULL. */
+ memset(&keyParams, 0, sizeof(SecItemImportExportKeyParameters));
+
+ format = kSecFormatPKCS12;
+ itemType = kSecItemTypeUnknown;
+
+ /* Try importing the identity until it succeeds, fails or the application
+ stops setting passphrases. */
+ while (1)
+ {
+ CFArrayRef items;
+ OSStatus osstatus;
+ apr_status_t status;
+ const char *passphrase;
+
+ osstatus = SecItemImport(databuf,
+ NULL,
+ &format,
+ &itemType,
+ 0, /* SecItemImportExportFlags */
+ &keyParams,
+ ssl_ctx->tempKeyChainRef,
+ &items);
+ if (osstatus == errSecSuccess)
+ {
+ *identity = NULL;
+
+ /* Identity successfully imported in the keychain, return the
+ (wrapped) reference to the caller. */
+ if (CFArrayGetCount(items) > 0)
+ {
+ SecIdentityRef identityref;
+
+ identityref = (SecIdentityRef)CFArrayGetValueAtIndex(items, 0);
+
+ if (!identityref)
+ return SERF_ERROR_SSL_CERT_FAILED;
+
+ *identity = serf__create_identity(&serf_ssl_bucket_type_macosxssl,
+ identityref, NULL, pool);
+
+ return APR_SUCCESS;
+ }
+
+ return SERF_ERROR_SSL_CERT_FAILED;
+ } else if (osstatus == errSecPassphraseRequired)
+ {
+ CFStringRef pwref;
+
+ status = callback_for_identity_password(
+ ssl_ctx,
+ label,
+ &passphrase);
+ if (status)
+ return status;
+
+ pwref = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
+ (unsigned char *)passphrase,
+ strlen(passphrase),
+ kCFStringEncodingMacRoman,
+ false,
+ kCFAllocatorNull);
+ keyParams.passphrase = pwref;
+ } else {
+ /* TODO: should be handled in translate_... */
+ return SERF_ERROR_SSL_CERT_FAILED;
+ }
+ }
+}
+
+/**** Run the SSL handshake. ****/
+/********************************/
+#pragma mark HANDSHAKE AND INITIALIZATION
+#pragma mark -
+static apr_status_t do_handshake(macosxssl_context_t *ssl_ctx)
+{
+ OSStatus osstatus;
+ apr_status_t status = APR_SUCCESS;
+
+ if (ssl_ctx->state == SERF_MACOSXSSL_INIT ||
+ ssl_ctx->state == SERF_MACOSXSSL_HANDSHAKE)
+ {
+ ssl_ctx->state = SERF_MACOSXSSL_HANDSHAKE;
+
+ serf__log(SSL_VERBOSE, __FILE__, "do_handshake called.\n");
+
+ if (ssl_ctx->evaluate_in_progress) {
+ if (ssl_ctx->result) {
+ status = validate_server_certificate(ssl_ctx);
+ ssl_ctx->evaluate_in_progress = 0;
+ if (!status)
+ return APR_EAGAIN;
+ return status;
+ } else {
+ serf__log(SSL_VERBOSE, __FILE__, "evaluation in progress, but "
+ "no results were received yet.\n");
+ return APR_EAGAIN;
+ }
+ }
+
+ osstatus = SSLHandshake(ssl_ctx->st_ctxr);
+ if (osstatus)
+ serf__log(SSL_VERBOSE, __FILE__, "do_handshake returned err %d.\n",
+ osstatus);
+
+ switch(osstatus) {
+ case noErr:
+ status = APR_SUCCESS;
+ break;
+ case errSSLServerAuthCompleted:
+ /* Server's cert validation was disabled, so we can to do this
+ here. */
+ status = validate_server_certificate(ssl_ctx);
+ if (!status)
+ return APR_EAGAIN;
+ break;
+ case errSSLClientCertRequested:
+ status = provide_client_certificate(ssl_ctx);
+ if (!status)
+ return APR_EAGAIN;
+ break;
+ default:
+ status = translate_macosxssl_status(osstatus);
+ break;
+ }
+
+ if (!status)
+ {
+ serf__log(SSL_VERBOSE, __FILE__, "ssl/tls handshake successful.\n");
+ ssl_ctx->state = SERF_MACOSXSSL_CONNECTED;
+
+ /* We can now safely cleanup the temporary resources created during
+ handshake (i.e. the temporary keychain used to load the client
+ identity. */
+ apr_pool_destroy(ssl_ctx->handshake_pool);
+ ssl_ctx->handshake_pool = NULL;
+
+ ssl_ctx->result = 0;
+ ssl_ctx->evaluate_in_progress = 0;
+ }
+ }
+
+ return status;
+}
+
+/* Callback function for the encrypt.pending and decrypt.pending stream-type
+ aggregate buckets.
+ */
+static apr_status_t
+pending_stream_eof(void *baton,
+ serf_bucket_t *pending)
+{
+ /* Both pending streams have to stay open so that the Secure Transport
+ library can keep appending data buckets. */
+ return APR_EAGAIN;
+}
+
+static macosxssl_context_t *
+macosxssl_init_context(serf_bucket_alloc_t *allocator)
+{
+ macosxssl_context_t *ssl_ctx;
+
+ ssl_ctx = serf_bucket_mem_calloc(allocator, sizeof(*ssl_ctx));
+ ssl_ctx->refcount = 0;
+
+ apr_pool_create(&ssl_ctx->pool, NULL);
+ apr_pool_create(&ssl_ctx->handshake_pool, ssl_ctx->pool);
+
+ /* Set up the stream objects. */
+ ssl_ctx->encrypt.pending = serf__bucket_stream_create(allocator,
+ pending_stream_eof,
+ NULL);
+ ssl_ctx->decrypt.pending = serf__bucket_stream_create(allocator,
+ pending_stream_eof,
+ NULL);
+
+ /* Set up a Secure Transport session. */
+ ssl_ctx->state = SERF_MACOSXSSL_INIT;
+
+ if (SSLNewContext(FALSE, &ssl_ctx->st_ctxr))
+ return NULL;
+
+ if (SSLSetIOFuncs(ssl_ctx->st_ctxr, macosxssl_read_cb, macosxssl_write_cb))
+ return NULL;
+
+ /* Ensure the macosxssl_context will be passed to the read and write callback
+ functions. */
+ if (SSLSetConnection(ssl_ctx->st_ctxr, ssl_ctx))
+ return NULL;
+
+ /* We do our own validation of server certificates.
+ Note that Secure Transport will not do any validation with this option
+ enabled, it's all or nothing. */
+ if (SSLSetSessionOption(ssl_ctx->st_ctxr,
+ kSSLSessionOptionBreakOnServerAuth,
+ true))
+ return NULL;
+ if (SSLSetEnableCertVerify(ssl_ctx->st_ctxr, false))
+ return NULL;
+
+ /* If the handshake needs a client identity to continue, break it
+ temporarily so we can call back to the application. */
+ if (SSLSetSessionOption(ssl_ctx->st_ctxr,
+ kSSLSessionOptionBreakOnCertRequested,
+ true))
+ return NULL;
+
+ return ssl_ctx;
+}
+
+static apr_status_t
+macosxssl_free_context(macosxssl_context_t *ssl_ctx,
+ serf_bucket_alloc_t *allocator)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ (void)SSLDisposeContext(ssl_ctx->st_ctxr);
+
+ if (ssl_ctx->handshake_pool)
+ apr_pool_destroy(ssl_ctx->handshake_pool);
+ apr_pool_destroy(ssl_ctx->pool);
+
+ serf_bucket_mem_free(allocator, ssl_ctx);
+
+ if (status) {
+ return APR_EGENERAL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/**** SSL_BUCKET API ****/
+/************************/
+#pragma mark SSL_BUCKET API
+#pragma mark -
+static void *
+serf__macosxssl_decrypt_create(serf_bucket_t *bucket,
+ serf_bucket_t *stream,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator)
+{
+ macosxssl_context_t *ssl_ctx;
+ bucket->type = &serf_bucket_type_macosxssl_decrypt;
+ bucket->allocator = allocator;
+
+ if (impl_ctx)
+ bucket->data = impl_ctx;
+ else
+ bucket->data = macosxssl_init_context(allocator);
+
+ ssl_ctx = bucket->data;
+ ssl_ctx->refcount++;
+ ssl_ctx->decrypt.stream = stream;
+ ssl_ctx->allocator = allocator;
+
+ return bucket->data;
+}
+
+static void *
+serf__macosxssl_encrypt_create(serf_bucket_t *bucket,
+ serf_bucket_t *stream,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator)
+{
+ macosxssl_context_t *ssl_ctx;
+ bucket->type = &serf_bucket_type_macosxssl_encrypt;
+ bucket->allocator = allocator;
+
+ if (impl_ctx)
+ bucket->data = impl_ctx;
+ else
+ bucket->data = macosxssl_init_context(allocator);
+
+ ssl_ctx = bucket->data;
+ ssl_ctx->refcount++;
+ ssl_ctx->encrypt.stream = stream;
+ ssl_ctx->allocator = allocator;
+
+ return bucket->data;
+}
+
+static void *
+serf__macosxssl_decrypt_context_get(serf_bucket_t *bucket)
+{
+ return NULL;
+}
+
+static void *
+serf__macosxssl_encrypt_context_get(serf_bucket_t *bucket)
+{
+ return NULL;
+}
+
+
+static void
+serf__macosxssl_client_cert_provider_set(
+ void *impl_ctx,
+ serf_ssl_need_client_cert_t callback,
+ void *data,
+ void *cache_pool)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->client_cert_callback = callback;
+ ssl_ctx->identity_userdata = data;
+}
+
+static void
+serf__macosxssl_identity_provider_set(void *impl_ctx,
+ serf_ssl_need_identity_t callback,
+ void *data,
+ void *cache_pool)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->identity_callback = callback;
+ ssl_ctx->identity_userdata = data;
+}
+
+static void
+serf__macosxssl_client_cert_password_set(
+ void *impl_ctx,
+ serf_ssl_need_cert_password_t callback,
+ void *data,
+ void *cache_pool)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->identity_pw_callback = callback;
+ ssl_ctx->identity_pw_userdata = data;
+}
+
+static void
+serf__macosxssl_server_cert_callback_set(
+ void *impl_ctx,
+ serf_ssl_need_server_cert_t callback,
+ void *data)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->server_cert_callback = callback;
+ ssl_ctx->server_cert_userdata = data;
+}
+
+static void
+serf__macosxssl_server_cert_chain_callback_set(
+ void *impl_ctx,
+ serf_ssl_need_server_cert_t cert_callback,
+ serf_ssl_server_cert_chain_cb_t cert_chain_callback,
+ void *data)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->server_cert_callback = cert_callback;
+ ssl_ctx->server_cert_chain_callback = cert_chain_callback;
+ ssl_ctx->server_cert_userdata = data;
+}
+
+static apr_status_t
+serf__macosxssl_set_hostname(void *impl_ctx, const char * hostname)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+ OSStatus osstatus;
+
+ ssl_ctx->hostname = serf_bstrdup(ssl_ctx->allocator, hostname);
+ osstatus= SSLSetPeerDomainName(ssl_ctx->st_ctxr,
+ ssl_ctx->hostname,
+ strlen(hostname));
+
+ return translate_macosxssl_status(osstatus);
+}
+
+static apr_status_t
+serf__macosxssl_use_default_certificates(void *impl_ctx)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ /* When constructing the trust object to validate the server certificate
+ chain, extract all root certificates from the System keychain first
+ to include them during validation. */
+ ssl_ctx->use_system_roots = 1;
+
+ return APR_SUCCESS;
+}
+
+apr_status_t
+load_CA_cert_from_buffer(serf_ssl_certificate_t **cert,
+ const char *buf,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ CFArrayRef items;
+ CFDataRef databuf;
+ apr_status_t status;
+
+ databuf = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
+ (unsigned char *)buf,
+ len,
+ kCFAllocatorNull);
+
+ status = load_certificate_from_databuf(databuf, &items, pool);
+ if (status)
+ return status;
+
+ if (CFArrayGetCount(items) > 0) {
+ SecCertificateRef ssl_cert =
+ (SecCertificateRef)CFArrayGetValueAtIndex(items, 0);
+
+ if (ssl_cert) {
+ *cert = create_ssl_certificate(ssl_cert,
+ 0,
+ pool);
+ return APR_SUCCESS;
+ }
+ }
+
+ /* TODO: cleanup databuf needed? */
+
+ return SERF_ERROR_SSL_CERT_FAILED;
+}
+
+static apr_status_t
+serf__macosxssl_load_CA_cert_from_file(serf_ssl_certificate_t **cert,
+ const char *file_path,
+ apr_pool_t *pool)
+{
+ CFArrayRef items;
+ CFDataRef databuf;
+ apr_status_t status;
+
+ status = load_data_from_file(file_path, &databuf, pool);
+ if (status)
+ return status;
+
+ status = load_certificate_from_databuf(databuf, &items, pool);
+ if (status)
+ return status;
+
+ if (CFArrayGetCount(items) > 0) {
+ SecCertificateRef ssl_cert =
+ (SecCertificateRef)CFArrayGetValueAtIndex(items, 0);
+
+ if (ssl_cert) {
+ *cert = create_ssl_certificate(ssl_cert,
+ 0,
+ pool);
+ return APR_SUCCESS;
+ }
+ }
+
+ return SERF_ERROR_SSL_CERT_FAILED;
+}
+
+static apr_status_t
+serf__macosxssl_load_identity_from_file(void *impl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ CFDataRef databuf;
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+
+ status = load_data_from_file(file_path, &databuf, pool);
+ if (status)
+ return status;
+
+ status = create_temp_keychain(ssl_ctx, ssl_ctx->handshake_pool);
+ if (status)
+ return status;
+
+ status = load_identity_from_databuf(ssl_ctx, identity, file_path,
+ databuf, pool);
+
+ return status;
+}
+
+static apr_status_t
+serf__macosxssl_trust_cert(void *impl_ctx,
+ serf_ssl_certificate_t *cert)
+{
+ macosxssl_context_t *ssl_ctx = impl_ctx;
+ macosxssl_certificate_t *macosxssl_cert = cert->impl_cert;
+
+ if (!ssl_ctx->anchor_certs)
+ ssl_ctx->anchor_certs = apr_array_make(ssl_ctx->pool, 1,
+ sizeof(SecCertificateRef));
+ APR_ARRAY_PUSH(ssl_ctx->anchor_certs,
+ SecCertificateRef) = macosxssl_cert->certref;
+
+ return APR_SUCCESS;
+}
+
+static apr_hash_t *
+serf__macosxssl_cert_certificate(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ apr_hash_t *tgt;
+ const char *date_str, *sha1;
+
+ macosxssl_certificate_t *macosxssl_cert = cert->impl_cert;
+
+ if (!macosxssl_cert->content) {
+ apr_status_t status;
+ status = serf__macosxssl_read_X509_DER_certificate(
+ &macosxssl_cert->content,
+ macosxssl_cert,
+ pool);
+ if (status)
+ return NULL;
+ }
+
+ tgt = apr_hash_make(pool);
+
+ date_str = apr_hash_get(macosxssl_cert->content, "notBefore",
+ APR_HASH_KEY_STRING);
+ apr_hash_set(tgt, "notBefore", APR_HASH_KEY_STRING, date_str);
+
+ date_str = apr_hash_get(macosxssl_cert->content, "notAfter",
+ APR_HASH_KEY_STRING);
+ apr_hash_set(tgt, "notAfter", APR_HASH_KEY_STRING, date_str);
+
+ sha1 = apr_hash_get(macosxssl_cert->content, "sha1", APR_HASH_KEY_STRING);
+ apr_hash_set(tgt, "sha1", APR_HASH_KEY_STRING, sha1);
+
+ /* TODO: array of subjectAltName's */
+
+ return tgt;
+}
+
+
+static apr_hash_t *
+serf__macosxssl_cert_issuer(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ macosxssl_certificate_t *macosxssl_cert = cert->impl_cert;
+
+ if (!macosxssl_cert->content) {
+ apr_status_t status;
+ status = serf__macosxssl_read_X509_DER_certificate(
+ &macosxssl_cert->content,
+ macosxssl_cert,
+ pool);
+ if (status)
+ return NULL;
+ }
+
+ return (apr_hash_t *)apr_hash_get(macosxssl_cert->content,
+ "issuer", APR_HASH_KEY_STRING);
+}
+
+static apr_hash_t *
+serf__macosxssl_cert_subject(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ macosxssl_certificate_t *macosxssl_cert = cert->impl_cert;
+
+ if (!macosxssl_cert->content) {
+ apr_status_t status;
+ status = serf__macosxssl_read_X509_DER_certificate(
+ &macosxssl_cert->content,
+ macosxssl_cert,
+ pool);
+ if (status)
+ return NULL;
+ }
+
+ return (apr_hash_t *)apr_hash_get(macosxssl_cert->content,
+ "subject", APR_HASH_KEY_STRING);
+}
+
+static const char *
+serf__macosxssl_cert_export(const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ macosxssl_certificate_t *macosxssl_cert = cert->impl_cert;
+ SecCertificateRef certref = macosxssl_cert->certref;
+ CFDataRef dataref = SecCertificateCopyData(certref);
+ const unsigned char *data = CFDataGetBytePtr(dataref);
+ char *encoded_cert;
+
+ CFIndex len = CFDataGetLength(dataref);
+
+ if (!len)
+ return NULL;
+
+ encoded_cert = apr_palloc(pool, apr_base64_encode_len(len));
+
+ apr_base64_encode(encoded_cert, (char*)data, len);
+
+ return encoded_cert;
+}
+
+static apr_status_t
+serf__macosxssl_use_compression(void *impl_ctx, int enabled)
+{
+ if (enabled) {
+ serf__log(SSL_VERBOSE, __FILE__,
+ "Secure Transport does not support any type of "
+ "SSL compression.\n");
+ return APR_ENOTIMPL;
+ } else {
+ return APR_SUCCESS;
+ }
+}
+
+/**** ENCRYPTION BUCKET API *****/
+/********************************/
+#pragma mark ENCRYPTION BUCKET API
+#pragma mark -
+static apr_status_t
+serf_macosxssl_encrypt_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+ apr_status_t status, status_unenc_stream;
+ const char *unenc_data;
+ struct iovec vecs[32];
+ int vecs_used;
+ size_t unenc_len;
+
+ serf__log(SSL_VERBOSE, __FILE__, "serf_macosxssl_encrypt_read called for "
+ "%d bytes.\n", requested);
+
+ /* Pending handshake? */
+ status = do_handshake(ssl_ctx);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+ if (status) {
+ /* Maybe the handshake algorithm put some data in the pending
+ outgoing bucket? */
+ return serf_bucket_read(ssl_ctx->encrypt.pending, requested, data, len);
+ }
+
+ /* Handshake successful. */
+
+ /* First use any pending encrypted data. */
+ status = serf_bucket_read(ssl_ctx->encrypt.pending, requested, data, len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ if (*len) {
+ /* status can be either APR_EAGAIN or APR_SUCCESS. In both cases,
+ we want the caller to try again as there's probably more data
+ to be encrypted. */
+ return APR_SUCCESS;
+ }
+
+ /* SSLWrite does not write directly to a socket, but to an aggregate bucket
+ which can be infinitely big. To ensure that the outgoing data is
+ written to the socket regularely, we only read one block at a time and
+ then return to the caller. */
+ if (requested == SERF_READ_ALL_AVAIL)
+ requested = 65536;
+
+ /* Encrypt more data. */
+ status_unenc_stream = serf_bucket_read_iovec(ssl_ctx->encrypt.stream,
+ requested,
+ 32, vecs,
+ &vecs_used);
+ if (SERF_BUCKET_READ_ERROR(status_unenc_stream))
+ return status_unenc_stream;
+
+ unenc_data = serf_bstrcatv(ssl_ctx->encrypt.stream->allocator,
+ vecs, vecs_used, &unenc_len);
+
+ serf__log(SSL_VERBOSE, __FILE__, "ready to encrypt %d bytes with status %d.\n",
+ unenc_len, status_unenc_stream);
+
+ if (unenc_len)
+ {
+ OSStatus osstatus;
+ size_t written;
+
+ osstatus = SSLWrite(ssl_ctx->st_ctxr, unenc_data, unenc_len,
+ &written);
+ status = translate_macosxssl_status(osstatus);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ serf__log(SSL_MSG_VERBOSE, __FILE__, "%dB ready with status %d, %d "
+ "encrypted and written:\n---%.*s-(%d)-\n", unenc_len,
+ status_unenc_stream, written, written, unenc_data, written);
+
+ /* Less data written than available! This situation can never happen,
+ because SSLWrite loops until all data is sent or macosxssl_write_cb
+ returns errSSLWouldBlock, and the callback will never return this
+ error because it can process all data that it's given. */
+ if (written < unenc_len)
+ {
+ serf__log(SSL_VERBOSE, __FILE__, "Less data written than was "
+ "available! Aborting, this should not be possible.\n");
+ return APR_EGENERAL;
+ }
+
+ status = serf_bucket_read(ssl_ctx->encrypt.pending, requested,
+ data, len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* Tell the caller there's more data readily available. */
+ if (status == APR_SUCCESS)
+ return status;
+ }
+
+ /* All encrypted data was returned, if there's more available depends
+ on what's pending on the to-be-encrypted stream. */
+ return status_unenc_stream;
+}
+
+static apr_status_t
+serf_macosxssl_encrypt_readline(serf_bucket_t *bucket,
+ int acceptable, int *found,
+ const char **data,
+ apr_size_t *len)
+{
+ serf__log(SSL_VERBOSE, __FILE__,
+ "function serf_macosxssl_encrypt_readline not implemented.\n");
+ return APR_ENOTIMPL;
+}
+
+
+static apr_status_t
+serf_macosxssl_encrypt_peek(serf_bucket_t *bucket,
+ const char **data,
+ apr_size_t *len)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+
+ return serf_bucket_peek(ssl_ctx->encrypt.pending, data, len);
+}
+
+static void
+serf_macosxssl_encrypt_destroy_and_data(serf_bucket_t *bucket)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+
+ if (!--ssl_ctx->refcount) {
+ macosxssl_free_context(ssl_ctx, bucket->allocator);
+ }
+
+ serf_bucket_ssl_destroy_and_data(bucket);
+}
+
+/**** DECRYPTION BUCKET API *****/
+/********************************/
+#pragma mark DECRYPTION BUCKET API
+#pragma mark -
+static apr_status_t
+serf_macosxssl_decrypt_peek(serf_bucket_t *bucket,
+ const char **data,
+ apr_size_t *len)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+
+ return serf_bucket_peek(ssl_ctx->decrypt.pending, data, len);
+}
+
+/* Ask Secure Transport to decrypt some more data. If anything was received,
+ add it to the to decrypt.pending buffer.
+ This function will read and decrypt all available data.
+ */
+static apr_status_t
+decrypt_more_data(macosxssl_context_t *ssl_ctx)
+{
+ /* Decrypt more data. */
+ serf_bucket_t *tmp;
+ char *dec_data;
+ size_t dec_len, available_len;
+ OSStatus osstatus;
+ apr_status_t status;
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "decrypt_more_data called.\n");
+
+ /* Read until the stream has no more data and until SSLRead decrypted
+ all data - which can take multiple calls! */
+ do {
+ /* We have to provide ST with a sufficiently large buffer for the
+ decrypted data. We can ask S.T. how many bytes are still buffered,
+ but this gives no indication on how many are still waiting to
+ be decrypted. Use it only to increase the bufsize over the
+ default (minimum) size READ_BUFSIZE. */
+ osstatus = SSLGetBufferedReadSize(ssl_ctx->st_ctxr, &available_len);
+ if (osstatus != noErr)
+ return translate_macosxssl_status(osstatus);
+ else {
+ if (available_len < READ_BUFSIZE)
+ available_len = READ_BUFSIZE;
+ }
+ /* TODO: SSLRead doesn't always return a lot of data in one go, so
+ it makes sense to continue receiving data in the remainder of this
+ buffer before allocating a new buffer. */
+ dec_data = serf_bucket_mem_alloc(ssl_ctx->decrypt.pending->allocator,
+ available_len);
+
+ osstatus = SSLRead(ssl_ctx->st_ctxr, dec_data,
+ available_len,
+ &dec_len);
+ status = translate_macosxssl_status(osstatus);
+
+ /* SSLRead can put data in dec_data while returning an error status. */
+ if (SERF_BUCKET_READ_ERROR(status) && !dec_len)
+ return status;
+
+ /* Successfully received and decrypted some data, add to pending. */
+ serf__log(SSL_MSG_VERBOSE, __FILE__, " received and decrypted data:"
+ "---\n%.*s\n-(%d)-\n", dec_len, dec_data, dec_len);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(dec_data, dec_len,
+ ssl_ctx->decrypt.pending->allocator);
+ serf_bucket_aggregate_append(ssl_ctx->decrypt.pending, tmp);
+
+ } while (status == APR_SUCCESS);
+
+ return status;
+}
+
+static apr_status_t
+serf_macosxssl_decrypt_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+ apr_status_t status;
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "serf_macosxssl_decrypt_read called for %d bytes.\n", requested);
+
+ /* Pending handshake? */
+ status = do_handshake(ssl_ctx);
+ if (status) {
+ *len = 0;
+ return status;
+ }
+
+ /* First use any pending encrypted data. */
+ status = serf_bucket_read(ssl_ctx->decrypt.pending,
+ requested, data, len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ if (*len)
+ return status;
+
+ /* Pending buffer empty, decrypt more. */
+ status = decrypt_more_data(ssl_ctx);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* We should now have more decrypted data in the pending buffer. */
+ return serf_bucket_read(ssl_ctx->decrypt.pending, requested, data,
+ len);
+}
+
+static apr_status_t
+serf_macosxssl_decrypt_readline(serf_bucket_t *bucket,
+ int acceptable, int *found,
+ const char **data,
+ apr_size_t *len)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+ apr_status_t status;
+
+ /* Pending handshake? */
+ status = do_handshake(ssl_ctx);
+ if (status) {
+ *len = 0;
+ *found = SERF_NEWLINE_NONE;
+ return status;
+ }
+
+ /* First use any pending encrypted data. */
+ status = serf_bucket_readline(ssl_ctx->decrypt.pending, acceptable, found,
+ data, len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ if (*len) {
+ /* We read a partial or complete line */
+ return status;
+ }
+
+ /* Pending buffer empty, decrypt more. */
+ status = decrypt_more_data(ssl_ctx);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* We have more decrypted data in the pending buffer. */
+ status = serf_bucket_readline(ssl_ctx->decrypt.pending, acceptable, found,
+ data, len);
+
+ return status;
+}
+
+static void
+serf_macosxssl_decrypt_destroy_and_data(serf_bucket_t *bucket)
+{
+ macosxssl_context_t *ssl_ctx = bucket->data;
+
+ if (!--ssl_ctx->refcount) {
+ macosxssl_free_context(ssl_ctx, bucket->allocator);
+ }
+
+ serf_bucket_ssl_destroy_and_data(bucket);
+}
+
+/**** Helper function to fetch info from Keychain ****/
+/*****************************************************/
+#pragma mark KEYCHAIN HELPER FUNCTIONS
+#pragma mark -
+apr_status_t
+serf_macosxssl_show_trust_certificate_panel(serf_ssl_context_t *ctx,
+ const char *message,
+ const char *ok_button,
+ const char *cancel_button)
+{
+#ifdef SERF_HAVE_MACOSXSSL
+ macosxssl_context_t *ssl_ctx = serf__ssl_get_impl_context(ctx);
+ SecTrustRef trust = ssl_ctx->trust;
+ apr_status_t status;
+
+ void *macosxssl_cls = objc_getClass("macosxssl_Buckets");
+ id tmp = objc_msgSend(macosxssl_cls,
+ sel_getUid("showTrustCertificateDialog:message:"
+ "ok_button:cancel_button:"),
+ trust, message, ok_button, cancel_button);
+ status = (apr_status_t)(SInt64)tmp;
+
+ return status;
+#else
+ return APR_ENOTIMPL;
+#endif
+}
+
+apr_status_t
+serf_macosxssl_show_select_identity_panel(serf_ssl_context_t *ctx,
+ const serf_ssl_identity_t **identity,
+ const char *message,
+ const char *ok_button,
+ const char *cancel_button,
+ apr_pool_t *pool)
+{
+#ifdef SERF_HAVE_MACOSXSSL
+ SecIdentityRef identityref;
+ OSStatus osstatus;
+
+ void *macosxssl_cls = objc_getClass("MacOSXSSL_Buckets");
+ id tmp = objc_msgSend(macosxssl_cls,
+ sel_getUid("showSelectIdentityDialog:message:"
+ "ok_button:cancel_button:"),
+ &identityref, message, ok_button, cancel_button);
+ osstatus = (OSStatus)(SInt64)tmp;
+ if (osstatus == errSecItemNotFound)
+ {
+ /* There is no single identity in the keychains. */
+ return SERF_ERROR_SSL_NO_IDENTITIES_AVAILABLE;
+ } else if (osstatus != noErr) {
+ return translate_macosxssl_status(osstatus);
+ }
+
+ if (!identityref)
+ return SERF_ERROR_SSL_CERT_FAILED;
+
+ *identity = serf__create_identity(&serf_ssl_bucket_type_macosxssl,
+ identityref, NULL,
+ pool);
+ return APR_SUCCESS;
+#else
+ return APR_ENOTIMPL;
+#endif
+}
+
+apr_status_t
+serf_macosxssl_find_preferred_identity_in_keychain(
+ serf_ssl_context_t *ctx,
+ const serf_ssl_identity_t **identity,
+ apr_pool_t *pool)
+{
+#ifdef SERF_HAVE_MACOSXSSL
+ apr_pool_t *tmppool;
+ macosxssl_context_t *ssl_ctx = serf__ssl_get_impl_context(ctx);
+ SecIdentityRef identityref = NULL;
+ CFStringRef labelref;
+ const char *label;
+ apr_status_t status;
+
+ /* We can get the distinguished names from the server with
+ SSLCopyDistinguishedNames to filter matching client certificates,
+ but we can't pass this list to the application, and
+ SecIdentityCopyPreferred doesn't have this feature implemented
+ either. So, don't bother. */
+
+ /* We absolutelely need an item that can sign. Otherwise we will get
+ an incomplete identity object, with which SecIdentityCopyCertificate
+ will crash. */
+ const void *keyUsage[] = { kSecAttrCanSign };
+ CFArrayRef keyUsageRef = CFArrayCreate(kCFAllocatorDefault,
+ (void *)keyUsage,
+ 1,
+ NULL);
+
+ apr_pool_create(&tmppool, pool);
+
+ /* Find an identity preference using label https://<hostname> */
+ label = apr_pstrcat(tmppool, "https://", ssl_ctx->hostname, NULL);
+ labelref = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
+ (unsigned char *)label,
+ strlen(label),
+ kCFStringEncodingMacRoman,
+ false,
+ kCFAllocatorNull);
+ identityref = SecIdentityCopyPreferred(labelref,
+ keyUsageRef, NULL);
+ if (identityref)
+ {
+
+ *identity = serf__create_identity(&serf_ssl_bucket_type_macosxssl,
+ identityref, NULL, pool);
+ status = APR_SUCCESS;
+ } else {
+ *identity = NULL;
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ }
+
+ CFRelease(labelref);
+ CFRelease(keyUsageRef);
+ apr_pool_destroy(tmppool);
+
+ return status;
+#else
+ return APR_ENOTIMPL;
+#endif
+}
+
+/*****************************************************************************/
+const serf_bucket_type_t serf_bucket_type_macosxssl_encrypt = {
+ "MACOSXSSLENCRYPT",
+ serf_macosxssl_encrypt_read,
+ serf_macosxssl_encrypt_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_macosxssl_encrypt_peek,
+ serf_macosxssl_encrypt_destroy_and_data,
+};
+
+const serf_bucket_type_t serf_bucket_type_macosxssl_decrypt = {
+ "MACOSXSSLDECRYPT",
+ serf_macosxssl_decrypt_read,
+ serf_macosxssl_decrypt_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_macosxssl_decrypt_peek,
+ serf_macosxssl_decrypt_destroy_and_data,
+};
+
+const serf_ssl_bucket_type_t serf_ssl_bucket_type_macosxssl = {
+ serf__macosxssl_decrypt_create,
+ serf__macosxssl_decrypt_context_get,
+ serf__macosxssl_encrypt_create,
+ serf__macosxssl_encrypt_context_get,
+ serf__macosxssl_set_hostname,
+ serf__macosxssl_client_cert_provider_set,
+ serf__macosxssl_identity_provider_set,
+ serf__macosxssl_client_cert_password_set,
+ serf__macosxssl_server_cert_callback_set,
+ serf__macosxssl_server_cert_chain_callback_set,
+ serf__macosxssl_use_default_certificates,
+ serf__macosxssl_load_CA_cert_from_file,
+ serf__macosxssl_load_identity_from_file,
+ serf__macosxssl_trust_cert,
+ serf__macosxssl_cert_issuer,
+ serf__macosxssl_cert_subject,
+ serf__macosxssl_cert_certificate,
+ serf__macosxssl_cert_export,
+ serf__macosxssl_use_compression,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+#else
+
+const serf_bucket_type_t serf_bucket_type_macosxssl_encrypt = {
+ "MACOSXSSLENCRYPT",
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+};
+const serf_bucket_type_t serf_bucket_type_macosxssl_decrypt = {
+ "MACOSXSSLDECRYPT",
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+};
+
+#endif /* SERF_HAVE_MACOSXSSL */
diff --git a/buckets/macosxssl_helper.m b/buckets/macosxssl_helper.m
new file mode 100644
index 0000000..9ece1e8
--- /dev/null
+++ b/buckets/macosxssl_helper.m
@@ -0,0 +1,170 @@
+/* Copyright 2013 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+#import <SecurityInterface/SFCertificateTrustPanel.h>
+#import <SecurityInterface/SFChooseIdentityPanel.h>
+
+#import "serf.h"
+
+@interface MacOSXSSL_Buckets : NSObject
+{
+}
++ (OSStatus) evaluate:(SecTrustRef)trust
+ trustResult:(SecTrustResultType *)trustResult;
+
++ (apr_status_t) showTrustCertificateDialog:(SecTrustRef)trust
+ message:(const char *)message
+ ok_button:(const char *)ok_button
+ cancel_button:(const char *)cancel_button;
+@end
+
+@implementation MacOSXSSL_Buckets
+
+/* Evaluate the trust object asynchronously. When the results are received,
+ store them in the provided resultPtr address. */
++ (OSStatus) evaluate:(SecTrustRef)trust
+ trustResult:(SecTrustResultType *)resultPtr
+{
+ dispatch_queue_t queue;
+ OSStatus osstatus;
+
+ SecTrustCallback block = ^(SecTrustRef trust, SecTrustResultType trustResult)
+ {
+ *resultPtr = trustResult;
+ };
+
+ queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0l);
+ osstatus = SecTrustEvaluateAsync(trust, queue, block);
+
+ return osstatus;
+}
+
++ (apr_status_t) showTrustCertificateDialog:(SecTrustRef)trust
+ message:(const char *)message
+ ok_button:(const char *)ok_button
+ cancel_button:(const char *)cancel_button
+{
+ NSString *MessageLbl = [[NSString alloc] initWithUTF8String:message];
+ NSString *OkButtonLbl = [[NSString alloc] initWithUTF8String:ok_button];
+ NSString *CancelButtonLbl;
+ NSApplication *app;
+ SFCertificateTrustPanel *panel;
+ NSInteger result;
+
+ if (cancel_button)
+ CancelButtonLbl = [[NSString alloc] initWithUTF8String:cancel_button];
+
+ app = [NSApplication sharedApplication];
+ panel = [SFCertificateTrustPanel sharedCertificateTrustPanel];
+
+ /* Put the dialog in front of the application, and give it the focus. */
+ [app setActivationPolicy:NSApplicationActivationPolicyRegular];
+ [app activateIgnoringOtherApps:YES];
+
+ [panel setShowsHelp:YES];
+ [panel setDefaultButtonTitle:OkButtonLbl];
+ if (cancel_button)
+ [panel setAlternateButtonTitle:CancelButtonLbl];
+
+ result = [panel runModalForTrust:trust
+ message:MessageLbl];
+
+ [panel release];
+ [MessageLbl release];
+ [OkButtonLbl release];
+ if (cancel_button)
+ [CancelButtonLbl release];
+
+ if (result)
+ return APR_SUCCESS;
+ else
+ return SERF_ERROR_SSL_USER_DENIED_CERT;
+}
+
++ (OSStatus) showSelectIdentityDialog:(SecIdentityRef *)identity
+ message:(const char *)message
+ ok_button:(const char *)ok_button
+ cancel_button:(const char *)cancel_button
+{
+ NSString *MessageLbl = [[NSString alloc] initWithUTF8String:message];
+ NSString *OkButtonLbl = [[NSString alloc] initWithUTF8String:ok_button];
+ NSString *CancelButtonLbl;
+ NSApplication *app;
+ SFChooseIdentityPanel *panel;
+ NSArray *identities;
+ NSDictionary *query;
+ NSInteger result;
+ OSStatus osstatus;
+
+ if (cancel_button)
+ CancelButtonLbl = [[NSString alloc] initWithUTF8String:cancel_button];
+
+ /* Find all matching identities in the keychains.
+ Note: SecIdentityRef items are not stored on the keychain but
+ generated when needed if both a certificate and matching private
+ key are available on the keychain. */
+ query = [NSDictionary dictionaryWithObjectsAndKeys:
+ (id)kSecClassIdentity, (id)kSecClass,
+ (id)kCFBooleanTrue, (id)kSecAttrCanSign,
+ (id)kSecMatchLimitAll, (id)kSecMatchLimit,
+ (id)kCFBooleanTrue, (id)kSecReturnRef,
+ nil];
+
+ osstatus = SecItemCopyMatching((CFDictionaryRef)query,
+ (CFTypeRef *)&identities);
+ [query release];
+
+ if (osstatus != noErr)
+ return osstatus;
+
+ /* TODO: filter on matching certificates. How?? Distinguished names? */
+
+ /* Found the identities, now let the user choose. */
+ app = [NSApplication sharedApplication];
+
+ panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
+
+ /* Put the dialog in front of the application, and give it the focus. */
+ [app setActivationPolicy:NSApplicationActivationPolicyRegular];
+ [app activateIgnoringOtherApps:YES];
+
+ [panel setShowsHelp:YES];
+ [panel setDefaultButtonTitle:OkButtonLbl];
+ if (cancel_button)
+ [panel setAlternateButtonTitle:CancelButtonLbl];
+
+ result = [panel runModalForIdentities:identities
+ message:MessageLbl];
+
+ if (result) {
+ *identity = [panel identity];
+ }
+ else {
+ *identity = nil;
+ }
+
+ [identities autorelease];
+ [MessageLbl release];
+ [OkButtonLbl release];
+ if (cancel_button)
+ [CancelButtonLbl release];
+ [panel release];
+
+ return noErr;
+}
+
+@end
diff --git a/buckets/macosxssl_x509_cert.c b/buckets/macosxssl_x509_cert.c
new file mode 100644
index 0000000..c081146
--- /dev/null
+++ b/buckets/macosxssl_x509_cert.c
@@ -0,0 +1,621 @@
+/* Copyright 2013 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef SERF_HAVE_MACOSXSSL
+
+#include <stdlib.h>
+
+#include "serf.h"
+#include "serf_private.h"
+#include "serf_bucket_util.h"
+#include "bucket_private.h"
+
+#include <apr_strings.h>
+#include <apr_sha1.h>
+
+#include <Security/SecCertificate.h>
+#include <Security/SecCertificateOIDs.h>
+
+#define ST_DEBUG 0
+
+/* This code reads all items DER-encoded certificate in X.509 format, as
+ provided by the Keychain services API. */
+
+/* Read ASN.1 data type OID from a DER encoded buffer. */
+static const char *
+read_DER_OID(const unsigned char* ptr, int clen, apr_pool_t *pool)
+{
+ unsigned char b;
+ char *value;
+
+ /* First two components of the OID are encoded in the first byte. */
+ b = *ptr++;
+ clen--;
+
+ value = apr_psprintf(pool, "%d.%d", b/40, b%40);
+
+
+ /* one or two bytes per component */
+ while (clen > 0) {
+ b = *ptr++;
+ clen--;
+
+ if (! (b & 0x80)) {
+ value = apr_psprintf(pool, "%s.%d", value, b);
+ } else {
+ unsigned comp = 0;
+
+ while (b & 0x80 && clen > 0) {
+ comp <<= 7;
+ comp |= (b & 0x7f);
+ b = *ptr++;
+ clen--;
+ }
+ comp <<= 7;
+ comp |= (b & 0x7f);
+
+ value = apr_psprintf(pool, "%s.%d", value, comp);
+ }
+ }
+
+ serf__log(ST_DEBUG, __FILE__, "OID of length %d, value: %s .\n",
+ clen, value);
+
+ return value;
+}
+
+/* Read ASN.1 data type PrintableString from a DER encoded buffer.
+ TODO: currently also used for UTF8String, use specific function for those. */
+static const char *
+read_DER_string(const unsigned char* ptr, int clen, apr_pool_t *pool)
+{
+ const char *value = apr_pstrndup(pool, (const char*)ptr, clen);
+
+ serf__log(ST_DEBUG, __FILE__, "string of length %d, value: %s.\n",
+ clen, value);
+
+ return value;
+}
+
+/* Read ASN.1 data type BOOLEAN from a DER encoded buffer. */
+static const char *
+read_DER_boolean(const unsigned char* ptr, int clen, apr_pool_t *pool)
+{
+ unsigned char v = *ptr++;
+
+ serf__log(ST_DEBUG, __FILE__, "Boolean of length %d, value: %s.\n",
+ clen, v ? "TRUE" : "FALSE");
+
+ return v ? "TRUE" : "FALSE";
+}
+
+/* Read ASN.1 data type BIT STRING from a DER encoded buffer. */
+static const char *
+read_DER_bitstring(const unsigned char* ptr, int clen, apr_pool_t *pool)
+{
+ char *value = "";
+
+ /* TODO: take into account unused_bits. */
+ /* unsigned char unused_bits = *ptr++; */
+ ptr++;
+ clen--;
+
+ while (clen-- > 0) {
+ unsigned char b = *ptr++;
+ value = apr_psprintf(pool, "%s %2x", value, b);
+ }
+
+ serf__log(ST_DEBUG, __FILE__, "Bitstring of length %d, value: %s.\n",
+ clen, value);
+
+ return value;
+}
+
+/* Read ASN.1 data type INTEGER from a DER encoded buffer. */
+static const char *
+read_DER_integer(const unsigned char* ptr, int clen, apr_pool_t *pool)
+{
+ unsigned char lb = *ptr++;
+ int positive = 0;
+ int i;
+ unsigned long value = 0;
+
+ if (lb == 0x00) {
+ positive = 1;
+ clen -= positive;
+
+
+ if (clen <= sizeof(long))
+ for (i = 0; i < clen; i++)
+ value = (value << 8) + *ptr++;
+ } else if (! (lb & 0x80)) {
+ positive = 1;
+ value = lb;
+ } else {
+ /* negative number */
+ return apr_psprintf(pool, "Negative integer not supported.");
+ }
+
+ serf__log(ST_DEBUG, __FILE__, "%s integer of length %d, value: %lx.\n",
+ positive ? "Positive" : "Negative", clen, value);
+
+ return apr_psprintf(pool, "%lx", value);
+}
+
+/* Read DER Tag and Length from a DER encoded buffer. */
+static apr_status_t
+read_DER_TL(const unsigned char* ptr, unsigned char *tag,
+ long *value_len, long *consumed)
+{
+ int constr_enc;
+ char lb1;
+ long clen = 0;
+
+ /* read tag */
+ *tag = *ptr++;
+ constr_enc = *tag & 0x20;
+ *consumed = 1;
+
+ /* read length */
+ lb1 = *ptr++;
+ if (! (lb1 & 0x80)) {
+ clen = lb1;
+ (*consumed)++;
+ } else {
+ int i;
+ lb1 &= 0x7f;
+ if (lb1 > sizeof(long))
+ return APR_ENOTIMPL;
+ for (i = 0; i < lb1; i++)
+ clen = (clen << 8) + *ptr++;
+ *consumed += (lb1 + 1);
+ }
+
+ *value_len = clen;
+
+ serf__log(ST_DEBUG, __FILE__,
+ "tag: %x, value length: %d, header length: %d.\n",
+ *tag, *value_len, *consumed);
+
+ return APR_SUCCESS;
+}
+
+/* Read DER Tag, Length and primitive Value from a DER encoded buffer.
+ Don't use this for constructed types SEQUENCE or SET! */
+static apr_status_t
+read_DER_TLvalue(const unsigned char *ptr, unsigned char *tag, long *consumed,
+ const char **value, apr_pool_t *pool)
+{
+ long tl_len, value_len;
+
+ read_DER_TL(ptr, tag, &value_len, &tl_len);
+ ptr += tl_len;
+
+ switch (*tag) {
+ case 0x01:
+ *value = read_DER_boolean(ptr, value_len, pool);
+ break;
+ case 0x02:
+ *value = read_DER_integer(ptr, value_len, pool);
+ break;
+ case 0x03:
+ *value = read_DER_bitstring(ptr, value_len, pool);
+ break;
+ case 0x05:
+ *value = "";
+ serf__log(ST_DEBUG, __FILE__, "NULL value.\n");
+ break;
+ case 0x06:
+ *value = read_DER_OID(ptr, value_len, pool);
+ break;
+ case 0x17: /* Date */
+ case 0x16: /* IA5String */
+ case 0x13: /* PrintableString */
+ case 0x14: /* TeletexString */
+ case 0x0c: /* UTF8String */
+ *value = read_DER_string(ptr, value_len, pool);
+ break;
+
+ case 0xa0: /* Explicit tag 0, in X509 used for version. */
+ default:
+ {
+ *value = "ERROR";
+ serf__log(ST_DEBUG, __FILE__, "UNSUPPORTED TAG TYPE %2x.\n", *tag);
+ return APR_ENOTIMPL;
+ break;
+ }
+ }
+
+ *consumed = tl_len + value_len;
+
+ return APR_SUCCESS;
+}
+
+/* Recursively skip an entire Tag-Length-Value block. */
+static apr_status_t
+skip_DER_TLV(const unsigned char *ptr, unsigned char *tag, long *consumed)
+{
+ long tl_len, value_len;
+ apr_status_t status;
+
+ status = read_DER_TL(ptr, tag, &value_len, &tl_len);
+ if (status)
+ return status;
+
+ *consumed = tl_len + value_len;
+
+ return APR_SUCCESS;
+}
+
+#define SERF_ERR(x) status = (x);\
+ if (status) goto cleanup;
+
+/* Reads an issuer or subject structure from PTR, which should point to the
+ value of tag type 0x30 grouping either issuer or subject.
+ Caller should clean up out_der. */
+static apr_status_t
+read_X509_DER_DistinguishedName(apr_hash_t **o, CFDataRef *out_der,
+ const unsigned char *ptr,
+ long *total_len, apr_pool_t *pool)
+{
+ unsigned char tag;
+ long len, object_len, consumed;
+ apr_status_t status;
+ apr_hash_t *tgt;
+ char *tmp;
+
+ tgt = apr_hash_make(pool);
+
+ /* RelativeDistinguishedName Sequence. */
+ SERF_ERR(read_DER_TL(ptr, &tag, &object_len, &consumed));
+
+ /* Copy this whole structure in out_der. */
+ tmp = apr_palloc(pool, object_len);
+ memcpy(tmp, ptr, object_len);
+ *out_der = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
+ (unsigned char *)tmp,
+ object_len,
+ kCFAllocatorNull);
+
+ ptr += consumed;
+
+ *total_len = object_len + consumed;
+ /* For each component */
+ while (object_len > 0) {
+ const char *key, *value;
+ long consumed;
+ CFStringRef keyref;
+
+ /* RelativeDistinguishedName Set. */
+ SERF_ERR(read_DER_TL(ptr, &tag, &len, &consumed));
+ ptr += consumed; object_len -= consumed;
+
+ /* AttributeTypeAndValue, containing OID-value pair. */
+ SERF_ERR(read_DER_TL(ptr, &tag, &len, &consumed));
+ ptr += consumed; object_len -= consumed;
+
+ /* Read key OID */
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &key, pool));
+ ptr += consumed; object_len -= consumed;
+
+ /* OID constants are CFStringRef, so need to use CFStringCompare. */
+ keyref = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
+ (unsigned char *)key,
+ strlen(key),
+ kCFStringEncodingMacRoman,
+ false,
+ kCFAllocatorNull);
+
+ if (CFStringCompare(keyref,
+ kSecOIDOrganizationalUnitName, 0) == kCFCompareEqualTo)
+ key = "OU";
+ else if (CFStringCompare(keyref,
+ kSecOIDOrganizationName, 0) == kCFCompareEqualTo)
+ key = "O";
+ else if (CFStringCompare(keyref,
+ kSecOIDLocalityName, 0) == kCFCompareEqualTo)
+ key = "L";
+ else if (CFStringCompare(keyref,
+ kSecOIDStateProvinceName, 0) == kCFCompareEqualTo)
+ key = "ST";
+ else if (CFStringCompare(keyref,
+ kSecOIDCountryName, 0) == kCFCompareEqualTo)
+ key = "C";
+ else if (CFStringCompare(keyref,
+ kSecOIDEmailAddress, 0) == kCFCompareEqualTo)
+ key = "E";
+ else if (CFStringCompare(keyref,
+ kSecOIDCommonName, 0) == kCFCompareEqualTo)
+ key = "CN";
+
+ CFRelease(keyref);
+
+ /* Read value */
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
+ ptr += consumed; object_len -= consumed;
+
+ serf__log(ST_DEBUG, __FILE__, "Remaining: %d.\n", object_len);
+
+ apr_hash_set(tgt, key, APR_HASH_KEY_STRING, value);
+ }
+
+ *o = tgt;
+ return APR_SUCCESS;
+
+cleanup:
+ return status;
+}
+
+/* Reads date/time string formatted as "YYMMDDHHmmSSZ" and converts it to
+ "MMM dd HH:mm:ss yyyy GMT". Allocate output string from pool.
+ Example: in: 130821204210Z out:Aug 13 20:42:10 2013 GMT . */
+static const char *
+convert_cert_date(const char *in, apr_pool_t *pool)
+{
+ apr_size_t len;
+ apr_time_exp_t te;
+ const int bufsize = 64;
+ char *datestr;
+
+ memset(&te, 0, sizeof(te));
+
+ datestr = apr_palloc(pool, bufsize);
+
+ /* Read 2000+yy into 1900+100+yy */
+ te.tm_year = 100 + (*in -'0') * 10 + *(in+1) - '0'; in+=2;
+ te.tm_mon = (*in -'0') * 10 + *(in+1) - '0' - 1; in+=2;
+ te.tm_mday = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
+ te.tm_hour = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
+ te.tm_min = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
+ te.tm_sec = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
+
+ apr_strftime(datestr, &len, bufsize, "%b %d %T %Y GMT", &te);
+
+ return datestr;
+}
+
+/* Calculate the sha1 hash value of data of length len. Resulting string will be
+ allocated from pool and encoded as hex bytes : separated (A1:81:3F ... ). */
+static const unsigned char *
+sha1digest(const unsigned char *data, long len, apr_pool_t *pool)
+{
+ apr_sha1_ctx_t context;
+ unsigned char *sha1 = apr_pcalloc(pool, APR_SHA1_DIGESTSIZE);
+ unsigned char *sha1hex = apr_pcalloc(pool, APR_SHA1_DIGESTSIZE * 3);
+ unsigned char *inptr = sha1, *outptr = sha1hex;
+ const char al[] = "0123456789ABCDEF";
+ int i;
+
+ apr_sha1_init(&context);
+ apr_sha1_update_binary(&context, data, len);
+ apr_sha1_final(sha1, &context);
+
+ /* concert to :-separated hex bytes */
+ for (i = 0; i < APR_SHA1_DIGESTSIZE; i++) {
+ unsigned char c = *inptr++;
+
+ *outptr++ = al[(c & 0xf0) >> 4];
+ *outptr++ = al[c & 0x0f];
+ *outptr++ = ':';
+ }
+ *(outptr-1) = '\0';
+
+ return sha1hex;
+}
+
+static apr_status_t dataref_cleanup(void *data)
+{
+ CFDataRef derdata = data;
+
+ CFRelease(derdata);
+
+ return APR_SUCCESS;
+}
+
+/* Read a Distinquished Name from a DER-encoded DN in X.509 format.
+ The resulting hash table will have following keys:
+ - CN, O, OU, L, ST, C, E.
+ Internal use only:
+ - _der
+ */
+
+apr_status_t
+serf__macosxssl_read_X509_DER_DN(apr_hash_t **o, CFDataRef dndata,
+ apr_pool_t *pool)
+{
+ CFDataRef dnder;
+ apr_hash_t *dn;
+ long consumed;
+ const unsigned char *data = CFDataGetBytePtr(dndata);
+ apr_status_t status;
+
+ SERF_ERR(read_X509_DER_DistinguishedName(&dn, &dnder,
+ data, &consumed, pool));
+
+ apr_hash_set(dn, "_der", APR_HASH_KEY_STRING, dnder);
+
+ *o = dn;
+
+cleanup:
+ return status;
+}
+
+/* Read all interesting data from a DER-encoded certificate in X.509 format.
+ The resulting hash table will have following keys:
+ - sha1
+ - serial_number (TODO: not used, can be removed if that remains so. )
+ - issuer: hash table with keys CN, O, OU, L, ST, C, E.
+ - subject: hash table with keys CN, O, OU, L, ST, C, E.
+ - notBefore
+ - notAfter
+ Internal use only:
+ - _issuer_der
+ - _subject_der
+ */
+apr_status_t
+serf__macosxssl_read_X509_DER_certificate(apr_hash_t **o,
+ const macosxssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ apr_hash_t *x509_cert, *issuer, *subject;
+ CFDataRef issuer_der, subject_der;
+ long consumed, x509_len, value_len, signature_start;
+ unsigned char tag;
+ apr_status_t status;
+ const char *serial, *tmp, *key, *value;
+ const unsigned char *ptr, *sha1;
+ int version;
+
+ SecCertificateRef certref = cert->certref;
+ CFDataRef dataref = SecCertificateCopyData(certref);
+ const unsigned char *data = CFDataGetBytePtr(dataref);
+ CFIndex totlen = CFDataGetLength(dataref);
+
+ if (!totlen)
+ return SERF_ERROR_SSL_CERT_FAILED;
+
+ x509_cert = apr_hash_make(pool);
+
+ /* SHA1 fingerprint of the full DER encoded cert. */
+ sha1 = sha1digest(data, totlen, pool);
+ apr_hash_set(x509_cert, "sha1", APR_HASH_KEY_STRING, sha1);
+ serf__log(ST_DEBUG, __FILE__, "SHA1 fingerprint:%s.\n", sha1);
+
+ ptr = data;
+ /* 4.1.1 Certificate sequence */
+ SERF_ERR(read_DER_TL(ptr, &tag, &x509_len, &consumed));
+ ptr += consumed;
+ signature_start = consumed;
+
+ serf__log(ST_DEBUG, __FILE__, "Parsing DER encoding of cert length: %d.\n",
+ x509_len + consumed);
+
+ /* 4.1.2 TBSCertificate (required) */
+ serf__log(ST_DEBUG, __FILE__, "---- TBSCertificate ----.\n");
+ SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
+ ptr += consumed; x509_len -= consumed;
+ signature_start += (value_len + consumed);
+
+ /* 4.1.2.1 Version (optional, default v1 (0x00)) */
+ serf__log(ST_DEBUG, __FILE__, "---- Version ----.\n");
+ SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
+ if (tag == 0xa0) {
+ ptr += consumed; x509_len -= consumed;
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &tmp, pool));
+ version = atoi(tmp) + 1;
+ ptr += consumed; x509_len -= consumed;
+ } else {
+ /* this was another tag than expected. Means that version wasn't set,
+ used the default v1. */
+ version = 1;
+ }
+
+ /* 4.1.2.2 Serial number */
+ serf__log(ST_DEBUG, __FILE__, "---- Serial Number ----.\n");
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &serial, pool));
+ ptr += consumed; x509_len -= consumed;
+ apr_hash_set(x509_cert, "serial_number", APR_HASH_KEY_STRING, serial);
+
+ /* 4.1.2.3 Signature (it's actually the Algorithm used to sign). */
+ serf__log(ST_DEBUG, __FILE__, "---- Algorithm ----.\n");
+ skip_DER_TLV(ptr, &tag, &consumed);
+ ptr += consumed; x509_len -= consumed;
+
+ /* 4.1.2.4 Issuer */
+ serf__log(ST_DEBUG, __FILE__, "---- Issuer ----.\n");
+ SERF_ERR(read_X509_DER_DistinguishedName(&issuer, &issuer_der,
+ ptr, &consumed, pool));
+ ptr += consumed; x509_len -= consumed;
+ apr_hash_set(x509_cert, "issuer", APR_HASH_KEY_STRING, issuer);
+ /* store the original der data buffer of the issuer, for internal use
+ (comparison of certificates). */
+ apr_hash_set(x509_cert, "_issuer_der", APR_HASH_KEY_STRING, issuer_der);
+
+ /* 4.1.2.5 Validity */
+ serf__log(ST_DEBUG, __FILE__, "---- Validity ----.\n");
+ SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
+ ptr += consumed; x509_len -= consumed;
+
+ /* notBefore */
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &tmp, pool));
+ ptr += consumed; x509_len -= consumed;
+ apr_hash_set(x509_cert, "notBefore", APR_HASH_KEY_STRING,
+ convert_cert_date(tmp, pool));
+
+ /* notAfter */
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &tmp, pool));
+ ptr += consumed; x509_len -= consumed;
+ apr_hash_set(x509_cert, "notAfter", APR_HASH_KEY_STRING,
+ convert_cert_date(tmp, pool));
+
+ /* 4.1.2.6 Subject */
+ serf__log(ST_DEBUG, __FILE__, "---- Subject ----.\n");
+ SERF_ERR(read_X509_DER_DistinguishedName(&subject, &subject_der,
+ ptr, &consumed, pool));
+ ptr += consumed; x509_len -= consumed;
+ apr_hash_set(x509_cert, "subject", APR_HASH_KEY_STRING, subject);
+ /* store the original der data buffer of the subject, for internal use
+ (comparison of certificates). */
+ apr_hash_set(x509_cert, "_subject_der", APR_HASH_KEY_STRING, subject_der);
+
+ /* 4.1.2.7 Subject Public Key Info */
+ serf__log(ST_DEBUG, __FILE__, "---- Subject Public Key Info ----.\n");
+ SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
+ ptr += consumed; x509_len -= consumed;
+ /* AlgorithmIdentifier */
+ SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
+ ptr += consumed; x509_len -= consumed;
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &key, pool));
+ ptr += consumed; x509_len -= consumed;
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
+ ptr += consumed; x509_len -= consumed;
+
+ /* TODO: read subjectAltName's from extensions. */
+
+ /* Skip the remainder of TBSCertificate*/
+ ptr = data + signature_start;
+
+#if 0 /* Not needed, cleanup if this remains so. */
+ /* 4.1.1.2 signatureAlgorithm (required) */
+ SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
+ ptr += consumed; x509_len -= consumed;
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &key, pool));
+ ptr += consumed; x509_len -= consumed;
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
+ ptr += consumed; x509_len -= consumed;
+
+ /* 4.1.1.3 signatureValue (required) */
+ SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
+ ptr += consumed; x509_len -= consumed;
+
+ serf__log(ST_DEBUG, __FILE__, "Remaining to read: %d.\n", x509_len);
+#endif
+
+ *o = x509_cert;
+
+ apr_pool_cleanup_register(pool, issuer_der, dataref_cleanup,
+ dataref_cleanup);
+ apr_pool_cleanup_register(pool, subject_der, dataref_cleanup,
+ dataref_cleanup);
+
+ return APR_SUCCESS;
+
+cleanup:
+ CFRelease(dataref);
+
+ return status;
+}
+
+#endif /* SERF_HAVE_MACOSXSSL */
diff --git a/buckets/openssl_buckets.c b/buckets/openssl_buckets.c
new file mode 100644
index 0000000..6c7d7a0
--- /dev/null
+++ b/buckets/openssl_buckets.c
@@ -0,0 +1,2069 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ----
+ *
+ * For the OpenSSL thread-safety locking code:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Originally developed by Aaron Bannert and Justin Erenkrantz, eBuilt.
+ */
+
+#include <apr_pools.h>
+#include <apr_network_io.h>
+#include <apr_portable.h>
+#include <apr_strings.h>
+#include <apr_base64.h>
+#include <apr_version.h>
+#include <apr_atomic.h>
+
+#include "serf.h"
+#include "serf_private.h"
+#include "serf_bucket_util.h"
+#include "bucket_private.h"
+
+#ifdef SERF_HAVE_OPENSSL
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/pkcs12.h>
+#include <openssl/x509v3.h>
+
+#ifndef APR_VERSION_AT_LEAST /* Introduced in APR 1.3.0 */
+#define APR_VERSION_AT_LEAST(major,minor,patch) \
+ (((major) < APR_MAJOR_VERSION) \
+ || ((major) == APR_MAJOR_VERSION && (minor) < APR_MINOR_VERSION) \
+ || ((major) == APR_MAJOR_VERSION && (minor) == APR_MINOR_VERSION && \
+ (patch) <= APR_PATCH_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
+
+#ifndef APR_ARRAY_PUSH
+#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary)))
+#endif
+
+
+/*
+ * Here's an overview of the SSL bucket's relationship to OpenSSL and serf.
+ *
+ * HTTP request: OPENSSLENCRYPT(REQUEST)
+ * [context.c reads from OPENSSLENCRYPT and writes out to the socket]
+ * HTTP response: RESPONSE(OPENSSLDECRYPT(SOCKET))
+ * [handler function reads from RESPONSE which in turn reads from
+ * OPENSSLDECRYPT]
+ *
+ * HTTP request read call path:
+ *
+ * write_to_connection
+ * |- serf_bucket_read on OPENSSLENCRYPT
+ * |- serf_openssl_read
+ * |- serf_databuf_read
+ * |- common_databuf_prep
+ * |- ssl_encrypt
+ * |- 1. Try to read pending encrypted data; If available, return.
+ * |- 2. Try to read from ctx->stream [REQUEST bucket]
+ * |- 3. Call SSL_write with read data
+ * |- ...
+ * |- bio_bucket_read can be called
+ * |- bio_bucket_write with encrypted data
+ * |- store in sink
+ * |- 4. If successful, read pending encrypted data and return.
+ * |- 5. If fails, place read data back in ctx->stream
+ *
+ * HTTP response read call path:
+ *
+ * read_from_connection
+ * |- acceptor
+ * |- handler
+ * |- ...
+ * |- serf_bucket_read on OPENSSLDECRYPT
+ * |- serf_openssl_read
+ * |- serf_databuf_read
+ * |- ssl_decrypt
+ * |- 1. SSL_read() for pending decrypted data; if any, return.
+ * |- 2. Try to read from ctx->stream [SOCKET bucket]
+ * |- 3. Append data to ssl_ctx->source
+ * |- 4. Call SSL_read()
+ * |- ...
+ * |- bio_bucket_write can be called
+ * |- bio_bucket_read
+ * |- read data from ssl_ctx->source
+ * |- If data read, return it.
+ * |- If an error, set the STATUS value and return.
+ *
+ */
+
+typedef struct bucket_list {
+ serf_bucket_t *bucket;
+ struct bucket_list *next;
+} bucket_list_t;
+
+typedef struct {
+ /* Helper to read data. Wraps stream. */
+ serf_databuf_t databuf;
+
+ /* Our source for more data. */
+ serf_bucket_t *stream;
+
+ /* The next set of buckets */
+ bucket_list_t *stream_next;
+
+ /* The status of the last thing we read. */
+ apr_status_t status;
+ apr_status_t exhausted;
+ int exhausted_reset;
+
+ /* Data we've read but not processed. */
+ serf_bucket_t *pending;
+} serf_ssl_stream_t;
+
+typedef struct openssl_context_t {
+ /* How many open buckets refer to this context. */
+ int refcount;
+
+ /* The pool that this context uses. */
+ apr_pool_t *pool;
+
+ /* The allocator associated with the above pool. */
+ serf_bucket_alloc_t *allocator;
+
+ /* Internal OpenSSL parameters */
+ SSL_CTX *ctx;
+ SSL *ssl;
+ BIO *bio;
+
+ serf_ssl_stream_t encrypt;
+ serf_ssl_stream_t decrypt;
+
+ /* Identity callbacks */
+ serf_ssl_need_client_cert_t cert_callback; /* Deprecated */
+ serf_ssl_need_identity_t identity_callback;
+ void *cert_userdata;
+ apr_pool_t *cert_cache_pool;
+ const char *cert_file_success;
+
+ /* Client cert PW callbacks */
+ serf_ssl_need_cert_password_t cert_pw_callback;
+ void *cert_pw_userdata;
+ apr_pool_t *cert_pw_cache_pool;
+ const char *cert_pw_success;
+
+ /* Server cert callbacks */
+ serf_ssl_need_server_cert_t server_cert_callback;
+ serf_ssl_server_cert_chain_cb_t server_cert_chain_callback;
+ void *server_cert_userdata;
+
+ serf_ssl_new_session_t new_session_cb;
+ void *new_session_cb_baton;
+
+ const serf_ssl_identity_t *cached_identity;
+
+ apr_status_t pending_err;
+
+ /* Status of a fatal error, returned on subsequent encrypt or decrypt
+ requests. */
+ apr_status_t fatal_err;
+} openssl_context_t;
+
+typedef struct {
+ /* The bucket-independent ssl context that this bucket is associated with */
+ openssl_context_t *ssl_ctx;
+
+ /* Pointer to the 'right' databuf. */
+ serf_databuf_t *databuf;
+
+ /* Pointer to our stream, so we can find it later. */
+ serf_bucket_t **our_stream;
+} ssl_context_t;
+
+static void disable_compression(openssl_context_t *ssl_ctx);
+static apr_status_t serf__openssl_load_identity_from_file(void *impl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path, apr_pool_t *pool);
+static apr_hash_t *convert_X509_NAME_to_table(X509_NAME *org, apr_pool_t *pool);
+
+#if SSL_VERBOSE
+/* Log all ssl alerts that we receive from the server. */
+static void
+apps_ssl_info_callback(const SSL *s, int where, int ret)
+{
+ const char *str;
+ int w;
+ w = where & ~SSL_ST_MASK;
+
+ if (w & SSL_ST_CONNECT)
+ str = "SSL_connect";
+ else if (w & SSL_ST_ACCEPT)
+ str = "SSL_accept";
+ else
+ str = "undefined";
+
+ if (where & SSL_CB_LOOP) {
+ serf__log(SSL_VERBOSE, __FILE__, "%s:%s\n", str,
+ SSL_state_string_long(s));
+ }
+ else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ serf__log(SSL_VERBOSE, __FILE__, "SSL3 alert %s:%s:%s\n",
+ str,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ }
+ else if (where & SSL_CB_EXIT) {
+ if (ret == 0)
+ serf__log(SSL_VERBOSE, __FILE__, "%s:failed in %s\n", str,
+ SSL_state_string_long(s));
+ else if (ret < 0) {
+ serf__log(SSL_VERBOSE, __FILE__, "%s:error in %s\n", str,
+ SSL_state_string_long(s));
+ }
+ }
+}
+#endif
+
+/* Returns the amount read. */
+static int bio_bucket_read(BIO *bio, char *in, int inlen)
+{
+ openssl_context_t *ctx = bio->ptr;
+ const char *data;
+ apr_status_t status;
+ apr_size_t len;
+
+ serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read called for %d bytes\n",
+ inlen);
+
+ if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN
+ && BIO_should_read(ctx->bio)) {
+ serf__log(SSL_VERBOSE, __FILE__,
+ "bio_bucket_read waiting: (%d %d %d)\n",
+ BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
+ BIO_get_retry_flags(ctx->bio));
+ /* Falling back... */
+ ctx->encrypt.exhausted_reset = 1;
+ BIO_clear_retry_flags(bio);
+ }
+
+ status = serf_bucket_read(ctx->decrypt.pending, inlen, &data, &len);
+
+ ctx->decrypt.status = status;
+
+ serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read received %d bytes (%d)\n",
+ len, status);
+
+ if (!SERF_BUCKET_READ_ERROR(status)) {
+ /* Oh suck. */
+ if (len) {
+ memcpy(in, data, len);
+ return len;
+ }
+ if (APR_STATUS_IS_EOF(status)) {
+ BIO_set_retry_read(bio);
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+/* Returns the amount written. */
+static int bio_bucket_write(BIO *bio, const char *in, int inl)
+{
+ openssl_context_t *ctx = bio->ptr;
+ serf_bucket_t *tmp;
+
+ serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_write called for %d bytes\n",
+ inl);
+
+ if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN
+ && !BIO_should_read(ctx->bio)) {
+ serf__log(SSL_VERBOSE, __FILE__,
+ "bio_bucket_write waiting: (%d %d %d)\n",
+ BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
+ BIO_get_retry_flags(ctx->bio));
+ /* Falling back... */
+ ctx->encrypt.exhausted_reset = 1;
+ BIO_clear_retry_flags(bio);
+ }
+
+ tmp = serf_bucket_simple_copy_create(in, inl,
+ ctx->encrypt.pending->allocator);
+
+ serf_bucket_aggregate_append(ctx->encrypt.pending, tmp);
+
+ return inl;
+}
+
+/* Returns the amount read. */
+static int bio_file_read(BIO *bio, char *in, int inlen)
+{
+ apr_file_t *file = bio->ptr;
+ apr_status_t status;
+ apr_size_t len;
+
+ BIO_clear_retry_flags(bio);
+
+ len = inlen;
+ status = apr_file_read(file, in, &len);
+
+ if (!SERF_BUCKET_READ_ERROR(status)) {
+ /* Oh suck. */
+ if (APR_STATUS_IS_EOF(status)) {
+ BIO_set_retry_read(bio);
+ return -1;
+ } else {
+ return len;
+ }
+ }
+
+ return -1;
+}
+
+/* Returns the amount written. */
+static int bio_file_write(BIO *bio, const char *in, int inl)
+{
+ apr_file_t *file = bio->ptr;
+ apr_size_t nbytes;
+
+ BIO_clear_retry_flags(bio);
+
+ nbytes = inl;
+ apr_file_write(file, in, &nbytes);
+
+ return nbytes;
+}
+
+static int bio_file_gets(BIO *bio, char *in, int inlen)
+{
+ return bio_file_read(bio, in, inlen);
+}
+
+static int bio_bucket_create(BIO *bio)
+{
+ bio->shutdown = 1;
+ bio->init = 1;
+ bio->num = -1;
+ bio->ptr = NULL;
+
+ return 1;
+}
+
+static int bio_bucket_destroy(BIO *bio)
+{
+ /* Did we already free this? */
+ if (bio == NULL) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static long bio_bucket_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ long ret = 1;
+
+ switch (cmd) {
+ default:
+ /* abort(); */
+ break;
+ case BIO_CTRL_FLUSH:
+ /* At this point we can't force a flush. */
+ break;
+ case BIO_CTRL_PUSH:
+ case BIO_CTRL_POP:
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static BIO_METHOD bio_bucket_method = {
+ BIO_TYPE_MEM,
+ "Serf SSL encryption and decryption buckets",
+ bio_bucket_write,
+ bio_bucket_read,
+ NULL, /* Is this called? */
+ NULL, /* Is this called? */
+ bio_bucket_ctrl,
+ bio_bucket_create,
+ bio_bucket_destroy,
+#ifdef OPENSSL_VERSION_NUMBER
+ NULL /* sslc does not have the callback_ctrl field */
+#endif
+};
+
+static BIO_METHOD bio_file_method = {
+ BIO_TYPE_FILE,
+ "Wrapper around APR file structures",
+ bio_file_write,
+ bio_file_read,
+ NULL, /* Is this called? */
+ bio_file_gets, /* Is this called? */
+ bio_bucket_ctrl,
+ bio_bucket_create,
+ bio_bucket_destroy,
+#ifdef OPENSSL_VERSION_NUMBER
+ NULL /* sslc does not have the callback_ctrl field */
+#endif
+};
+
+static int
+validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx)
+{
+ SSL *ssl;
+ openssl_context_t *ctx;
+ X509 *server_cert;
+ int err, depth;
+ int failures = 0;
+
+ ssl = X509_STORE_CTX_get_ex_data(store_ctx,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+ ctx = SSL_get_app_data(ssl);
+
+ server_cert = X509_STORE_CTX_get_current_cert(store_ctx);
+ depth = X509_STORE_CTX_get_error_depth(store_ctx);
+
+ /* If the certification was found invalid, get the error and convert it to
+ something our caller will understand. */
+ if (! cert_valid) {
+ err = X509_STORE_CTX_get_error(store_ctx);
+
+ switch(err) {
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ failures |= SERF_SSL_CERT_NOTYETVALID;
+ break;
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ failures |= SERF_SSL_CERT_EXPIRED;
+ break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ failures |= SERF_SSL_CERT_SELF_SIGNED;
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ case X509_V_ERR_CERT_UNTRUSTED:
+ case X509_V_ERR_INVALID_CA:
+ failures |= SERF_SSL_CERT_UNKNOWNCA;
+ break;
+ case X509_V_ERR_CERT_REVOKED:
+ failures |= SERF_SSL_CERT_REVOKED;
+ break;
+ default:
+ failures |= SERF_SSL_CERT_UNKNOWN_FAILURE;
+ break;
+ }
+ }
+
+ /* Check certificate expiry dates. */
+ if (X509_cmp_current_time(X509_get_notBefore(server_cert)) >= 0) {
+ failures |= SERF_SSL_CERT_NOTYETVALID;
+ }
+ else if (X509_cmp_current_time(X509_get_notAfter(server_cert)) <= 0) {
+ failures |= SERF_SSL_CERT_EXPIRED;
+ }
+
+ if (ctx->server_cert_callback &&
+ (depth == 0 || failures)) {
+ apr_status_t status;
+ serf_ssl_certificate_t *cert;
+
+ cert = serf__create_certificate(ctx->allocator,
+ &serf_ssl_bucket_type_openssl,
+ server_cert,
+ depth);
+
+ /* Callback for further verification. */
+ status = ctx->server_cert_callback(ctx->server_cert_userdata,
+ failures, cert);
+ if (status == APR_SUCCESS)
+ cert_valid = 1;
+ else {
+ /* Even if openssl found the certificate valid, the application
+ told us to reject it. */
+ cert_valid = 0;
+ /* Pass the error back to the caller through the context-run. */
+ ctx->pending_err = status;
+ }
+ }
+
+ if (ctx->server_cert_chain_callback
+ && (depth == 0 || failures)) {
+ apr_status_t status;
+ STACK_OF(X509) *chain;
+ const serf_ssl_certificate_t **certs;
+ int certs_len;
+ apr_pool_t *subpool;
+ serf_bucket_alloc_t *allocator;
+
+ apr_pool_create(&subpool, ctx->pool);
+ allocator = serf_bucket_allocator_create(subpool, NULL, NULL);
+
+ /* Borrow the chain to pass to the callback. */
+ chain = X509_STORE_CTX_get_chain(store_ctx);
+
+ /* If the chain can't be retrieved, just pass the current
+ certificate. */
+ /* ### can this actually happen with _get_chain() ? */
+ if (!chain) {
+ serf_ssl_certificate_t *cert;
+
+ cert = serf__create_certificate(allocator,
+ &serf_ssl_bucket_type_openssl,
+ server_cert,
+ depth);
+
+ /* Room for the server_cert and a trailing NULL. */
+ certs = apr_palloc(subpool, sizeof(*certs) * 2);
+ certs[0] = cert;
+
+ certs_len = 1;
+ } else {
+ int i;
+
+ certs_len = sk_X509_num(chain);
+
+ /* Room for all the certs and a trailing NULL. */
+ certs = apr_palloc(subpool, sizeof(*certs) * (certs_len + 1));
+ for (i = 0; i < certs_len; ++i) {
+ serf_ssl_certificate_t *cert;
+
+ cert = serf__create_certificate(allocator,
+ &serf_ssl_bucket_type_openssl,
+ sk_X509_value(chain, i),
+ i);
+
+ certs[i] = cert;
+ }
+ }
+ certs[certs_len] = NULL;
+
+ /* Callback for further verification. */
+ status = ctx->server_cert_chain_callback(ctx->server_cert_userdata,
+ failures, depth,
+ certs, certs_len);
+ if (status == APR_SUCCESS) {
+ cert_valid = 1;
+ } else {
+ /* Even if openssl found the certificate valid, the application
+ told us to reject it. */
+ cert_valid = 0;
+ /* Pass the error back to the caller through the context-run. */
+ ctx->pending_err = status;
+ }
+
+ apr_pool_destroy(subpool);
+ }
+
+ /* Return a specific error if the server certificate is not accepted by
+ OpenSSL and the application has not set callbacks to override this. */
+ if (!cert_valid &&
+ !ctx->server_cert_chain_callback &&
+ !ctx->server_cert_callback)
+ {
+ ctx->pending_err = SERF_ERROR_SSL_CERT_FAILED;
+ }
+
+ return cert_valid;
+}
+
+/* This function reads an encrypted stream and returns the decrypted stream. */
+static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize,
+ char *buf, apr_size_t *len)
+{
+ openssl_context_t *ctx = baton;
+ apr_size_t priv_len;
+ apr_status_t status;
+ const char *data;
+ int ssl_len;
+
+ if (ctx->fatal_err)
+ return ctx->fatal_err;
+
+ serf__log(SSL_VERBOSE, __FILE__, "ssl_decrypt: begin %d\n", bufsize);
+
+ /* Is there some data waiting to be read? */
+ ssl_len = SSL_read(ctx->ssl, buf, bufsize);
+ if (ssl_len > 0) {
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_decrypt: %d bytes (%d); status: %d; flags: %d\n",
+ ssl_len, bufsize, ctx->decrypt.status,
+ BIO_get_retry_flags(ctx->bio));
+ *len = ssl_len;
+ return APR_SUCCESS;
+ }
+
+ status = serf_bucket_read(ctx->decrypt.stream, bufsize, &data, &priv_len);
+
+ if (!SERF_BUCKET_READ_ERROR(status) && priv_len) {
+ serf_bucket_t *tmp;
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_decrypt: read %d bytes (%d); status: %d\n",
+ priv_len, bufsize, status);
+
+ tmp = serf_bucket_simple_copy_create(data, priv_len,
+ ctx->decrypt.pending->allocator);
+
+ serf_bucket_aggregate_append(ctx->decrypt.pending, tmp);
+
+ ssl_len = SSL_read(ctx->ssl, buf, bufsize);
+ if (ssl_len < 0) {
+ int ssl_err;
+
+ ssl_err = SSL_get_error(ctx->ssl, ssl_len);
+ switch (ssl_err) {
+ case SSL_ERROR_SYSCALL:
+ *len = 0;
+ status = ctx->decrypt.status;
+ break;
+ case SSL_ERROR_WANT_READ:
+ *len = 0;
+ status = APR_EAGAIN;
+ break;
+ case SSL_ERROR_SSL:
+ *len = 0;
+ if (ctx->pending_err) {
+ status = ctx->pending_err;
+ ctx->pending_err = 0;
+ } else {
+ ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
+ }
+ break;
+ default:
+ *len = 0;
+ ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
+ break;
+ }
+ } else if (ssl_len == 0) {
+ /* The server shut down the connection. */
+ int ssl_err, shutdown;
+ *len = 0;
+
+ /* Check for SSL_RECEIVED_SHUTDOWN */
+ shutdown = SSL_get_shutdown(ctx->ssl);
+ /* Check for SSL_ERROR_ZERO_RETURN */
+ ssl_err = SSL_get_error(ctx->ssl, ssl_len);
+
+ if (shutdown == SSL_RECEIVED_SHUTDOWN &&
+ ssl_err == SSL_ERROR_ZERO_RETURN) {
+ /* The server closed the SSL session. While this doesn't
+ necessary mean the connection is closed, let's close
+ it here anyway.
+ We can optimize this later. */
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_decrypt: SSL read error: server"
+ " shut down connection!\n");
+ status = APR_EOF;
+ } else {
+ /* A fatal error occurred. */
+ ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
+ }
+ } else {
+ *len = ssl_len;
+ serf__log(SSL_MSG_VERBOSE, __FILE__,
+ "---\n%.*s\n-(%d)-\n", *len, buf, *len);
+ }
+ }
+ else {
+ *len = 0;
+ }
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_decrypt: %d %d %d\n", status, *len,
+ BIO_get_retry_flags(ctx->bio));
+
+ return status;
+}
+
+/* This function reads a decrypted stream and returns an encrypted stream. */
+static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize,
+ char *buf, apr_size_t *len)
+{
+ const char *data;
+ apr_size_t interim_bufsize;
+ openssl_context_t *ctx = baton;
+ apr_status_t status;
+
+ if (ctx->fatal_err)
+ return ctx->fatal_err;
+
+ serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: begin %d\n", bufsize);
+
+ /* Try to read already encrypted but unread data first. */
+ status = serf_bucket_read(ctx->encrypt.pending, bufsize, &data, len);
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+
+ /* Aha, we read something. Return that now. */
+ if (*len) {
+ memcpy(buf, data, *len);
+ if (APR_STATUS_IS_EOF(status)) {
+ status = APR_SUCCESS;
+ }
+
+ serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: %d %d %d (quick read)\n",
+ status, *len, BIO_get_retry_flags(ctx->bio));
+
+ return status;
+ }
+
+ if (BIO_should_retry(ctx->bio) && BIO_should_write(ctx->bio)) {
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt: %d %d %d (should write exit)\n",
+ status, *len, BIO_get_retry_flags(ctx->bio));
+
+ return APR_EAGAIN;
+ }
+
+ /* If we were previously blocked, unblock ourselves now. */
+ if (BIO_should_read(ctx->bio)) {
+ serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: reset %d %d (%d %d %d)\n",
+ status, ctx->encrypt.status,
+ BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
+ BIO_get_retry_flags(ctx->bio));
+
+ ctx->encrypt.status = APR_SUCCESS;
+ ctx->encrypt.exhausted_reset = 0;
+ }
+
+ /* Oh well, read from our stream now. */
+ interim_bufsize = bufsize;
+ do {
+ apr_size_t interim_len;
+
+ if (!ctx->encrypt.status) {
+ struct iovec vecs[64];
+ int vecs_read;
+
+ status = serf_bucket_read_iovec(ctx->encrypt.stream,
+ interim_bufsize, 64, vecs,
+ &vecs_read);
+
+ if (!SERF_BUCKET_READ_ERROR(status) && vecs_read) {
+ char *vecs_data;
+ int i, cur, vecs_data_len;
+ int ssl_len;
+
+ /* Combine the buffers of the iovec into one buffer, as
+ that is with SSL_write requires. */
+ vecs_data_len = 0;
+ for (i = 0; i < vecs_read; i++) {
+ vecs_data_len += vecs[i].iov_len;
+ }
+
+ vecs_data = serf_bucket_mem_alloc(ctx->allocator,
+ vecs_data_len);
+
+ cur = 0;
+ for (i = 0; i < vecs_read; i++) {
+ memcpy(vecs_data + cur, vecs[i].iov_base, vecs[i].iov_len);
+ cur += vecs[i].iov_len;
+ }
+
+ interim_bufsize -= vecs_data_len;
+ interim_len = vecs_data_len;
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt: bucket read %d bytes; "\
+ "status %d\n", interim_len, status);
+ serf__log(SSL_MSG_VERBOSE, __FILE__, "---\n%.*s\n-(%d)-\n",
+ interim_len, vecs_data, interim_len);
+
+ /* Stash our status away. */
+ ctx->encrypt.status = status;
+
+ ssl_len = SSL_write(ctx->ssl, vecs_data, interim_len);
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt: SSL write: %d\n", ssl_len);
+
+ /* If we failed to write... */
+ if (ssl_len < 0) {
+ int ssl_err;
+
+ /* Ah, bugger. We need to put that data back.
+ Note: use the copy here, we do not own the original iovec
+ data buffer so it will be freed on next read. */
+ serf_bucket_t *vecs_copy =
+ serf_bucket_simple_own_create(vecs_data,
+ vecs_data_len,
+ ctx->allocator);
+ serf_bucket_aggregate_prepend(ctx->encrypt.stream,
+ vecs_copy);
+
+ ssl_err = SSL_get_error(ctx->ssl, ssl_len);
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt: SSL write error: %d\n", ssl_err);
+
+ if (ssl_err == SSL_ERROR_SYSCALL) {
+ status = ctx->encrypt.status;
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+ }
+ else {
+ /* Oh, no. */
+ if (ssl_err == SSL_ERROR_WANT_READ) {
+ status = SERF_ERROR_WAIT_CONN;
+ }
+ else {
+ ctx->fatal_err = status =
+ SERF_ERROR_SSL_COMM_FAILED;
+ }
+ }
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt: SSL write error: %d %d\n",
+ status, *len);
+ } else {
+ /* We're done with this data. */
+ serf_bucket_mem_free(ctx->allocator, vecs_data);
+ }
+ }
+ }
+ else {
+ interim_len = 0;
+ *len = 0;
+ status = ctx->encrypt.status;
+ }
+
+ } while (!status && interim_bufsize);
+
+ /* Okay, we exhausted our underlying stream. */
+ if (!SERF_BUCKET_READ_ERROR(status)) {
+ apr_status_t agg_status;
+ struct iovec vecs[64];
+ int vecs_read, i;
+
+ /* We read something! */
+ agg_status = serf_bucket_read_iovec(ctx->encrypt.pending, bufsize,
+ 64, vecs, &vecs_read);
+ *len = 0;
+ for (i = 0; i < vecs_read; i++) {
+ memcpy(buf + *len, vecs[i].iov_base, vecs[i].iov_len);
+ *len += vecs[i].iov_len;
+ }
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt read agg: %d %d %d %d\n", status, agg_status,
+ ctx->encrypt.status, *len);
+
+ if (!agg_status) {
+ status = agg_status;
+ }
+ }
+
+ if (status == SERF_ERROR_WAIT_CONN
+ && BIO_should_retry(ctx->bio) && BIO_should_read(ctx->bio)) {
+ ctx->encrypt.exhausted = ctx->encrypt.status;
+ ctx->encrypt.status = SERF_ERROR_WAIT_CONN;
+ }
+
+ serf__log(SSL_VERBOSE, __FILE__,
+ "ssl_encrypt finished: %d %d (%d %d %d)\n", status, *len,
+ BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
+ BIO_get_retry_flags(ctx->bio));
+
+ return status;
+}
+
+#if APR_HAS_THREADS
+static apr_pool_t *ssl_pool;
+static apr_thread_mutex_t **ssl_locks;
+
+typedef struct CRYPTO_dynlock_value {
+ apr_thread_mutex_t *lock;
+} CRYPTO_dynlock_value;
+
+static CRYPTO_dynlock_value *ssl_dyn_create(const char* file, int line)
+{
+ CRYPTO_dynlock_value *l;
+ apr_status_t rv;
+
+ l = apr_palloc(ssl_pool, sizeof(CRYPTO_dynlock_value));
+ rv = apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, ssl_pool);
+ if (rv != APR_SUCCESS) {
+ /* FIXME: return error here */
+ }
+ return l;
+}
+
+static void ssl_dyn_lock(int mode, CRYPTO_dynlock_value *l, const char *file,
+ int line)
+{
+ if (mode & CRYPTO_LOCK) {
+ apr_thread_mutex_lock(l->lock);
+ }
+ else if (mode & CRYPTO_UNLOCK) {
+ apr_thread_mutex_unlock(l->lock);
+ }
+}
+
+static void ssl_dyn_destroy(CRYPTO_dynlock_value *l, const char *file,
+ int line)
+{
+ apr_thread_mutex_destroy(l->lock);
+}
+
+static void ssl_lock(int mode, int n, const char *file, int line)
+{
+ if (mode & CRYPTO_LOCK) {
+ apr_thread_mutex_lock(ssl_locks[n]);
+ }
+ else if (mode & CRYPTO_UNLOCK) {
+ apr_thread_mutex_unlock(ssl_locks[n]);
+ }
+}
+
+static unsigned long ssl_id(void)
+{
+ /* FIXME: This is lame and not portable. -aaron */
+ return (unsigned long) apr_os_thread_current();
+}
+
+static apr_status_t cleanup_ssl(void *data)
+{
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_id_callback(NULL);
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ return APR_SUCCESS;
+}
+
+#endif
+
+static apr_uint32_t have_init_ssl = 0;
+
+static void init_ssl_libraries(void)
+{
+ apr_uint32_t val;
+#if APR_VERSION_AT_LEAST(1,0,0)
+ val = apr_atomic_xchg32(&have_init_ssl, 1);
+#else
+ val = apr_atomic_cas(&have_init_ssl, 1, 0);
+#endif
+
+ if (!val) {
+#if APR_HAS_THREADS
+ int i, numlocks;
+#endif
+
+#ifdef SSL_VERBOSE
+ /* Warn when compile-time and run-time version of OpenSSL differ in
+ major/minor version number. */
+ long libver = SSLeay();
+
+ if ((libver ^ OPENSSL_VERSION_NUMBER) & 0xFFF00000) {
+ serf__log(SSL_VERBOSE, __FILE__,
+ "Warning: OpenSSL library version mismatch, compile-time "
+ "was %lx, runtime is %lx.\n",
+ OPENSSL_VERSION_NUMBER, libver);
+ }
+#endif
+
+ CRYPTO_malloc_init();
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+ SSL_library_init();
+ OpenSSL_add_all_algorithms();
+
+#if APR_HAS_THREADS
+ numlocks = CRYPTO_num_locks();
+ apr_pool_create(&ssl_pool, NULL);
+ ssl_locks = apr_palloc(ssl_pool, sizeof(apr_thread_mutex_t*)*numlocks);
+ for (i = 0; i < numlocks; i++) {
+ apr_status_t rv;
+
+ /* Intraprocess locks don't /need/ a filename... */
+ rv = apr_thread_mutex_create(&ssl_locks[i],
+ APR_THREAD_MUTEX_DEFAULT, ssl_pool);
+ if (rv != APR_SUCCESS) {
+ /* FIXME: error out here */
+ }
+ }
+ CRYPTO_set_locking_callback(ssl_lock);
+ CRYPTO_set_id_callback(ssl_id);
+ CRYPTO_set_dynlock_create_callback(ssl_dyn_create);
+ CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock);
+ CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy);
+
+ apr_pool_cleanup_register(ssl_pool, NULL, cleanup_ssl, cleanup_ssl);
+#endif
+ }
+}
+
+static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey)
+{
+ openssl_context_t *ctx = SSL_get_app_data(ssl);
+ apr_hash_t **dnlist = NULL;
+ apr_size_t dnlen = 0;
+ apr_pool_t *tmppool;
+ apr_status_t status;
+
+ if (ctx->cached_identity) {
+ *cert = ctx->cached_identity->impl_cert;
+ *pkey = ctx->cached_identity->impl_pkey;
+ return 1;
+ }
+
+ if (!ctx->cert_callback && !ctx->identity_callback)
+ return 0;
+
+ apr_pool_create(&tmppool, ctx->pool);
+
+ /* Loop until a client identity is returned by the application, a .p12 file
+ is read and parsed successfully, or as long as the application keeps
+ providing .p12 paths and passwords. */
+ while (1) {
+ const char *cert_path;
+ const serf_ssl_identity_t *identity;
+
+ if (ctx->cert_callback)
+ {
+ /* If we have a path to a .p12 file cached from a previous session,
+ we will reuse it here so we don't have to call the application.
+ */
+ if (ctx->cert_file_success) {
+ cert_path = ctx->cert_file_success;
+ } else {
+ status = ctx->cert_callback(ctx->cert_userdata, &cert_path);
+ if (status || !cert_path)
+ break;
+ }
+ status = serf__openssl_load_identity_from_file(ctx,
+ &identity,
+ cert_path,
+ ctx->pool);
+ /* No need to read & parse this file again. If it was parsed
+ successfully we will cache the identity object. If not we
+ don't want to use it anyway. */
+ ctx->cert_file_success = NULL;
+ }
+ else
+ {
+ if (!dnlist) {
+ STACK_OF(X509_NAME) *cas;
+ int i;
+
+ cas = SSL_get_client_CA_list(ctx->ssl);
+ dnlen = sk_X509_NAME_num(cas);
+
+ dnlist = apr_palloc(tmppool, dnlen * sizeof(apr_hash_t*));
+
+ for (i = 0; i < dnlen; i++) {
+ apr_hash_t *issuer;
+
+ X509_NAME *ca = (X509_NAME *)sk_X509_NAME_value(cas, i);
+ issuer = convert_X509_NAME_to_table(ca, tmppool);
+
+ dnlist[i] = issuer;
+ }
+ }
+ status = ctx->identity_callback(ctx->cert_userdata,
+ dnlist, dnlen, &identity,
+ ctx->pool);
+ if (status || !identity)
+ break;
+ }
+
+ if (status == APR_SUCCESS)
+ {
+ ctx->cached_identity = identity;
+
+ *cert = identity->impl_cert;
+ *pkey = identity->impl_pkey;
+
+ apr_pool_destroy(tmppool);
+
+ return 1;
+ }
+
+ /* A file was read but not parsed successfully, so ask the application
+ for another file. */
+ }
+
+ apr_pool_destroy(tmppool);
+
+ return 0;
+}
+
+
+static void serf__openssl_client_cert_provider_set(
+ void *impl_ctx,
+ serf_ssl_need_client_cert_t callback,
+ void *data,
+ void *cache_pool)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->cert_callback = callback;
+ ssl_ctx->cert_userdata = data;
+ ssl_ctx->cert_cache_pool = cache_pool;
+ if (ssl_ctx->cert_cache_pool) {
+ apr_pool_userdata_get((void**)&ssl_ctx->cert_file_success,
+ "serf:ssl:cert", cache_pool);
+ }
+}
+
+
+static void serf__openssl_client_cert_password_set(
+ void *impl_ctx,
+ serf_ssl_need_cert_password_t callback,
+ void *data,
+ void *cache_pool)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->cert_pw_callback = callback;
+ ssl_ctx->cert_pw_userdata = data;
+ ssl_ctx->cert_pw_cache_pool = cache_pool;
+ if (ssl_ctx->cert_pw_cache_pool) {
+ apr_pool_userdata_get((void**)&ssl_ctx->cert_pw_success,
+ "serf:ssl:certpw", cache_pool);
+ }
+}
+
+
+static void
+serf__openssl_identity_provider_set(void *impl_ctx,
+ serf_ssl_need_identity_t callback,
+ void *data,
+ void *cache_pool)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->identity_callback = callback;
+ ssl_ctx->cert_userdata = data;
+ ssl_ctx->cert_cache_pool = cache_pool;
+ if (ssl_ctx->cert_cache_pool) {
+ apr_pool_userdata_get((void**)&ssl_ctx->cert_file_success,
+ "serf:ssl:cert", cache_pool);
+ }
+}
+
+static void serf__openssl_server_cert_callback_set(
+ void *impl_ctx,
+ serf_ssl_need_server_cert_t callback,
+ void *data)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->server_cert_callback = callback;
+ ssl_ctx->server_cert_userdata = data;
+}
+
+static void serf__openssl_server_cert_chain_callback_set(
+ void *impl_ctx,
+ serf_ssl_need_server_cert_t cert_callback,
+ serf_ssl_server_cert_chain_cb_t cert_chain_callback,
+ void *data)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->server_cert_callback = cert_callback;
+ ssl_ctx->server_cert_chain_callback = cert_chain_callback;
+ ssl_ctx->server_cert_userdata = data;
+}
+
+static void serf__openssl_new_session_callback_set(
+ void *impl_ctx,
+ serf_ssl_new_session_t new_session_cb,
+ void *baton)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ ssl_ctx->new_session_cb = new_session_cb;
+ ssl_ctx->new_session_cb_baton = baton;
+}
+
+static int new_session(SSL *ssl, SSL_SESSION *sess)
+{
+ openssl_context_t *ssl_ctx = SSL_get_app_data(ssl);
+
+ if (ssl_ctx->new_session_cb) {
+ serf_ssl_session_t session;
+ apr_pool_t *subpool;
+
+/* session.type = &serf_ssl_bucket_type_openssl;*/
+ session.impl_session_obj = sess;
+ apr_pool_create(&subpool, ssl_ctx->pool);
+
+ ssl_ctx->new_session_cb(&session, ssl_ctx->new_session_cb_baton,
+ subpool);
+
+ apr_pool_destroy(subpool);
+ }
+
+ return 0;
+}
+
+static apr_status_t
+serf__openssl_session_export(void **data_p,
+ apr_size_t *len_p,
+ const serf_ssl_session_t *session,
+ apr_pool_t *pool)
+{
+ int sess_len;
+ void *sess_data;
+ unsigned char *unused;
+
+ sess_len = i2d_SSL_SESSION(session->impl_session_obj, NULL);
+ if (!sess_len) {
+ return APR_EGENERAL;
+ }
+
+ sess_data = apr_palloc(pool, sess_len);
+
+ unused = sess_data;
+ /* unused is incremented */
+ sess_len = i2d_SSL_SESSION(session->impl_session_obj, &unused);
+ if (!sess_len) {
+ return APR_EGENERAL;
+ }
+
+ *data_p = sess_data;
+ *len_p = sess_len;
+ return APR_SUCCESS;
+}
+
+static apr_status_t cleanup_session(void *data)
+{
+ serf_ssl_session_t *session = data;
+
+ SSL_SESSION_free(session->impl_session_obj);
+ session->impl_session_obj = NULL;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t
+serf__openssl_session_import(const serf_ssl_session_t **session_p,
+ void *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ SSL_SESSION *sess;
+ serf_ssl_session_t *session;
+ const unsigned char *unused;
+
+ unused = data;
+ sess = d2i_SSL_SESSION(NULL, &unused, len); /* unused is incremented */
+
+ if (!sess) {
+ return APR_EGENERAL;
+ }
+
+ session = apr_pcalloc(pool, sizeof(serf_ssl_session_t));
+/* session->type = &serf_ssl_bucket_type_openssl;*/
+ session->impl_session_obj = sess;
+ apr_pool_cleanup_register(pool, session, cleanup_session, cleanup_session);
+
+ *session_p = session;
+ return APR_SUCCESS;
+}
+
+static openssl_context_t *ssl_init_context(void)
+{
+ openssl_context_t *ssl_ctx;
+ apr_pool_t *pool;
+ serf_bucket_alloc_t *allocator;
+
+ init_ssl_libraries();
+
+ apr_pool_create(&pool, NULL);
+ allocator = serf_bucket_allocator_create(pool, NULL, NULL);
+
+ ssl_ctx = serf_bucket_mem_alloc(allocator, sizeof(*ssl_ctx));
+
+ ssl_ctx->refcount = 0;
+ ssl_ctx->pool = pool;
+ ssl_ctx->allocator = allocator;
+
+ ssl_ctx->ctx = SSL_CTX_new(SSLv23_client_method());
+
+ SSL_CTX_set_client_cert_cb(ssl_ctx->ctx, ssl_need_client_cert);
+ ssl_ctx->cached_identity = 0;
+ ssl_ctx->pending_err = APR_SUCCESS;
+ ssl_ctx->fatal_err = APR_SUCCESS;
+
+ ssl_ctx->cert_callback = NULL;
+ ssl_ctx->identity_callback = NULL;
+ ssl_ctx->cert_pw_callback = NULL;
+ ssl_ctx->server_cert_callback = NULL;
+ ssl_ctx->server_cert_chain_callback = NULL;
+
+ SSL_CTX_set_verify(ssl_ctx->ctx, SSL_VERIFY_PEER,
+ validate_server_certificate);
+ SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_ALL);
+
+ ssl_ctx->new_session_cb = NULL;
+
+ /* Enable SSL callback for new sessions and disable internal session
+ handling. */
+ SSL_CTX_set_session_cache_mode(
+ ssl_ctx->ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
+ SSL_CTX_sess_set_new_cb(ssl_ctx->ctx, new_session);
+
+ /* Disable SSL compression by default. */
+ disable_compression(ssl_ctx);
+
+ ssl_ctx->ssl = SSL_new(ssl_ctx->ctx);
+ ssl_ctx->bio = BIO_new(&bio_bucket_method);
+ ssl_ctx->bio->ptr = ssl_ctx;
+
+ SSL_set_bio(ssl_ctx->ssl, ssl_ctx->bio, ssl_ctx->bio);
+
+ SSL_set_connect_state(ssl_ctx->ssl);
+
+ SSL_set_app_data(ssl_ctx->ssl, ssl_ctx);
+
+#if SSL_VERBOSE
+ SSL_CTX_set_info_callback(ssl_ctx->ctx, apps_ssl_info_callback);
+#endif
+
+ ssl_ctx->encrypt.stream = NULL;
+ ssl_ctx->encrypt.stream_next = NULL;
+ ssl_ctx->encrypt.pending = serf_bucket_aggregate_create(allocator);
+ ssl_ctx->encrypt.status = APR_SUCCESS;
+ serf_databuf_init(&ssl_ctx->encrypt.databuf);
+ ssl_ctx->encrypt.databuf.read = ssl_encrypt;
+ ssl_ctx->encrypt.databuf.read_baton = ssl_ctx;
+
+ ssl_ctx->decrypt.stream = NULL;
+ ssl_ctx->decrypt.pending = serf_bucket_aggregate_create(allocator);
+ ssl_ctx->decrypt.status = APR_SUCCESS;
+ serf_databuf_init(&ssl_ctx->decrypt.databuf);
+ ssl_ctx->decrypt.databuf.read = ssl_decrypt;
+ ssl_ctx->decrypt.databuf.read_baton = ssl_ctx;
+
+ return ssl_ctx;
+}
+
+apr_status_t serf__openssl_resume_session(void *impl_ctx,
+ const serf_ssl_session_t *session,
+ apr_pool_t *pool)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ SSL_set_session(ssl_ctx->ssl, session->impl_session_obj);
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_free_context(openssl_context_t *ssl_ctx)
+{
+ apr_pool_t *p;
+
+ /* If never had the pending buckets, don't try to free them. */
+ if (ssl_ctx->decrypt.pending != NULL) {
+ serf_bucket_destroy(ssl_ctx->decrypt.pending);
+ }
+ if (ssl_ctx->encrypt.pending != NULL) {
+ serf_bucket_destroy(ssl_ctx->encrypt.pending);
+ }
+
+ /* SSL_free implicitly frees the underlying BIO. */
+ SSL_free(ssl_ctx->ssl);
+ SSL_CTX_free(ssl_ctx->ctx);
+
+ p = ssl_ctx->pool;
+
+ serf_bucket_mem_free(ssl_ctx->allocator, ssl_ctx);
+ apr_pool_destroy(p);
+
+ return APR_SUCCESS;
+}
+
+static void bucket_create(
+ serf_bucket_t *bucket,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator,
+ const serf_bucket_type_t *type)
+{
+ ssl_context_t *ctx;
+
+ ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
+ if (!impl_ctx) {
+ ctx->ssl_ctx = ssl_init_context();
+ }
+ else {
+ ctx->ssl_ctx = impl_ctx;
+ }
+ ctx->ssl_ctx->refcount++;
+
+ bucket->data = ctx;
+ bucket->type = type;
+ bucket->allocator = allocator;
+}
+
+static apr_status_t serf__openssl_set_hostname(void *impl_ctx,
+ const char * hostname)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+#ifdef SSL_set_tlsext_host_name
+ if (SSL_set_tlsext_host_name(ssl_ctx->ssl, hostname) != 1) {
+ ERR_clear_error();
+ }
+#endif
+ return APR_SUCCESS;
+}
+
+static apr_status_t serf__openssl_use_default_certificates(void *impl_ctx)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx);
+
+ int result = X509_STORE_set_default_paths(store);
+
+ return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED;
+}
+
+static apr_status_t serf__openssl_load_CA_cert_from_file(
+ serf_ssl_certificate_t **cert,
+ const char *file_path,
+ apr_pool_t *pool)
+{
+ FILE *fp = fopen(file_path, "r");
+
+ if (fp) {
+ X509 *ssl_cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ fclose(fp);
+
+ if (ssl_cert) {
+ serf_bucket_alloc_t *allocator =
+ serf_bucket_allocator_create(pool, NULL, NULL);
+
+ *cert = serf__create_certificate(allocator,
+ &serf_ssl_bucket_type_openssl,
+ ssl_cert,
+ 0);
+ return APR_SUCCESS;
+ }
+ }
+
+ return SERF_ERROR_SSL_CERT_FAILED;
+}
+
+
+static apr_status_t
+callback_for_identity_pw(openssl_context_t *ctx,
+ const char *cert_path,
+ const char **password)
+{
+ apr_status_t status;
+
+ if (ctx->cert_pw_success) {
+ status = APR_SUCCESS;
+ *password = ctx->cert_pw_success;
+ ctx->cert_pw_success = NULL;
+ } else if (ctx->cert_pw_callback)
+ {
+ status = ctx->cert_pw_callback(ctx->cert_pw_userdata,
+ cert_path,
+ password);
+ } else
+ {
+ status = SERF_ERROR_SSL_CLIENT_CERT_PW_FAILED;
+ }
+
+ return status;
+}
+
+/* Opens a .p12 file and reads the client certificate and private key.
+ When parsing the file without password fails, this function will call the
+ application's identity password callback.
+
+ On successful parsing of the .p12 file, the file name & password will be
+ cached in the cache_pool provided in the calls:
+ client_cert_provider_set/client_cert_password_set (deprecated)
+ or identity_provider_set/client_cert_password_set.
+ */
+static apr_status_t
+serf__openssl_load_identity_from_file(void *impl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path,
+ apr_pool_t *pool)
+{
+ openssl_context_t *ctx = impl_ctx;
+ apr_file_t *cert_file;
+ BIO *bio;
+ PKCS12 *p12;
+ const char *password = NULL;
+ int i, retry_once = 2;
+ apr_status_t status;
+
+ /* Load the x.509 cert file stored in PKCS12 */
+ status = apr_file_open(&cert_file, file_path, APR_READ, APR_OS_DEFAULT,
+ ctx->pool);
+ if (status)
+ return status;
+
+ bio = BIO_new(&bio_file_method);
+ bio->ptr = cert_file;
+
+ p12 = d2i_PKCS12_bio(bio, NULL);
+ apr_file_close(cert_file);
+
+ /* Try parsing the file without password. If that doesn'twork, call the
+ application to get the password and try again. */
+ while (retry_once)
+ {
+ X509 *cert;
+ EVP_PKEY *pkey;
+
+ retry_once--;
+
+ i = PKCS12_parse(p12, password, &pkey, &cert, NULL);
+
+ if (i == 1)
+ {
+ *identity = serf__create_identity(&serf_ssl_bucket_type_openssl,
+ cert, pkey, pool);
+
+ /* If we haven't done so for this file already, store the file path
+ and associated password in the pool that will survive this
+ session for reuse. */
+ if (!ctx->cert_file_success && ctx->cert_cache_pool) {
+ const char *c;
+
+ c = apr_pstrdup(ctx->cert_cache_pool,
+ file_path);
+
+ apr_pool_userdata_setn(c, "serf:ssl:cert",
+ apr_pool_cleanup_null,
+ ctx->cert_cache_pool);
+ }
+ if (!ctx->cert_file_success && ctx->cert_pw_cache_pool) {
+ const char *c;
+
+ c = apr_pstrdup(ctx->cert_pw_cache_pool,
+ password);
+
+ apr_pool_userdata_setn(c, "serf:ssl:certpw",
+ apr_pool_cleanup_null,
+ ctx->cert_pw_cache_pool);
+ }
+
+ status = APR_SUCCESS;
+ goto cleanup;
+ } else {
+ int err = ERR_get_error();
+ ERR_clear_error();
+
+ /* If we haven't done so yet, call the application to get the
+ matching passphrase and retry parsing the file. */
+ if (retry_once &&
+ ERR_GET_LIB(err) == ERR_LIB_PKCS12 &&
+ ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE)
+ {
+ status = callback_for_identity_pw(ctx, file_path,
+ &password);
+ if (status)
+ goto cleanup;
+
+ /* continue with new password */
+ } else {
+ if (retry_once)
+ serf__log(SSL_VERBOSE, __FILE__,
+ "OpenSSL cert error: %d %d %d.\n",
+ ERR_GET_LIB(err), ERR_GET_FUNC(err),
+ ERR_GET_REASON(err));
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ BIO_free(bio);
+ PKCS12_free(p12);
+
+ return status;
+}
+
+
+static apr_status_t serf__openssl_trust_cert(
+ void *impl_ctx,
+ serf_ssl_certificate_t *cert)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx);
+
+ int result = X509_STORE_add_cert(store, cert->impl_cert);
+
+ return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED;
+}
+
+
+static void *
+serf__openssl_bucket_decrypt_create(serf_bucket_t *bucket,
+ serf_bucket_t *stream,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator)
+{
+ ssl_context_t *ctx;
+
+ bucket_create(bucket, impl_ctx, allocator,
+ &serf_bucket_type_openssl_decrypt);
+
+ ctx = bucket->data;
+
+ ctx->databuf = &ctx->ssl_ctx->decrypt.databuf;
+ if (ctx->ssl_ctx->decrypt.stream != NULL) {
+ return NULL;
+ }
+ ctx->ssl_ctx->decrypt.stream = stream;
+ ctx->our_stream = &ctx->ssl_ctx->decrypt.stream;
+
+ return ctx->ssl_ctx;
+}
+
+
+static void *serf__openssl_bucket_decrypt_context_get(
+ serf_bucket_t *bucket)
+{
+ ssl_context_t *ctx = bucket->data;
+ return ctx->ssl_ctx;
+}
+
+static void *
+serf__openssl_bucket_encrypt_create(serf_bucket_t *bucket,
+ serf_bucket_t *stream,
+ void *impl_ctx,
+ serf_bucket_alloc_t *allocator)
+{
+ ssl_context_t *ctx;
+
+ bucket_create(bucket, impl_ctx, allocator,
+ &serf_bucket_type_openssl_encrypt);
+
+ ctx = bucket->data;
+
+ ctx->databuf = &ctx->ssl_ctx->encrypt.databuf;
+ ctx->our_stream = &ctx->ssl_ctx->encrypt.stream;
+ if (ctx->ssl_ctx->encrypt.stream == NULL) {
+ serf_bucket_t *tmp = serf_bucket_aggregate_create(stream->allocator);
+ serf_bucket_aggregate_append(tmp, stream);
+ ctx->ssl_ctx->encrypt.stream = tmp;
+ }
+ else {
+ bucket_list_t *new_list;
+
+ new_list = serf_bucket_mem_alloc(ctx->ssl_ctx->allocator,
+ sizeof(*new_list));
+ new_list->bucket = stream;
+ new_list->next = NULL;
+ if (ctx->ssl_ctx->encrypt.stream_next == NULL) {
+ ctx->ssl_ctx->encrypt.stream_next = new_list;
+ }
+ else {
+ bucket_list_t *scan = ctx->ssl_ctx->encrypt.stream_next;
+
+ while (scan->next != NULL)
+ scan = scan->next;
+ scan->next = new_list;
+ }
+ }
+
+ return ctx->ssl_ctx;
+}
+
+
+static void *serf__openssl_bucket_encrypt_context_get(
+ serf_bucket_t *bucket)
+{
+ ssl_context_t *ctx = bucket->data;
+ return ctx->ssl_ctx;
+}
+
+/* Functions to read a serf_ssl_certificate structure. */
+
+/* Creates a hash_table with keys (E, CN, OU, O, L, ST and C). */
+static apr_hash_t *
+convert_X509_NAME_to_table(X509_NAME *org, apr_pool_t *pool)
+{
+ char buf[1024];
+ int ret;
+
+ apr_hash_t *tgt = apr_hash_make(pool);
+
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_commonName,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "CN", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_pkcs9_emailAddress,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "E", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_organizationalUnitName,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "OU", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_organizationName,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "O", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_localityName,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "L", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_stateOrProvinceName,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "ST", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+ ret = X509_NAME_get_text_by_NID(org,
+ NID_countryName,
+ buf, 1024);
+ if (ret != -1)
+ apr_hash_set(tgt, "C", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
+
+ return tgt;
+}
+
+
+static apr_hash_t *serf__openssl_cert_issuer(
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ X509_NAME *issuer = X509_get_issuer_name(cert->impl_cert);
+
+ if (!issuer)
+ return NULL;
+
+ return convert_X509_NAME_to_table(issuer, pool);
+}
+
+
+static apr_hash_t *serf__openssl_cert_subject(
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ X509_NAME *subject = X509_get_subject_name(cert->impl_cert);
+
+ if (!subject)
+ return NULL;
+
+ return convert_X509_NAME_to_table(subject, pool);
+}
+
+
+static apr_hash_t *serf__openssl_cert_certificate(
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ apr_hash_t *tgt = apr_hash_make(pool);
+ unsigned int md_size;
+ unsigned char md[EVP_MAX_MD_SIZE];
+ BIO *bio;
+ STACK_OF(GENERAL_NAME) *names;
+ X509 *ssl_cert = cert->impl_cert;
+
+ /* sha1 fingerprint */
+ if (X509_digest(ssl_cert, EVP_sha1(), md, &md_size)) {
+ unsigned int i;
+ const char hex[] = "0123456789ABCDEF";
+ char fingerprint[EVP_MAX_MD_SIZE * 3];
+
+ for (i=0; i<md_size; i++) {
+ fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4];
+ fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)];
+ fingerprint[(3*i)+2] = ':';
+ }
+ if (md_size > 0)
+ fingerprint[(3*(md_size-1))+2] = '\0';
+ else
+ fingerprint[0] = '\0';
+
+ apr_hash_set(tgt, "sha1", APR_HASH_KEY_STRING,
+ apr_pstrdup(pool, fingerprint));
+ }
+
+ /* set expiry dates */
+ bio = BIO_new(BIO_s_mem());
+ if (bio) {
+ ASN1_TIME *notBefore, *notAfter;
+ char buf[256];
+
+ memset (buf, 0, sizeof (buf));
+ notBefore = X509_get_notBefore(ssl_cert);
+ if (ASN1_TIME_print(bio, notBefore)) {
+ BIO_read(bio, buf, 255);
+ apr_hash_set(tgt, "notBefore", APR_HASH_KEY_STRING,
+ apr_pstrdup(pool, buf));
+ }
+ memset (buf, 0, sizeof (buf));
+ notAfter = X509_get_notAfter(ssl_cert);
+ if (ASN1_TIME_print(bio, notAfter)) {
+ BIO_read(bio, buf, 255);
+ apr_hash_set(tgt, "notAfter", APR_HASH_KEY_STRING,
+ apr_pstrdup(pool, buf));
+ }
+ }
+ BIO_free(bio);
+
+ /* Get subjectAltNames */
+ names = X509_get_ext_d2i(ssl_cert, NID_subject_alt_name, NULL, NULL);
+ if (names) {
+ int names_count = sk_GENERAL_NAME_num(names);
+ int name_idx;
+
+ apr_array_header_t *san_arr = apr_array_make(pool, names_count,
+ sizeof(char*));
+ apr_hash_set(tgt, "subjectAltName", APR_HASH_KEY_STRING, san_arr);
+ for (name_idx = 0; name_idx < names_count; name_idx++) {
+ char *p = NULL;
+ GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, name_idx);
+
+ switch (nm->type) {
+ case GEN_DNS:
+ p = apr_pstrmemdup(pool, (const char *)nm->d.ia5->data,
+ nm->d.ia5->length);
+ break;
+ default:
+ /* Don't know what to do - skip. */
+ break;
+ }
+ if (p) {
+ APR_ARRAY_PUSH(san_arr, char*) = p;
+ }
+ }
+ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+ }
+
+ return tgt;
+}
+
+
+static const char *serf__openssl_cert_export(
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *pool)
+{
+ char *binary_cert;
+ char *encoded_cert;
+ int len;
+ unsigned char *unused;
+
+ /* find the length of the DER encoding. */
+ len = i2d_X509(cert->impl_cert, NULL);
+ if (len < 0) {
+ return NULL;
+ }
+
+ binary_cert = apr_palloc(pool, len);
+ unused = (unsigned char *)binary_cert;
+ len = i2d_X509(cert->impl_cert, &unused); /* unused is incremented */
+ if (len < 0) {
+ return NULL;
+ }
+
+ encoded_cert = apr_palloc(pool, apr_base64_encode_len(len));
+ apr_base64_encode(encoded_cert, binary_cert, len);
+
+ return encoded_cert;
+}
+
+/* Disables compression for all SSL sessions. */
+static void disable_compression(openssl_context_t *ssl_ctx)
+{
+#ifdef SSL_OP_NO_COMPRESSION
+ SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_COMPRESSION);
+#endif
+}
+
+static apr_status_t
+serf__openssl_use_compression(void *impl_ctx, int enabled)
+{
+ openssl_context_t *ssl_ctx = impl_ctx;
+
+ if (enabled) {
+#ifdef SSL_OP_NO_COMPRESSION
+ SSL_clear_options(ssl_ctx->ssl, SSL_OP_NO_COMPRESSION);
+ return APR_SUCCESS;
+#endif
+ } else {
+#ifdef SSL_OP_NO_COMPRESSION
+ SSL_set_options(ssl_ctx->ssl, SSL_OP_NO_COMPRESSION);
+ return APR_SUCCESS;
+#endif
+ }
+
+ return APR_EGENERAL;
+}
+
+static void openssl_destroy_and_data(serf_bucket_t *bucket)
+{
+ ssl_context_t *ctx = bucket->data;
+
+ if (!--ctx->ssl_ctx->refcount) {
+ ssl_free_context(ctx->ssl_ctx);
+ }
+
+ serf_default_destroy_and_data(bucket);
+}
+
+static void serf_openssl_decrypt_destroy_and_data(serf_bucket_t *bucket)
+{
+ ssl_context_t *ctx = bucket->data;
+
+ serf_bucket_destroy(*ctx->our_stream);
+
+ openssl_destroy_and_data(bucket);
+}
+
+static void serf_openssl_encrypt_destroy_and_data(serf_bucket_t *bucket)
+{
+ ssl_context_t *ctx = bucket->data;
+ openssl_context_t *ssl_ctx = ctx->ssl_ctx;
+
+ if (ssl_ctx->encrypt.stream == *ctx->our_stream) {
+ serf_bucket_destroy(*ctx->our_stream);
+ serf_bucket_destroy(ssl_ctx->encrypt.pending);
+
+ /* Reset our encrypted status and databuf. */
+ ssl_ctx->encrypt.status = APR_SUCCESS;
+ ssl_ctx->encrypt.databuf.status = APR_SUCCESS;
+
+ /* Advance to the next stream - if we have one. */
+ if (ssl_ctx->encrypt.stream_next == NULL) {
+ ssl_ctx->encrypt.stream = NULL;
+ ssl_ctx->encrypt.pending = NULL;
+ }
+ else {
+ bucket_list_t *cur;
+
+ cur = ssl_ctx->encrypt.stream_next;
+ ssl_ctx->encrypt.stream = cur->bucket;
+ ssl_ctx->encrypt.pending =
+ serf_bucket_aggregate_create(cur->bucket->allocator);
+ ssl_ctx->encrypt.stream_next = cur->next;
+ serf_bucket_mem_free(ssl_ctx->allocator, cur);
+ }
+ }
+ else {
+ /* Ah, darn. We haven't sent this one along yet. */
+ return;
+ }
+ openssl_destroy_and_data(bucket);
+}
+
+static apr_status_t serf_openssl_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ ssl_context_t *ctx = bucket->data;
+
+ return serf_databuf_read(ctx->databuf, requested, data, len);
+}
+
+static apr_status_t serf_openssl_readline(serf_bucket_t *bucket,
+ int acceptable, int *found,
+ const char **data,
+ apr_size_t *len)
+{
+ ssl_context_t *ctx = bucket->data;
+
+ return serf_databuf_readline(ctx->databuf, acceptable, found, data, len);
+}
+
+static apr_status_t serf_openssl_peek(serf_bucket_t *bucket,
+ const char **data,
+ apr_size_t *len)
+{
+ ssl_context_t *ctx = bucket->data;
+
+ return serf_databuf_peek(ctx->databuf, data, len);
+}
+
+
+const serf_bucket_type_t serf_bucket_type_openssl_encrypt = {
+ "OPENSSLENCRYPT",
+ serf_openssl_read,
+ serf_openssl_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_openssl_peek,
+ serf_openssl_encrypt_destroy_and_data,
+};
+
+const serf_bucket_type_t serf_bucket_type_openssl_decrypt = {
+ "OPENSSLDECRYPT",
+ serf_openssl_read,
+ serf_openssl_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_openssl_peek,
+ serf_openssl_decrypt_destroy_and_data,
+};
+
+const serf_ssl_bucket_type_t serf_ssl_bucket_type_openssl = {
+ serf__openssl_bucket_decrypt_create,
+ serf__openssl_bucket_decrypt_context_get,
+ serf__openssl_bucket_encrypt_create,
+ serf__openssl_bucket_encrypt_context_get,
+ serf__openssl_set_hostname,
+ serf__openssl_client_cert_provider_set,
+ serf__openssl_identity_provider_set,
+ serf__openssl_client_cert_password_set,
+ serf__openssl_server_cert_callback_set,
+ serf__openssl_server_cert_chain_callback_set,
+ serf__openssl_use_default_certificates,
+ serf__openssl_load_CA_cert_from_file,
+ serf__openssl_load_identity_from_file,
+ serf__openssl_trust_cert,
+ serf__openssl_cert_issuer,
+ serf__openssl_cert_subject,
+ serf__openssl_cert_certificate,
+ serf__openssl_cert_export,
+ serf__openssl_use_compression,
+ serf__openssl_session_export,
+ serf__openssl_session_import,
+ serf__openssl_new_session_callback_set,
+ serf__openssl_resume_session,
+};
+#else
+
+const serf_bucket_type_t serf_bucket_type_openssl_encrypt = {
+ "OPENSSLENCRYPT",
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+};
+const serf_bucket_type_t serf_bucket_type_openssl_decrypt = {
+ "OPENSSLDECRYPT",
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+};
+
+#endif /* SERF_HAVE_OPENSSL */
diff --git a/buckets/ssl_buckets.c b/buckets/ssl_buckets.c
index d3a5c48..a460bd7 100644
--- a/buckets/ssl_buckets.c
+++ b/buckets/ssl_buckets.c
@@ -1,4 +1,4 @@
-/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+/* Copyright 2013 Justin Erenkrantz and Greg Stein
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,1458 +11,129 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
- * ----
- *
- * For the OpenSSL thread-safety locking code:
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Originally developed by Aaron Bannert and Justin Erenkrantz, eBuilt.
*/
-#include <apr_pools.h>
-#include <apr_network_io.h>
-#include <apr_portable.h>
-#include <apr_strings.h>
-#include <apr_base64.h>
-#include <apr_version.h>
-#include <apr_atomic.h>
-
#include "serf.h"
#include "serf_private.h"
#include "serf_bucket_util.h"
+#include "bucket_private.h"
-#include <openssl/bio.h>
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/pkcs12.h>
-#include <openssl/x509v3.h>
+typedef struct serf_ssl_bucket_t {
+ serf_bucket_t bucket;
+
+ /* context (including the type of this bucket) shared between encrypt and
+ decrypt ssl_bucket */
+ serf_ssl_context_t *ssl_ctx;
-#ifndef APR_ARRAY_PUSH
-#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary)))
-#endif
+ /** the allocator used for this context (needed at destroy time) */
+ serf_bucket_alloc_t *allocator;
+} serf_ssl_bucket_t;
-
-/*
- * Here's an overview of the SSL bucket's relationship to OpenSSL and serf.
- *
- * HTTP request: SSLENCRYPT(REQUEST)
- * [context.c reads from SSLENCRYPT and writes out to the socket]
- * HTTP response: RESPONSE(SSLDECRYPT(SOCKET))
- * [handler function reads from RESPONSE which in turn reads from SSLDECRYPT]
- *
- * HTTP request read call path:
- *
- * write_to_connection
- * |- serf_bucket_read on SSLENCRYPT
- * |- serf_ssl_read
- * |- serf_databuf_read
- * |- common_databuf_prep
- * |- ssl_encrypt
- * |- 1. Try to read pending encrypted data; If available, return.
- * |- 2. Try to read from ctx->stream [REQUEST bucket]
- * |- 3. Call SSL_write with read data
- * |- ...
- * |- bio_bucket_read can be called
- * |- bio_bucket_write with encrypted data
- * |- store in sink
- * |- 4. If successful, read pending encrypted data and return.
- * |- 5. If fails, place read data back in ctx->stream
- *
- * HTTP response read call path:
- *
- * read_from_connection
- * |- acceptor
- * |- handler
- * |- ...
- * |- serf_bucket_read(SSLDECRYPT)
- * |- serf_ssl_read
- * |- serf_databuf_read
- * |- ssl_decrypt
- * |- 1. SSL_read() for pending decrypted data; if any, return.
- * |- 2. Try to read from ctx->stream [SOCKET bucket]
- * |- 3. Append data to ssl_ctx->source
- * |- 4. Call SSL_read()
- * |- ...
- * |- bio_bucket_write can be called
- * |- bio_bucket_read
- * |- read data from ssl_ctx->source
- * |- If data read, return it.
- * |- If an error, set the STATUS value and return.
- *
- */
-
-typedef struct bucket_list {
- serf_bucket_t *bucket;
- struct bucket_list *next;
-} bucket_list_t;
-
-typedef struct {
- /* Helper to read data. Wraps stream. */
- serf_databuf_t databuf;
-
- /* Our source for more data. */
- serf_bucket_t *stream;
-
- /* The next set of buckets */
- bucket_list_t *stream_next;
-
- /* The status of the last thing we read. */
- apr_status_t status;
- apr_status_t exhausted;
- int exhausted_reset;
-
- /* Data we've read but not processed. */
- serf_bucket_t *pending;
-} serf_ssl_stream_t;
-
-struct serf_ssl_context_t {
+struct serf_ssl_context_t
+{
/* How many open buckets refer to this context. */
int refcount;
- /* The pool that this context uses. */
- apr_pool_t *pool;
+ /* Which SSL implementation is used for this context. */
+ const serf_ssl_bucket_type_t *type;
- /* The allocator associated with the above pool. */
- serf_bucket_alloc_t *allocator;
-
- /* Internal OpenSSL parameters */
- SSL_CTX *ctx;
- SSL *ssl;
- BIO *bio;
-
- serf_ssl_stream_t encrypt;
- serf_ssl_stream_t decrypt;
-
- /* Client cert callbacks */
- serf_ssl_need_client_cert_t cert_callback;
- void *cert_userdata;
- apr_pool_t *cert_cache_pool;
- const char *cert_file_success;
-
- /* Client cert PW callbacks */
- serf_ssl_need_cert_password_t cert_pw_callback;
- void *cert_pw_userdata;
- apr_pool_t *cert_pw_cache_pool;
- const char *cert_pw_success;
-
- /* Server cert callbacks */
- serf_ssl_need_server_cert_t server_cert_callback;
- serf_ssl_server_cert_chain_cb_t server_cert_chain_callback;
- void *server_cert_userdata;
-
- serf_ssl_new_session_t new_session_cb;
- void *new_session_cb_baton;
-
- const char *cert_path;
-
- X509 *cached_cert;
- EVP_PKEY *cached_cert_pw;
-
- apr_status_t pending_err;
-
- /* Status of a fatal error, returned on subsequent encrypt or decrypt
- requests. */
- apr_status_t fatal_err;
+ /** implementation specific context */
+ void *impl_ctx;
};
-typedef struct {
- /* The bucket-independent ssl context that this bucket is associated with */
- serf_ssl_context_t *ssl_ctx;
-
- /* Pointer to the 'right' databuf. */
- serf_databuf_t *databuf;
-
- /* Pointer to our stream, so we can find it later. */
- serf_bucket_t **our_stream;
-} ssl_context_t;
-
-struct serf_ssl_certificate_t {
- X509 *ssl_cert;
- int depth;
-};
-
-struct serf_ssl_session_t {
- SSL_SESSION *session_obj;
-};
-
-static void disable_compression(serf_ssl_context_t *ssl_ctx);
-
-#if SSL_VERBOSE
-/* Log all ssl alerts that we receive from the server. */
-static void
-apps_ssl_info_callback(const SSL *s, int where, int ret)
+void *serf__ssl_get_impl_context(serf_ssl_context_t *ssl_ctx)
{
- const char *str;
- int w;
- w = where & ~SSL_ST_MASK;
-
- if (w & SSL_ST_CONNECT)
- str = "SSL_connect";
- else if (w & SSL_ST_ACCEPT)
- str = "SSL_accept";
- else
- str = "undefined";
-
- if (where & SSL_CB_LOOP) {
- serf__log(SSL_VERBOSE, __FILE__, "%s:%s\n", str,
- SSL_state_string_long(s));
- }
- else if (where & SSL_CB_ALERT) {
- str = (where & SSL_CB_READ) ? "read" : "write";
- serf__log(SSL_VERBOSE, __FILE__, "SSL3 alert %s:%s:%s\n",
- str,
- SSL_alert_type_string_long(ret),
- SSL_alert_desc_string_long(ret));
- }
- else if (where & SSL_CB_EXIT) {
- if (ret == 0)
- serf__log(SSL_VERBOSE, __FILE__, "%s:failed in %s\n", str,
- SSL_state_string_long(s));
- else if (ret < 0) {
- serf__log(SSL_VERBOSE, __FILE__, "%s:error in %s\n", str,
- SSL_state_string_long(s));
- }
- }
+ return ssl_ctx->impl_ctx;
}
+
+static const serf_ssl_bucket_type_t *decide_ssl_bucket_type(void)
+{
+ apr_uint32_t bucket_impls = serf_config_get_bucket_impls();
+
+ /* Prefer SSL implementation integrated in host platform, depending
+ on what's builtin and what the application allows. */
+#ifdef SERF_HAVE_MACOSXSSL
+ if (bucket_impls & SERF_IMPL_SSL_MACOSXSSL)
+ return &serf_ssl_bucket_type_macosxssl;
+#endif
+#ifdef SERF_HAVE_OPENSSL
+ if (bucket_impls & SERF_IMPL_SSL_OPENSSL)
+ return &serf_ssl_bucket_type_openssl;
#endif
-/* Returns the amount read. */
-static int bio_bucket_read(BIO *bio, char *in, int inlen)
-{
- serf_ssl_context_t *ctx = bio->ptr;
- const char *data;
- apr_status_t status;
- apr_size_t len;
-
- serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read called for %d bytes\n",
- inlen);
-
- if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN
- && BIO_should_read(ctx->bio)) {
- serf__log(SSL_VERBOSE, __FILE__,
- "bio_bucket_read waiting: (%d %d %d)\n",
- BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
- BIO_get_retry_flags(ctx->bio));
- /* Falling back... */
- ctx->encrypt.exhausted_reset = 1;
- BIO_clear_retry_flags(bio);
- }
-
- status = serf_bucket_read(ctx->decrypt.pending, inlen, &data, &len);
-
- ctx->decrypt.status = status;
-
- serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read received %d bytes (%d)\n",
- len, status);
-
- if (!SERF_BUCKET_READ_ERROR(status)) {
- /* Oh suck. */
- if (len) {
- memcpy(in, data, len);
- return len;
- }
- if (APR_STATUS_IS_EOF(status)) {
- BIO_set_retry_read(bio);
- return -1;
- }
- }
-
- return -1;
+ return NULL;
}
-/* Returns the amount written. */
-static int bio_bucket_write(BIO *bio, const char *in, int inl)
-{
- serf_ssl_context_t *ctx = bio->ptr;
- serf_bucket_t *tmp;
-
- serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_write called for %d bytes\n",
- inl);
-
- if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN
- && !BIO_should_read(ctx->bio)) {
- serf__log(SSL_VERBOSE, __FILE__,
- "bio_bucket_write waiting: (%d %d %d)\n",
- BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
- BIO_get_retry_flags(ctx->bio));
- /* Falling back... */
- ctx->encrypt.exhausted_reset = 1;
- BIO_clear_retry_flags(bio);
- }
-
- tmp = serf_bucket_simple_copy_create(in, inl,
- ctx->encrypt.pending->allocator);
-
- serf_bucket_aggregate_append(ctx->encrypt.pending, tmp);
-
- return inl;
-}
-
-/* Returns the amount read. */
-static int bio_file_read(BIO *bio, char *in, int inlen)
-{
- apr_file_t *file = bio->ptr;
- apr_status_t status;
- apr_size_t len;
-
- BIO_clear_retry_flags(bio);
-
- len = inlen;
- status = apr_file_read(file, in, &len);
-
- if (!SERF_BUCKET_READ_ERROR(status)) {
- /* Oh suck. */
- if (APR_STATUS_IS_EOF(status)) {
- BIO_set_retry_read(bio);
- return -1;
- } else {
- return len;
- }
- }
-
- return -1;
-}
-
-/* Returns the amount written. */
-static int bio_file_write(BIO *bio, const char *in, int inl)
-{
- apr_file_t *file = bio->ptr;
- apr_size_t nbytes;
-
- BIO_clear_retry_flags(bio);
-
- nbytes = inl;
- apr_file_write(file, in, &nbytes);
-
- return nbytes;
-}
-
-static int bio_file_gets(BIO *bio, char *in, int inlen)
-{
- return bio_file_read(bio, in, inlen);
-}
-
-static int bio_bucket_create(BIO *bio)
-{
- bio->shutdown = 1;
- bio->init = 1;
- bio->num = -1;
- bio->ptr = NULL;
-
- return 1;
-}
-
-static int bio_bucket_destroy(BIO *bio)
-{
- /* Did we already free this? */
- if (bio == NULL) {
- return 0;
- }
-
- return 1;
-}
-
-static long bio_bucket_ctrl(BIO *bio, int cmd, long num, void *ptr)
-{
- long ret = 1;
-
- switch (cmd) {
- default:
- /* abort(); */
- break;
- case BIO_CTRL_FLUSH:
- /* At this point we can't force a flush. */
- break;
- case BIO_CTRL_PUSH:
- case BIO_CTRL_POP:
- ret = 0;
- break;
- }
- return ret;
-}
-
-static BIO_METHOD bio_bucket_method = {
- BIO_TYPE_MEM,
- "Serf SSL encryption and decryption buckets",
- bio_bucket_write,
- bio_bucket_read,
- NULL, /* Is this called? */
- NULL, /* Is this called? */
- bio_bucket_ctrl,
- bio_bucket_create,
- bio_bucket_destroy,
-#ifdef OPENSSL_VERSION_NUMBER
- NULL /* sslc does not have the callback_ctrl field */
-#endif
-};
-
-static BIO_METHOD bio_file_method = {
- BIO_TYPE_FILE,
- "Wrapper around APR file structures",
- bio_file_write,
- bio_file_read,
- NULL, /* Is this called? */
- bio_file_gets, /* Is this called? */
- bio_bucket_ctrl,
- bio_bucket_create,
- bio_bucket_destroy,
-#ifdef OPENSSL_VERSION_NUMBER
- NULL /* sslc does not have the callback_ctrl field */
-#endif
-};
-
-static int
-validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx)
-{
- SSL *ssl;
- serf_ssl_context_t *ctx;
- X509 *server_cert;
- int err, depth;
- int failures = 0;
-
- ssl = X509_STORE_CTX_get_ex_data(store_ctx,
- SSL_get_ex_data_X509_STORE_CTX_idx());
- ctx = SSL_get_app_data(ssl);
-
- server_cert = X509_STORE_CTX_get_current_cert(store_ctx);
- depth = X509_STORE_CTX_get_error_depth(store_ctx);
-
- /* If the certification was found invalid, get the error and convert it to
- something our caller will understand. */
- if (! cert_valid) {
- err = X509_STORE_CTX_get_error(store_ctx);
-
- switch(err) {
- case X509_V_ERR_CERT_NOT_YET_VALID:
- failures |= SERF_SSL_CERT_NOTYETVALID;
- break;
- case X509_V_ERR_CERT_HAS_EXPIRED:
- failures |= SERF_SSL_CERT_EXPIRED;
- break;
- case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
- case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
- failures |= SERF_SSL_CERT_SELF_SIGNED;
- break;
- case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
- case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
- case X509_V_ERR_CERT_UNTRUSTED:
- case X509_V_ERR_INVALID_CA:
- failures |= SERF_SSL_CERT_UNKNOWNCA;
- break;
- case X509_V_ERR_CERT_REVOKED:
- failures |= SERF_SSL_CERT_REVOKED;
- break;
- default:
- failures |= SERF_SSL_CERT_UNKNOWN_FAILURE;
- break;
- }
- }
-
- /* Check certificate expiry dates. */
- if (X509_cmp_current_time(X509_get_notBefore(server_cert)) >= 0) {
- failures |= SERF_SSL_CERT_NOTYETVALID;
- }
- else if (X509_cmp_current_time(X509_get_notAfter(server_cert)) <= 0) {
- failures |= SERF_SSL_CERT_EXPIRED;
- }
-
- if (ctx->server_cert_callback &&
- (depth == 0 || failures)) {
- apr_status_t status;
- serf_ssl_certificate_t *cert;
- apr_pool_t *subpool;
-
- apr_pool_create(&subpool, ctx->pool);
-
- cert = apr_palloc(subpool, sizeof(serf_ssl_certificate_t));
- cert->ssl_cert = server_cert;
- cert->depth = depth;
-
- /* Callback for further verification. */
- status = ctx->server_cert_callback(ctx->server_cert_userdata,
- failures, cert);
- if (status == APR_SUCCESS)
- cert_valid = 1;
- else {
- /* Even if openssl found the certificate valid, the application
- told us to reject it. */
- cert_valid = 0;
- /* Pass the error back to the caller through the context-run. */
- ctx->pending_err = status;
- }
- apr_pool_destroy(subpool);
- }
-
- if (ctx->server_cert_chain_callback
- && (depth == 0 || failures)) {
- apr_status_t status;
- STACK_OF(X509) *chain;
- const serf_ssl_certificate_t **certs;
- int certs_len;
- apr_pool_t *subpool;
-
- apr_pool_create(&subpool, ctx->pool);
-
- /* Borrow the chain to pass to the callback. */
- chain = X509_STORE_CTX_get_chain(store_ctx);
-
- /* If the chain can't be retrieved, just pass the current
- certificate. */
- /* ### can this actually happen with _get_chain() ? */
- if (!chain) {
- serf_ssl_certificate_t *cert = apr_palloc(subpool, sizeof(*cert));
-
- cert->ssl_cert = server_cert;
- cert->depth = depth;
-
- /* Room for the server_cert and a trailing NULL. */
- certs = apr_palloc(subpool, sizeof(*certs) * 2);
- certs[0] = cert;
-
- certs_len = 1;
- } else {
- int i;
-
- certs_len = sk_X509_num(chain);
-
- /* Room for all the certs and a trailing NULL. */
- certs = apr_palloc(subpool, sizeof(*certs) * (certs_len + 1));
- for (i = 0; i < certs_len; ++i) {
- serf_ssl_certificate_t *cert;
-
- cert = apr_palloc(subpool, sizeof(*cert));
- cert->ssl_cert = sk_X509_value(chain, i);
- cert->depth = i;
-
- certs[i] = cert;
- }
- }
- certs[certs_len] = NULL;
-
- /* Callback for further verification. */
- status = ctx->server_cert_chain_callback(ctx->server_cert_userdata,
- failures, depth,
- certs, certs_len);
- if (status == APR_SUCCESS) {
- cert_valid = 1;
- } else {
- /* Even if openssl found the certificate valid, the application
- told us to reject it. */
- cert_valid = 0;
- /* Pass the error back to the caller through the context-run. */
- ctx->pending_err = status;
- }
-
- apr_pool_destroy(subpool);
- }
-
- /* Return a specific error if the server certificate is not accepted by
- OpenSSL and the application has not set callbacks to override this. */
- if (!cert_valid &&
- !ctx->server_cert_chain_callback &&
- !ctx->server_cert_callback)
- {
- ctx->pending_err = SERF_ERROR_SSL_CERT_FAILED;
- }
-
- return cert_valid;
-}
-
-/* This function reads an encrypted stream and returns the decrypted stream. */
-static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize,
- char *buf, apr_size_t *len)
-{
- serf_ssl_context_t *ctx = baton;
- apr_size_t priv_len;
- apr_status_t status;
- const char *data;
- int ssl_len;
-
- if (ctx->fatal_err)
- return ctx->fatal_err;
-
- serf__log(SSL_VERBOSE, __FILE__, "ssl_decrypt: begin %d\n", bufsize);
-
- /* Is there some data waiting to be read? */
- ssl_len = SSL_read(ctx->ssl, buf, bufsize);
- if (ssl_len > 0) {
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_decrypt: %d bytes (%d); status: %d; flags: %d\n",
- ssl_len, bufsize, ctx->decrypt.status,
- BIO_get_retry_flags(ctx->bio));
- *len = ssl_len;
- return APR_SUCCESS;
- }
-
- status = serf_bucket_read(ctx->decrypt.stream, bufsize, &data, &priv_len);
-
- if (!SERF_BUCKET_READ_ERROR(status) && priv_len) {
- serf_bucket_t *tmp;
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_decrypt: read %d bytes (%d); status: %d\n",
- priv_len, bufsize, status);
-
- tmp = serf_bucket_simple_copy_create(data, priv_len,
- ctx->decrypt.pending->allocator);
-
- serf_bucket_aggregate_append(ctx->decrypt.pending, tmp);
-
- ssl_len = SSL_read(ctx->ssl, buf, bufsize);
- if (ssl_len < 0) {
- int ssl_err;
-
- ssl_err = SSL_get_error(ctx->ssl, ssl_len);
- switch (ssl_err) {
- case SSL_ERROR_SYSCALL:
- *len = 0;
- status = ctx->decrypt.status;
- break;
- case SSL_ERROR_WANT_READ:
- *len = 0;
- status = APR_EAGAIN;
- break;
- case SSL_ERROR_SSL:
- *len = 0;
- if (ctx->pending_err) {
- status = ctx->pending_err;
- ctx->pending_err = 0;
- } else {
- ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
- }
- break;
- default:
- *len = 0;
- ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
- break;
- }
- } else if (ssl_len == 0) {
- /* The server shut down the connection. */
- int ssl_err, shutdown;
- *len = 0;
-
- /* Check for SSL_RECEIVED_SHUTDOWN */
- shutdown = SSL_get_shutdown(ctx->ssl);
- /* Check for SSL_ERROR_ZERO_RETURN */
- ssl_err = SSL_get_error(ctx->ssl, ssl_len);
-
- if (shutdown == SSL_RECEIVED_SHUTDOWN &&
- ssl_err == SSL_ERROR_ZERO_RETURN) {
- /* The server closed the SSL session. While this doesn't
- necessary mean the connection is closed, let's close
- it here anyway.
- We can optimize this later. */
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_decrypt: SSL read error: server"
- " shut down connection!\n");
- status = APR_EOF;
- } else {
- /* A fatal error occurred. */
- ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
- }
- } else {
- *len = ssl_len;
- serf__log(SSL_MSG_VERBOSE, __FILE__,
- "---\n%.*s\n-(%d)-\n", *len, buf, *len);
- }
- }
- else {
- *len = 0;
- }
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_decrypt: %d %d %d\n", status, *len,
- BIO_get_retry_flags(ctx->bio));
-
- return status;
-}
-
-/* This function reads a decrypted stream and returns an encrypted stream. */
-static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize,
- char *buf, apr_size_t *len)
-{
- const char *data;
- apr_size_t interim_bufsize;
- serf_ssl_context_t *ctx = baton;
- apr_status_t status;
-
- if (ctx->fatal_err)
- return ctx->fatal_err;
-
- serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: begin %d\n", bufsize);
-
- /* Try to read already encrypted but unread data first. */
- status = serf_bucket_read(ctx->encrypt.pending, bufsize, &data, len);
- if (SERF_BUCKET_READ_ERROR(status)) {
- return status;
- }
-
- /* Aha, we read something. Return that now. */
- if (*len) {
- memcpy(buf, data, *len);
- if (APR_STATUS_IS_EOF(status)) {
- status = APR_SUCCESS;
- }
-
- serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: %d %d %d (quick read)\n",
- status, *len, BIO_get_retry_flags(ctx->bio));
-
- return status;
- }
-
- if (BIO_should_retry(ctx->bio) && BIO_should_write(ctx->bio)) {
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt: %d %d %d (should write exit)\n",
- status, *len, BIO_get_retry_flags(ctx->bio));
-
- return APR_EAGAIN;
- }
-
- /* If we were previously blocked, unblock ourselves now. */
- if (BIO_should_read(ctx->bio)) {
- serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: reset %d %d (%d %d %d)\n",
- status, ctx->encrypt.status,
- BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
- BIO_get_retry_flags(ctx->bio));
-
- ctx->encrypt.status = APR_SUCCESS;
- ctx->encrypt.exhausted_reset = 0;
- }
-
- /* Oh well, read from our stream now. */
- interim_bufsize = bufsize;
- do {
- apr_size_t interim_len;
-
- if (!ctx->encrypt.status) {
- struct iovec vecs[64];
- int vecs_read;
-
- status = serf_bucket_read_iovec(ctx->encrypt.stream,
- interim_bufsize, 64, vecs,
- &vecs_read);
-
- if (!SERF_BUCKET_READ_ERROR(status) && vecs_read) {
- char *vecs_data;
- int i, cur, vecs_data_len;
- int ssl_len;
-
- /* Combine the buffers of the iovec into one buffer, as
- that is with SSL_write requires. */
- vecs_data_len = 0;
- for (i = 0; i < vecs_read; i++) {
- vecs_data_len += vecs[i].iov_len;
- }
-
- vecs_data = serf_bucket_mem_alloc(ctx->allocator,
- vecs_data_len);
-
- cur = 0;
- for (i = 0; i < vecs_read; i++) {
- memcpy(vecs_data + cur, vecs[i].iov_base, vecs[i].iov_len);
- cur += vecs[i].iov_len;
- }
-
- interim_bufsize -= vecs_data_len;
- interim_len = vecs_data_len;
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt: bucket read %d bytes; "\
- "status %d\n", interim_len, status);
- serf__log(SSL_MSG_VERBOSE, __FILE__, "---\n%.*s\n-(%d)-\n",
- interim_len, vecs_data, interim_len);
-
- /* Stash our status away. */
- ctx->encrypt.status = status;
-
- ssl_len = SSL_write(ctx->ssl, vecs_data, interim_len);
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt: SSL write: %d\n", ssl_len);
-
- /* If we failed to write... */
- if (ssl_len < 0) {
- int ssl_err;
-
- /* Ah, bugger. We need to put that data back.
- Note: use the copy here, we do not own the original iovec
- data buffer so it will be freed on next read. */
- serf_bucket_t *vecs_copy =
- serf_bucket_simple_own_create(vecs_data,
- vecs_data_len,
- ctx->allocator);
- serf_bucket_aggregate_prepend(ctx->encrypt.stream,
- vecs_copy);
-
- ssl_err = SSL_get_error(ctx->ssl, ssl_len);
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt: SSL write error: %d\n", ssl_err);
-
- if (ssl_err == SSL_ERROR_SYSCALL) {
- status = ctx->encrypt.status;
- if (SERF_BUCKET_READ_ERROR(status)) {
- return status;
- }
- }
- else {
- /* Oh, no. */
- if (ssl_err == SSL_ERROR_WANT_READ) {
- status = SERF_ERROR_WAIT_CONN;
- }
- else {
- ctx->fatal_err = status =
- SERF_ERROR_SSL_COMM_FAILED;
- }
- }
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt: SSL write error: %d %d\n",
- status, *len);
- } else {
- /* We're done with this data. */
- serf_bucket_mem_free(ctx->allocator, vecs_data);
- }
- }
- }
- else {
- interim_len = 0;
- *len = 0;
- status = ctx->encrypt.status;
- }
-
- } while (!status && interim_bufsize);
-
- /* Okay, we exhausted our underlying stream. */
- if (!SERF_BUCKET_READ_ERROR(status)) {
- apr_status_t agg_status;
- struct iovec vecs[64];
- int vecs_read, i;
-
- /* We read something! */
- agg_status = serf_bucket_read_iovec(ctx->encrypt.pending, bufsize,
- 64, vecs, &vecs_read);
- *len = 0;
- for (i = 0; i < vecs_read; i++) {
- memcpy(buf + *len, vecs[i].iov_base, vecs[i].iov_len);
- *len += vecs[i].iov_len;
- }
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt read agg: %d %d %d %d\n", status, agg_status,
- ctx->encrypt.status, *len);
-
- if (!agg_status) {
- status = agg_status;
- }
- }
-
- if (status == SERF_ERROR_WAIT_CONN
- && BIO_should_retry(ctx->bio) && BIO_should_read(ctx->bio)) {
- ctx->encrypt.exhausted = ctx->encrypt.status;
- ctx->encrypt.status = SERF_ERROR_WAIT_CONN;
- }
-
- serf__log(SSL_VERBOSE, __FILE__,
- "ssl_encrypt finished: %d %d (%d %d %d)\n", status, *len,
- BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio),
- BIO_get_retry_flags(ctx->bio));
-
- return status;
-}
-
-#if APR_HAS_THREADS
-static apr_pool_t *ssl_pool;
-static apr_thread_mutex_t **ssl_locks;
-
-typedef struct CRYPTO_dynlock_value {
- apr_thread_mutex_t *lock;
-} CRYPTO_dynlock_value;
-
-static CRYPTO_dynlock_value *ssl_dyn_create(const char* file, int line)
-{
- CRYPTO_dynlock_value *l;
- apr_status_t rv;
-
- l = apr_palloc(ssl_pool, sizeof(CRYPTO_dynlock_value));
- rv = apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, ssl_pool);
- if (rv != APR_SUCCESS) {
- /* FIXME: return error here */
- }
- return l;
-}
-
-static void ssl_dyn_lock(int mode, CRYPTO_dynlock_value *l, const char *file,
- int line)
-{
- if (mode & CRYPTO_LOCK) {
- apr_thread_mutex_lock(l->lock);
- }
- else if (mode & CRYPTO_UNLOCK) {
- apr_thread_mutex_unlock(l->lock);
- }
-}
-
-static void ssl_dyn_destroy(CRYPTO_dynlock_value *l, const char *file,
- int line)
-{
- apr_thread_mutex_destroy(l->lock);
-}
-
-static void ssl_lock(int mode, int n, const char *file, int line)
-{
- if (mode & CRYPTO_LOCK) {
- apr_thread_mutex_lock(ssl_locks[n]);
- }
- else if (mode & CRYPTO_UNLOCK) {
- apr_thread_mutex_unlock(ssl_locks[n]);
- }
-}
-
-static unsigned long ssl_id(void)
-{
- /* FIXME: This is lame and not portable. -aaron */
- return (unsigned long) apr_os_thread_current();
-}
-
-static apr_status_t cleanup_ssl(void *data)
-{
- CRYPTO_set_locking_callback(NULL);
- CRYPTO_set_id_callback(NULL);
- CRYPTO_set_dynlock_create_callback(NULL);
- CRYPTO_set_dynlock_lock_callback(NULL);
- CRYPTO_set_dynlock_destroy_callback(NULL);
-
- return APR_SUCCESS;
-}
-
-#endif
-
-static apr_uint32_t have_init_ssl = 0;
-
-static void init_ssl_libraries(void)
-{
- apr_uint32_t val;
-#if APR_VERSION_AT_LEAST(1,0,0)
- val = apr_atomic_xchg32(&have_init_ssl, 1);
-#else
- val = apr_atomic_cas(&have_init_ssl, 1, 0);
-#endif
-
- if (!val) {
-#if APR_HAS_THREADS
- int i, numlocks;
-#endif
-
-#ifdef SSL_VERBOSE
- /* Warn when compile-time and run-time version of OpenSSL differ in
- major/minor version number. */
- long libver = SSLeay();
-
- if ((libver ^ OPENSSL_VERSION_NUMBER) & 0xFFF00000) {
- serf__log(SSL_VERBOSE, __FILE__,
- "Warning: OpenSSL library version mismatch, compile-time "
- "was %lx, runtime is %lx.\n",
- OPENSSL_VERSION_NUMBER, libver);
- }
-#endif
-
- CRYPTO_malloc_init();
- ERR_load_crypto_strings();
- SSL_load_error_strings();
- SSL_library_init();
- OpenSSL_add_all_algorithms();
-
-#if APR_HAS_THREADS
- numlocks = CRYPTO_num_locks();
- apr_pool_create(&ssl_pool, NULL);
- ssl_locks = apr_palloc(ssl_pool, sizeof(apr_thread_mutex_t*)*numlocks);
- for (i = 0; i < numlocks; i++) {
- apr_status_t rv;
-
- /* Intraprocess locks don't /need/ a filename... */
- rv = apr_thread_mutex_create(&ssl_locks[i],
- APR_THREAD_MUTEX_DEFAULT, ssl_pool);
- if (rv != APR_SUCCESS) {
- /* FIXME: error out here */
- }
- }
- CRYPTO_set_locking_callback(ssl_lock);
- CRYPTO_set_id_callback(ssl_id);
- CRYPTO_set_dynlock_create_callback(ssl_dyn_create);
- CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock);
- CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy);
-
- apr_pool_cleanup_register(ssl_pool, NULL, cleanup_ssl, cleanup_ssl);
-#endif
- }
-}
-
-static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey)
-{
- serf_ssl_context_t *ctx = SSL_get_app_data(ssl);
- apr_status_t status;
-
- if (ctx->cached_cert) {
- *cert = ctx->cached_cert;
- *pkey = ctx->cached_cert_pw;
- return 1;
- }
-
- while (ctx->cert_callback) {
- const char *cert_path;
- apr_file_t *cert_file;
- BIO *bio;
- PKCS12 *p12;
- int i;
- int retrying_success = 0;
-
- if (ctx->cert_file_success) {
- status = APR_SUCCESS;
- cert_path = ctx->cert_file_success;
- ctx->cert_file_success = NULL;
- retrying_success = 1;
- } else {
- status = ctx->cert_callback(ctx->cert_userdata, &cert_path);
- }
-
- if (status || !cert_path) {
- break;
- }
-
- /* Load the x.509 cert file stored in PKCS12 */
- status = apr_file_open(&cert_file, cert_path, APR_READ, APR_OS_DEFAULT,
- ctx->pool);
-
- if (status) {
- continue;
- }
-
- bio = BIO_new(&bio_file_method);
- bio->ptr = cert_file;
-
- ctx->cert_path = cert_path;
- p12 = d2i_PKCS12_bio(bio, NULL);
- apr_file_close(cert_file);
-
- i = PKCS12_parse(p12, NULL, pkey, cert, NULL);
-
- if (i == 1) {
- PKCS12_free(p12);
- ctx->cached_cert = *cert;
- ctx->cached_cert_pw = *pkey;
- if (!retrying_success && ctx->cert_cache_pool) {
- const char *c;
-
- c = apr_pstrdup(ctx->cert_cache_pool, ctx->cert_path);
-
- apr_pool_userdata_setn(c, "serf:ssl:cert",
- apr_pool_cleanup_null,
- ctx->cert_cache_pool);
- }
- return 1;
- }
- else {
- int err = ERR_get_error();
- ERR_clear_error();
- if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 &&
- ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {
- if (ctx->cert_pw_callback) {
- const char *password;
-
- if (ctx->cert_pw_success) {
- status = APR_SUCCESS;
- password = ctx->cert_pw_success;
- ctx->cert_pw_success = NULL;
- } else {
- status = ctx->cert_pw_callback(ctx->cert_pw_userdata,
- ctx->cert_path,
- &password);
- }
-
- if (!status && password) {
- i = PKCS12_parse(p12, password, pkey, cert, NULL);
- if (i == 1) {
- PKCS12_free(p12);
- ctx->cached_cert = *cert;
- ctx->cached_cert_pw = *pkey;
- if (!retrying_success && ctx->cert_cache_pool) {
- const char *c;
-
- c = apr_pstrdup(ctx->cert_cache_pool,
- ctx->cert_path);
-
- apr_pool_userdata_setn(c, "serf:ssl:cert",
- apr_pool_cleanup_null,
- ctx->cert_cache_pool);
- }
- if (!retrying_success && ctx->cert_pw_cache_pool) {
- const char *c;
-
- c = apr_pstrdup(ctx->cert_pw_cache_pool,
- password);
-
- apr_pool_userdata_setn(c, "serf:ssl:certpw",
- apr_pool_cleanup_null,
- ctx->cert_pw_cache_pool);
- }
- return 1;
- }
- }
- }
- PKCS12_free(p12);
- return 0;
- }
- else {
- printf("OpenSSL cert error: %d %d %d\n", ERR_GET_LIB(err),
- ERR_GET_FUNC(err),
- ERR_GET_REASON(err));
- PKCS12_free(p12);
- }
- }
- }
-
- return 0;
-}
-
-
void serf_ssl_client_cert_provider_set(
- serf_ssl_context_t *context,
+ serf_ssl_context_t *ssl_ctx,
serf_ssl_need_client_cert_t callback,
void *data,
void *cache_pool)
{
- context->cert_callback = callback;
- context->cert_userdata = data;
- context->cert_cache_pool = cache_pool;
- if (context->cert_cache_pool) {
- apr_pool_userdata_get((void**)&context->cert_file_success,
- "serf:ssl:cert", cache_pool);
- }
+ return ssl_ctx->type->client_cert_provider_set(ssl_ctx->impl_ctx, callback,
+ data, cache_pool);
}
-
void serf_ssl_client_cert_password_set(
- serf_ssl_context_t *context,
+ serf_ssl_context_t *ssl_ctx,
serf_ssl_need_cert_password_t callback,
void *data,
void *cache_pool)
{
- context->cert_pw_callback = callback;
- context->cert_pw_userdata = data;
- context->cert_pw_cache_pool = cache_pool;
- if (context->cert_pw_cache_pool) {
- apr_pool_userdata_get((void**)&context->cert_pw_success,
- "serf:ssl:certpw", cache_pool);
- }
+ return ssl_ctx->type->client_cert_password_set(ssl_ctx->impl_ctx, callback,
+ data, cache_pool);
}
+void serf_ssl_identity_provider_set(serf_ssl_context_t *ssl_ctx,
+ serf_ssl_need_identity_t callback,
+ void *data,
+ void *cache_pool)
+{
+ return ssl_ctx->type->identity_provider_set(ssl_ctx->impl_ctx, callback,
+ data, cache_pool);
+}
+
+void serf_ssl_identity_password_callback_set(
+ serf_ssl_context_t *ssl_ctx,
+ serf_ssl_need_cert_password_t callback,
+ void *data,
+ void *cache_pool)
+{
+ return ssl_ctx->type->client_cert_password_set(ssl_ctx->impl_ctx, callback,
+ data, cache_pool);
+}
void serf_ssl_server_cert_callback_set(
- serf_ssl_context_t *context,
+ serf_ssl_context_t *ssl_ctx,
serf_ssl_need_server_cert_t callback,
void *data)
{
- context->server_cert_callback = callback;
- context->server_cert_userdata = data;
+ return ssl_ctx->type->server_cert_callback_set(ssl_ctx->impl_ctx,
+ callback, data);
}
void serf_ssl_server_cert_chain_callback_set(
- serf_ssl_context_t *context,
+ serf_ssl_context_t *ssl_ctx,
serf_ssl_need_server_cert_t cert_callback,
serf_ssl_server_cert_chain_cb_t cert_chain_callback,
void *data)
{
- context->server_cert_callback = cert_callback;
- context->server_cert_chain_callback = cert_chain_callback;
- context->server_cert_userdata = data;
+ return ssl_ctx->type->server_cert_chain_callback_set(ssl_ctx->impl_ctx,
+ cert_callback,
+ cert_chain_callback,
+ data);
}
-void serf_ssl_new_session_callback_set(
- serf_ssl_context_t *context,
- serf_ssl_new_session_t new_session_cb,
- void *baton)
-{
- context->new_session_cb = new_session_cb;
- context->new_session_cb_baton = baton;
-}
-
-static int new_session(SSL *ssl, SSL_SESSION *sess)
-{
- serf_ssl_context_t *ctx = SSL_get_app_data(ssl);
-
- if (ctx->new_session_cb) {
- serf_ssl_session_t session;
- apr_pool_t *subpool;
-
- session.session_obj = sess;
- apr_pool_create(&subpool, ctx->pool);
-
- ctx->new_session_cb(&session, ctx->new_session_cb_baton, subpool);
-
- apr_pool_destroy(subpool);
- }
-
- return 0;
-}
-
-apr_status_t serf_ssl_session_export(void **data_p,
- apr_size_t *len_p,
- const serf_ssl_session_t *session,
- apr_pool_t *pool)
-{
- int sess_len;
- void *sess_data;
- unsigned char *unused;
-
- sess_len = i2d_SSL_SESSION(session->session_obj, NULL);
- if (!sess_len) {
- return APR_EGENERAL;
- }
-
- sess_data = apr_palloc(pool, sess_len);
-
- unused = sess_data;
- /* unused is incremented */
- sess_len = i2d_SSL_SESSION(session->session_obj, &unused);
- if (!sess_len) {
- return APR_EGENERAL;
- }
-
- *data_p = sess_data;
- *len_p = sess_len;
- return APR_SUCCESS;
-}
-
-static apr_status_t cleanup_session(void *data)
-{
- serf_ssl_session_t *session = data;
-
- SSL_SESSION_free(session->session_obj);
- session->session_obj = NULL;
-
- return APR_SUCCESS;
-}
-
-apr_status_t serf_ssl_session_import(const serf_ssl_session_t **session_p,
- void *data,
- apr_size_t len,
- apr_pool_t *pool)
-{
- SSL_SESSION *sess;
- serf_ssl_session_t *session;
- const unsigned char *unused;
-
- unused = data;
- sess = d2i_SSL_SESSION(NULL, &unused, len); /* unused is incremented */
-
- if (!sess) {
- return APR_EGENERAL;
- }
-
- session = apr_pcalloc(pool, sizeof(serf_ssl_session_t));
- session->session_obj = sess;
- apr_pool_cleanup_register(pool, session, cleanup_session, cleanup_session);
-
- *session_p = session;
- return APR_SUCCESS;
-}
-
-static serf_ssl_context_t *ssl_init_context(void)
-{
- serf_ssl_context_t *ssl_ctx;
- apr_pool_t *pool;
- serf_bucket_alloc_t *allocator;
-
- init_ssl_libraries();
-
- apr_pool_create(&pool, NULL);
- allocator = serf_bucket_allocator_create(pool, NULL, NULL);
-
- ssl_ctx = serf_bucket_mem_alloc(allocator, sizeof(*ssl_ctx));
-
- ssl_ctx->refcount = 0;
- ssl_ctx->pool = pool;
- ssl_ctx->allocator = allocator;
-
- ssl_ctx->ctx = SSL_CTX_new(SSLv23_client_method());
-
- SSL_CTX_set_client_cert_cb(ssl_ctx->ctx, ssl_need_client_cert);
- ssl_ctx->cached_cert = 0;
- ssl_ctx->cached_cert_pw = 0;
- ssl_ctx->pending_err = APR_SUCCESS;
- ssl_ctx->fatal_err = APR_SUCCESS;
-
- ssl_ctx->cert_callback = NULL;
- ssl_ctx->cert_pw_callback = NULL;
- ssl_ctx->server_cert_callback = NULL;
- ssl_ctx->server_cert_chain_callback = NULL;
-
- SSL_CTX_set_verify(ssl_ctx->ctx, SSL_VERIFY_PEER,
- validate_server_certificate);
- SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_ALL);
-
- ssl_ctx->new_session_cb = NULL;
-
- /* Enable SSL callback for new sessions and disable internal session
- handling. */
- SSL_CTX_set_session_cache_mode(
- ssl_ctx->ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
- SSL_CTX_sess_set_new_cb(ssl_ctx->ctx, new_session);
-
- /* Disable SSL compression by default. */
- disable_compression(ssl_ctx);
-
- ssl_ctx->ssl = SSL_new(ssl_ctx->ctx);
- ssl_ctx->bio = BIO_new(&bio_bucket_method);
- ssl_ctx->bio->ptr = ssl_ctx;
-
- SSL_set_bio(ssl_ctx->ssl, ssl_ctx->bio, ssl_ctx->bio);
-
- SSL_set_connect_state(ssl_ctx->ssl);
-
- SSL_set_app_data(ssl_ctx->ssl, ssl_ctx);
-
-#if SSL_VERBOSE
- SSL_CTX_set_info_callback(ssl_ctx->ctx, apps_ssl_info_callback);
-#endif
-
- ssl_ctx->encrypt.stream = NULL;
- ssl_ctx->encrypt.stream_next = NULL;
- ssl_ctx->encrypt.pending = serf_bucket_aggregate_create(allocator);
- ssl_ctx->encrypt.status = APR_SUCCESS;
- serf_databuf_init(&ssl_ctx->encrypt.databuf);
- ssl_ctx->encrypt.databuf.read = ssl_encrypt;
- ssl_ctx->encrypt.databuf.read_baton = ssl_ctx;
-
- ssl_ctx->decrypt.stream = NULL;
- ssl_ctx->decrypt.pending = serf_bucket_aggregate_create(allocator);
- ssl_ctx->decrypt.status = APR_SUCCESS;
- serf_databuf_init(&ssl_ctx->decrypt.databuf);
- ssl_ctx->decrypt.databuf.read = ssl_decrypt;
- ssl_ctx->decrypt.databuf.read_baton = ssl_ctx;
-
- return ssl_ctx;
-}
-
-apr_status_t serf_ssl_resume_session(serf_ssl_context_t *ssl_ctx,
- const serf_ssl_session_t *session,
- apr_pool_t *pool)
-{
- SSL_set_session(ssl_ctx->ssl, session->session_obj);
- return APR_SUCCESS;
-}
-
-static apr_status_t ssl_free_context(
- serf_ssl_context_t *ssl_ctx)
-{
- apr_pool_t *p;
-
- /* If never had the pending buckets, don't try to free them. */
- if (ssl_ctx->decrypt.pending != NULL) {
- serf_bucket_destroy(ssl_ctx->decrypt.pending);
- }
- if (ssl_ctx->encrypt.pending != NULL) {
- serf_bucket_destroy(ssl_ctx->encrypt.pending);
- }
-
- /* SSL_free implicitly frees the underlying BIO. */
- SSL_free(ssl_ctx->ssl);
- SSL_CTX_free(ssl_ctx->ctx);
-
- p = ssl_ctx->pool;
-
- serf_bucket_mem_free(ssl_ctx->allocator, ssl_ctx);
- apr_pool_destroy(p);
-
- return APR_SUCCESS;
-}
-
-static serf_bucket_t * serf_bucket_ssl_create(
- serf_ssl_context_t *ssl_ctx,
- serf_bucket_alloc_t *allocator,
- const serf_bucket_type_t *type)
-{
- ssl_context_t *ctx;
-
- ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
- if (!ssl_ctx) {
- ctx->ssl_ctx = ssl_init_context();
- }
- else {
- ctx->ssl_ctx = ssl_ctx;
- }
- ctx->ssl_ctx->refcount++;
-
- return serf_bucket_create(type, allocator, ctx);
-}
-
-apr_status_t serf_ssl_set_hostname(serf_ssl_context_t *context,
+apr_status_t serf_ssl_set_hostname(serf_ssl_context_t *ssl_ctx,
const char * hostname)
{
-#ifdef SSL_set_tlsext_host_name
- if (SSL_set_tlsext_host_name(context->ssl, hostname) != 1) {
- ERR_clear_error();
- }
-#endif
- return APR_SUCCESS;
+ return ssl_ctx->type->set_hostname(ssl_ctx->impl_ctx, hostname);
}
-apr_status_t serf_ssl_use_default_certificates(serf_ssl_context_t *ssl_ctx)
+apr_status_t
+serf_ssl_use_compression(serf_ssl_context_t *ssl_ctx, int enabled)
{
- X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx);
-
- int result = X509_STORE_set_default_paths(store);
-
- return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED;
-}
-
-apr_status_t serf_ssl_load_cert_file(
- serf_ssl_certificate_t **cert,
- const char *file_path,
- apr_pool_t *pool)
-{
- FILE *fp = fopen(file_path, "r");
-
- if (fp) {
- X509 *ssl_cert = PEM_read_X509(fp, NULL, NULL, NULL);
- fclose(fp);
-
- if (ssl_cert) {
- *cert = apr_palloc(pool, sizeof(serf_ssl_certificate_t));
- (*cert)->ssl_cert = ssl_cert;
-
- return APR_SUCCESS;
- }
- }
-
- return SERF_ERROR_SSL_CERT_FAILED;
-}
-
-
-apr_status_t serf_ssl_trust_cert(
- serf_ssl_context_t *ssl_ctx,
- serf_ssl_certificate_t *cert)
-{
- X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx);
-
- int result = X509_STORE_add_cert(store, cert->ssl_cert);
-
- return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED;
+ return ssl_ctx->type->use_compression(ssl_ctx->impl_ctx, enabled);
}
@@ -1471,30 +142,41 @@
serf_ssl_context_t *ssl_ctx,
serf_bucket_alloc_t *allocator)
{
- serf_bucket_t *bkt;
- ssl_context_t *ctx;
+ const serf_ssl_bucket_type_t *type = decide_ssl_bucket_type();
+ serf_ssl_bucket_t *ssl_bkt;
- bkt = serf_bucket_ssl_create(ssl_ctx, allocator,
- &serf_bucket_type_ssl_decrypt);
-
- ctx = bkt->data;
-
- ctx->databuf = &ctx->ssl_ctx->decrypt.databuf;
- if (ctx->ssl_ctx->decrypt.stream != NULL) {
+ /* If no SSL implementation is available or allowed, we can't create
+ a bucket. */
+ if (!type)
return NULL;
- }
- ctx->ssl_ctx->decrypt.stream = stream;
- ctx->our_stream = &ctx->ssl_ctx->decrypt.stream;
- return bkt;
+ ssl_bkt = serf_bucket_mem_alloc(allocator, sizeof(*ssl_bkt));
+ ssl_bkt->allocator = allocator;
+
+ if (!ssl_ctx) {
+ ssl_ctx = serf_bucket_mem_alloc(allocator, sizeof(*ssl_ctx));
+ ssl_ctx->type = type;
+ ssl_ctx->refcount = 0;
+ ssl_ctx->impl_ctx = NULL;
+ }
+
+ ssl_ctx->impl_ctx = type->decrypt_create(&ssl_bkt->bucket,
+ stream,
+ ssl_ctx->impl_ctx,
+ allocator);
+ ssl_ctx->refcount++;
+ ssl_bkt->ssl_ctx = ssl_ctx;
+
+ return (serf_bucket_t*)ssl_bkt;
}
serf_ssl_context_t *serf_bucket_ssl_decrypt_context_get(
serf_bucket_t *bucket)
{
- ssl_context_t *ctx = bucket->data;
- return ctx->ssl_ctx;
+ serf_ssl_bucket_t *ssl_bucket = (serf_ssl_bucket_t *)bucket;
+
+ return ssl_bucket->ssl_ctx;
}
@@ -1503,376 +185,207 @@
serf_ssl_context_t *ssl_ctx,
serf_bucket_alloc_t *allocator)
{
- serf_bucket_t *bkt;
- ssl_context_t *ctx;
+ const serf_ssl_bucket_type_t *type = decide_ssl_bucket_type();
+ serf_ssl_bucket_t *ssl_bkt;
- bkt = serf_bucket_ssl_create(ssl_ctx, allocator,
- &serf_bucket_type_ssl_encrypt);
+ /* If no SSL implementation is available or allowed, we can't create
+ a bucket. */
+ if (!type)
+ return NULL;
- ctx = bkt->data;
+ ssl_bkt = serf_bucket_mem_alloc(allocator, sizeof(*ssl_bkt));
+ ssl_bkt->allocator = allocator;
- ctx->databuf = &ctx->ssl_ctx->encrypt.databuf;
- ctx->our_stream = &ctx->ssl_ctx->encrypt.stream;
- if (ctx->ssl_ctx->encrypt.stream == NULL) {
- serf_bucket_t *tmp = serf_bucket_aggregate_create(stream->allocator);
- serf_bucket_aggregate_append(tmp, stream);
- ctx->ssl_ctx->encrypt.stream = tmp;
- }
- else {
- bucket_list_t *new_list;
-
- new_list = serf_bucket_mem_alloc(ctx->ssl_ctx->allocator,
- sizeof(*new_list));
- new_list->bucket = stream;
- new_list->next = NULL;
- if (ctx->ssl_ctx->encrypt.stream_next == NULL) {
- ctx->ssl_ctx->encrypt.stream_next = new_list;
- }
- else {
- bucket_list_t *scan = ctx->ssl_ctx->encrypt.stream_next;
-
- while (scan->next != NULL)
- scan = scan->next;
- scan->next = new_list;
- }
+ if (!ssl_ctx) {
+ ssl_ctx = serf_bucket_mem_alloc(allocator, sizeof(*ssl_ctx));
+ ssl_ctx->type = type;
+ ssl_ctx->refcount = 0;
+ ssl_ctx->impl_ctx = NULL;
}
- return bkt;
+ ssl_ctx->impl_ctx = type->encrypt_create(&ssl_bkt->bucket,
+ stream,
+ ssl_ctx->impl_ctx,
+ allocator);
+ ssl_ctx->refcount++;
+ ssl_bkt->ssl_ctx = ssl_ctx;
+
+ return (serf_bucket_t*)ssl_bkt;
}
-serf_ssl_context_t *serf_bucket_ssl_encrypt_context_get(
- serf_bucket_t *bucket)
+serf_ssl_context_t *
+serf_bucket_ssl_encrypt_context_get(serf_bucket_t *bucket)
{
- ssl_context_t *ctx = bucket->data;
- return ctx->ssl_ctx;
+ serf_ssl_bucket_t *ssl_bucket = (serf_ssl_bucket_t *)bucket;
+
+ return ssl_bucket->ssl_ctx;
+}
+
+void
+serf_bucket_ssl_destroy_and_data(serf_bucket_t *bucket)
+{
+ serf_ssl_bucket_t *ssl_bucket = (serf_ssl_bucket_t *)bucket;
+ serf_ssl_context_t *ssl_ctx = ssl_bucket->ssl_ctx;
+
+ if (!--ssl_ctx->refcount) {
+ serf_bucket_mem_free(ssl_bucket->allocator, ssl_ctx);
+ }
+}
+
+apr_status_t serf_ssl_use_default_certificates(serf_ssl_context_t *ssl_ctx)
+{
+ return ssl_ctx->type->use_default_certificates(ssl_ctx->impl_ctx);
+}
+
+
+
+apr_status_t serf_ssl_trust_cert(serf_ssl_context_t *ssl_ctx,
+ serf_ssl_certificate_t *cert)
+{
+ return ssl_ctx->type->trust_cert(ssl_ctx->impl_ctx, cert);
+}
+
+/* Create a implementation-independent serf_ssl_certificate_t object */
+serf_ssl_certificate_t *
+serf__create_certificate(serf_bucket_alloc_t *allocator,
+ const serf_ssl_bucket_type_t *type,
+ void *impl_cert,
+ int depth_of_error)
+{
+ serf_ssl_certificate_t *cert;
+
+ cert = serf_bucket_mem_alloc(allocator,
+ sizeof(serf_ssl_certificate_t));
+ cert->impl_cert = impl_cert;
+ cert->type = type;
+ cert->depth_of_error = depth_of_error;
+
+ return cert;
}
/* Functions to read a serf_ssl_certificate structure. */
-
-/* Creates a hash_table with keys (E, CN, OU, O, L, ST and C). */
-static apr_hash_t *
-convert_X509_NAME_to_table(X509_NAME *org, apr_pool_t *pool)
-{
- char buf[1024];
- int ret;
-
- apr_hash_t *tgt = apr_hash_make(pool);
-
- ret = X509_NAME_get_text_by_NID(org,
- NID_commonName,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "CN", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
- ret = X509_NAME_get_text_by_NID(org,
- NID_pkcs9_emailAddress,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "E", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
- ret = X509_NAME_get_text_by_NID(org,
- NID_organizationalUnitName,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "OU", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
- ret = X509_NAME_get_text_by_NID(org,
- NID_organizationName,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "O", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
- ret = X509_NAME_get_text_by_NID(org,
- NID_localityName,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "L", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
- ret = X509_NAME_get_text_by_NID(org,
- NID_stateOrProvinceName,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "ST", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
- ret = X509_NAME_get_text_by_NID(org,
- NID_countryName,
- buf, 1024);
- if (ret != -1)
- apr_hash_set(tgt, "C", APR_HASH_KEY_STRING, apr_pstrdup(pool, buf));
-
- return tgt;
-}
-
-
int serf_ssl_cert_depth(const serf_ssl_certificate_t *cert)
{
- return cert->depth;
+ return cert->depth_of_error;
}
-
apr_hash_t *serf_ssl_cert_issuer(
const serf_ssl_certificate_t *cert,
apr_pool_t *pool)
{
- X509_NAME *issuer = X509_get_issuer_name(cert->ssl_cert);
-
- if (!issuer)
- return NULL;
-
- return convert_X509_NAME_to_table(issuer, pool);
+ return cert->type->cert_issuer(cert, pool);
}
-
apr_hash_t *serf_ssl_cert_subject(
const serf_ssl_certificate_t *cert,
apr_pool_t *pool)
{
- X509_NAME *subject = X509_get_subject_name(cert->ssl_cert);
-
- if (!subject)
- return NULL;
-
- return convert_X509_NAME_to_table(subject, pool);
+ return cert->type->cert_subject(cert, pool);
}
-
apr_hash_t *serf_ssl_cert_certificate(
const serf_ssl_certificate_t *cert,
apr_pool_t *pool)
{
- apr_hash_t *tgt = apr_hash_make(pool);
- unsigned int md_size;
- unsigned char md[EVP_MAX_MD_SIZE];
- BIO *bio;
- STACK_OF(GENERAL_NAME) *names;
-
- /* sha1 fingerprint */
- if (X509_digest(cert->ssl_cert, EVP_sha1(), md, &md_size)) {
- unsigned int i;
- const char hex[] = "0123456789ABCDEF";
- char fingerprint[EVP_MAX_MD_SIZE * 3];
-
- for (i=0; i<md_size; i++) {
- fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4];
- fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)];
- fingerprint[(3*i)+2] = ':';
- }
- if (md_size > 0)
- fingerprint[(3*(md_size-1))+2] = '\0';
- else
- fingerprint[0] = '\0';
-
- apr_hash_set(tgt, "sha1", APR_HASH_KEY_STRING,
- apr_pstrdup(pool, fingerprint));
- }
-
- /* set expiry dates */
- bio = BIO_new(BIO_s_mem());
- if (bio) {
- ASN1_TIME *notBefore, *notAfter;
- char buf[256];
-
- memset (buf, 0, sizeof (buf));
- notBefore = X509_get_notBefore(cert->ssl_cert);
- if (ASN1_TIME_print(bio, notBefore)) {
- BIO_read(bio, buf, 255);
- apr_hash_set(tgt, "notBefore", APR_HASH_KEY_STRING,
- apr_pstrdup(pool, buf));
- }
- memset (buf, 0, sizeof (buf));
- notAfter = X509_get_notAfter(cert->ssl_cert);
- if (ASN1_TIME_print(bio, notAfter)) {
- BIO_read(bio, buf, 255);
- apr_hash_set(tgt, "notAfter", APR_HASH_KEY_STRING,
- apr_pstrdup(pool, buf));
- }
- }
- BIO_free(bio);
-
- /* Get subjectAltNames */
- names = X509_get_ext_d2i(cert->ssl_cert, NID_subject_alt_name, NULL, NULL);
- if (names) {
- int names_count = sk_GENERAL_NAME_num(names);
- int name_idx;
-
- apr_array_header_t *san_arr = apr_array_make(pool, names_count,
- sizeof(char*));
- apr_hash_set(tgt, "subjectAltName", APR_HASH_KEY_STRING, san_arr);
- for (name_idx = 0; name_idx < names_count; name_idx++) {
- char *p = NULL;
- GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, name_idx);
-
- switch (nm->type) {
- case GEN_DNS:
- p = apr_pstrmemdup(pool, (const char *)nm->d.ia5->data,
- nm->d.ia5->length);
- break;
- default:
- /* Don't know what to do - skip. */
- break;
- }
- if (p) {
- APR_ARRAY_PUSH(san_arr, char*) = p;
- }
- }
- sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
- }
-
- return tgt;
+ return cert->type->cert_certificate(cert, pool);
}
-
const char *serf_ssl_cert_export(
const serf_ssl_certificate_t *cert,
apr_pool_t *pool)
{
- char *binary_cert;
- char *encoded_cert;
- int len;
- unsigned char *unused;
-
- /* find the length of the DER encoding. */
- len = i2d_X509(cert->ssl_cert, NULL);
- if (len < 0) {
- return NULL;
- }
-
- binary_cert = apr_palloc(pool, len);
- unused = (unsigned char *)binary_cert;
- len = i2d_X509(cert->ssl_cert, &unused); /* unused is incremented */
- if (len < 0) {
- return NULL;
- }
-
- encoded_cert = apr_palloc(pool, apr_base64_encode_len(len));
- apr_base64_encode(encoded_cert, binary_cert, len);
-
- return encoded_cert;
+ return cert->type->cert_export(cert, pool);
}
-/* Disables compression for all SSL sessions. */
-static void disable_compression(serf_ssl_context_t *ssl_ctx)
+/* Create a implementation-independent serf_ssl_identity_t object */
+serf_ssl_identity_t *
+serf__create_identity(const serf_ssl_bucket_type_t *type,
+ void *impl_cert,
+ void *impl_pkey,
+ apr_pool_t *pool)
{
-#ifdef SSL_OP_NO_COMPRESSION
- SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_COMPRESSION);
-#endif
+ serf_ssl_identity_t *identity;
+
+ identity = apr_palloc(pool, sizeof(serf_ssl_identity_t));
+ identity->impl_cert = impl_cert;
+ identity->impl_pkey = impl_pkey;
+ identity->type = type;
+
+ return identity;
}
-apr_status_t serf_ssl_use_compression(serf_ssl_context_t *ssl_ctx, int enabled)
+apr_status_t serf_ssl_load_identity_from_file(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path,
+ apr_pool_t *pool)
{
- if (enabled) {
-#ifdef SSL_OP_NO_COMPRESSION
- SSL_clear_options(ssl_ctx->ssl, SSL_OP_NO_COMPRESSION);
- return APR_SUCCESS;
-#endif
- } else {
-#ifdef SSL_OP_NO_COMPRESSION
- SSL_set_options(ssl_ctx->ssl, SSL_OP_NO_COMPRESSION);
- return APR_SUCCESS;
-#endif
- }
-
- return APR_EGENERAL;
+ return ssl_ctx->type->load_identity_from_file(ssl_ctx->impl_ctx, identity,
+ file_path, pool);
}
-static void serf_ssl_destroy_and_data(serf_bucket_t *bucket)
+apr_status_t serf_ssl_load_CA_cert_from_file(serf_ssl_context_t *ssl_ctx,
+ serf_ssl_certificate_t **cert,
+ const char *file_path,
+ apr_pool_t *pool)
{
- ssl_context_t *ctx = bucket->data;
-
- if (!--ctx->ssl_ctx->refcount) {
- ssl_free_context(ctx->ssl_ctx);
- }
-
- serf_default_destroy_and_data(bucket);
+ /* The ssl_ctx is not needed to load the certificate, only to determine
+ which SSL library we're using. */
+ return ssl_ctx->type->load_CA_cert_from_file(cert, file_path, pool);
}
-static void serf_ssl_decrypt_destroy_and_data(serf_bucket_t *bucket)
+apr_status_t serf_ssl_load_cert_file(serf_ssl_certificate_t **cert,
+ const char *file_path,
+ apr_pool_t *pool)
{
- ssl_context_t *ctx = bucket->data;
+ /* ### This is a hack, depends on the SSL implementation type to be
+ always the same during multiple ssl sessions. While that's currently
+ the case, it's not guaranteed to stay this way in future versions of
+ serf. */
+ const serf_ssl_bucket_type_t *type = decide_ssl_bucket_type();
- serf_bucket_destroy(*ctx->our_stream);
-
- serf_ssl_destroy_and_data(bucket);
+ return type->load_CA_cert_from_file(cert, file_path, pool);
}
-static void serf_ssl_encrypt_destroy_and_data(serf_bucket_t *bucket)
+/* SSL Session Resumption API's */
+void serf_ssl_new_session_callback_set(serf_ssl_context_t *ssl_ctx,
+ serf_ssl_new_session_t new_session_cb,
+ void *baton)
{
- ssl_context_t *ctx = bucket->data;
- serf_ssl_context_t *ssl_ctx = ctx->ssl_ctx;
-
- if (ssl_ctx->encrypt.stream == *ctx->our_stream) {
- serf_bucket_destroy(*ctx->our_stream);
- serf_bucket_destroy(ssl_ctx->encrypt.pending);
-
- /* Reset our encrypted status and databuf. */
- ssl_ctx->encrypt.status = APR_SUCCESS;
- ssl_ctx->encrypt.databuf.status = APR_SUCCESS;
-
- /* Advance to the next stream - if we have one. */
- if (ssl_ctx->encrypt.stream_next == NULL) {
- ssl_ctx->encrypt.stream = NULL;
- ssl_ctx->encrypt.pending = NULL;
- }
- else {
- bucket_list_t *cur;
-
- cur = ssl_ctx->encrypt.stream_next;
- ssl_ctx->encrypt.stream = cur->bucket;
- ssl_ctx->encrypt.pending =
- serf_bucket_aggregate_create(cur->bucket->allocator);
- ssl_ctx->encrypt.stream_next = cur->next;
- serf_bucket_mem_free(ssl_ctx->allocator, cur);
- }
- }
- else {
- /* Ah, darn. We haven't sent this one along yet. */
- return;
- }
- serf_ssl_destroy_and_data(bucket);
+ ssl_ctx->type->new_session_callback_set(ssl_ctx->impl_ctx,
+ new_session_cb, baton);
}
-static apr_status_t serf_ssl_read(serf_bucket_t *bucket,
- apr_size_t requested,
- const char **data, apr_size_t *len)
+apr_status_t serf_ssl_resume_session(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_session_t *ssl_session,
+ apr_pool_t *pool)
{
- ssl_context_t *ctx = bucket->data;
-
- return serf_databuf_read(ctx->databuf, requested, data, len);
+ return ssl_ctx->type->resume_session(ssl_ctx->impl_ctx, ssl_session,
+ pool);
}
-static apr_status_t serf_ssl_readline(serf_bucket_t *bucket,
- int acceptable, int *found,
- const char **data,
- apr_size_t *len)
+apr_status_t serf_ssl_session_export(serf_ssl_context_t *ssl_ctx,
+ void **data,
+ apr_size_t *len,
+ const serf_ssl_session_t *ssl_session,
+ apr_pool_t *pool)
{
- ssl_context_t *ctx = bucket->data;
-
- return serf_databuf_readline(ctx->databuf, acceptable, found, data, len);
+ return ssl_ctx->type->session_export(data, len, ssl_session, pool);
}
-static apr_status_t serf_ssl_peek(serf_bucket_t *bucket,
- const char **data,
- apr_size_t *len)
+apr_status_t serf_ssl_session_import(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_session_t **ssl_session,
+ void *data,
+ apr_size_t len,
+ apr_pool_t *pool)
{
- ssl_context_t *ctx = bucket->data;
-
- return serf_databuf_peek(ctx->databuf, data, len);
+ return ssl_ctx->type->session_import(ssl_session, data, len, pool);
}
-
-const serf_bucket_type_t serf_bucket_type_ssl_encrypt = {
- "SSLENCRYPT",
- serf_ssl_read,
- serf_ssl_readline,
- serf_default_read_iovec,
- serf_default_read_for_sendfile,
- serf_default_read_bucket,
- serf_ssl_peek,
- serf_ssl_encrypt_destroy_and_data,
-};
-
+/* Define dummy ssl_decrypt and ssl_encrypt buckets. These are needed because
+ they are exported symbols, previously (not anymore) used in the
+ SERF_BUCKET_IS_SSL_DECRYPT and SERF_BUCKET_IS_SSL_ENCRYPT macro's. */
const serf_bucket_type_t serf_bucket_type_ssl_decrypt = {
- "SSLDECRYPT",
- serf_ssl_read,
- serf_ssl_readline,
- serf_default_read_iovec,
- serf_default_read_for_sendfile,
- serf_default_read_bucket,
- serf_ssl_peek,
- serf_ssl_decrypt_destroy_and_data,
-};
+ "ABSTRACT DECRYPT", NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+const serf_bucket_type_t serf_bucket_type_ssl_encrypt = {
+ "ABSTRACT ENCRYPT", NULL, NULL, NULL, NULL, NULL, NULL, NULL };
diff --git a/context.c b/context.c
index 2930471..203e373 100644
--- a/context.c
+++ b/context.c
@@ -374,6 +374,23 @@
return "An error occurred during SSL communication";
case SERF_ERROR_SSL_CERT_FAILED:
return "An SSL certificate related error occurred ";
+ case SERF_ERROR_SSL_CLIENT_CERT_PW_FAILED:
+ return "The client certificate requires a password, but the necessary "
+ "application callback isn't configured.";
+ case SERF_ERROR_SSL_USER_DENIED_CERT:
+ return "The user denied the use of the server SSL certificate.";
+ case SERF_ERROR_SSL_FATAL_CERT_INVALID:
+ return "Invalid setting or result during server certificate chain "
+ "evaluation.";
+ case SERF_ERROR_SSL_FATAL_CERT_DENIED_IN_KEYCHAIN:
+ return "The user set the certificate as 'not trusted' in Keychain.";
+ case SERF_ERROR_SSL_FATAL_CERT_TRUST_FAILURE:
+ return "Error in the OS X trust framework during server certificate "
+ "chain evaluation.";
+ case SERF_ERROR_SSL_FATAL_CERT_FAILED:
+ return "Unspecified error during server certificate chain evaluation.";
+ case SERF_ERROR_SSL_NO_IDENTITIES_AVAILABLE:
+ return "Can't find matching identities in any of the keychains.";
case SERF_ERROR_AUTHN_FAILED:
return "An error occurred during authentication";
case SERF_ERROR_AUTHN_NOT_SUPPORTED:
diff --git a/serf.h b/serf.h
index 3145e3c..1b7a0e1 100644
--- a/serf.h
+++ b/serf.h
@@ -92,12 +92,33 @@
/* The server unexpectedly closed the connection prematurely. */
#define SERF_ERROR_ABORTED_CONNECTION (SERF_ERROR_START + 8)
-/* SSL certificates related errors */
+/* General SSL certificate related errors */
#define SERF_ERROR_SSL_CERT_FAILED (SERF_ERROR_START + 70)
-/* SSL communications related errors */
+/* General SSL communications related errors */
#define SERF_ERROR_SSL_COMM_FAILED (SERF_ERROR_START + 71)
+/* Password for client certificate not provided or valid. */
+#define SERF_ERROR_SSL_CLIENT_CERT_PW_FAILED (SERF_ERROR_START + 72)
+
+/* User denied the use of the certificate. */
+#define SERF_ERROR_SSL_USER_DENIED_CERT (SERF_ERROR_START + 74)
+
+/* Fatal error during server certificate validation */
+#define SERF_ERROR_SSL_FATAL_CERT_INVALID (SERF_ERROR_START + 75)
+
+/* Certificate was marked as 'Never Trust' in Keychain. */
+#define SERF_ERROR_SSL_FATAL_CERT_DENIED_IN_KEYCHAIN (SERF_ERROR_START + 76)
+
+/* Fatal error during server certificate validation */
+#define SERF_ERROR_SSL_FATAL_CERT_TRUST_FAILURE (SERF_ERROR_START + 77)
+
+/* Fatal error during server certificate validation */
+#define SERF_ERROR_SSL_FATAL_CERT_FAILED (SERF_ERROR_START + 78)
+
+/* There are no identities available in the keychains to select from. */
+#define SERF_ERROR_SSL_NO_IDENTITIES_AVAILABLE (SERF_ERROR_START + 79)
+
/* General authentication related errors */
#define SERF_ERROR_AUTHN_FAILED (SERF_ERROR_START + 90)
diff --git a/serf_bucket_types.h b/serf_bucket_types.h
index 1d657d1..1edac7c 100644
--- a/serf_bucket_types.h
+++ b/serf_bucket_types.h
@@ -35,6 +35,32 @@
extern "C" {
#endif
+
+#define SERF_IMPL_SSL_MACOSXSSL 0x0001
+#define SERF_IMPL_SSL_OPENSSL 0x0002
+#define SERF_IMPL_SSL_ALL 0x0003
+
+#define SERF_IMPL_NONE 0x0000
+#define SERF_IMPL_ALL 0xFFFF
+
+/**
+ * From the compiled bucket implementations, activate only those specified
+ * in the SELECTED mask.
+ */
+apr_uint32_t serf_config_enable_bucket_impls(apr_uint32_t selected);
+
+/**
+ * From the currently active bucket implementations, disable those
+ * specified in the SELECTED mask.
+ */
+apr_uint32_t serf_config_disable_bucket_impls(apr_uint32_t selected);
+
+/**
+ * Get the current list of active bucket implementations. Returns a mask
+ * made of SERF_IMPL* bits.
+ */
+apr_uint32_t serf_config_get_bucket_impls(void);
+
/* ==================================================================== */
@@ -488,24 +514,57 @@
/* ==================================================================== */
-#define SERF_SSL_CERT_NOTYETVALID 1
-#define SERF_SSL_CERT_EXPIRED 2
-#define SERF_SSL_CERT_UNKNOWNCA 4
-#define SERF_SSL_CERT_SELF_SIGNED 8
-#define SERF_SSL_CERT_UNKNOWN_FAILURE 16
-#define SERF_SSL_CERT_REVOKED 32
+#define SERF_SSL_CERT_NOTYETVALID 0x1
+#define SERF_SSL_CERT_EXPIRED 0x2
+#define SERF_SSL_CERT_UNKNOWNCA 0x4
+#define SERF_SSL_CERT_SELF_SIGNED 0x8
+#define SERF_SSL_CERT_UNKNOWN_FAILURE 0x10
+#define SERF_SSL_CERT_REVOKED 0x20
+#define SERF_SSL_CERT_CONFIRM_NEEDED 0x40
+
+extern const serf_bucket_type_t serf_bucket_type_openssl_encrypt;
+extern const serf_bucket_type_t serf_bucket_type_macosxssl_encrypt;
extern const serf_bucket_type_t serf_bucket_type_ssl_encrypt;
-#define SERF_BUCKET_IS_SSL_ENCRYPT(b) SERF_BUCKET_CHECK((b), ssl_encrypt)
+#define SERF_BUCKET_IS_SSL_ENCRYPT(b) \
+ (((b)->type == &serf_bucket_type_openssl_encrypt) || \
+ ((b)->type == &serf_bucket_type_macosxssl_encrypt))
typedef struct serf_ssl_context_t serf_ssl_context_t;
typedef struct serf_ssl_certificate_t serf_ssl_certificate_t;
+typedef struct serf_ssl_identity_t serf_ssl_identity_t;
+/* Deprecated, replaced by serf_ssl_need_identity_t. */
typedef apr_status_t (*serf_ssl_need_client_cert_t)(
void *data,
const char **cert_path);
-typedef apr_status_t (*serf_ssl_need_cert_password_t)(
+/**
+ * Callback to let the application provide a client identity as requested by
+ * the server during the SSL/TLS handshake.
+ * The baton provided in serf_ssl_identity_provider_set is passed as @a data.
+ *
+ * The list of acceptable CA's as provided in @a dn_list should be used to
+ * filter allowed client identities. The dn_list array contains @a dn_len
+ * elements of type apr_hash_t *, each with following keys:
+ * - CN, O, OU, L, ST, C, E.
+ *
+ * The returned identity can be allocated in @a pool.
+ */
+typedef apr_status_t (*serf_ssl_need_identity_t)(
+ void *data,
+ apr_hash_t **dn_list,
+ apr_size_t dn_len,
+ const serf_ssl_identity_t **identity,
+ apr_pool_t *pool);
+
+/* TODO: this callback type doesn't make sense for serf_ssl_need_identity_t,
+ as that function doesn't return a file path, but an identity which doesn't
+ necessarily come from a file.
+ Need serf_ssl_need_identity_password_t. and another label to identify
+ for which identity we ask the password. Maybe just pass the identity itself.
+ */
+ typedef apr_status_t (*serf_ssl_need_cert_password_t)(
void *data,
const char *cert_path,
const char **password);
@@ -522,20 +581,39 @@
const serf_ssl_certificate_t * const * certs,
apr_size_t certs_len);
+/* Deprecated, replaced by serf_ssl_identity_provider_set. */
void serf_ssl_client_cert_provider_set(
serf_ssl_context_t *context,
serf_ssl_need_client_cert_t callback,
void *data,
void *cache_pool);
-void serf_ssl_client_cert_password_set(
- serf_ssl_context_t *context,
- serf_ssl_need_cert_password_t callback,
- void *data,
- void *cache_pool);
+/* Deprecated, replaced by serf_ssl_identity_password_callback_set. */
+void serf_ssl_client_cert_password_set(serf_ssl_context_t *context,
+ serf_ssl_need_cert_password_t callback,
+ void *data,
+ void *cache_pool);
/**
- * Set a callback to override the default SSL server certificate validation
+ * Set a callback to provide an identity - client certificate and private key -
+ * for authentication to the server.
+ */
+void serf_ssl_identity_provider_set(serf_ssl_context_t *context,
+ serf_ssl_need_identity_t callback,
+ void *data,
+ void *cache_pool);
+
+/**
+ * Set a callback to provide the password of an identity.
+ */
+void serf_ssl_identity_password_callback_set(
+ serf_ssl_context_t *context,
+ serf_ssl_need_cert_password_t callback,
+ void *data,
+ void *cache_pool);
+
+/**
+ * Set a callback to override the default SSL server certificate validation
* algorithm.
*/
void serf_ssl_server_cert_callback_set(
@@ -556,14 +634,16 @@
typedef struct serf_ssl_session_t serf_ssl_session_t;
/* Exports @a session to continous memory block. */
-apr_status_t serf_ssl_session_export(void **data,
+apr_status_t serf_ssl_session_export(serf_ssl_context_t *ssl_ctx,
+ void **data,
apr_size_t *len,
const serf_ssl_session_t *session,
apr_pool_t *pool);
/* Restores previously saved session from continuous memory block @a data with
* @a len length. */
-apr_status_t serf_ssl_session_import(const serf_ssl_session_t **session,
+apr_status_t serf_ssl_session_import(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_session_t **session,
void *data,
apr_size_t len,
apr_pool_t *pool);
@@ -640,7 +720,9 @@
/**
* Load a CA certificate file from a path @a file_path. If the file was loaded
* and parsed correctly, a certificate @a cert will be created and returned.
- * This certificate object will be alloced in @a pool.
+ * This certificate object will be allocated in @a pool.
+ *
+ * @deprecated: use serf_ssl_load_CA_cert_from_file instead.
*/
apr_status_t serf_ssl_load_cert_file(
serf_ssl_certificate_t **cert,
@@ -648,6 +730,29 @@
apr_pool_t *pool);
/**
+ * Load a CA certificate file from a path @a file_path. If the file was loaded
+ * and parsed correctly, a certificate @a cert will be created and returned.
+ * This certificate object will be allocated in @a pool.
+ *
+ * The certificate can be reused in ssl_context's.
+ */
+/* TODO: ssl_ctx is currently not needed as the choice of ssl buckets is
+ for the whole process, not per context. Probably can be removed. */
+apr_status_t serf_ssl_load_CA_cert_from_file(serf_ssl_context_t *ssl_ctx,
+ serf_ssl_certificate_t **cert,
+ const char *file_path,
+ apr_pool_t *pool);
+
+apr_status_t serf_ssl_load_identity_from_file(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *file_path,
+ apr_pool_t *pool);
+
+/* TODO: add serf_ssl_get_certificate_from_identity, to give the application
+ a view on the client identity. This can be useful when matching an identity
+ with a list of acceptable CA's. */
+
+/**
* Adds the certificate @a cert to the list of trusted certificates in
* @a ssl_ctx that will be used for verification.
* See also @a serf_ssl_load_cert_file.
@@ -665,6 +770,67 @@
serf_ssl_context_t *ssl_ctx,
int enabled);
+/* Show a SFCertificateTrustPanel. This is the Mac OS X default dialog to
+ ask the user to confirm or deny the use of the certificate. This panel
+ also gives the option to store the user's decision for this certificate
+ permanently in the Keychain (requires password).
+
+ This function will return APR_ENOTIMPL when SERF_HAVE_MACOSXSSL is not
+ defined.
+ */
+apr_status_t
+serf_macosxssl_show_trust_certificate_panel(serf_ssl_context_t *ssl_ctx,
+ const char *message,
+ const char *ok_button_label,
+ const char *cancel_button_label);
+
+
+/* Note: both serf_macosxssl_show_select_identity_panel and
+ serf_macosxssl_find_preferred_identity_in_keychain support smart cards.
+
+ As soon as the card is inserted in the reader, an extra keychain will be
+ created containing the certificate(s) and private key(s) stored on the smart
+ card. From there it can be used just like any other identity: The client
+ identity can be set as preferred identity for a host (or with wildcards) or
+ will be shown in the identity selection dialog if no such preference was set.
+
+ Tested successfully with a Belgian Personal ID Card (BELPIC) and
+ Smartcard services v2.0b2-mtlion on Mac OS X 10.8.3
+ */
+
+/* Show a SFChooseIdentityPanel. This is the Mac OS X default dialog to
+ ask the user which client certificate to use for this server. The choice
+ of client certificate will not be saved.
+
+ TODO: should take list of acceptable CA's.
+
+ This function will return APR_ENOTIMPL when SERF_HAVE_MACOSXSSL is not
+ defined.
+ */
+apr_status_t
+serf_macosxssl_show_select_identity_panel(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_identity_t **identity,
+ const char *message,
+ const char *ok_button_label,
+ const char *cancel_button_label,
+ apr_pool_t *pool);
+
+/* Find a preferred identity for this hostname in the kechains.
+ (identity preference entry).
+
+ This function will return APR_ENOTIMPL when SERF_HAVE_MACOSXSSL is not
+ defined.
+ */
+apr_status_t
+serf_macosxssl_find_preferred_identity_in_keychain(
+ serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_identity_t **identity,
+ apr_pool_t *pool);
+
+
+
+void serf_bucket_ssl_destroy_and_data(serf_bucket_t *bucket);
+
serf_bucket_t *serf_bucket_ssl_encrypt_create(
serf_bucket_t *stream,
serf_ssl_context_t *ssl_context,
@@ -675,9 +841,13 @@
/* ==================================================================== */
+extern const serf_bucket_type_t serf_bucket_type_openssl_decrypt;
+extern const serf_bucket_type_t serf_bucket_type_macosxssl_decrypt;
extern const serf_bucket_type_t serf_bucket_type_ssl_decrypt;
-#define SERF_BUCKET_IS_SSL_DECRYPT(b) SERF_BUCKET_CHECK((b), ssl_decrypt)
+#define SERF_BUCKET_IS_SSL_DECRYPT(b) \
+ (((b)->type == &serf_bucket_type_openssl_decrypt) || \
+ ((b)->type == &serf_bucket_type_macosxssl_decrypt))
serf_bucket_t *serf_bucket_ssl_decrypt_create(
serf_bucket_t *stream,
diff --git a/test/certificates.pem b/test/certificates.pem
new file mode 100644
index 0000000..1f3858a
--- /dev/null
+++ b/test/certificates.pem
@@ -0,0 +1,289 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 1048593 (0x100011)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, ST=Antwerp, L=Mechelen, O=In Serf we trust, Inc., OU=Test Suite CA, CN=Serf CA/emailAddress=serfca@example.com
+ Validity
+ Not Before: Apr 18 19:50:11 2013 GMT
+ Not After : Apr 18 19:50:11 2014 GMT
+ Subject: C=BE, ST=Antwerp, L=Mechelen, O=In Serf we trust, Inc., OU=Test Suite Server, CN=Serf Server/emailAddress=serfserver@example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:ea:92:6f:ab:ed:ce:e1:c3:a8:2e:3e:03:9e:a0:
+ d8:c0:e4:df:e3:58:1f:bf:eb:18:23:c8:ca:a5:9a:
+ f0:f2:b5:97:ce:df:47:15:9a:0e:96:41:cc:b9:92:
+ 66:0c:f0:11:f7:f1:8f:3c:29:48:31:4a:1d:93:db:
+ 0a:f0:e9:98:5f:98:ed:07:1c:4e:2e:9f:e3:f3:3f:
+ 2c:2f:20:d8:f1:5e:8a:8f:3a:aa:3e:9c:f5:99:07:
+ 8c:a6:e9:9c:df:03:59:c1:ff:37:5a:bf:69:e8:41:
+ bd:c0:28:66:c4:4b:0a:ab:dd:93:a1:8f:3e:5e:34:
+ 5c:e6:04:6c:1d:8b:a3:88:6b
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha1WithRSAEncryption
+ 19:2f:4f:a7:a4:ea:b4:31:94:0b:9c:e8:9e:47:59:bd:2c:87:
+ 7d:70:c3:28:72:1d:79:36:69:9f:fa:d7:58:6c:d6:6b:68:c0:
+ be:99:ff:55:09:82:7e:c6:25:bb:35:6c:2c:7f:5c:89:f9:8e:
+ d8:cf:45:1e:93:a3:b5:6c:d7:93:9c:d6:53:5a:fb:3d:08:d6:
+ 21:d0:dc:f2:66:ed:e2:56:2c:bd:63:e1:57:f3:04:1f:e3:96:
+ d8:16:5c:44:ac:3c:13:98:cb:21:02:bf:23:67:0c:c1:4a:df:
+ b0:c8:d9:16:cf:b4:a7:c0:3a:6d:cb:d8:1e:ce:fe:c9:1c:00:
+ 68:e4
+-----BEGIN CERTIFICATE-----
+MIICvzCCAigCAxAAETANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMCQkUxEDAO
+BgNVBAgMB0FudHdlcnAxETAPBgNVBAcMCE1lY2hlbGVuMR8wHQYDVQQKDBZJbiBT
+ZXJmIHdlIHRydXN0LCBJbmMuMRYwFAYDVQQLDA1UZXN0IFN1aXRlIENBMRAwDgYD
+VQQDDAdTZXJmIENBMSEwHwYJKoZIhvcNAQkBFhJzZXJmY2FAZXhhbXBsZS5jb20w
+HhcNMTMwNDE4MTk1MDExWhcNMTQwNDE4MTk1MDExWjCBrDELMAkGA1UEBhMCQkUx
+EDAOBgNVBAgMB0FudHdlcnAxETAPBgNVBAcMCE1lY2hlbGVuMR8wHQYDVQQKDBZJ
+biBTZXJmIHdlIHRydXN0LCBJbmMuMRowGAYDVQQLDBFUZXN0IFN1aXRlIFNlcnZl
+cjEUMBIGA1UEAwwLU2VyZiBTZXJ2ZXIxJTAjBgkqhkiG9w0BCQEWFnNlcmZzZXJ2
+ZXJAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOqSb6vt
+zuHDqC4+A56g2MDk3+NYH7/rGCPIyqWa8PK1l87fRxWaDpZBzLmSZgzwEffxjzwp
+SDFKHZPbCvDpmF+Y7QccTi6f4/M/LC8g2PFeio86qj6c9ZkHjKbpnN8DWcH/N1q/
+aehBvcAoZsRLCqvdk6GPPl40XOYEbB2Lo4hrAgMBAAEwDQYJKoZIhvcNAQEFBQAD
+gYEAGS9Pp6TqtDGUC5zonkdZvSyHfXDDKHIdeTZpn/rXWGzWa2jAvpn/VQmCfsYl
+uzVsLH9cifmO2M9FHpOjtWzXk5zWU1r7PQjWIdDc8mbt4lYsvWPhV/MEH+OW2BZc
+RKw8E5jLIQK/I2cMwUrfsMjZFs+0p8A6bcvYHs7+yRwAaOQ=
+-----END CERTIFICATE-----
+================================================================
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 14060582211199810902 (0xc321390661bdbd56)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, ST=Antwerp, L=Mechelen, O=In Serf we trust, Inc., OU=Test Suite Root CA, CN=Serf Root CA/emailAddress=serfrootca@example.com
+ Validity
+ Not Before: Apr 13 11:19:14 2013 GMT
+ Not After : Apr 11 11:19:14 2023 GMT
+ Subject: C=BE, ST=Antwerp, L=Mechelen, O=In Serf we trust, Inc., OU=Test Suite Root CA, CN=Serf Root CA/emailAddress=serfrootca@example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:e1:dd:69:ea:ac:fd:f2:73:81:ec:ed:b6:b1:0e:
+ 70:23:8c:01:6d:ab:f3:43:ab:0f:fc:8a:6a:23:eb:
+ 6c:48:37:c9:c0:8f:29:61:00:7e:89:1f:00:d1:68:
+ dd:70:de:bd:34:32:0e:41:ac:f9:ea:c1:a6:0d:b5:
+ 65:be:5c:9e:f1:b4:27:54:c1:79:61:63:d4:2d:06:
+ 11:5f:cc:4c:d9:d3:ef:4e:da:9f:a4:26:16:cb:3f:
+ 86:f8:21:7d:c5:3a:32:34:c8:cb:85:ad:c4:3f:e4:
+ b3:ad:8e:a7:67:9e:0c:3b:5a:58:29:5f:ce:96:3b:
+ e3:f5:ca:42:eb:7b:44:d5:75
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ B2:3E:19:35:C1:C4:4F:23:79:ED:BF:E8:DC:5C:31:03:F0:2F:15:77
+ X509v3 Authority Key Identifier:
+ keyid:B2:3E:19:35:C1:C4:4F:23:79:ED:BF:E8:DC:5C:31:03:F0:2F:15:77
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ c3:ec:1f:3e:b1:87:d0:80:10:f9:bf:73:1b:38:d4:b1:b7:80:
+ 4d:ea:20:c1:79:7d:f5:58:42:11:13:28:ab:b1:b4:0a:88:9c:
+ 20:4d:9c:b5:5a:41:28:5e:f6:69:5e:55:bb:e2:1a:b9:c6:62:
+ 38:86:32:7b:93:28:ca:9e:af:d1:06:f9:93:c2:5d:92:c0:25:
+ 68:6a:e1:fe:85:2a:19:a7:6b:17:4d:23:9a:72:d6:d0:c1:80:
+ ff:74:10:8b:62:7a:11:c3:9a:87:2a:e4:7d:d1:8c:72:a6:bf:
+ c1:3b:d8:b8:33:c0:ff:b0:f7:d6:0e:a3:dd:36:fe:8a:41:a0:
+ 98:cc
+-----BEGIN CERTIFICATE-----
+MIIDLDCCApWgAwIBAgIJAMMhOQZhvb1WMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
+VQQGEwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAd
+BgNVBAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGzAZBgNVBAsMElRlc3QgU3Vp
+dGUgUm9vdCBDQTEVMBMGA1UEAwwMU2VyZiBSb290IENBMSUwIwYJKoZIhvcNAQkB
+FhZzZXJmcm9vdGNhQGV4YW1wbGUuY29tMB4XDTEzMDQxMzExMTkxNFoXDTIzMDQx
+MTExMTkxNFowga4xCzAJBgNVBAYTAkJFMRAwDgYDVQQIDAdBbnR3ZXJwMREwDwYD
+VQQHDAhNZWNoZWxlbjEfMB0GA1UECgwWSW4gU2VyZiB3ZSB0cnVzdCwgSW5jLjEb
+MBkGA1UECwwSVGVzdCBTdWl0ZSBSb290IENBMRUwEwYDVQQDDAxTZXJmIFJvb3Qg
+Q0ExJTAjBgkqhkiG9w0BCQEWFnNlcmZyb290Y2FAZXhhbXBsZS5jb20wgZ8wDQYJ
+KoZIhvcNAQEBBQADgY0AMIGJAoGBAOHdaeqs/fJzgezttrEOcCOMAW2r80OrD/yK
+aiPrbEg3ycCPKWEAfokfANFo3XDevTQyDkGs+erBpg21Zb5cnvG0J1TBeWFj1C0G
+EV/MTNnT707an6QmFss/hvghfcU6MjTIy4WtxD/ks62Op2eeDDtaWClfzpY74/XK
+Qut7RNV1AgMBAAGjUDBOMB0GA1UdDgQWBBSyPhk1wcRPI3ntv+jcXDED8C8VdzAf
+BgNVHSMEGDAWgBSyPhk1wcRPI3ntv+jcXDED8C8VdzAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4GBAMPsHz6xh9CAEPm/cxs41LG3gE3qIMF5ffVYQhETKKux
+tAqInCBNnLVaQShe9mleVbviGrnGYjiGMnuTKMqer9EG+ZPCXZLAJWhq4f6FKhmn
+axdNI5py1tDBgP90EItiehHDmocq5H3RjHKmv8E72LgzwP+w99YOo902/opBoJjM
+-----END CERTIFICATE-----
+================================================================
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 58:0b:05:6c:53:24:db:b2:50:57:18:5f:f9:e5:a6:50
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, CN=Belgium Root CA
+ Validity
+ Not Before: Jan 26 23:00:00 2003 GMT
+ Not After : Jan 26 23:00:00 2014 GMT
+ Subject: C=BE, CN=Belgium Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c8:a1:71:e9:1c:46:42:79:78:71:6f:9d:ae:a9:
+ a8:ab:28:b7:4d:c7:20:eb:30:91:5a:75:f5:e2:d2:
+ cf:c8:4c:14:98:42:58:ad:c7:11:c5:40:40:6a:5a:
+ f9:74:12:27:87:e9:9c:e5:71:4e:22:2c:d1:12:18:
+ aa:30:5e:a2:21:b9:d9:bb:ff:f6:74:eb:31:01:e7:
+ 3b:7e:58:0f:91:16:4d:76:89:a8:01:4f:ad:22:66:
+ 70:fa:4b:1d:95:c1:30:58:ea:bc:d9:65:d8:9a:b4:
+ 88:eb:49:46:52:df:d2:53:15:76:cb:14:5d:19:49:
+ b1:6f:6a:d3:d3:fd:bc:c2:2d:ec:45:3f:09:3f:58:
+ be:fc:d4:ef:00:8c:81:35:72:bf:f7:18:ea:96:62:
+ 7d:2b:28:7f:15:6c:63:d2:ca:ca:7d:05:ac:c8:6d:
+ 07:6d:32:be:68:b8:05:40:ae:54:98:56:3e:66:f1:
+ 30:e8:ef:c4:ab:93:5e:07:de:32:8f:12:74:aa:5b:
+ 34:23:54:c0:ea:6c:ce:fe:36:92:a8:09:17:ea:a1:
+ 2d:cf:6c:e3:84:1d:de:87:2e:33:0b:3c:74:e2:21:
+ 50:38:95:2e:5c:e0:e5:c6:31:f9:db:40:fa:6a:a1:
+ a4:8a:93:9b:a7:21:06:87:1d:27:d3:c4:a1:c9:4c:
+ b0:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: 2.16.56.1.1.1
+ CPS: http://repository.eid.belgium.be
+
+ X509v3 Subject Key Identifier:
+ 10:F0:0C:56:9B:61:EA:57:3A:B6:35:97:6D:9F:DD:B9:14:8E:DB:E6
+ Netscape Cert Type:
+ SSL CA, S/MIME CA, Object Signing CA
+ X509v3 Authority Key Identifier:
+ keyid:10:F0:0C:56:9B:61:EA:57:3A:B6:35:97:6D:9F:DD:B9:14:8E:DB:E6
+
+ Signature Algorithm: sha1WithRSAEncryption
+ c8:6d:22:51:8a:61:f8:0f:96:6e:d5:20:b2:81:f8:c6:dc:a3:
+ 16:00:da:cd:6a:e7:6b:2a:fa:59:48:a7:4c:49:37:d7:73:a1:
+ 6a:01:65:5e:32:bd:e7:97:d3:d0:2e:3c:73:d3:8c:7b:83:ef:
+ d6:42:c1:3f:a8:a9:5d:0f:37:ba:76:d2:40:bd:cc:2d:3f:d3:
+ 44:41:49:9c:fd:5b:29:f4:02:23:22:5b:71:1b:bf:58:d9:28:
+ 4e:2d:45:f4:da:e7:b5:63:45:44:11:0d:2a:7f:33:7f:36:49:
+ b4:ce:6e:a9:02:31:ae:5c:fd:c8:89:bf:42:7b:d7:f1:60:f2:
+ d7:87:f6:57:2e:7a:7e:6a:13:80:1d:dc:e3:d0:63:1e:3d:71:
+ 31:b1:60:d4:9e:08:ca:ab:f0:94:c7:48:75:54:81:f3:1b:ad:
+ 77:9c:e8:b2:8f:db:83:ac:8f:34:6b:e8:bf:c3:d9:f5:43:c3:
+ 64:55:eb:1a:bd:36:86:36:ba:21:8c:97:1a:21:d4:ea:2d:3b:
+ ac:ba:ec:a7:1d:ab:be:b9:4a:9b:35:2f:1c:5c:1d:51:a7:1f:
+ 54:ed:12:97:ff:f2:6e:87:7d:46:c9:74:d6:ef:eb:3d:7d:e6:
+ 59:6e:06:94:04:e4:a2:55:87:38:28:6a:22:5e:e2:be:74:12:
+ b0:04:43:2a
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnygAwIBAgIQWAsFbFMk27JQVxhf+eWmUDANBgkqhkiG9w0BAQUFADAn
+MQswCQYDVQQGEwJCRTEYMBYGA1UEAxMPQmVsZ2l1bSBSb290IENBMB4XDTAzMDEy
+NjIzMDAwMFoXDTE0MDEyNjIzMDAwMFowJzELMAkGA1UEBhMCQkUxGDAWBgNVBAMT
+D0JlbGdpdW0gUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMihcekcRkJ5eHFvna6pqKsot03HIOswkVp19eLSz8hMFJhCWK3HEcVAQGpa+XQS
+J4fpnOVxTiIs0RIYqjBeoiG52bv/9nTrMQHnO35YD5EWTXaJqAFPrSJmcPpLHZXB
+MFjqvNll2Jq0iOtJRlLf0lMVdssUXRlJsW9q09P9vMIt7EU/CT9YvvzU7wCMgTVy
+v/cY6pZifSsofxVsY9LKyn0FrMhtB20yvmi4BUCuVJhWPmbxMOjvxKuTXgfeMo8S
+dKpbNCNUwOpszv42kqgJF+qhLc9s44Qd3ocuMws8dOIhUDiVLlzg5cYx+dtA+mqh
+pIqTm6chBocdJ9PEoclMsG8CAwEAAaOBuzCBuDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGBWA4AQEBMC4wLAYIKwYBBQUHAgEW
+IGh0dHA6Ly9yZXBvc2l0b3J5LmVpZC5iZWxnaXVtLmJlMB0GA1UdDgQWBBQQ8AxW
+m2HqVzq2NZdtn925FI7b5jARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAU
+EPAMVpth6lc6tjWXbZ/duRSO2+YwDQYJKoZIhvcNAQEFBQADggEBAMhtIlGKYfgP
+lm7VILKB+MbcoxYA2s1q52sq+llIp0xJN9dzoWoBZV4yveeX09AuPHPTjHuD79ZC
+wT+oqV0PN7p20kC9zC0/00RBSZz9Wyn0AiMiW3Ebv1jZKE4tRfTa57VjRUQRDSp/
+M382SbTObqkCMa5c/ciJv0J71/Fg8teH9lcuen5qE4Ad3OPQYx49cTGxYNSeCMqr
+8JTHSHVUgfMbrXec6LKP24OsjzRr6L/D2fVDw2RV6xq9NoY2uiGMlxoh1OotO6y6
+7Kcdq765Sps1LxxcHVGnH1TtEpf/8m6HfUbJdNbv6z195lluBpQE5KJVhzgoaiJe
+4r50ErAEQyo=
+-----END CERTIFICATE-----
+================================================================
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 3098404661496965511 (0x2affbe9fa2f0e987)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, CN=Belgium Root CA2
+ Validity
+ Not Before: Oct 4 10:00:00 2007 GMT
+ Not After : Dec 15 08:00:00 2021 GMT
+ Subject: C=BE, CN=Belgium Root CA2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:73:42:1e:92:ff:75:0f:8b:bf:74:86:a7:3f:
+ ed:b2:18:2d:2d:97:f9:a9:fb:98:4a:23:df:d5:8d:
+ e0:8c:c6:32:1c:16:2d:07:42:d6:2d:b6:6e:2f:a7:
+ f5:fc:c6:85:82:5d:95:de:b5:22:00:cc:a8:53:40:
+ 9f:af:0a:35:a2:7a:c7:e1:ca:f7:60:10:67:0e:a7:
+ 50:73:17:53:95:9f:22:9f:0c:5d:6f:b6:41:bc:8c:
+ eb:da:1d:46:bd:a7:94:bf:f3:1a:cb:d4:fb:8d:0e:
+ 1e:33:c8:96:d7:ec:8c:53:de:93:1e:34:1e:8a:50:
+ 71:26:58:b4:5d:c2:88:89:da:60:d2:89:21:3f:de:
+ d7:01:18:36:6e:e1:2c:70:03:04:65:ef:98:06:2b:
+ 5d:1e:62:dc:55:6b:fe:66:64:21:5f:f8:2f:e1:d7:
+ 9b:29:af:6f:cd:9f:aa:0c:46:d1:88:c5:9d:a2:95:
+ 9f:ac:3f:15:ae:f2:61:df:ef:c3:6b:9a:22:d8:2c:
+ 71:fd:58:1d:ec:00:a4:38:20:95:5a:c7:d5:37:63:
+ ba:4f:9b:aa:fe:56:46:2e:63:d9:0b:23:d5:8a:fa:
+ b5:23:f0:89:1b:a1:14:0d:26:da:41:f0:38:8f:30:
+ ed:05:26:79:c1:4b:4e:a1:b8:7e:55:52:3a:69:46:
+ 84:75
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: 2.16.56.9.1.1
+ CPS: http://repository.eid.belgium.be
+
+ X509v3 Subject Key Identifier:
+ 85:8A:EB:F4:C5:BB:BE:0E:59:03:94:DE:D6:80:01:15:E3:10:9C:39
+ Netscape Cert Type:
+ SSL CA, S/MIME CA, Object Signing CA
+ X509v3 Authority Key Identifier:
+ keyid:85:8A:EB:F4:C5:BB:BE:0E:59:03:94:DE:D6:80:01:15:E3:10:9C:39
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 51:d8:85:dd:bb:57:6f:cc:a0:6c:b5:a3:20:9c:53:09:f3:4a:
+ 01:0c:74:bf:2b:b3:9a:9a:ba:18:f2:0b:88:ac:1c:b3:33:af:
+ ce:e5:13:01:27:92:84:58:9a:10:b9:f7:cc:14:92:6b:74:16:
+ 8a:96:e8:51:ef:bf:fa:4a:25:a7:89:b6:63:2b:5d:94:58:d1:
+ cf:11:72:b6:1e:b9:39:41:16:4d:29:bc:35:53:0b:da:de:8e:
+ 0e:cd:a9:95:77:25:ca:94:5a:e9:b2:69:ae:d8:c0:13:be:98:
+ fc:96:9c:84:7f:55:13:e6:3c:87:e3:bc:20:a4:a4:36:68:6b:
+ 4d:60:66:1c:f9:bf:ac:80:94:66:2e:b9:41:8a:d3:65:d3:84:
+ 80:02:ef:50:1d:5e:46:dc:f7:c9:ba:b5:34:7c:2a:f3:c6:d8:
+ 5f:5f:54:9d:db:4d:cd:11:e7:fd:14:02:83:66:5e:c8:a6:00:
+ 12:a0:5f:be:ce:14:fe:bb:1f:a7:61:f7:ab:4a:f1:06:14:9f:
+ ca:49:42:c2:a9:bc:ed:85:b1:ab:81:41:e6:0d:c5:42:69:53:
+ 87:39:9d:4c:1f:00:0e:3e:07:0d:75:57:44:a8:53:b4:36:76:
+ 64:99:dc:6e:eb:3d:46:6e:14:5d:5e:47:53:8d:78:4d:e0:27:
+ bb:8e:85:76
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIIKv++n6Lw6YcwDQYJKoZIhvcNAQEFBQAwKDELMAkGA1UE
+BhMCQkUxGTAXBgNVBAMTEEJlbGdpdW0gUm9vdCBDQTIwHhcNMDcxMDA0MTAwMDAw
+WhcNMjExMjE1MDgwMDAwWjAoMQswCQYDVQQGEwJCRTEZMBcGA1UEAxMQQmVsZ2l1
+bSBSb290IENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZzQh6S
+/3UPi790hqc/7bIYLS2X+an7mEoj39WN4IzGMhwWLQdC1i22bi+n9fzGhYJdld61
+IgDMqFNAn68KNaJ6x+HK92AQZw6nUHMXU5WfIp8MXW+2QbyM69odRr2nlL/zGsvU
++40OHjPIltfsjFPekx40HopQcSZYtF3CiInaYNKJIT/e1wEYNm7hLHADBGXvmAYr
+XR5i3FVr/mZkIV/4L+HXmymvb82fqgxG0YjFnaKVn6w/Fa7yYd/vw2uaItgscf1Y
+HewApDgglVrH1Tdjuk+bqv5WRi5j2Qsj1Yr6tSPwiRuhFA0m2kHwOI8w7QUmecFL
+TqG4flVSOmlGhHUCAwEAAaOBuzCBuDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zBCBgNVHSAEOzA5MDcGBWA4CQEBMC4wLAYIKwYBBQUHAgEWIGh0dHA6
+Ly9yZXBvc2l0b3J5LmVpZC5iZWxnaXVtLmJlMB0GA1UdDgQWBBSFiuv0xbu+DlkD
+lN7WgAEV4xCcOTARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUhYrr9MW7
+vg5ZA5Te1oABFeMQnDkwDQYJKoZIhvcNAQEFBQADggEBAFHYhd27V2/MoGy1oyCc
+UwnzSgEMdL8rs5qauhjyC4isHLMzr87lEwEnkoRYmhC598wUkmt0FoqW6FHvv/pK
+JaeJtmMrXZRY0c8RcrYeuTlBFk0pvDVTC9rejg7NqZV3JcqUWumyaa7YwBO+mPyW
+nIR/VRPmPIfjvCCkpDZoa01gZhz5v6yAlGYuuUGK02XThIAC71AdXkbc98m6tTR8
+KvPG2F9fVJ3bTc0R5/0UAoNmXsimABKgX77OFP67H6dh96tK8QYUn8pJQsKpvO2F
+sauBQeYNxUJpU4c5nUwfAA4+Bw11V0SoU7Q2dmSZ3G7rPUZuFF1eR1ONeE3gJ7uO
+hXY=
+-----END CERTIFICATE-----
+================================================================
diff --git a/test/serf_get.c b/test/serf_get.c
index 1c9559a..708d061 100644
--- a/test/serf_get.c
+++ b/test/serf_get.c
@@ -26,7 +26,7 @@
#include "serf.h"
/* Add Connection: close header to each request. */
-/* #define CONNECTION_CLOSE_HDR */
+#define CONNECTION_CLOSE_HDR
typedef struct {
const char *hostinfo;
@@ -155,7 +155,8 @@
app_baton_t *ctx = baton;
if (ctx->session_filename) {
- status = serf_ssl_session_export(&data, &len, session, pool);
+ status = serf_ssl_session_export(ctx->ssl_ctx,
+ &data, &len, session, pool);
if (status) {
return status;
@@ -176,7 +177,8 @@
return APR_SUCCESS;
}
-static apr_status_t read_ssl_session(const serf_ssl_session_t **session,
+static apr_status_t read_ssl_session(serf_ssl_context_t *ssl_ctx,
+ const serf_ssl_session_t **session,
const char *filename,
apr_pool_t *pool)
{
@@ -194,7 +196,7 @@
/* We should reach EOF. */
if (status == APR_EOF) {
- status = serf_ssl_session_import(session, buf, len, pool);
+ status = serf_ssl_session_import(ssl_ctx, session, buf, len, pool);
} else {
status = APR_EGENERAL;
}
@@ -230,7 +232,8 @@
serf_ssl_new_session_callback_set(ctx->ssl_ctx, new_ssl_session,
ctx);
- status = read_ssl_session(&session, ctx->session_filename, pool);
+ status = read_ssl_session(ctx->ssl_ctx, &session,
+ ctx->session_filename, pool);
if (status == APR_SUCCESS) {
fprintf(stderr, "Using SSL session from '%s'\n",
ctx->session_filename);
diff --git a/test/server/test_sslserver.c b/test/server/test_sslserver.c
index 5b23801..9f19f96 100644
--- a/test/server/test_sslserver.c
+++ b/test/server/test_sslserver.c
@@ -190,7 +190,10 @@
SSL_CTX_set_default_passwd_cb(ssl_ctx->ctx, pem_passwd_cb);
SSL_CTX_use_PrivateKey_file(ssl_ctx->ctx, keyfile, SSL_FILETYPE_PEM);
- /* Set server certificate, add ca certificates if provided. */
+ /* Set server certificate, add CA certificates if provided.
+ Also, use the same CA certificates as acceptable CA's for the client
+ certificate. This doesn't have to be the same list, but for now it's
+ sufficient. */
certfile = certfiles[0];
SSL_CTX_use_certificate_file(ssl_ctx->ctx, certfile, SSL_FILETYPE_PEM);
@@ -207,6 +210,9 @@
SSL_CTX_add_extra_chain_cert(ssl_ctx->ctx, ssl_cert);
X509_STORE_add_cert(store, ssl_cert);
+
+ /* acceptable CA for the client cert */
+ SSL_CTX_add_client_CA(ssl_ctx->ctx, ssl_cert);
}
certfile = certfiles[i++];
}
diff --git a/test/test_context.c b/test/test_context.c
index 7c5469a..365f37a 100644
--- a/test/test_context.c
+++ b/test/test_context.c
@@ -1073,6 +1073,21 @@
tb->result_flags |= TEST_RESULT_SERVERCERTCB_CALLED;
+#if 0
+ /* Example of how to ask the user to validate a server certificate
+ via a platform-specific dialog. */
+ if (failures) {
+ apr_status_t status;
+
+ status = serf_macosxssl_show_trust_certificate_panel(tb->ssl_context,
+ "Server certificate requires validation",
+ "Accept",
+ "Cancel");
+ if (status && status != APR_ENOTIMPL)
+ return status;
+ }
+#endif
+
/* We expect an error from the certificate validation function. */
if (failures & expected_failures)
return APR_SUCCESS;
@@ -1669,6 +1684,144 @@
CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTPWCB_CALLED);
}
+static apr_status_t identity_cb(void *data,
+ apr_hash_t **dnlist,
+ apr_size_t dnlen,
+ const serf_ssl_identity_t **identity,
+ apr_pool_t *pool)
+{
+ test_baton_t *tb = data;
+ const char *cert_path = "test/server/serfclientcert.p12";
+ apr_hash_t *ca;
+ int i;
+ apr_status_t status;
+
+ tb->result_flags |= TEST_RESULT_CLIENT_CERTCB_CALLED;
+
+ if (!dnlist || dnlen != 2)
+ return SERF_ERROR_ISSUE_IN_TESTSUITE;
+
+ for (i = 0; i < 2; i++) {
+ ca = dnlist[i];
+ if (!ca) return SERF_ERROR_ISSUE_IN_TESTSUITE;
+
+ if (strcmp("Serf Root CA",
+ apr_hash_get(ca, "CN", APR_HASH_KEY_STRING)) == 0)
+ continue;
+
+ if (strcmp("Serf CA",
+ apr_hash_get(ca, "CN", APR_HASH_KEY_STRING)) != 0)
+ return SERF_ERROR_ISSUE_IN_TESTSUITE;
+ }
+
+#if 0
+ /* Example of how to use Keychain to fetch a client identity for a server.
+
+ How to test:
+ 1. Import file test/server/serfclientcert.p12 in a keychain.
+ -> run the test test_ssl_identity now, you should get a dialog
+ where you can select "Serf Client".
+ 2. If you have a smart card with a client identity, plug it in.
+ -> run the test again, the dialog should show both the "Serf Client"
+ identity as your personal (stored on the smart card) identity.
+ 3. Add an identity preferences for 'https://localhost' to the
+ "Serf Client" certificate in the login keychain.
+ -> run the test test_ssl_identity now. It should continue without
+ showing a dialog.
+
+ Note: in all these cases the test will fail, because the identity
+ password callback isn't called - Keychain handles that.
+ */
+
+ /* First check if the user has set a preferred identity for this server. */
+ status = serf_macosxssl_find_preferred_identity_in_keychain(tb->ssl_context,
+ identity,
+ pool);
+ if (status == APR_SUCCESS)
+ return APR_SUCCESS;
+
+ /* Choose an identity from the available identities in the keychains. */
+ status = serf_macosxssl_show_select_identity_panel(tb->ssl_context,
+ identity,
+ "Select client identity.", "Accept", "Cancel",
+ pool);
+ if (status != APR_ENOTIMPL)
+ return status;
+#endif
+
+ status = serf_ssl_load_identity_from_file(tb->ssl_context,
+ identity,
+ cert_path, pool);
+
+ return status;
+}
+
+static apr_status_t
+identity_conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ test_baton_t *tb = setup_baton;
+ apr_status_t status;
+
+ status = https_set_root_ca_conn_setup(skt, input_bkt, output_bkt,
+ setup_baton, pool);
+ if (status)
+ return status;
+
+ serf_ssl_identity_provider_set(tb->ssl_context,
+ identity_cb,
+ tb,
+ pool);
+
+ serf_ssl_identity_password_callback_set(tb->ssl_context,
+ client_cert_pw_cb,
+ tb,
+ pool);
+
+ return APR_SUCCESS;
+}
+
+static void test_ssl_identity(CuTest *tc)
+{
+ test_baton_t *tb;
+ handler_baton_t handler_ctx[1];
+ const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ };
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+ apr_status_t status;
+
+ /* Set up a test context with a server */
+ apr_pool_t *test_pool = tc->testBaton;
+
+ /* The SSL server the complete certificate chain to validate the client
+ certificate. */
+ status = test_https_server_setup(&tb,
+ message_list, num_requests,
+ action_list, num_requests, 0,
+ identity_conn_setup,
+ "test/server/serfserverkey.pem",
+ all_server_certs,
+ "Serf Client",
+ NULL, /* No server cert callback */
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ create_new_request(tb, &handler_ctx[0], "GET", "/", 1);
+
+ test_helper_run_requests_expect_ok(tc, tb, num_requests,
+ handler_ctx, test_pool);
+
+ CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTCB_CALLED);
+ CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTPWCB_CALLED);
+}
+
/* Validate that the expired certificate is reported as failure in the
callback. */
static void test_ssl_expired_server_cert(CuTest *tc)
@@ -2188,6 +2341,7 @@
CuSuite *test_context(void)
{
CuSuite *suite = CuSuiteNew();
+ CuSuite *openssl_suite, *macosxssl_suite;
CuSuiteSetSetupTeardownCallbacks(suite, test_setup, test_teardown);
@@ -2201,23 +2355,56 @@
SUITE_ADD_TEST(suite, test_request_timeout);
SUITE_ADD_TEST(suite, test_connection_large_response);
SUITE_ADD_TEST(suite, test_connection_large_request);
- SUITE_ADD_TEST(suite, test_ssl_handshake);
- SUITE_ADD_TEST(suite, test_ssl_trust_rootca);
- SUITE_ADD_TEST(suite, test_ssl_application_rejects_cert);
- SUITE_ADD_TEST(suite, test_ssl_certificate_chain_with_anchor);
- SUITE_ADD_TEST(suite, test_ssl_certificate_chain_all_from_server);
- SUITE_ADD_TEST(suite, test_ssl_no_servercert_callback_allok);
- SUITE_ADD_TEST(suite, test_ssl_no_servercert_callback_fail);
- SUITE_ADD_TEST(suite, test_ssl_large_response);
- SUITE_ADD_TEST(suite, test_ssl_large_request);
- SUITE_ADD_TEST(suite, test_ssl_client_certificate);
- SUITE_ADD_TEST(suite, test_ssl_expired_server_cert);
- SUITE_ADD_TEST(suite, test_ssl_future_server_cert);
SUITE_ADD_TEST(suite, test_setup_ssltunnel);
SUITE_ADD_TEST(suite, test_ssltunnel_no_creds_cb);
SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth);
SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth_keepalive_off);
SUITE_ADD_TEST(suite, test_ssltunnel_digest_auth);
+#ifdef SERF_HAVE_OPENSSL
+ openssl_suite = CuSuiteNew();
+
+ CuSuiteSetSetupTeardownCallbacks(openssl_suite, test_openssl_setup,
+ test_openssl_teardown);
+
+ SUITE_ADD_TEST(openssl_suite, test_ssl_handshake);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_trust_rootca);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_application_rejects_cert);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_certificate_chain_with_anchor);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_certificate_chain_all_from_server);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_no_servercert_callback_allok);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_no_servercert_callback_fail);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_large_response);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_large_request);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_client_certificate);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_identity);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_expired_server_cert);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_future_server_cert);
+
+ CuSuiteAddSuite(suite, openssl_suite);
+#endif
+#ifdef SERF_HAVE_MACOSXSSL
+ macosxssl_suite = CuSuiteNew();
+
+ CuSuiteSetSetupTeardownCallbacks(macosxssl_suite, test_macosxssl_setup,
+ test_macosxssl_teardown);
+
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_handshake);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_trust_rootca);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_application_rejects_cert);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_certificate_chain_with_anchor);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_certificate_chain_all_from_server);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_no_servercert_callback_allok);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_no_servercert_callback_fail);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_large_response);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_large_request);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_client_certificate);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_identity);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_expired_server_cert);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_future_server_cert);
+
+ CuSuiteAddSuite(suite, macosxssl_suite);
+#endif
+
return suite;
}
diff --git a/test/test_serf.h b/test/test_serf.h
index 29ad8a4..3e5b46f 100644
--- a/test/test_serf.h
+++ b/test/test_serf.h
@@ -186,6 +186,10 @@
void *test_setup(void *baton);
void *test_teardown(void *baton);
+void *test_openssl_setup(void *baton);
+void *test_openssl_teardown(void *baton);
+void *test_macosxssl_setup(void *baton);
+void *test_macosxssl_teardown(void *baton);
typedef struct {
serf_response_acceptor_t acceptor;
diff --git a/test/test_ssl.c b/test/test_ssl.c
index 54844bd..2775a2f 100644
--- a/test/test_ssl.c
+++ b/test/test_ssl.c
@@ -21,6 +21,9 @@
#include "serf.h"
#include "serf_bucket_types.h"
+/* Access to internal functions, for DER decoding. */
+#include "buckets/bucket_private.h"
+
#include "test_serf.h"
#if defined(WIN32) && defined(_DEBUG)
@@ -44,10 +47,13 @@
bkt = serf_bucket_ssl_decrypt_create(stream, NULL,
alloc);
+ CuAssertTrue(tc, SERF_BUCKET_IS_SSL_DECRYPT(bkt));
+
ssl_context = serf_bucket_ssl_decrypt_context_get(bkt);
bkt = serf_bucket_ssl_encrypt_create(stream, ssl_context,
alloc);
+ CuAssertTrue(tc, SERF_BUCKET_IS_SSL_ENCRYPT(bkt));
status = serf_ssl_use_default_certificates(ssl_context);
@@ -81,39 +87,95 @@
CuAssertPtrNotNull(tc, cert);
}
+typedef struct
+{
+ const char *cn;
+ const char *o;
+ const char *ou;
+ const char *l;
+ const char *st;
+ const char *c;
+ const char *e;
+} test_dn_t;
+
+typedef struct {
+ test_dn_t issuer;
+ test_dn_t subject;
+ const char *sha1;
+ const char *notBefore;
+ const char *notAfter;
+ /* serial number, subjectAltNames */
+} test_cert_t;
+
+static void
+validate_dn(CuTest *tc, const test_dn_t *expected, const apr_hash_t *actual)
+{
+ apr_hash_t *dn = (apr_hash_t *)actual;
+
+ CuAssertStrEquals(tc, expected->cn,
+ apr_hash_get(dn, "CN", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->ou,
+ apr_hash_get(dn, "OU", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->o,
+ apr_hash_get(dn, "O", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->l,
+ apr_hash_get(dn, "L", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->st,
+ apr_hash_get(dn, "ST", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->c,
+ apr_hash_get(dn, "C", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->e,
+ apr_hash_get(dn, "E", APR_HASH_KEY_STRING));
+}
+
+static void
+validate_cert(CuTest *tc, const test_cert_t *expected, const apr_hash_t *actual)
+{
+ const apr_hash_t *subject, *issuer;
+ apr_hash_t *cert = (apr_hash_t *)actual;
+
+ subject = apr_hash_get(cert, "subject", APR_HASH_KEY_STRING);
+ CuAssertPtrNotNullMsg(tc, "Expected subject", subject);
+
+ validate_dn(tc, &expected->subject, subject);
+
+ issuer = apr_hash_get(cert, "issuer", APR_HASH_KEY_STRING);
+ CuAssertPtrNotNullMsg(tc, "Expected issuer", issuer);
+
+ validate_dn(tc, &expected->issuer, issuer);
+ /*
+ TODO: sha1sum of DER encoded cert for signature.
+ CuAssertStrEquals(tc, expected->sha1,
+ apr_hash_get(cert, "sha1", APR_HASH_KEY_STRING));
+ */
+ CuAssertStrEquals(tc, expected->notBefore,
+ apr_hash_get(cert, "notBefore", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, expected->notAfter,
+ apr_hash_get(cert, "notAfter", APR_HASH_KEY_STRING));
+ /* TODO: subjectAltNames */
+}
+
/* Test that reading the subject from a custom CA certificate file works. */
static void test_ssl_cert_subject(CuTest *tc)
{
apr_hash_t *subject;
serf_ssl_certificate_t *cert = NULL;
apr_status_t status;
+ const test_dn_t exp_subject = { "Serf", "In Serf we trust, Inc.",
+ "Test Suite", "Mechelen", "Antwerp", "BE", "serf@example.com" };
apr_pool_t *test_pool = tc->testBaton;
status = serf_ssl_load_cert_file(&cert, get_ca_file(test_pool,
"test/serftestca.pem"),
test_pool);
-
CuAssertIntEquals(tc, APR_SUCCESS, status);
CuAssertPtrNotNull(tc, cert);
subject = serf_ssl_cert_subject(cert, test_pool);
CuAssertPtrNotNull(tc, subject);
- CuAssertStrEquals(tc, "Serf",
- apr_hash_get(subject, "CN", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Test Suite",
- apr_hash_get(subject, "OU", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "In Serf we trust, Inc.",
- apr_hash_get(subject, "O", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Mechelen",
- apr_hash_get(subject, "L", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Antwerp",
- apr_hash_get(subject, "ST", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "BE",
- apr_hash_get(subject, "C", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "serf@example.com",
- apr_hash_get(subject, "E", APR_HASH_KEY_STRING));
+ validate_dn(tc, &exp_subject, subject);
}
/* Test that reading the issuer from a custom CA certificate file works. */
@@ -122,6 +184,8 @@
apr_hash_t *issuer;
serf_ssl_certificate_t *cert = NULL;
apr_status_t status;
+ const test_dn_t exp_issuer = { "Serf", "In Serf we trust, Inc.",
+ "Test Suite", "Mechelen", "Antwerp", "BE", "serf@example.com" };
apr_pool_t *test_pool = tc->testBaton;
@@ -135,21 +199,7 @@
issuer = serf_ssl_cert_issuer(cert, test_pool);
CuAssertPtrNotNull(tc, issuer);
- /* TODO: create a new test certificate with different issuer and subject. */
- CuAssertStrEquals(tc, "Serf",
- apr_hash_get(issuer, "CN", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Test Suite",
- apr_hash_get(issuer, "OU", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "In Serf we trust, Inc.",
- apr_hash_get(issuer, "O", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Mechelen",
- apr_hash_get(issuer, "L", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Antwerp",
- apr_hash_get(issuer, "ST", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "BE",
- apr_hash_get(issuer, "C", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "serf@example.com",
- apr_hash_get(issuer, "E", APR_HASH_KEY_STRING));
+ validate_dn(tc, &exp_issuer, issuer);
}
/* Test that reading the notBefore,notAfter,sha1 fingerprint and subjectAltNames
@@ -184,7 +234,33 @@
CuAssertTrue(tc, san_arr == NULL);
}
+static void test_ssl_load_CA_cert_from_file(CuTest *tc)
+{
+ serf_ssl_certificate_t *cert = NULL;
+ serf_bucket_t *bkt, *stream;
+ serf_ssl_context_t *ssl_context;
+ apr_status_t status;
+
+ apr_pool_t *test_pool = tc->testBaton;
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
+ bkt = serf_bucket_ssl_decrypt_create(stream, NULL, alloc);
+ ssl_context = serf_bucket_ssl_decrypt_context_get(bkt);
+
+ status = serf_ssl_load_CA_cert_from_file(ssl_context,
+ &cert,
+ get_ca_file(test_pool, "test/serftestca.pem"),
+ test_pool);
+
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertPtrNotNull(tc, cert);
+}
+
static const char *extract_cert_from_pem(const char *pemdata,
+ apr_size_t *pemlen,
+ int copy_delimiters,
apr_pool_t *pool)
{
enum { INIT, CERT_BEGIN, CERT_FOUND } state;
@@ -205,6 +281,7 @@
const char *data;
apr_size_t len;
int found;
+ int delimiter = FALSE;
status = serf_bucket_readline(pembkt, SERF_NEWLINE_ANY, &found,
&data, &len);
@@ -212,31 +289,46 @@
return NULL;
if (state == INIT) {
- if (strncmp(begincert, data, strlen(begincert)) == 0)
- state = CERT_BEGIN;
+ if (strncmp(begincert, data, strlen(begincert)) != 0)
+ continue;
+
+ state = CERT_BEGIN;
+ delimiter = TRUE;
+ if (!copy_delimiters)
+ continue;
} else if (state == CERT_BEGIN) {
if (strncmp(endcert, data, strlen(endcert)) == 0)
+ {
state = CERT_FOUND;
- else {
- certdata = apr_pstrcat(pool, certdata, data, NULL);
- certlen += len;
- switch (found) {
- case SERF_NEWLINE_CR:
- case SERF_NEWLINE_LF:
- certdata[certlen-1] = '\0';
- certlen --;
- break;
- case SERF_NEWLINE_CRLF:
- certdata[certlen-2] = '\0';
- certlen-=2;
- break;
- }
+ delimiter = TRUE;
+ if (!copy_delimiters)
+ break;
+ certdata = apr_pstrcat(pool, certdata, CRLF, NULL);
+ certlen += 2;
}
}
+ /* Copy the line to the output buffer. Remove linefeeds from the
+ base64 DER encoded certificate. */
+ certdata = apr_pstrcat(pool, certdata, data, NULL);
+ certlen += len;
+ switch (found && !delimiter)
+ {
+ case SERF_NEWLINE_CR:
+ case SERF_NEWLINE_LF:
+ certdata[certlen-1] = '\0';
+ certlen--;
+ break;
+ case SERF_NEWLINE_CRLF:
+ certdata[certlen-2] = '\0';
+ certlen-=2;
+ break;
+ }
}
- if (state == CERT_FOUND)
+ if (state == CERT_FOUND) {
+ *pemlen = certlen;
return certdata;
+ }
else
return NULL;
}
@@ -275,22 +367,201 @@
base64derbuf = serf_ssl_cert_export(cert, test_pool);
CuAssertStrEquals(tc,
- extract_cert_from_pem(pembuf, test_pool),
+ extract_cert_from_pem(pembuf, &pemlen, 0, test_pool),
base64derbuf);
}
+/****************************************************************************/
+/* Test the DER decoding with certificates collected in file
+ test/certificates.pem.
+ */
+
+static apr_status_t read_certificate(serf_bucket_t *bkt,
+ char *buf,
+ apr_size_t buf_len,
+ apr_size_t *read_len)
+{
+ apr_size_t total_read;
+ apr_status_t status;
+ const char *delimiter = "=========================";
+ int acceptable = SERF_NEWLINE_CRLF | SERF_NEWLINE_LF | SERF_NEWLINE_CR;
+
+ total_read = 0;
+ do
+ {
+ const char *data;
+ apr_size_t len;
+ int found;
+
+ status = serf_bucket_readline(bkt, acceptable, &found,
+ &data, &len);
+ if (!SERF_BUCKET_READ_ERROR(status))
+ {
+ if (len >= sizeof(delimiter) &&
+ strncmp(delimiter, data, sizeof(delimiter)) == 0)
+ {
+ /* end of certificate found, return current data. */
+ status = APR_SUCCESS;
+ break;
+ }
+ if (total_read + len > buf_len)
+ {
+ /* Buffer is not large enough to read all data */
+ status = SERF_ERROR_ISSUE_IN_TESTSUITE;
+ }
+ memcpy(buf + total_read, data, len);
+ total_read += len;
+ }
+ } while(status == APR_SUCCESS);
+
+ *read_len = total_read;
+ return status;
+}
+
+const test_cert_t test_certs[] =
+{
+ { { "Serf CA", "In Serf we trust, Inc.", "Test Suite CA",
+ "Mechelen", "Antwerp", "BE", "serfca@example.com"},
+ { "Serf Server", "In Serf we trust, Inc.", "Test Suite Server",
+ "Mechelen", "Antwerp", "BE", "serfserver@example.com"},
+ "", "Apr 18 19:50:11 2013 GMT", "Apr 18 19:50:11 2014 GMT",
+ },
+ { { "Serf Root CA", "In Serf we trust, Inc.", "Test Suite Root CA",
+ "Mechelen", "Antwerp", "BE", "serfrootca@example.com"},
+ { "Serf Root CA", "In Serf we trust, Inc.", "Test Suite Root CA",
+ "Mechelen", "Antwerp", "BE", "serfrootca@example.com"},
+ "", "Apr 13 11:19:14 2013 GMT", "Apr 11 11:19:14 2023 GMT",
+ },
+ { { "Belgium Root CA", NULL, NULL, NULL, NULL, "BE", NULL},
+ { "Belgium Root CA", NULL, NULL, NULL, NULL, "BE", NULL},
+ "", "Jan 26 23:00:00 2003 GMT", "Jan 26 23:00:00 2014 GMT",
+ },
+ { { "Belgium Root CA2", NULL, NULL, NULL, NULL, "BE", NULL},
+ { "Belgium Root CA2", NULL, NULL, NULL, NULL, "BE", NULL},
+ "", "Oct 04 10:00:00 2007 GMT", "Dec 15 08:00:00 2021 GMT",
+ },
+};
+
+static void test_macosxssl_DER_decoding(CuTest *tc)
+{
+#ifdef SERF_HAVE_MACOSXSSL
+ serf_bucket_t *filebkt;
+ apr_file_t *fp;
+ char buf[16384];
+ apr_size_t len;
+ apr_status_t status;
+ int current_cert;
+
+ apr_pool_t *test_pool = tc->testBaton;
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool,
+ NULL, NULL);
+
+ status = apr_file_open(&fp, "test/certificates.pem",
+ APR_FOPEN_READ | APR_FOPEN_BINARY,
+ APR_FPROT_OS_DEFAULT, test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ filebkt = serf_bucket_file_create(fp, alloc);
+
+ current_cert = 0;
+ do
+ {
+ status = read_certificate(filebkt, buf, sizeof(buf), &len);
+ CuAssertIntEquals(tc, SERF_BUCKET_READ_ERROR(status), 0);
+
+ /* A certificate was read from test/certificates.pem */
+ if (status == APR_SUCCESS)
+ {
+ apr_size_t pemlen;
+ const char *pemdata = extract_cert_from_pem(buf, &pemlen, 1, test_pool);
+ serf_ssl_certificate_t *cert;
+ apr_hash_t *actual;
+
+ test_cert_t thiscert = test_certs[current_cert];
+
+/* printf("Certificate found: %.*s\n\n", pemlen, pemdata);**/
+ status = load_CA_cert_from_buffer(&cert, pemdata, pemlen, test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf__macosxssl_read_X509_DER_certificate(&actual,
+ cert->impl_cert,
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ validate_cert(tc, &thiscert, actual);
+
+ current_cert++;
+ }
+ } while (status == APR_SUCCESS &&
+ current_cert < sizeof(test_certs) / sizeof(test_certs[0]));
+
+#endif
+}
+
+/* Disable all available SSL implementations, test for no crash. */
+static void test_ssl_no_implementations(CuTest *tc)
+{
+ serf_bucket_t *bkt, *stream;
+
+ apr_pool_t *test_pool = tc->testBaton;
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+ stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
+
+ serf_config_disable_bucket_impls(SERF_IMPL_SSL_ALL);
+
+ bkt = serf_bucket_ssl_decrypt_create(stream, NULL, alloc);
+ CuAssertPtrEquals(tc, NULL, bkt);
+
+ bkt = serf_bucket_ssl_encrypt_create(stream, NULL, alloc);
+ CuAssertPtrEquals(tc, NULL, bkt);
+
+ serf_config_enable_bucket_impls(SERF_IMPL_SSL_ALL);
+}
+
CuSuite *test_ssl(void)
{
CuSuite *suite = CuSuiteNew();
+ CuSuite *openssl_suite, *macosxssl_suite;
CuSuiteSetSetupTeardownCallbacks(suite, test_setup, test_teardown);
- SUITE_ADD_TEST(suite, test_ssl_init);
- SUITE_ADD_TEST(suite, test_ssl_load_cert_file);
- SUITE_ADD_TEST(suite, test_ssl_cert_subject);
- SUITE_ADD_TEST(suite, test_ssl_cert_issuer);
- SUITE_ADD_TEST(suite, test_ssl_cert_certificate);
- SUITE_ADD_TEST(suite, test_ssl_cert_export);
+#ifdef SERF_HAVE_OPENSSL
+ openssl_suite = CuSuiteNew();
+
+ CuSuiteSetSetupTeardownCallbacks(openssl_suite, test_openssl_setup,
+ test_openssl_teardown);
+
+ SUITE_ADD_TEST(openssl_suite, test_ssl_init);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_load_cert_file);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_cert_subject);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_cert_issuer);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_cert_certificate);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_load_CA_cert_from_file);
+ SUITE_ADD_TEST(openssl_suite, test_ssl_cert_export);
+ SUITE_ADD_TEST(openssl_suite, test_macosxssl_DER_decoding);
+
+ CuSuiteAddSuite(suite, openssl_suite);
+#endif
+#ifdef SERF_HAVE_MACOSXSSL
+ macosxssl_suite = CuSuiteNew();
+
+ CuSuiteSetSetupTeardownCallbacks(macosxssl_suite, test_macosxssl_setup,
+ test_macosxssl_teardown);
+
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_init);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_load_cert_file);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_subject);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_issuer);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_certificate);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_load_CA_cert_from_file);
+ SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_export);
+ SUITE_ADD_TEST(macosxssl_suite, test_macosxssl_DER_decoding);
+
+ CuSuiteAddSuite(suite, macosxssl_suite);
+#endif
+
+ SUITE_ADD_TEST(suite, test_ssl_no_implementations);
return suite;
}
diff --git a/test/test_util.c b/test/test_util.c
index c9d6aab..ff37fe2 100644
--- a/test/test_util.c
+++ b/test/test_util.c
@@ -378,6 +378,33 @@
return NULL;
}
+void *test_openssl_setup(void *dummy)
+{
+ serf_config_disable_bucket_impls(SERF_IMPL_SSL_ALL);
+ serf_config_enable_bucket_impls(SERF_IMPL_SSL_OPENSSL);
+
+ return test_setup(dummy);
+}
+
+void *test_openssl_teardown(void *baton)
+{
+ serf_config_enable_bucket_impls(SERF_IMPL_SSL_ALL);
+ return test_teardown(baton);
+}
+
+void *test_macosxssl_setup(void *dummy)
+{
+ serf_config_disable_bucket_impls(SERF_IMPL_SSL_ALL);
+ serf_config_enable_bucket_impls(SERF_IMPL_SSL_MACOSXSSL);
+ return test_setup(dummy);
+}
+
+void *test_macosxssl_teardown(void *baton)
+{
+ serf_config_enable_bucket_impls(SERF_IMPL_SSL_ALL);
+ return test_teardown(baton);
+}
+
/* Helper function, runs the client and server context loops and validates
that no errors were encountered, and all messages were sent and received. */
apr_status_t