On the ocsp-verification branch: Sync with trunk up to r1774385.

git-svn-id: https://svn.apache.org/repos/asf/serf/branches/ocsp-verification@1774386 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/BRANCH-README b/BRANCH-README
new file mode 100644
index 0000000..1219e31
--- /dev/null
+++ b/BRANCH-README
@@ -0,0 +1,240 @@
+Support for OCSP Verification in Serf
+=====================================
+
+Serf trunk currently supports OCSP stapling for verifying server
+certificates. The purpose of this branch is to add minimal support
+for issuing OCSP requests to responders from the client application.
+
+The idea is that the application decides when and where to send OCSP
+requests and how to verify responses, and Serf provides some basic
+utility functions for constructing the requests and parsing the
+responses.
+
+These are the proposed changes:
+
+1. serf_ssl_cert_certificate()
+
+   Extract the OCSP responder locations from the certificate's x509v3
+   extension field authorityInfoAccess:OCSP;URI and, if it is present,
+   insert the array into the returned hash table with key "OCSP".
+
+
+2. serf_ssl_cert_export2()
+
+   /**
+    * Export a certificate to base64-encoded, zero-terminated string.
+    * The returned string is allocated in @a result_pool.
+    * Uses @a scratch_pool for temporary allocations.
+    * Returns NULL on failure.
+    */
+   const char *serf_ssl_cert_export2(
+       const serf_ssl_certificate_t *cert,
+       apr_pool_t *result_pool,
+       apr_pool_t *scratch_pool);
+
+
+   Discussion:
+
+     serf_ssl_cert_export() uses a single pool for both the result and
+     for teporary allocations. The size of the temporary buffer is on
+     the order of the size of the result, so if we can use a scratch
+     pool for that, we can effectively halve the memory used by
+     exported certs.
+
+
+3. serf_ssl_cert_import()
+
+   Add a new function that is the inverse of serf_ssl_cert_export():
+
+   /**
+    * Import a certificate from a base64-encoded, zero-terminated string.
+    * The returned certificate is allocated in @a result_pool.
+    * Uses @a scratch_pool for temporary allocations.
+    * Returns NULL on failure.
+    */
+   serf_ssl_certificate_t *serf_ssl_cert_import(
+       const char *encoded_cert,
+       apr_pool_t *result_pool,
+       apr_pool_t *scratch_pool);
+
+   Discussion:
+
+     In order to create an OCSP request, the application needs both
+     the server certificate and its issuer certtificate. An
+     application may have to issue OCSP requests independently and
+     asynchronously of any other processing, so it's nice if it can
+     store the certificates in a form that's independent of pool
+     lifetimes. We provide this form with serf_ssl_cert_export(), but
+     there's no easy way to consume the exported form in existing Serf
+     APIs (writing it to a file in PEM format and reading it back
+     through serf_ssl_load_cert_file() is neither easy nor sane).
+
+
+4. OCSP requests
+
+   Add a new opaque type and accessor functions that can be used from
+   within a request setup handler to create an OCSP request.
+
+   Discussion:
+
+     HTTP OCSP requests can be sent using eithe the GET or POST
+     methods; see https://www.ietf.org/rfc/rfc2560.txt section A.1.1.
+     It's up to the application to decide which method to use, so we
+     don't provide a function to create the request body or set
+     request headers.
+
+   /**
+    * Internal representation of an OCSP request.
+    */
+   typedef struct serf_ssl_ocsp_request_t serf_ssl_ocsp_request_t;
+
+   /**
+    * Constructs an OCSP verification request for @a server_cert with
+    * issuer certificate @a issuer_cert. If @a generate_nonce is
+    * non-zero, the request will contain a random nonce.
+    *
+    * The request will be allocated from @a result_pool.
+    *
+    * Use @a scratch_pool for temporary allocations.
+    *
+    * Returns @c NULL on failure, e.g., if @a issuer_cert is not the
+    * issuer certificate of @a server_cert.
+    *
+    * @note The @a server_cert and @a issuer_cert will be copied into the
+    * OCSP request structure. The lifetime of the copies is controlled by
+    * the lifetime of @a result_pool.
+    */
+   serf_ssl_ocsp_request_t *serf_ssl_ocsp_request_create(
+       const serf_ssl_certificate_t *server_cert,
+       const serf_ssl_certificate_t *issuer_cert,
+       int generate_nonce,
+       apr_pool_t *result_pool,
+       apr_pool_t *scratch_pool);
+
+   /**
+    * Returns an pointer to the DER-encoded OCSP request
+    * body within the @a ocsp_request structure.
+    *
+    * The application must decide whether to use this data as the body of
+    * an HTTP POST request or Base64-encoded as part of the URI for a GET
+    * request; see RFC 560, section A.1.1.
+    *
+    * @see serf_ssl_ocsp_request_body_size()
+    */
+   const void *serf_ssl_ocsp_request_body(
+       const serf_ssl_ocsp_request_t *ocsp_request);
+
+   /**
+    * Returns the size of the DER-encoded OCSP request body.
+    * @see serf_ssl_ocsp_request_body().
+    */
+   apr_size_t serf_ssl_ocsp_request_body_size(
+       const serf_ssl_ocsp_request_t *ocsp_request);
+
+   /**
+    * Export @a ocsp_request, including request, server and issuer
+    * certificates, to a zero-terminated string, allocated from
+    * @a result_pool.
+    *
+    * Use @a scratch_pool for temporary allocations.
+    *
+    * Returns @c NULL on failure.
+    */
+   const char *serf_ssl_ocsp_request_export(
+       const serf_ssl_ocsp_request_t *ocsp_request,
+       apr_pool_t *result_pool,
+       apr_pool_t *scratch_pool);
+
+   /**
+    * Create an OCSP request from a previously exported zero-terminated
+    * string @a encoded_ocsp_request. The returned request will be
+    * allocated from @a result_pool.
+    *
+    * Use @a scratch_pool for temporary allocations.
+    *
+    * Returns @c NULL on failure.
+    */
+   serf_ssl_ocsp_request_t *serf_ssl_ocsp_request_import(
+       const char *encoded_ocsp_request,
+       apr_pool_t *result_pool,
+       apr_pool_t *scratch_pool);
+
+
+5. OCSP responses
+
+   Add a new opaque type, response body parser and response validator.
+
+   Add a new function that can be used from within a response handler
+   to verify an OCSP response:
+
+   Discussion:
+
+     Parses and verifies the OCSP response received in the HTTP response
+     body as per RFC 2560, section 3.2.
+
+   /**
+    * Internal representation of an OCSP response.
+    */
+   typedef struct serf_ssl_ocsp_response_t serf_ssl_ocsp_response_t;
+
+   /**
+    * Parse the body of an HTTP OCSP response, @a ocsp_response_body,
+    * of size @a ocsp_response_size, and construct an OCSP response,
+    * allocated from @a result pool.
+    *
+    * Use @a scratch_pool for temporary allocations.
+    *
+    * Returns @c NULL if the response body is not well-formed.
+    */
+   serf_ssl_ocsp_request_t *serf_ssl_ocsp_response_parse(
+       const void *ocsp_response_body,
+       apr_size_t ocsp_response_size,
+       apr_pool_t *result_pool,
+       apr_pool_t *scratch_pool);
+
+   /**
+    * Check if the given @a ocsp_response is valid for the given
+    * @a ocsp_request, per the algorighm documented in RFC 2560,
+    * section 3.5.
+    *
+    * The returned value will be:
+    *
+    *   - APR_SUCCESS,
+    *     if all steps of the verification succeeded;
+    *   - SERF_ERROR_SSL_OCSP_RESPONSE_CERT_REVOKED,
+    *     if the certificate was revoked;
+    *   - SERF_ERROR_SSL_OCSP_RESPONSE_CERT_UNKNOWN,
+    *     if the responder knows nothing about the certificate;
+    *   - SERF_ERROR_SSL_OCSP_RESPONSE_INVALID,
+    *     if the response itself is invalid or not well-formed.
+    *
+    * The @a this_update, @a next_update and @a produced_at output
+    * arguments are described in RFC 2560, section 2.4 and, when not
+    * @c NULL and if the verificateion succeeded, will be parsed from
+    * the response. Any of these times that are not present in the
+    * response will be set to the epoch, i.e., @c APR_TIME_C(0).
+    *
+    * Uses @a scratch_pool for temporary allocations.
+    */
+   apr_status_t serf_ssl_ocsp_response_verify(
+       const serf_ssl_ocsp_response_t *ocsp_response,
+       const serf_ssl_ocsp_request_t *ocsp_request,
+       apr_time_t *this_update,
+       apr_time_t *next_update,
+       apr_time_t *produced_at,
+       apr_pool_t *scratch_pool);
+
+
+6. New error codes and macros
+
+       #define SERF_ERROR_SSL_OCSP_RESPONSE_CERT_REVOKED
+       #define SERF_ERROR_SSL_OCSP_RESPONSE_CERT_UNKNOWN
+       #define SERF_ERROR_SSL_OCSP_RESPONSE_INVALID
+
+       #define SERF_OCSP_UNGOOD_ERROR(status)
+
+   Discussion:
+
+     These error codes are returned from serf_ssl_ocsp_response_verify().
+     The SERF_OCSP_UNGOOD_ERROR() macro combines the _CERT_REVOKED
+     and _CERT_UNKNOWN error codes..
diff --git a/buckets/ssl_buckets.c b/buckets/ssl_buckets.c
index 2756020..cd2b0b2 100644
--- a/buckets/ssl_buckets.c
+++ b/buckets/ssl_buckets.c
@@ -733,6 +733,42 @@
     return APR_SUCCESS;
 }
 
+
+static apr_status_t
+get_ocsp_responders(apr_array_header_t **ocsp_arr, X509 *ssl_cert,
+                    apr_pool_t *pool)
+{
+    /* assert: (ocsp_arr && pool) */
+
+    if (ocsp_arr) {
+        STACK_OF(OPENSSL_STRING) *uris;
+
+        *ocsp_arr = NULL;
+        uris = X509_get1_ocsp(ssl_cert);
+        if (uris) {
+            int uris_count = sk_OPENSSL_STRING_num(uris);
+            int uri_idx;
+
+            *ocsp_arr = apr_array_make(pool, uris_count, sizeof(char*));
+
+            for (uri_idx = 0; uri_idx < uris_count; ++uri_idx) {
+                OPENSSL_STRING uri = sk_OPENSSL_STRING_value(uris, uri_idx);
+                if (uri) {
+                    char *p = apr_pstrdup(pool, uri);
+
+                    if (p) {
+                        APR_ARRAY_PUSH(*ocsp_arr, char*) = p;
+                    }
+                }
+            }
+        }
+        X509_email_free(uris);
+    }
+
+    return APR_SUCCESS;
+}
+
+
 static apr_status_t validate_cert_hostname(X509 *server_cert, apr_pool_t *pool)
 {
     char buf[1024];
@@ -2276,6 +2312,7 @@
     unsigned char md[EVP_MAX_MD_SIZE];
     BIO *bio;
     apr_array_header_t *san_arr;
+    apr_array_header_t *ocsp_arr;
 
     /* sha1 fingerprint */
     if (X509_digest(cert->ssl_cert, EVP_sha1(), md, &md_size)) {
@@ -2324,6 +2361,10 @@
     if (!get_subject_alt_names(&san_arr, cert->ssl_cert, EscapeNulAndCopy, pool))
       apr_hash_set(tgt, "subjectAltName", APR_HASH_KEY_STRING, san_arr);
 
+    /* Get authorityAccessInfo.OCSP */
+    if (!get_ocsp_responders(&ocsp_arr, cert->ssl_cert, pool))
+      apr_hash_set(tgt, "OCSP", APR_HASH_KEY_STRING, ocsp_arr);
+
     return tgt;
 }
 
@@ -2332,6 +2373,14 @@
     const serf_ssl_certificate_t *cert,
     apr_pool_t *pool)
 {
+    return serf_ssl_cert_export2(cert, pool, pool);
+}
+
+const char *serf_ssl_cert_export2(
+    const serf_ssl_certificate_t *cert,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool)
+{
     char *binary_cert;
     char *encoded_cert;
     int len;
@@ -2343,19 +2392,47 @@
         return NULL;
     }
 
-    binary_cert = apr_palloc(pool, len);
+    binary_cert = apr_palloc(scratch_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));
+    encoded_cert = apr_palloc(result_pool, apr_base64_encode_len(len));
     apr_base64_encode(encoded_cert, binary_cert, len);
 
     return encoded_cert;
 }
 
+
+serf_ssl_certificate_t *serf_ssl_cert_import(
+    const char *encoded_cert,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool)
+{
+    char *binary_cert;
+    int binary_len;
+    const unsigned char *unused;
+    X509* ssl_cert;
+    serf_ssl_certificate_t *cert;
+
+    binary_cert = apr_palloc(scratch_pool, apr_base64_decode_len(encoded_cert));
+    binary_len = apr_base64_decode(binary_cert, encoded_cert);
+
+    unused = (unsigned char*) binary_cert; /* unused is incremented  */
+    ssl_cert = d2i_X509(NULL, &unused, binary_len);
+    if (!ssl_cert) {
+        return NULL;
+    }
+
+    /* TODO: Setup pool cleanup to free certificate */
+    cert = apr_palloc(result_pool, sizeof(serf_ssl_certificate_t));
+    cert->ssl_cert = ssl_cert;
+    return cert;
+}
+
+
 /* Disables compression for all SSL sessions. */
 static void disable_compression(serf_ssl_context_t *ssl_ctx)
 {
diff --git a/serf.h b/serf.h
index 5f3d3ae..70e59ef 100644
--- a/serf.h
+++ b/serf.h
@@ -143,6 +143,19 @@
    on a connection that uses HTTP pipelining. */
 #define SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS (SERF_ERROR_START + 73)
 
+/* OCSP responder says that the certificate is revoked. */
+#define SERF_ERROR_SSL_OCSP_RESPONSE_CERT_REVOKED (SERF_ERROR_START + 74)
+
+/* OCSP responder says that the certificate is unknown. */
+#define SERF_ERROR_SSL_OCSP_RESPONSE_CERT_UNKNOWN (SERF_ERROR_START + 75)
+
+/* The response from an OCSP responder was not valid. */
+#define SERF_ERROR_SSL_OCSP_RESPONSE_INVALID (SERF_ERROR_START + 76)
+
+#define SERF_OCSP_UNGOOD_ERROR(status) ((status) \
+    && ((SERF_ERROR_SSL_OCSP_CERT_REVOKED == (status)) \
+        ||(SERF_ERROR_SSL_OCSP_CERT_UNKNOWN == (status))))
+
 /* 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 71b6311..58f4b7e 100644
--- a/serf_bucket_types.h
+++ b/serf_bucket_types.h
@@ -701,22 +701,45 @@
     apr_pool_t *pool);
 
 /**
- * Extract the fields of the certificate in a table with keys (sha1, notBefore,
- * notAfter, subjectAltName). The returned table will be allocated in @a pool.
+ * Extract the fields of the certificate in a table with keys
+ *   (sha1, notBefore, notAfter, subjectAltName, OCSP).
+ * The returned table will be allocated in @a pool.
  */
 apr_hash_t *serf_ssl_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.
+ * Like serf_ssl_cert_export2() but uses a single pool for both the
+ * result and temporary allocations.
  */
 const char *serf_ssl_cert_export(
     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 result_pool.
+ * Uses @a scratch_pool for temporary allocations.
+ * Returns NULL on failure.
+ */
+const char *serf_ssl_cert_export2(
+    const serf_ssl_certificate_t *cert,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool);
+
+/**
+ * Import a certificate from a base64-encoded, zero-terminated string.
+ * The returned certificate is allocated in @a result_pool.
+ * Uses @a scratch_pool for temporary allocations.
+ * Returns NULL on failure.
+ */
+serf_ssl_certificate_t *serf_ssl_cert_import(
+    const char *encoded_cert,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_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 alloced in @a pool.
@@ -778,6 +801,135 @@
 
 /* ==================================================================== */
 
+/**
+ * Internal representation of an OCSP request.
+ */
+typedef struct serf_ssl_ocsp_request_t serf_ssl_ocsp_request_t;
+
+/**
+ * Constructs an OCSP verification request for @a server_cert with
+ * issuer certificate @a issuer_cert. If @a generate_nonce is
+ * non-zero, the request will contain a random nonce.
+ *
+ * The request will be allocated from @a result_pool.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * Returns @c NULL on failure, e.g., if @a issuer_cert is not the
+ * issuer certificate of @a server_cert.
+ *
+ * @note The @a server_cert and @a issuer_cert will be copied into the
+ * OCSP request structure. The lifetime of the copies is controlled by
+ * the lifetime of @a result_pool.
+ */
+serf_ssl_ocsp_request_t *serf_ssl_ocsp_request_create(
+    const serf_ssl_certificate_t *server_cert,
+    const serf_ssl_certificate_t *issuer_cert,
+    int generate_nonce,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool);
+
+/**
+ * Returns an pointer to the DER-encoded OCSP request
+ * body within the @a ocsp_request structure.
+ *
+ * The application must decide whether to use this data as the body of
+ * an HTTP POST request or Base64-encoded as part of the URI for a GET
+ * request; see RFC 560, section A.1.1.
+ *
+ * @see serf_ssl_ocsp_request_body_size()
+ */
+const void *serf_ssl_ocsp_request_body(
+    const serf_ssl_ocsp_request_t *ocsp_request);
+
+/**
+ * Returns the size of the DER-encoded OCSP request body.
+ * @see serf_ssl_ocsp_request_body().
+ */
+apr_size_t serf_ssl_ocsp_request_body_size(
+    const serf_ssl_ocsp_request_t *ocsp_request);
+
+/**
+ * Export @a ocsp_request, including request, server and issuer
+ * certificates, to a zero-terminated string, allocated from
+ * @a result_pool.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * Returns @c NULL on failure.
+ */
+const char *serf_ssl_ocsp_request_export(
+    const serf_ssl_ocsp_request_t *ocsp_request,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool);
+
+/**
+ * Create an OCSP request from a previously exported zero-terminated
+ * string @a encoded_ocsp_request. The returned request will be
+ * allocated from @a result_pool.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * Returns @c NULL on failure.
+ */
+serf_ssl_ocsp_request_t *serf_ssl_ocsp_request_import(
+    const char *encoded_ocsp_request,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool);
+
+/**
+ * Internal representation of an OCSP response.
+ */
+typedef struct serf_ssl_ocsp_response_t serf_ssl_ocsp_response_t;
+
+/**
+ * Parse the body of an HTTP OCSP response, @a ocsp_response_body,
+ * of size @a ocsp_response_size, and construct an OCSP response,
+ * allocated from @a result pool.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * Returns @c NULL if the response body is not well-formed.
+ */
+serf_ssl_ocsp_request_t *serf_ssl_ocsp_response_parse(
+    const void *ocsp_response_body,
+    apr_size_t ocsp_response_size,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool);
+
+/**
+ * Check if the given @a ocsp_response is valid for the given
+ * @a ocsp_request, per the algorighm documented in RFC 2560,
+ * section 3.5.
+ *
+ * The returned value will be:
+ *
+ *   - APR_SUCCESS,
+ *     if all steps of the verification succeeded;
+ *   - SERF_ERROR_SSL_OCSP_RESPONSE_CERT_REVOKED,
+ *     if the certificate was revoked;
+ *   - SERF_ERROR_SSL_OCSP_RESPONSE_CERT_UNKNOWN,
+ *     if the responder knows nothing about the certificate;
+ *   - SERF_ERROR_SSL_OCSP_RESPONSE_INVALID,
+ *     if the response itself is invalid or not well-formed.
+ *
+ * The @a this_update, @a next_update and @a produced_at output
+ * arguments are described in RFC 2560, section 2.4 and, when not
+ * @c NULL and if the verificateion succeeded, will be parsed from
+ * the response. Any of these times that are not present in the
+ * response will be set to the epoch, i.e., @c APR_TIME_C(0).
+ *
+ * Uses @a scratch_pool for temporary allocations.
+ */
+apr_status_t serf_ssl_ocsp_response_verify(
+    const serf_ssl_ocsp_response_t *ocsp_response,
+    const serf_ssl_ocsp_request_t *ocsp_request,
+    apr_time_t *this_update,
+    apr_time_t *next_update,
+    apr_time_t *produced_at,
+    apr_pool_t *scratch_pool);
+
+/* ==================================================================== */
 
 extern const serf_bucket_type_t serf_bucket_type_ssl_decrypt;
 #define SERF_BUCKET_IS_SSL_DECRYPT(b) SERF_BUCKET_CHECK((b), ssl_decrypt)
diff --git a/src/context.c b/src/context.c
index f471fed..a33ff04 100644
--- a/src/context.c
+++ b/src/context.c
@@ -389,6 +389,13 @@
         return "An error occurred during SSL setup";
     case SERF_ERROR_SSL_CERT_FAILED:
         return "An SSL certificate related error occurred ";
+    case SERF_ERROR_SSL_OCSP_RESPONSE_CERT_REVOKED:
+        return "An OCSP responder declared an SSL certificate is revoked";
+    case SERF_ERROR_SSL_OCSP_RESPONSE_CERT_UNKNOWN:
+        return "An OCSP responder declared an SSL certificate is unknown";
+    case SERF_ERROR_SSL_OCSP_RESPONSE_INVALID:
+        return "An OCSP responder returned an invalid response";
+
     case SERF_ERROR_AUTHN_FAILED:
         return "An error occurred during authentication";
     case SERF_ERROR_AUTHN_NOT_SUPPORTED:
diff --git a/test/certs/create_certs.py b/test/certs/create_certs.py
index 2668afe..59af0b1 100755
--- a/test/certs/create_certs.py
+++ b/test/certs/create_certs.py
@@ -83,7 +83,8 @@
 # subjectAltName
 def create_cert(subjectkey, certfile, issuer=None, issuerkey=None, country='', 
                 state='', city='', org='', ou='', cn='', email='', ca=False, 
-                valid_before=0, days_valid=VALID_DAYS, subjectAltName=None):
+                valid_before=0, days_valid=VALID_DAYS, subjectAltName=None,
+                ocsp_responder_url=None):
     '''
     Create a X509 signed certificate.
     
@@ -130,6 +131,11 @@
         cert.add_extensions([
             crypto.X509Extension('subjectAltName', critical, ", ".join(subjectAltName))])
 
+    if ocsp_responder_url:
+        cert.add_extensions([
+            crypto.X509Extension('authorityInfoAccess', False,
+                                 'OCSP;URI:' + ocsp_responder_url)])
+
     cert.sign(issuerkey, SIGN_ALGO)
 
     open(certfile, "wt").write(crypto.dump_certificate(crypto.FILETYPE_PEM, 
@@ -204,6 +210,20 @@
                                days_valid=13*365,
                                subjectAltName=['DNS:localhost'])
 
+    # server certificate with OCSP responder URL
+    ocspcert = create_cert(subjectkey=serverkey,
+                           certfile='serfserver_san_ocsp_cert.pem',
+                           issuer=cacert, issuerkey=cakey,
+                           country='BE', state='Antwerp', city='Mechelen',
+                           org='In Serf we trust, Inc.',
+                           ou='Test Suite Server',
+                           cn='localhost',
+                           email='serfserver@example.com',
+                           days_valid=13*365,
+                           subjectAltName=['DNS:localhost'],
+                           ocsp_responder_url='http://localhost:17080')
+
+
     # client key pair and certificate
     clientkey = create_key('private/serfclientkey.pem', 'serftest')
 
diff --git a/test/certs/serfserver_san_ocsp_cert.pem b/test/certs/serfserver_san_ocsp_cert.pem
new file mode 100644
index 0000000..28970e9
--- /dev/null
+++ b/test/certs/serfserver_san_ocsp_cert.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIDAYa0MA0GCSqGSIb3DQEBCwUAMIGgMQswCQYDVQQGEwJC
+RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
+FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
+EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
+LmNvbTAeFw0xNjEyMDkwNTIzMDlaFw0yOTEyMDYwNTIzMDlaMIGqMQswCQYDVQQG
+EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
+BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
+U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QxJTAjBgkqhkiG9w0BCQEWFnNlcmZz
+ZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDXzZBsDTAl0j5MKCElC3LtmOMv+PH1CRItKIHXEA//dIJbtr6RAxwgJmD8sY8K
+89kkLXdAnmWDlYb95no6sONB1xNFjeNf/lUnxuU5P5VUzpCAkWj9BzIcvOmkTKuJ
+aOH7u1TQRDJxyds/r2lrsJ1Fv63JK+I3mdAKINElqKxHCy4D18FP5g+slndvYPWs
+47mpSqt0on6pplmLWJjDqzdDPQJU5YYSFHKvHEenk7finjh/qkB+q941FGeoNjNv
+nB0fP6BmzMwg2Zvwi4xELic3CDY4jdXfSb0RZo/WVJr2N4Ivi70IiC6zFkvjV6Yn
+tLOftmpakMi/eOSQpqqRGChfAgMBAAGjTDBKMBQGA1UdEQQNMAuCCWxvY2FsaG9z
+dDAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9sb2NhbGhvc3Q6
+MTcwODAwDQYJKoZIhvcNAQELBQADggEBAJoCiAuiVEiuvDSQ7JS9lodlyYlGKedq
+UMnTTTKULKBqO4MMT8gXgGzlQukZZQj1T5IvgLUa3vt40m6wjWDSD9q4gIS3m2vO
+dHuUbBePqn9iNqKh4i++288WCmIr60IdvSp98ureWUyjilEXBp7ZqlNkGehmBqi7
+HuOag3bpejCKjKK1rw5UfNlJ94gzWnDoJsfrGs4ZwVGF2xZKPXVDQXJsQV5gsxa/
+UXkumapzUFj/RnjwKYRydTPZCKUQdY8CzlQG6uba1iKeuq12P9zGZi9FiP0Ahova
+SSBQMjk5JThVE/7OsJReA9BPTFt2lMq88fCu9muiRoBvzTs9q96zq6o=
+-----END CERTIFICATE-----
diff --git a/test/test_ssl.c b/test/test_ssl.c
index 717aeca..105f0dd 100644
--- a/test/test_ssl.c
+++ b/test/test_ssl.c
@@ -168,29 +168,38 @@
     apr_hash_t *kv;
     serf_ssl_certificate_t *cert = NULL;
     apr_array_header_t *san_arr;
+    apr_array_header_t *ocsp_arr;
     apr_status_t status;
 
 
-    status = serf_ssl_load_cert_file(&cert,
-                                     get_srcdir_file(tb->pool,
-                                                     "test/serftestca.pem"),
-                                     tb->pool);
+    status = serf_ssl_load_cert_file(
+        &cert,
+        get_srcdir_file(tb->pool, "test/certs/serfserver_san_ocsp_cert.pem"),
+        tb->pool);
     CuAssertIntEquals(tc, APR_SUCCESS, status);
     CuAssertPtrNotNull(tc, cert);
 
     kv = serf_ssl_cert_certificate(cert, tb->pool);
     CuAssertPtrNotNull(tc, kv);
 
-    CuAssertStrEquals(tc, "8A:4C:19:D5:F2:52:4E:35:49:5E:7A:14:80:B2:02:BD:B4:4D:22:18",
+    CuAssertStrEquals(tc, "3D:EC:C8:3B:C7:DB:FD:FB:9C:5D:5E:29:9F:ED:C1:A8:79:3B:28:14",
                       apr_hash_get(kv, "sha1", APR_HASH_KEY_STRING));
-    CuAssertStrEquals(tc, "Mar 21 13:18:17 2008 GMT",
+    CuAssertStrEquals(tc, "Dec  9 05:23:09 2016 GMT",
                       apr_hash_get(kv, "notBefore", APR_HASH_KEY_STRING));
-    CuAssertStrEquals(tc, "Mar 21 13:18:17 2011 GMT",
+    CuAssertStrEquals(tc, "Dec  6 05:23:09 2029 GMT",
                       apr_hash_get(kv, "notAfter", APR_HASH_KEY_STRING));
 
-    /* TODO: create a new test certificate with a/some sAN's. */
     san_arr = apr_hash_get(kv, "subjectAltName", APR_HASH_KEY_STRING);
-    CuAssertTrue(tc, san_arr == NULL);
+    CuAssertPtrNotNull(tc, san_arr);
+    CuAssertIntEquals(tc, 1, san_arr->nelts);
+    CuAssertStrEquals(tc, "localhost",
+                      APR_ARRAY_IDX(san_arr, 0, const char*));
+
+    ocsp_arr = apr_hash_get(kv, "OCSP", APR_HASH_KEY_STRING);
+    CuAssertPtrNotNull(tc, ocsp_arr);
+    CuAssertIntEquals(tc, 1, ocsp_arr->nelts);
+    CuAssertStrEquals(tc, "http://localhost:17080",
+                      APR_ARRAY_IDX(ocsp_arr, 0, const char*));
 }
 
 static const char *extract_cert_from_pem(const char *pemdata,
@@ -252,15 +261,38 @@
         return NULL;
 }
 
+static const char* load_cert_file_der(CuTest *tc,
+                                      const char *path,
+                                      apr_pool_t *pool)
+{
+    apr_file_t *fp;
+    apr_finfo_t file_info;
+    char *pembuf;
+    apr_size_t pemlen;
+    apr_status_t status;
+
+    status = apr_file_open(&fp, path,
+                           APR_FOPEN_READ | APR_FOPEN_BINARY,
+                           APR_FPROT_OS_DEFAULT, pool);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+    status = apr_file_info_get(&file_info, APR_FINFO_SIZE, fp);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+    pembuf = apr_palloc(pool, file_info.size + 1);
+
+    status = apr_file_read_full(fp, pembuf, file_info.size, &pemlen);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+    pembuf[file_info.size] = '\0';
+
+    return extract_cert_from_pem(pembuf, pool);
+}
+
 static void test_ssl_cert_export(CuTest *tc)
 {
     test_baton_t *tb = tc->testBaton;
     serf_ssl_certificate_t *cert = NULL;
-    apr_file_t *fp;
-    apr_finfo_t file_info;
+    const char *extractedbuf;
     const char *base64derbuf;
-    char *pembuf;
-    apr_size_t pemlen;
     apr_status_t status;
 
 
@@ -273,25 +305,43 @@
 
     /* A .pem file contains a Base64 encoded DER certificate, which is exactly
        what serf_ssl_cert_export is supposed to be returning. */
-    status = apr_file_open(&fp,
-                           get_srcdir_file(tb->pool, "test/serftestca.pem"),
-                           APR_FOPEN_READ | APR_FOPEN_BINARY,
-                           APR_FPROT_OS_DEFAULT, tb->pool);
-    CuAssertIntEquals(tc, APR_SUCCESS, status);
-
-    status = apr_file_info_get(&file_info, APR_FINFO_SIZE, fp);
-    CuAssertIntEquals(tc, APR_SUCCESS, status);
-    pembuf = apr_palloc(tb->pool, file_info.size + 1);
-
-    status = apr_file_read_full(fp, pembuf, file_info.size, &pemlen);
-    CuAssertIntEquals(tc, APR_SUCCESS, status);
-    pembuf[file_info.size] = '\0';
-
+    extractedbuf = load_cert_file_der(tc,
+                                      get_srcdir_file(tb->pool,
+                                                      "test/serftestca.pem"),
+                                      tb->pool);
     base64derbuf = serf_ssl_cert_export(cert, tb->pool);
 
-    CuAssertStrEquals(tc,
-                      extract_cert_from_pem(pembuf, tb->pool),
-                      base64derbuf);
+    CuAssertStrEquals(tc, extractedbuf, base64derbuf);
+}
+
+static void test_ssl_cert_import(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_ssl_certificate_t *cert = NULL;
+    serf_ssl_certificate_t *imported_cert = NULL;
+    const char *extractedbuf;
+    const char *base64derbuf;
+    apr_status_t status;
+
+    status = serf_ssl_load_cert_file(&cert,
+                                     get_srcdir_file(tb->pool,
+                                                     "test/serftestca.pem"),
+                                     tb->pool);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+    CuAssertPtrNotNull(tc, cert);
+
+    /* A .pem file contains a Base64 encoded DER certificate, which is exactly
+       what serf_ssl_cert_import expects as input. */
+    extractedbuf = load_cert_file_der(tc,
+                                      get_srcdir_file(tb->pool,
+                                                      "test/serftestca.pem"),
+                                      tb->pool);
+
+    imported_cert = serf_ssl_cert_import(extractedbuf, tb->pool, tb->pool);
+    CuAssertPtrNotNull(tc, imported_cert);
+
+    base64derbuf = serf_ssl_cert_export2(imported_cert, tb->pool, tb->pool);
+    CuAssertStrEquals(tc, extractedbuf, base64derbuf);
 }
 
 /*****************************************************************************
@@ -2265,6 +2315,7 @@
     SUITE_ADD_TEST(suite, test_ssl_cert_issuer);
     SUITE_ADD_TEST(suite, test_ssl_cert_certificate);
     SUITE_ADD_TEST(suite, test_ssl_cert_export);
+    SUITE_ADD_TEST(suite, test_ssl_cert_import);
     SUITE_ADD_TEST(suite, test_ssl_handshake);
     SUITE_ADD_TEST(suite, test_ssl_handshake_nosslv2);
     SUITE_ADD_TEST(suite, test_ssl_trust_rootca);