On the ocsp-verification branch: sync with trunk up to r1828523.

git-svn-id: https://svn.apache.org/repos/asf/serf/branches/ocsp-verification@1828524 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/BRANCH-README b/BRANCH-README
new file mode 100644
index 0000000..16de82a
--- /dev/null
+++ b/BRANCH-README
@@ -0,0 +1,245 @@
+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 certificate. 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.
+    */
+   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 2560, 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 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 OCSP response in DER form, @a ocsp_response,
+    * of size @a ocsp_response_size, and construct an internal
+    * representation, allocated from @a result_pool.
+    *
+    * If @a failures is not @c NULL, it will be set as in
+    * #serf_ssl_need_server_cert_t.
+    *
+    * Use @a scratch_pool for temporary allocations.
+    *
+    * Returns @c NULL if on failure.
+    */
+   serf_ssl_ocsp_response_t *serf_ssl_ocsp_response_parse(
+       const void *ocsp_response,
+       apr_size_t ocsp_response_size,
+       int *failures,
+       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 in the provided @a ssl_ctx, as defined by
+    * the algorithm documented in RFC 2560, section 3.5.
+    *
+    * The OCSP responder and application wall clocks may be out of sync
+    * by @a clock_skew, and @a max_age is the acceptable age of the
+    * request. Their values are truncated to the nearest second.
+    *
+    * 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 and @a next_update 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(
+       serf_ssl_context_t *ssl_ctx,
+       const serf_ssl_ocsp_response_t *ocsp_response,
+       const serf_ssl_ocsp_request_t *ocsp_request,
+       apr_time_t clock_skew,
+       apr_time_t max_age,
+       apr_time_t *this_update,
+       apr_time_t *next_update,
+       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 431315e..2a3b586 100644
--- a/buckets/ssl_buckets.c
+++ b/buckets/ssl_buckets.c
@@ -589,7 +589,30 @@
 #endif
 }
 
-#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
+#ifndef OPENSSL_NO_OCSP
+static int ocsp_response_status(int failures, OCSP_RESPONSE *response)
+{
+    long resp_status = OCSP_response_status(response);
+    switch (resp_status) {
+        case OCSP_RESPONSE_STATUS_SUCCESSFUL:
+            break;
+        case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST:
+        case OCSP_RESPONSE_STATUS_INTERNALERROR:
+        case OCSP_RESPONSE_STATUS_SIGREQUIRED:
+        case OCSP_RESPONSE_STATUS_UNAUTHORIZED:
+            failures |= SERF_SSL_OCSP_RESPONDER_ERROR;
+            break;
+        case OCSP_RESPONSE_STATUS_TRYLATER:
+            failures |= SERF_SSL_OCSP_RESPONDER_TRYLATER;
+            break;
+        default:
+            failures |= SERF_SSL_OCSP_RESPONDER_UNKNOWN_FAILURE;
+            break;
+    }
+    return failures;
+}
+
+#  ifndef OPENSSL_NO_TLSEXT
 /* Callback called when the server response has some OCSP info.
    Returns 1 if the application accepts the OCSP response as successful,
            0 in case of error.
@@ -600,7 +623,6 @@
     OCSP_RESPONSE *response;
     const unsigned char *resp_der;
     int len;
-    long resp_status;
     int failures = 0;
     int cert_valid = 0;
 
@@ -620,23 +642,7 @@
     }
 
     /* Did the server get a valid response from the OCSP responder */
-    resp_status = OCSP_response_status(response);
-    switch (resp_status) {
-        case OCSP_RESPONSE_STATUS_SUCCESSFUL:
-            break;
-        case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST:
-        case OCSP_RESPONSE_STATUS_INTERNALERROR:
-        case OCSP_RESPONSE_STATUS_SIGREQUIRED:
-        case OCSP_RESPONSE_STATUS_UNAUTHORIZED:
-            failures |= SERF_SSL_OCSP_RESPONDER_ERROR;
-            break;
-        case OCSP_RESPONSE_STATUS_TRYLATER:
-            failures |= SERF_SSL_OCSP_RESPONDER_TRYLATER;
-            break;
-        default:
-            failures |= SERF_SSL_OCSP_RESPONDER_UNKNOWN_FAILURE;
-            break;
-    }
+    failures = ocsp_response_status(failures, response);
 
     /* TODO: check certificate status */
 
@@ -672,7 +678,8 @@
 
     return cert_valid;
 }
-#endif  /* OPENSSL_NO_TLSEXT && OPENSSL_NO_OCSP */
+#  endif  /* OPENSSL_NO_TLSEXT */
+#endif    /* OPENSSL_NO_OCSP */
 
 typedef enum san_copy_t {
     EscapeNulAndCopy = 0,
@@ -735,6 +742,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];
@@ -2280,6 +2323,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)) {
@@ -2328,6 +2372,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;
 }
 
@@ -2336,6 +2384,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;
@@ -2347,19 +2403,50 @@
         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;
+    }
+
+    cert = apr_palloc(result_pool, sizeof(serf_ssl_certificate_t));
+    cert->ssl_cert = ssl_cert;
+
+    apr_pool_cleanup_register(result_pool, ssl_cert, free_ssl_cert,
+                              apr_pool_cleanup_null);
+
+    return cert;
+}
+
+
 /* Disables compression for all SSL sessions. */
 static void disable_compression(serf_ssl_context_t *ssl_ctx)
 {
@@ -2538,3 +2625,432 @@
     serf_default_get_remaining,
     serf_ssl_set_config,
 };
+
+
+/*
+ * OCSP bits are here because they depend on OpenSSL and private types
+ * defined in this file.
+ */
+
+#ifndef OPENSSL_NO_OCSP
+struct serf_ssl_ocsp_request_t {
+    /* OpenSSL's internal representation of the OCSP request. */
+    OCSP_REQUEST *request;
+
+    /* The certificate ID of the request. */
+    OCSP_CERTID *cert_id;
+
+    /* DER-encoded request and size. */
+    const void *der_request;
+    apr_size_t der_request_size;
+};
+
+static apr_status_t free_ocsp_request(void *data)
+{
+    OCSP_REQUEST_free(data);
+    return APR_SUCCESS;
+}
+
+static apr_status_t free_ocsp_cert_id(void *data)
+{
+    OCSP_CERTID_free(data);
+    return APR_SUCCESS;
+}
+#endif  /* OPENSSL_NO_OCSP */
+
+
+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)
+{
+#ifndef OPENSSL_NO_OCSP
+    X509 *const cert = server_cert->ssl_cert;
+    X509 *const issuer = issuer_cert->ssl_cert;
+
+    serf_ssl_ocsp_request_t *req = NULL;
+    OCSP_REQUEST *ocsp_req = NULL;
+    OCSP_CERTID *cert_id = NULL;
+    unsigned char *unused;
+    void *der;
+    int len;
+
+    if (X509_V_OK != X509_check_issued(issuer, cert))
+        goto cleanup;
+
+    /* TODO: Support other hash algorithms besides the default SHA1. */
+    cert_id = OCSP_cert_to_id(NULL, cert, issuer);
+    if (!cert_id)
+        goto cleanup;
+
+    ocsp_req = OCSP_REQUEST_new();
+    if (!ocsp_req)
+        goto cleanup;
+
+    if (!OCSP_request_add0_id(ocsp_req, cert_id))
+        goto cleanup;
+
+    /* Generate another ID to put into the result struct.
+       TODO: see above re hash algorithms. */
+    cert_id = OCSP_cert_to_id(NULL, cert, issuer);
+    if (!cert_id)
+        goto cleanup;
+
+    if (generate_nonce) {
+        /* Generates a random nonce, using the internal random generator. */
+        if (!OCSP_request_add1_nonce(ocsp_req, NULL, -1))
+            goto cleanup;
+    }
+
+    /* Generate the DER form of the request. */
+    len = i2d_OCSP_REQUEST(ocsp_req, NULL);
+    if (len < 0)
+        goto cleanup;
+
+    unused = der = apr_palloc(result_pool, len);
+    len = i2d_OCSP_REQUEST(ocsp_req, &unused); /* unused is incremented */
+    if (len < 0)
+        goto cleanup;
+
+    req = apr_palloc(result_pool, sizeof(*req));
+    req->der_request = der;
+    req->der_request_size = len;
+
+    req->request = ocsp_req;
+    apr_pool_cleanup_register(result_pool, ocsp_req,
+                              free_ocsp_request,
+                              apr_pool_cleanup_null);
+    ocsp_req = NULL;
+
+    req->cert_id = cert_id;
+    apr_pool_cleanup_register(result_pool, cert_id,
+                              free_ocsp_cert_id,
+                              apr_pool_cleanup_null);
+    cert_id = NULL;
+
+  cleanup:
+    if (ocsp_req)
+        OCSP_REQUEST_free(ocsp_req);
+    if (cert_id)
+        OCSP_CERTID_free(cert_id);
+    return req;
+#else
+    return NULL;
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
+const void *serf_ssl_ocsp_request_body(
+    const serf_ssl_ocsp_request_t *ocsp_request)
+{
+#ifndef OPENSSL_NO_OCSP
+    return ocsp_request->der_request;
+#else
+    return NULL;
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+apr_size_t serf_ssl_ocsp_request_body_size(
+    const serf_ssl_ocsp_request_t *ocsp_request)
+{
+#ifndef OPENSSL_NO_OCSP
+    return ocsp_request->der_request_size;
+#else
+    return 0;
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
+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)
+{
+#ifndef OPENSSL_NO_OCSP
+
+    /*
+      The structure of the exported request is:
+
+        "Base64-DER-formatted-request" "\x1"
+        "Base64-DER-formatted-cert-id" "\0"
+    */
+
+    OCSP_CERTID *const cert_id = ocsp_request->cert_id;
+    int id_len, req_size, id_size;
+    unsigned char *unused;
+    char *buffer = NULL;
+    char *p;
+    void *id_der;
+
+    /* Generate the DER form of the certificate ID. */
+    id_len = i2d_OCSP_CERTID(cert_id, NULL);
+    if (id_len < 0)
+        return NULL;
+
+    unused = id_der = apr_palloc(scratch_pool, id_len);
+    id_len = i2d_OCSP_CERTID(cert_id, &unused); /* unused is incremented */
+    if (id_len < 0)
+        return NULL;
+
+    req_size = apr_base64_encode_len(ocsp_request->der_request_size);
+    id_size = apr_base64_encode_len(id_len);
+
+    buffer = apr_palloc(result_pool, req_size + id_size + 2);
+    req_size = apr_base64_encode(buffer, ocsp_request->der_request,
+                                 ocsp_request->der_request_size);
+    p = buffer + req_size - 1;  /* The trailing \0 is part of the size! */
+    *p++ = '\x1';
+    apr_base64_encode(p, id_der, id_len);
+
+    return buffer;
+#else
+    return NULL;
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
+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)
+{
+#ifndef OPENSSL_NO_OCSP
+    serf_ssl_ocsp_request_t *req = NULL;
+    const char *end_request = strchr(encoded_ocsp_request, '\x1');
+
+    if (end_request) {
+        const char *base64_id = end_request + 1;
+        const char *base64_request = apr_pstrmemdup(
+            scratch_pool, encoded_ocsp_request,
+            end_request - encoded_ocsp_request);
+        long der_request_size = apr_base64_decode_len(base64_request);
+        long der_id_size = apr_base64_decode_len(base64_id);
+
+        OCSP_REQUEST *ocsp_req;
+        OCSP_CERTID *cert_id;
+        const unsigned char *unused;
+        void *der_request;
+        void *der_id;
+
+        unused = der_request = apr_palloc(result_pool, der_request_size);
+        der_request_size = apr_base64_decode(der_request, base64_request);
+        ocsp_req = d2i_OCSP_REQUEST(NULL, &unused, der_request_size);
+        if (!ocsp_req)
+            return NULL;
+
+        unused = der_id = apr_palloc(scratch_pool, der_id_size);
+        der_id_size = apr_base64_decode(der_id, base64_id);
+        cert_id = d2i_OCSP_CERTID(NULL, &unused, der_id_size);
+        if (!cert_id)
+            return NULL;
+
+        req = apr_palloc(result_pool, sizeof(*req));
+        req->der_request = der_request;
+        req->der_request_size = der_request_size;
+
+        req->request = ocsp_req;
+        apr_pool_cleanup_register(result_pool, ocsp_req,
+                                  free_ocsp_request,
+                                  apr_pool_cleanup_null);
+
+        req->cert_id = cert_id;
+        apr_pool_cleanup_register(result_pool, cert_id,
+                                  free_ocsp_cert_id,
+                                  apr_pool_cleanup_null);
+    }
+
+    return req;
+#else
+    return NULL;
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
+#ifndef OPENSSL_NO_OCSP
+struct serf_ssl_ocsp_response_t {
+    /* OpenSSL's internal representation of the OCSP response. */
+    OCSP_RESPONSE *response;
+};
+
+static apr_status_t free_ocsp_response(void *data)
+{
+    OCSP_RESPONSE_free(data);
+    return APR_SUCCESS;
+}
+#endif  /* OPENSSL_NO_OCSP */
+
+
+serf_ssl_ocsp_response_t *serf_ssl_ocsp_response_parse(
+    const void *ocsp_response,
+    apr_size_t ocsp_response_size,
+    int *failures,
+    apr_pool_t *result_pool,
+    apr_pool_t *scratch_pool)
+{
+#ifndef OPENSSL_NO_OCSP
+    serf_ssl_ocsp_response_t *rsp = NULL;
+    OCSP_RESPONSE *ocsp_rsp = NULL;
+    const unsigned char *unused;
+    int rsp_failures = 0;
+
+    unused = ocsp_response;
+    ocsp_rsp = d2i_OCSP_RESPONSE(NULL, &unused, ocsp_response_size);
+    if (!ocsp_rsp)
+        goto cleanup;
+
+    rsp_failures = ocsp_response_status(rsp_failures, ocsp_rsp);
+    if (rsp_failures)
+        goto cleanup;
+
+    rsp = apr_palloc(result_pool, sizeof(*rsp));
+    rsp->response = ocsp_rsp;
+    apr_pool_cleanup_register(result_pool, ocsp_rsp,
+                              free_ocsp_response,
+                              apr_pool_cleanup_null);
+    ocsp_rsp = NULL;
+
+  cleanup:
+    if (failures)
+        *failures = rsp_failures;
+    if (ocsp_rsp)
+        OCSP_RESPONSE_free(ocsp_rsp);
+    return rsp;
+#else
+    return NULL;
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
+#ifndef OPENSSL_NO_OCSP
+/* Ripped from Subversion and well kneaded. */
+static apr_status_t
+convert_asn1_generalized_time(ASN1_GENERALIZEDTIME *asn1_time,
+                              apr_time_t *humane_time,
+                              apr_pool_t *scratch_pool,
+                              apr_status_t parse_error)
+{
+    apr_time_exp_t xt = { 0 };
+    void *data;
+    char *date;
+    int len;
+    char tz;
+
+    if (ASN1_STRING_type(asn1_time) != V_ASN1_GENERALIZEDTIME)
+        return 0;
+
+    len = ASN1_STRING_length(asn1_time);
+    data = ASN1_STRING_data(asn1_time);
+    date = apr_pstrndup(scratch_pool, data, len);
+
+    if (6 > sscanf(date, "%4d%2d%2d%2d%2d%2d%c",
+                   &xt.tm_year, &xt.tm_mon, &xt.tm_mday,
+                   &xt.tm_hour, &xt.tm_min, &xt.tm_sec, &tz))
+        return parse_error;
+
+    /* GeneralizedTime has the full 4 digit year.  But apr_time_exp_t
+       wants years as the number of years since 1900. */
+    xt.tm_year -= 1900;
+
+    /* Check that the timezone is GMT.
+       ASN.1 allows for the timezone to be specified but X.509 says it
+       must always be GMT.  A little bit of extra paranoia here seems
+       like a good idea. */
+    if (tz != 'Z')
+        return parse_error;
+
+    /* apr_time_exp_t expects months to be zero indexed, 0=Jan, 11=Dec. */
+    xt.tm_mon -= 1;
+
+    return apr_time_exp_gmt_get(humane_time, &xt);
+}
+#endif  /* OPENSSL_NO_OCSP */
+
+apr_status_t serf_ssl_ocsp_response_verify(
+    serf_ssl_context_t *ssl_ctx,
+    const serf_ssl_ocsp_response_t *ocsp_response,
+    const serf_ssl_ocsp_request_t *ocsp_request,
+    apr_time_t clock_skew,
+    apr_time_t max_age,
+    apr_time_t *this_update,
+    apr_time_t *next_update,
+    apr_pool_t *scratch_pool)
+{
+#ifndef OPENSSL_NO_OCSP
+    OCSP_BASICRESP *ocsp_basic = NULL;
+    apr_status_t status = SERF_ERROR_SSL_OCSP_RESPONSE_INVALID;
+    ASN1_GENERALIZEDTIME *asn1_revoked_at;
+    ASN1_GENERALIZEDTIME *asn1_this_update;
+    ASN1_GENERALIZEDTIME *asn1_next_update;
+    int cert_status, cert_reason;
+    X509_STORE *store;
+
+    ocsp_basic = OCSP_response_get1_basic(ocsp_response->response);
+    if (!ocsp_basic)
+        goto cleanup;
+
+    if (0 >= OCSP_check_nonce(ocsp_request->request, ocsp_basic))
+        goto cleanup;
+
+    store = SSL_CTX_get_cert_store(ssl_ctx->ctx);
+    if (0 >= OCSP_basic_verify(ocsp_basic, NULL, store, 0))
+        goto cleanup;
+
+    if (!OCSP_resp_find_status(ocsp_basic, ocsp_request->cert_id,
+                               &cert_status, &cert_reason,
+                               &asn1_revoked_at,
+                               &asn1_this_update,
+                               &asn1_next_update))
+        goto cleanup;
+
+    if (!OCSP_check_validity(asn1_this_update, asn1_next_update,
+                             (long)apr_time_sec(clock_skew),
+                             (long)apr_time_sec(max_age)))
+      goto cleanup;
+
+    if (this_update) {
+        if (asn1_this_update) {
+            status = convert_asn1_generalized_time(asn1_this_update, this_update,
+                                                   scratch_pool, status);
+            if (status)
+                goto cleanup;
+        }
+        else
+            *this_update = APR_TIME_C(0);
+    }
+
+    if (next_update) {
+        if (asn1_next_update) {
+            status = convert_asn1_generalized_time(asn1_next_update, next_update,
+                                                   scratch_pool, status);
+            if (status)
+                goto cleanup;
+        }
+        else
+            *next_update = APR_TIME_C(0);
+    }
+
+    switch (cert_status)
+    {
+    case V_OCSP_CERTSTATUS_REVOKED:
+        status = SERF_ERROR_SSL_OCSP_RESPONSE_CERT_REVOKED;
+        break;
+
+    case V_OCSP_CERTSTATUS_UNKNOWN:
+        status = SERF_ERROR_SSL_OCSP_RESPONSE_CERT_UNKNOWN;
+        break;
+
+    case V_OCSP_CERTSTATUS_GOOD:
+    default:
+        status = APR_SUCCESS;
+    }
+
+  cleanup:
+    if (ocsp_basic)
+        OCSP_BASICRESP_free(ocsp_basic);
+    return status;
+#else
+    return SERF_ERROR_SSL_OCSP_RESPONSE_INVALID;
+#endif  /* OPENSSL_NO_OCSP */
+}
diff --git a/serf.h b/serf.h
index 9a504a4..164632f 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 fdb1534..ffa3fa7 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,140 @@
 
 /* ==================================================================== */
 
+/**
+ * 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.
+ */
+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 2560, 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 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 OCSP response in DER form, @a ocsp_response,
+ * of size @a ocsp_response_size, and construct an internal
+ * representation, allocated from @a result_pool.
+ *
+ * If @a failures is not @c NULL, it will be set as in
+ * #serf_ssl_need_server_cert_t.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * Returns @c NULL on failure.
+ */
+serf_ssl_ocsp_response_t *serf_ssl_ocsp_response_parse(
+    const void *ocsp_response,
+    apr_size_t ocsp_response_size,
+    int *failures,
+    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 in the provided @a ssl_ctx, as defined by
+ * the algorithm documented in RFC 2560, section 3.5.
+ *
+ * The OCSP responder and application wall clocks may be out of sync
+ * by @a clock_skew, and @a max_age is the acceptable age of the
+ * request. Their values are truncated to the nearest second.
+ *
+ * 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 and @a next_update 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(
+    serf_ssl_context_t *ssl_ctx,
+    const serf_ssl_ocsp_response_t *ocsp_response,
+    const serf_ssl_ocsp_request_t *ocsp_request,
+    apr_time_t clock_skew,
+    apr_time_t max_age,
+    apr_time_t *this_update,
+    apr_time_t *next_update,
+    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 66426f4..072ebdc 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);
 }
 
 /*****************************************************************************
@@ -2259,6 +2309,99 @@
 #endif /* OPENSSL_NO_TLSEXT */
 }
 
+
+#ifndef OPENSSL_NO_OCSP
+static void load_ocsp_test_certs(CuTest *tc,
+                                 serf_ssl_certificate_t **cert,
+                                 serf_ssl_certificate_t **issuer)
+{
+    test_baton_t *tb = tc->testBaton;
+    apr_status_t status;
+
+    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);
+
+    status = serf_ssl_load_cert_file(
+        issuer,
+        get_srcdir_file(tb->pool, "test/certs/serfcacert.pem"),
+        tb->pool);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+    CuAssertPtrNotNull(tc, *issuer);
+}
+#endif  /* OPENSSL_NO_OCSP */
+
+static void test_ssl_ocsp_request_create(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+    test_baton_t *tb = tc->testBaton;
+    serf_ssl_certificate_t *cert = NULL;
+    serf_ssl_certificate_t *issuer = NULL;
+    serf_ssl_ocsp_request_t *req = NULL;
+
+    load_ocsp_test_certs(tc, &cert, &issuer);
+
+    /* no nonce */
+    req = serf_ssl_ocsp_request_create(cert, issuer, 0, tb->pool, tb->pool);
+    CuAssertPtrNotNull(tc, req);
+
+    /* add nonce */
+    req = serf_ssl_ocsp_request_create(cert, issuer, 1, tb->pool, tb->pool);
+    CuAssertPtrNotNull(tc, req);
+
+    /* certs switched */
+    req = serf_ssl_ocsp_request_create(issuer, cert, 0, tb->pool, tb->pool);
+    CuAssertPtrEquals(tc, NULL, req);
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
+static void test_ssl_ocsp_request_export_import(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+    test_baton_t *tb = tc->testBaton;
+    serf_ssl_certificate_t *cert = NULL;
+    serf_ssl_certificate_t *issuer = NULL;
+    serf_ssl_ocsp_request_t *req = NULL;
+    serf_ssl_ocsp_request_t *impreq = NULL;
+    const char *expreq = NULL;
+
+    load_ocsp_test_certs(tc, &cert, &issuer);
+
+    impreq = serf_ssl_ocsp_request_import("foo", tb->pool, tb->pool);
+    CuAssertPtrEquals(tc, NULL, impreq);
+
+    impreq = serf_ssl_ocsp_request_import("foo" "\x1" "bar", tb->pool, tb->pool);
+    CuAssertPtrEquals(tc, NULL, impreq);
+
+    impreq = serf_ssl_ocsp_request_import("foo" "\x1" "bar" "\x1" "baz", tb->pool, tb->pool);
+    CuAssertPtrEquals(tc, NULL, impreq);
+
+    req = serf_ssl_ocsp_request_create(cert, issuer, 0, tb->pool, tb->pool);
+    CuAssertPtrNotNull(tc, req);
+    CuAssertPtrNotNull(tc, serf_ssl_ocsp_request_body(req));
+    CuAssertTrue(tc, 0 < serf_ssl_ocsp_request_body_size(req));
+
+    expreq = serf_ssl_ocsp_request_export(req, tb->pool, tb->pool);
+    CuAssertPtrNotNull(tc, expreq);
+
+    impreq = serf_ssl_ocsp_request_import(expreq, tb->pool, tb->pool);
+    CuAssertPtrNotNull(tc, impreq);
+
+    CuAssertIntEquals(tc,
+                      serf_ssl_ocsp_request_body_size(req),
+                      serf_ssl_ocsp_request_body_size(impreq));
+    CuAssertTrue(tc,
+                 0 == memcmp(serf_ssl_ocsp_request_body(req),
+                             serf_ssl_ocsp_request_body(impreq),
+                             serf_ssl_ocsp_request_body_size(req)));
+#endif  /* OPENSSL_NO_OCSP */
+}
+
+
 CuSuite *test_ssl(void)
 {
     CuSuite *suite = CuSuiteNew();
@@ -2271,6 +2414,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);
@@ -2304,6 +2448,7 @@
     SUITE_ADD_TEST(suite, test_ssl_server_cert_with_san_and_empty_cb);
     SUITE_ADD_TEST(suite, test_ssl_renegotiate);
     SUITE_ADD_TEST(suite, test_ssl_alpn_negotiate);
-
+    SUITE_ADD_TEST(suite, test_ssl_ocsp_request_create);
+    SUITE_ADD_TEST(suite, test_ssl_ocsp_request_export_import);
     return suite;
 }