On the ocsp-verification branch: Sync with trunk up to r1830693.
git-svn-id: https://svn.apache.org/repos/asf/serf/branches/ocsp-verification@1830694 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 be51021..fdf880f 100644
--- a/buckets/ssl_buckets.c
+++ b/buckets/ssl_buckets.c
@@ -601,7 +601,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.
@@ -612,7 +635,6 @@
OCSP_RESPONSE *response;
const unsigned char *resp_der;
int len;
- long resp_status;
int failures = 0;
int cert_valid = 0;
@@ -632,23 +654,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 */
@@ -684,7 +690,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,
@@ -747,6 +754,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];
@@ -2298,6 +2341,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)) {
@@ -2346,6 +2390,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;
}
@@ -2354,6 +2402,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;
@@ -2365,19 +2421,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)
{
@@ -2556,3 +2643,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..5f93fb5 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, ocsp_signer=False):
'''
Create a X509 signed certificate.
@@ -130,6 +131,16 @@
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)])
+
+ if ocsp_signer:
+ cert.add_extensions([
+ crypto.X509Extension('extendedKeyUsage', True, 'OCSPSigning')
+ ])
+
cert.sign(issuerkey, SIGN_ALGO)
open(certfile, "wt").write(crypto.dump_certificate(crypto.FILETYPE_PEM,
@@ -204,6 +215,31 @@
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')
+
+ # OCSP responder certifi
+ ocsprspcert = create_cert(subjectkey=serverkey,
+ certfile='serfocspresponder.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,
+ ocsp_signer=True)
+
# client key pair and certificate
clientkey = create_key('private/serfclientkey.pem', 'serftest')
diff --git a/test/certs/private/serfcakey.pem b/test/certs/private/serfcakey.pem
index 10a0f05..f5d5223 100644
--- a/test/certs/private/serfcakey.pem
+++ b/test/certs/private/serfcakey.pem
@@ -1,30 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
-MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQILLzXORRRKjsCAggA
-MBQGCCqGSIb3DQMHBAh+8siMoYRrLQSCBMj0z6sOqdQ0cx0fHLiKj23bIV598/K1
-dFEZXiYP3jiDly2m81DJEghCr07NTKXqyFYE0nkFKRA38Q1hVc3nBHtSjmDQSc8v
-Vtgm55ygB1S1ww5QfYtmcxQeydJwlbKZnHJnS+hOdstVbxfJZ4ksaaBs8IIcY1f6
-AbfY+3N7SW2AvIWwtLsqBLsAURY6zhtfgSmDFqRYxAShNZs1PmhbY3gg1IMx2GQK
-SSxlJTDEjRK8I5dJ+++VrGjJzf7SltFH/Cm5OFdpKQS/y2hZmD+hwnoUz0oVCsPJ
-z4YehDaFygNIcfcWBqUmz6dwhcE1Khb5Vc5fq+BTH3VbNnjlpcLb7MCLuRK21kew
-Nf2DQeM083Wg9pyREm8UzSYeCODcdwP/Pz5uO5SBMWpYSZI0i2NUyeSO6Y0Kgs62
-l6DJAqc3gRHHQun5Ynh5kGLZ3Pg3QTOj10H4sC2tehfknz8T+9try02fjERYW5c6
-2FKUsizVQhxHEL4tobpoUZpDAJOrqoF+3CFXj0oe4gDjIboK1ihCWt/gZEJ43LEg
-ZZnLRzOk3lubAoeeQevWXXJlyHQgXpFgyAzO6qBs3JI5XbIi4gcpdNrK7ebfVieZ
-69fGyGAZk2cKnH+aLAfruxQoLOccY8LvJDzjDwet+o8PfUgsru0g5x58TuUqzFSs
-RV6Wo8ufQo5NGo1MiS9BpT/T2ouWtXUQlnmXdCzk4SBKbFVDX3ENxTXUCVOgMsO/
-g22dre9Q/8ZvVV9t0NQTdTe8YvXmWEUnBKY4E+ABnF9A224t3Qme11+5hcsLv0g9
-zVdZJm9CFVqKseDvBnE0la7/3V8/YEuecRFS9DB09PXnRBCcNF35Miy6L9gEtWUO
-aPzBPTxTg6/bzgoYL2UnGWs09MsXp09Rkz5tF0ZDPcVnltQGxTAule7Ek78VSklc
-/3fT8dgPMR2rAr5V4tUXYhROu80mg8DEqKRC5W9h9vnXXQp1kg+jOpRSdf0E3Z9m
-bxwXETgqEMPJXV4Qeurg9YIYlYbZA8uZaNQGsBr3vB76LI36kQl68/hzqu7gbSQm
-tUcbdnFPHfpEJh0gDJPANYrhmHp3PXpCglDTTUuZKWYt5MNLl2iqSMkdEFY00XU5
-9vrMXKClh66JY0iQzGufCb0xSRJf/J+OPRZjlnYgSmUUWEP61ZiUTxT+0YqSAUnG
-Zpae12xaoUdym6ldKX2yVnAfXeNrfvntNrYfEyh+qVJWSuPnceX9N4bTiwPlI71A
-oLY+NqA4EiF86Nt7rq0HHhRPLT4fQmfE4b5yr/0qvnrsVz1apVSGgCuWvYQUF50z
-eQRfezYghHK2fdhf+j0ZJmr+TdAQpZMUUU0y5yo7I1wYsoWR3nvBZ+uMz4FUHz2+
-jTqi7rwCn3pfGBdi6l8ROF4Hit2L1A87LHfXBaoNB2ryKI33aAfCkXGUwuVVkNOB
-CVuc53PqDkXHvcLM9KycSfTSG6WMOKnJQ13/jtjcs0gcQJD4nzOfJrc5x/hsEKnO
-o7RvljQN42p9Kih1Y+MXsmlEr6i2C0eek6ueaIq6b4Wj/DYUEiHRVl/i4LBBo40o
-CZgTeQyvx7agLp2ikCTUPvSi7MSU0LpgjJAsw3zvXH1r7i00OG4Z5ClPxwvKGc8Z
-GGM=
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQITdZwJpEyq5ACAggA
+MBQGCCqGSIb3DQMHBAhOSMRBTCP0LgSCBMjcamYF/d3R67N2iNkxp3uw+y0jgJMH
+8HDa6l5xC82qWO/JnLwrftZMT3VW1xPXo5AyOClquxYz0vO35+foMLZMEKVsdwSa
+TqjJInBvapftgSgjq0QGXgJDmi4/lyafPwC27R3UzQq3g5V95KGwyzU2SQAbDKXV
+J+VNpeZ9oxoVyWY3h+NvuRbbVwsld47ibnk7gL9/9Nfam+PW7hWVNG4ro1L19uIt
+QxFg3hAZCUtlKs1lm8qYbZcMbV1kaBsPOdzDwCG7zwCRTOqojP0N5MGwyOUnd3ji
+CvCboZY4mr13klDdHaRwAYVAWNCT8MPJApq7PcDrAXmUTmW1+L/K1nns9n9l/xTR
+NuIleFMT9QTgrBiCU3RcA62GfTLYGQ0La/pcSyP3rxb8Pkc1PvWzL+dSY4HwFics
+VSIzdO8NsMKaZDeGhg+K4u0RXpHWRQzNfVyNpEaDKAC/wQfuf3dE0iln4OCzGw/1
+cOlAdUSgr7h7vmagv0/T0I/2a8Cn69ONk3yAL0GOYrwvZvzx44dyXWTwiPniFv53
+sqThHMQqs+Y5L6nJJNuVY2SL9EX9dE4WcUuvpTYrHwvv9N+iP5QMFNoIG3l2BD8x
+AqzKtdtz7ClbSBwsU9BCSUnQ9UZnRx78n2Wbbu5Kza9M0x3v3/lczaXlNtCsmDIA
+qzWhSSPMCMLdUgKueRhTN+3dPo1UJPwoYZrgdm3BLjgOb5ECn5XcTIxxKsr0ViYi
+e8Z2oh4Yg8CZrchZUOaGoIh1ZLq5VtuFGUlem8RidXKFswIU4dWmXoEfiLyVpNFJ
+sa6rQPipWtqO09NJjPiG1R57/XI2xA7w3k1MNODMHdcygxk09526MB4dUG0QtrF5
+5MYRSfcEXbaAFdAlF88AQfGLj3joWczlac4iZdvTomMy6rhNMTuJwvSdQoQoBeYU
+1ljmFPMZiJXn7ulf3dpNPKm9R1ejITdp8X1CQZIdznR38VPtfRUMHHnn3+AECsju
+mZjwoGX1Z85Md7Wv3OUOlTms2CNGLgB9GIr1xhXh5bH+ZsVTOakZ0BoBfpabog69
+ZJHpqP1ePbA+lv+uHhTe09H+9VG9yOg+D1ZAYul1Ay8W1HvQ9Uvl63lRfveE4eD/
+qf1dMbh5TWBe4o+7QcprweQO3YOrNs3xGsv5iKaHMxx1AfaZNCD1iH7Zqaq2yKVZ
+y48stJJYQYrLyXEDPdLzbeZnlDWA3Bv+a132Fr5wBCIMzC8OGNwZ6XIiZso939Ti
+8IPmmf8WIwrgUgyu7u960h0pU+CBWVQhpsTSIfOP1d5VPwUcykUfHYNViMP+iRYE
+NefV3IY2DsDrv/GQ6/49u9rwlbXJVC2Qhk2ldd7o1Gi9Cuesh0awck3Y2DCiDlAb
+u2usiC6VBA41IsYgx4fD03Far08fi0MB6L7KSlyns6IMt5d796KFm7i/P7bPbICB
+uGPS4QwMT7MnM51uhCPFmVDLX8YyG/TfmC+2ZtWULypoKeCtd8aIPQMHbyUEZNZQ
+JAZEJDNf88TL/13HxCpH55WmfGrspWUsKK6T3qjlxlbzOd0UAic1YYSCcbtf0KSb
+eZnlkCTQL6Gl/4h45bPQfqxW9ksqWDtYFeM6CEv8/YSZ1ta7fMs52zdz2B0ks7U6
+IVU=
-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/certs/private/serfclientkey.pem b/test/certs/private/serfclientkey.pem
index 379a8fb..2b31104 100644
--- a/test/certs/private/serfclientkey.pem
+++ b/test/certs/private/serfclientkey.pem
@@ -1,30 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
-MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIWyUAUyy/ZrsCAggA
-MBQGCCqGSIb3DQMHBAiP2ZkeR/0y/wSCBMgyIVmlwhscMzqOyND4dEQnGFiplvoN
-gO77WgAb25GZHPaNsp2vQZDxwzp8VeoNqChwmgQ/4lwcPcKUEfusD2vP+W5ykWQZ
-8mOaY1fosj3A/y4LN8bgM3c6fkQAKbWe9ohmnaVgvh6+EhFFnbV/+96hHdQgiaBZ
-vNwlihA/G7CoA7a53amYVKxCfYYKsAs6Q9tjgxocxJzO+0W27jardco3FyJCg8oc
-SRre2ND1938g/PUiANXlJIEf7fdPexIvTxV+4Ek43E7iIu7AOyGgN5/QO9/Inwuz
-xCyiKmTpvkZ6326OFZ7NvN5/N/m7r3Ft+FeBSfTNAquDrWwaoXm4WufwL6vUo4J/
-8lGcDtOU4Ha+6JvKyYzY/SRmH3SL9wZIcFZC2zpzyY5gUgKS5W6J5xHnLJsV1Xdm
-jJdj4YrI9sOFzgpSdFINbUySFEKXui1R09AgcJT3ePgFFpvIQqMQD2r/CyhZmDi+
-Ee/jUD/TSoaDQ16cB5A+C48bNegwQvezCK/SH6JfJgx+e1793uPqXU+G6v1ftAod
-T958Rk0jOKbFk/Klya9RkjGa6+iFDMv1vfQJ/5N3SFbybel/7hHX8sjgNPc6Gh5p
-5BAKlh36ylae3FYx300HzieHNbnEZVBxchxLczSXeCD5iO6w5oeAbw4p7H850Bjj
-WKn/CDw3m+CzhLRR7s3vUxk1xlSari+9jEBNuGsY5fS9IxiGGmiWR3zT9WcFpDGy
-tz3bOxLsHg8V/VajNM8cSEZKuXBVV89tHZ7LUhCxgeiN0LaG2OWFwf8DYC50j6te
-boVUSQvl/GAuuKGuYYjb324M8MHJDOvR5VlVqD2LosS0Mumnj64FE0JfAoQm06Fa
-FZza2NCqkPdy3Xt26d6yxNDH867TTz3o4xbvk9YTjet9amHR6lHq5gPZO4oSnqVi
-eFhwo9byxPAiWHKMwI8IPJpKo7N5QoY/xa9Hiv/o2Z7wuEeGIPcZGgJYs0CUSSNz
-vZpJnuauHF42lLUC5Hh981HKVXFjrwUQ3M6piONZ0tSk3O1+jfRwKifmx11RbW75
-3LTyVoC/hOFAiPEfHP5J1F2xPkJ7DV67mhuD9naykJdhoNdXs5R35/2Jh18oZTpb
-Ly/hd6GhHKm/mebHZZJdNBwjiofRvHPhcCXPUNyHuLjdKd+iv/VWaFA2gWwv8P0x
-u3zwL91Qetq6bO7c1KXpiJyapST9CJEfxjKqJMm2x5iMaIXCuxKQk0Ga9CYsvQIx
-MhapWJku6lJS+K3z/Q4w1DzPQuJifEq78WzwZiu6e+PljZHcIwy1+3sDd50y1lKW
-N0WwhQowXSSolcP4Gf35O4C42GSEt0mElH2QDGOJJ5/b3kpV5zdHWSJ/hh2v7G5w
-3opjxPqzp0Rzo61MYH0PlVGvrW/y1v5on67AGDXmU5e1tnomUsVwTc5tbfz+Lx9U
-75PiNPy0l9UMA54VVDCXFPulA30EZVgjT1QGIGte9aeERKGDGf8U/XrqcvBNDdjy
-YnSk32Rpk78ZuN/NMNX+TLbiuJMeQlmM00zt1qt5PfVxHd5Q6ZGwetN/1MHZda4V
-PSlzipLpqA7ZdTlQgq55QwmnNuMMECSemay2n0fAzASuR5xmjuWXUU4YKvqs6dx3
-XPY=
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIiwBSTzqM26QCAggA
+MBQGCCqGSIb3DQMHBAhAi9KvnXCelwSCBMg4lxlEAmD0m1yRfW4jMzoQij8Q6LBt
+FvhtBALMhhj5EsupPfpa9c/x8rQcpkhO6RxH1O25xCcc1GqNVuUjKA2qQqzM31Ys
+vtxs44vK+5xMq4zGyukVb5zMNzXeu4VRgQNYdNK56HHWBV2GY0CeZc98ZAYkWvKZ
+tdRdlwrd7GJdN82BXIlQFutWLtCGZYTQXNpnEk0r1JVlHKPL7UK6xHkHg9yorB5p
+yYwvtNXM4OYGA8g8mqAIY/1ONeCzM5dSwxswnLPcQVLs/J2wQooaNeFqwvQmY7fD
+x5vGqCwoqAN9txQpKhWeeCXF7+LfhExYJ/YjAWpU/Nkot1tDqG9xo23qiDj3qix3
+D/QufHhF8JAMK6ty2NRVGtl6LbLeTnP5ExFPnp8lWXaq1ksh9HEjM99b6SYCiDBe
+SQCTYBNQ/JGoNAq0Z+VNsEu4kCdwSg6W5AKkqM5wJWkZWjiC9WCVhruSmLcwqMtM
+rMG9LzFNn8N9iPzY2ZAZRgVMKnn2dKV51EAWwag5RZq6bM1eVNVfOYtcbJAnM/nO
+UMvcQ/ltukjG36QRjTMrel8Et025imbN1AnTkmHbWw6t+jO9HGYf23xiOW+BAsVK
+faxJ+58Lqp4i5GmPAMtn2xlSqfHVCC6H+NjgWnSYsBisKezsDl5p20b9xKYBCMM6
+zjFzqXbltRGv6B1Kd7ZO0e6eK67gHv5tHgcIOZEOtOxBxJrkPMCI7J/TzE17SPYe
+hQQ3hbU8MbChtHQ0CA9TCNPAk3rFoq4mEjrpjcuXUg9c4W5K8HOfjyabxw2Bk3bT
+0+PBRvZpz3M0QMUj0+EljJf5AVaLmpiGl3FJDzKspOnOrAqGVazJfZrfTRgO5nB7
+eds8loJ3eUKs8YSxhHXc54MUSZR6SHrrhXOuFahJ3Zx6fFo58s5vCXANrMmsOuqM
+wjL5Z+b5Z6USEsAVD5FZv5g59Qy/Q9RwOKaoUlG6c3wRIX3PqztV55AxDKi6BlA3
+gEgVk8PY2quM2b+pi2yzV2j05Q2q/Dj2e5pdGU/ukkbU11ldQf9kCshnkudAmNQJ
+RCGT9RX4xA5PzZcMbqIpXKAF+T3mQ8ViK/fKTSRIhOcQIMNWBGqpHFTXkGkgTDGH
+S7HilQsbQTUJp2LpifwtzHmne7PEvQiEiwOXrxUfICl/TmkG4wYoq0Pp8jp2mghc
+L+24tywOAlxmaTRZ1RXkbwucCqiDGi71S6AyMHOZMAW/WeoiyEUzAHOz1tiuzpH3
+eC3anbDCzh+24LUUL5J/zix+xXCMK5cotaTdmOn1PI4ryfqWZ7tNQhC7CDZ23Kux
+/Xy5xKw0MFOvnTDUpYdUMgn5Vwvv4D33LSXoGmJ9nCdr+VfPgPHdPfpIYNDnhB4T
+nLdHunYkqbTvxWyQ3NVgBmbMNiSw2o1cgDetC1BNXiAz0cXECvK+D6MZVCJIQO5j
+7VWF1Ji8iaUr28s6vdWMQJ2f6k3gvvif1FeFVZcRyBMKceazaV4PQcQJBzqHv/MI
+xi3dL2jpZX3SJC7fqfZYNH3gtTTbY3jikuB3gbY2zmQ1aq1U+vNLZfUPJzyqDa9o
+WDZobm6DSDYl4QMQXRpZ/EsmdHqdbAwozyCOEW3eRLlFNQjwhaNm4IjimYE3kDCB
+nuo=
-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/certs/private/serfrootcakey.pem b/test/certs/private/serfrootcakey.pem
index 1ea6224..11bc378 100644
--- a/test/certs/private/serfrootcakey.pem
+++ b/test/certs/private/serfrootcakey.pem
@@ -1,30 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
-MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIHqz0UXetk3gCAggA
-MBQGCCqGSIb3DQMHBAjru4ouS+aQfQSCBMin1dohldNnilVkD1GKDjkHwOqi3CnX
-HuKaOEPKmGUS5Ad0sy8s7+UdB+aq6otMbnn946r2K9+mdNNYnQoTM79c2zi5yRWc
-4fls04RXkNFLBfIu6+xuR7Yj68INDiDIfDopjCUDyhNYDZb+WQLSxEfeZ7oZzoSx
-ANpVQjbzf9A91L5BGrUGNYaFd51GaVi3HwTrhbcYCFZ3DsyioMMSCxM2vULCNsf0
-ZSxr+gueLVijirGJaw2J7dN5OQWsPg2joCH0LL7hjU51dcHinI0VG34WXFFKV5D9
-WOasvz1+lNWjiWyMUX6St3lES32OBNU+ROQH10ViJETuk22c60k/KjSVrhZ8web9
-nC6Z9kJDgspm7C8zM+0QTTNbonqe7omrSoni4DYkEgGzrQmg+gTLK6HfCCJnksSX
-oX3tBrzSq3VrjfO8oBsCA7m+Kue7rODCvUKX84bSlI3CaaVkMIrvvYq5ZHIIDxCd
-i/i7JnQJwVh7UD/rbzvB9fQ4myu2j4FN2xqoczLVGylePPrNr/J679063M0ZlOOr
-pJNPQVOEcRP66xbN3x57fEdAY/gI/3m43eFfiXyDAC9HNdn5XrhIVaXTTgs6XChF
-xaMERH125XFTBPPc6zIWLEqlme1m68UwWESkFZu/qjH1VwxJl9jkYBiuHC9TxLLj
-ns38YqHe5QGCBuwbiCD9iAnVQHwlkeTR0xK5YgtY9G3z5LUkSKWV/Vp3wiTY1Jmr
-9ZoFGJQO2FF0e6gTos4UGq435LzGHC6zZQTLx8kVfnlXLuTIZ40+4ACZHAZsjmlf
-jJPGj81c+1W++m82XBbtIIDLKhHrtPFs2RP3qrXX70E0Z5/RD0mJMFLZraK5ivAm
-STvJAuID581gEaw80BdAuNidUPohXUtdx7tJRSCvu2JJdFuE8U1BrT9D3lUUyqBx
-Tzs5ZyZ5P4x/l8vTSyMczojCbh8+V3qNCnNB0mw0fsjOYQcxe4DNr5/9gMNDYLui
-0fZOpoW62m264Ju0X2N4cTbDdlKus2E0fO20tXkDI0QZXA1/w1g+bAT+QWUrwDVV
-XAxlaZLTTSZ64M0tg9wvhxmu33OeduovwgOdx1+KziQNIEH4DpReAKGv0hO1z3Wi
-1VqhK/VDaqLFYqIZSzJW7vEzxPtxN3qqAkFF9rIjXH1QZwSi9Xc4qbNyDUwJerB/
-AgUXucs4LFCSmuQa2ztglU5c8mPEQHt2fxGH1Lu3QEcZLrMwLbnq3qmZ3M8+2+k9
-6K8RvyCh19oDP/XPocH6HnLewbbkoaiu1E+9bZH28oClJ5qhscCkeceRYaimwHVi
-bvjs8NQEKRKsLIGjXShUWgWVpGyqw78SJTLA/YW91PS+jhMtVTl3HYrAIEvbdAq0
-Ht+FGeOCiIlfxOIiXyXgjwWebuN1pLY4N2o903kDFFm5MSvcDYXrHDtrMscFnaPa
-EA87QDBicaFuDwL3EVySXMJpChExypFQTs2oeW77DJkuEtF51GdIHVdZbu3VFhgQ
-kgX+7t6NnjNBx2U6Ecs+j7qIJCDdxj/b30CJEXy7l4ofEHn28vpRwVcf8DwoXz5W
-Os2TPYvNUflnjympCPzJO29G5kXNWMGm0RhvmCwByYnhw6ymcx3wtX8Bj6n3uc86
-sDI=
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIzJhUNxtSQj0CAggA
+MBQGCCqGSIb3DQMHBAiwKxWvDWJQ7ASCBMj19QUufDeLX71uuo1VnXuNujM5JJkH
+OIe1l9+AaFz9LCeB8a0i8rM+az9E2Nk9LaBgCnThdxcV7L6bKO71stFpHaA2dJdq
+mSHIYavNzkbU+0GQGZxe8yoTmRMYtQ1jHQ/hpQ+cet2+UbxOlE5C/bjKpSFAFY4B
+/NZssw2+wTJMMLyMoAofDlbI0JIqo51ecI7oJMhDxmTHINgdUib3V4PcPbB91Irp
+YbkOxfEPdwKPbChBfCHdqNlkd2a+uSTkiY17cWL2eCfUmtf04PfT86JElvf8P3er
+yCfTbLN9VuwA7K0FboAGlGSKh8pzC16Fr5keqxHZff8EDn9n+mKldXAUlSnWncLZ
+CZc253hBd/r7LjH53O7AMLDOtgvrXwr4FhpiYdWnfNJyT0Gi1BWPufbB8I1TLE3q
+iPLiSWragOvH9VC94sRrh5Xivzqygy68OtoCayS05CRIe5hQTW5jesSlL3ZBCjJv
+/SkiiLcKFS4SY7y/TO1wtP+FxsZ+IjwIS5HKZOzQenvcpCEApODuWOAWYsyUKmwc
+TKebXX50wfIy6EggiXBizyW0DxdWzt6DZ7ASD9r0YZLowFA0sQkRKb0jXDXtfOx1
+LNsEUpw6U0fem+J8WrRbJZUSwfiao22fXGXYMYB9VmYQ73EC/16Qjcn3WZA5ZxmN
+zAEszVD88VDIxlHt9RLzeTHcVfXPI04+FTbb8CgIXwZtSz9Rf1yA0zKLsL4+i9qQ
+O39kWgkcpKXHJZT1r+UQzR03jTgf95IAs9jGZFYgf0WW2fd2ETe8xmhbfXP6WQMw
+XQJP9p48mwHwwmn4igBR/1cmSrO2ck7N/I20fLJZukDDGFlqFxoRz+u4ZFeBIP+n
+J56J+dRZknFN39weuMtHXGQeYggiSQ+KXnBYewgdBUvO1dgTExWLJthB/qump8NI
+rSSIpwcMCioJbN1CzXS7Ol+npxKje6acSC1RSEEq0Ho4WN5d0KmhLH2gM/TFjqKn
+QxdOt2Sq1nlL4gM1LwyLd9CNnzK+ZWfes0akmVimbZvhMufFlDXJJqoMOZ1nep/g
+cgqh4iATINB6VmaAdPJLoXpkh/JuN9B5Qab9aF1kEH4S4PgZmG8yCFOwhNJuE6FN
+LW0r77wtsg8KMg7F1S36GPmMi33dBfvuIcs7UENNM1Wrs7lTPJDNk42kNogr0Z3W
+agVlh4BGqM4CGpaBU3J+APpILDJuG2byoymB6ltEJH9cDyQPbHuIV0Y3CNM2IAKX
+gUh40933RpI83e5TImCyJc1d5SB0Q+J4wD+fFAT3MnTs/q8GTkEiRFlEKRCfCz/k
+s4rpea+vgeZ7hVwnJMspq6FxrsMPUxBWaUhiNUx9VQ8n5mUtmNOS6wiV1RYUMp1k
++z60oE7Po/gtyHqKL45fpjVVncfBzDWkZWUJZRpR/IO29AkFVwYDmh1uYjfztex9
+F0L3DIf1qLtdg7p9fhNHq/8nKcgpaMDN1UVEqg4o3dw3a+lu4M4+GXPy5bPQwuew
+t+kQml+Ha0dhvxPfTPV+FQn9sotXJ/UTKYu7zAw+kfMAw1HNc3omBjsbslm3jZJX
+2AS4MJ1CSYY8mwHM4oJ9c5FcjBXUqfpXIA1MAs61ZST2XDhdHNw89N41DPNKxZh1
+pg8=
-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/certs/private/serfserverkey.pem b/test/certs/private/serfserverkey.pem
index 063b9e8..6bd609f 100644
--- a/test/certs/private/serfserverkey.pem
+++ b/test/certs/private/serfserverkey.pem
@@ -1,30 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
-MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIlu67PS+3+cACAggA
-MBQGCCqGSIb3DQMHBAhaSMe0OcI+zASCBMgDwEtC7nCmW9OIf8vqZDDOXlyiHNTm
-2nEYtKMfr8QSGYgzQwbVF7z+OUOLQDxJgFJbrKFNbw7ummGaAR8WszH3GoCm6p4H
-zAx61MyvM1uQzY1eykAj0/JD2/rwuwA68rV845OpB6nzz1ClrffvRPeiBuVyelyf
-WwcV57D+lD421FDATtF46inMtT/g7hv7EaFJKBMXWTbnPY3M7NIK8jKaoL5NgQ9E
-stZCZ4jmWVnhM00lUjevWz8RTsb6kZguTe7WxFl7TH0AlrpFjsYglwkZDzYnnvsJ
-KBwIFYFYRNA24+adRwa1TurRCzaxtj5DxRDoAaBVw5uiYVNKbrrB4q2uqePB6aSo
-Z6toK83FqZtTt1rHB8qbxWdKIJJiZsa0xSl1kGcWI8APmgzzVqyzDzBalI/Ze9pb
-5LR9IpLqcYhpvbYRmf8lwU104G5tab1n7ZY8Q37J7HrPVG0g5WIj/SrwvwjY9e/B
-oO/IFRsoyh3NJXUl2L75ra7vnk2h823R1k2YYOcnthRjQetq7CgblFQyS6Wa3bIJ
-iTSCSauTqNolDCER0XbX3p80gVoh9wV9ELKws+b3Fx+jvFgvDtdBRnSRenD548XD
-wy9n9/S2muffJw4D0xXFNfk2yVMjt4d7x9gbTgU8PUImoPZuNzj/bH9LKMgBYrdj
-a1tmRLXV5x6BuVmwoCk4ao10cpyOKmFpl2Ba79QqOIQqTOveSJyGK18fQBu6OJar
-WbvVbNhdISlU+/Qz9skxxRMBd87/MFK4D0YlWCdLBTvyVYEVQNk+HZjbuSEt34gZ
-ST3eSNDcUD5IzPLWk08qaOVt18EDrUMNcqNiG/xBTh+Ya0Q8QrU5Wii3eeFpLlcP
-3XOHadChmc/17OUyRcFgtfg1NbmOLtyk0Nbo9rLGmF1+3nrKba001P3oP9mbRNio
-iD/zaXsAUXt8PGLomcntXidyAH3Qgjw8Y1WI3GIUFS9EYND7YrK3kaotdv790+Hi
-xReOaL5zdviD9stKhNJicT33YsEu5XsTv1xxv/pAdDtMTP+YvUJURpju1G3S2RBO
-8gcs/X8A/bCjREEOm7VQYFvcBl02KCS5HQ0R5NKy/cj4Yx27/y8r4GUDIhxOOul9
-JhCL1rLyEBSmuoL/tNTyCjPH/Mp5UP1/oqm0ZiFl8zfu4DfkZP1w19S1SFLlLRH1
-dTclmX090orsTJcNksFNmH8sXOsLbWYjWcMome1NiKHyFfjTsdWhA4dZrw86tP/j
-4mOtGKwt+TFPtD/0UzwkhDVcW4Zs7jjPBtNu0Tts+XbNyRN8Oy38N6HCf+iHTuIN
-yjZBZvpyAFIfoTRCENn9D77LvmohU4LjNLJb6YwfOACnDrEikb0TYAj7AMw042q5
-0Do+84bJwN1PXenVDQ1p+rc4zBB3dkDQjLXw7BaGn4X93egKNCZl3tirjf23pIsL
-yY1vyrODf44+p9PJoJmnmyOn3WzgJgdUAycX/JnGxdKxZWKrmvV+MMqyq3KUO+2C
-0JkEc3tJoR2crvOzLpvojLu3yhZOC/BQKitAYW54PL7lacxDgkgN6fSHg1lX1Y6Y
-/y0+IxImb5Rfees9++2f6kv87xQBBs3fMAYZR3/vA1oKzHn5RYx8x43D2DOgN6Vu
-35g=
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIx8nlZtTYZ5MCAggA
+MBQGCCqGSIb3DQMHBAhody28ldRltwSCBMjSUNhRHVtBWVu4xHr4u3of2MLc/eh2
+oSfF+4RRpI7EGZ3R90QliW564fuYHbDnmgUOiL/vYSPQpvEcXjZGKiB7RphDjvRA
+UUb4xBtpAQN/Qkj2MhiJFAeozZes1MM/5zxOvIx+afeUEfXs/jKn5QGD4C42QCKv
+WECxCBYymuvJ0FSYIva7ID7UoMtvJhgpvocqaHGp+gijP9SIQznw+gulLachQJSn
+PWjeYiGt9rMYrF1rsh1slQ79tJdriBJSzccUMew9m1yJZ1OWHXy1992r7YReeh33
+SB+JQ4bOVdj55e0OOkspezXV+XgrLjdg+oaDFff+Ns3nlos/q9tKUT9ZnSzcz8aW
+0K0fZ1xKEnBjX3kVBomgDxE417pnupV9AZ4jCDJ32d4Z7zxtPSmmtjE6yrzYiCOW
+4VyLFP787pUbFsPB6ft3q435QoNlj6Kk8lJa/1Zs3e69An7s4upLRgAnww3sydb5
+AZaNC39UwYWY54mHkr6Q/Rqm4bLZu+B2WhmAGJLEwtj44j03x1cezkfNJt03xZsY
+M+v+A2kdYFq8hgxXwt1s+Sa0XtCgVJPWx6+YUnckqSId3+4orfrjAuTk4m3ZNkD1
+BzgJj/taCKFvjhfhlfLjE6WN9/DYpuW+FC+eTpPI7LFmOvIL30ed7E0oXIzS6ct0
+F5EPe10YZK/3Q+OZ0lyLXWjpZt0zK36FCC+YBWefnI+KM9nvKXbMC36dCbEpQ7se
++iA1/ONu1RGlUM3RiLPPyKHmxEXHWukkmxIQ1vmuuV5423MFGfctbU0t+5PzEfFD
+8hE5HlhvHJH0BUxTxtP/+1CbYic8NDyWJuEyHrj9n3gAzPxpRkmANW9PYzhTd4My
+0k+rPrW7AQIERMt3KJbRmcmbulwGM8zFMvcKKLgzvGsAnwS+fWrfTk28A906Dwoe
+5r6hz+dUK6wwF/yYxutalMKCUz25onjUsnpZ6zTd+wlpELLxcyLHAMrZtRKvM3nW
+X88R9TpnsNKv3/1qLj8xBEUw3AcATozmEYocepzDiNwUDPG6QKyhZqUZjQ9+iRdz
+l3x2vmsy7CnIa/W4VYOvEd3t4D4QyQRoVwRlbLlNE92J4g9bxrEJIhj/wnoWH5t2
+MekorXUYUSr6JaN5PcyitSqNQ0PRHM65Kb4bbAl8Vkhg6KRojxyij/kG3XfXtVa4
+FhSc2W67UcdGEf81kk0vKBMWYRlWdPJsfnlsIUZNVlp/+Lnzmr+Mizlk3zDVsZvX
+eQjlX4Pd7T4ChFHldjKFteXuUhkoC/NPVAsagRsR9xR9Ksp4Pz1EeuGbN0Jnc/ph
+mRDMXxpxj4m0UCzJvjopouc2BqZWRrz/DgNxMOP15QPhh/hrzE6rbmPk+PfWn2d4
+yPYl8kccmiW9QQY0Fw1AkkRSRIE4oVgoJ0FIvOE6kWbGqDsF/IXQTbVh8BaTORmz
+HqNDA/OP3JuwX6wQ/6mM0/89/NkXc/ChlP7a0pH7CKUNs6Sc6dFvXAHdM1qu31ef
+GLO7C8IFqPOUfSueCfkico/Xh/1z3jjmsCtWg8P7RKf3GbRhrlOMEr+I63ke8ND8
+f1HBodeS6H7il3co/C27WpIjpx6s4Hh/KioAwar1X3SAj5Kie0Q+UD9bRdRjBIsJ
+eQY=
-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/certs/serfcacert.pem b/test/certs/serfcacert.pem
index 4a7e663..4cb0441 100644
--- a/test/certs/serfcacert.pem
+++ b/test/certs/serfcacert.pem
@@ -3,23 +3,23 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGzAZBgNVBAsMElRlc3QgU3VpdGUgUm9v
dCBDQTEVMBMGA1UEAwwMU2VyZiBSb290IENBMSUwIwYJKoZIhvcNAQkBFhZzZXJm
-cm9vdGNhQGV4YW1wbGUuY29tMB4XDTE1MDkyMDE5MTg1MloXDTE4MDkxOTE5MTg1
-MlowgaAxCzAJBgNVBAYTAkJFMRAwDgYDVQQIDAdBbnR3ZXJwMREwDwYDVQQHDAhN
+cm9vdGNhQGV4YW1wbGUuY29tMB4XDTE4MDQyOTA4NTAzNVoXDTIxMDQyODA4NTAz
+NVowgaAxCzAJBgNVBAYTAkJFMRAwDgYDVQQIDAdBbnR3ZXJwMREwDwYDVQQHDAhN
ZWNoZWxlbjEfMB0GA1UECgwWSW4gU2VyZiB3ZSB0cnVzdCwgSW5jLjEWMBQGA1UE
CwwNVGVzdCBTdWl0ZSBDQTEQMA4GA1UEAwwHU2VyZiBDQTEhMB8GCSqGSIb3DQEJ
ARYSc2VyZmNhQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEArNOyobONvsFz/bA9pvhKTeCGhFVjMv1Jw7ooLW0jLn3yWaQ7iMFBLXp4
-HjMbI9oLdVPfJgv7S/vpiX00eG6t/xuQb4+g2/0mcmOHuqK9bsEElkWyCP77SqV3
-uqLcw9pqjz0JO2pUcNu4Uh7PtEJjXHHC3l18akZIX+GwISGzBP/RnWbiFP56vnT2
-/SIgB3LIy0QBZHTyvggHFM9s7FvWcgoyoYhSXsvTwlRRjaty1RAKYQP13sbpwCh8
-qTJHaG4gKoDC/IrO69tZIS7ulcWLLFpd7Qkv4RPQzmx5vezlZRkgqxjVjYGm2CcU
-sU+AbKp48mCQLHYrt22GB+2mLX8X9QIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0G
-A1UdDgQWBBTVcpNL7Ey5qBIxCHgYEyK4wCuVsjAfBgNVHSMEGDAWgBTdURuV/Fid
-HUONkLf8COJ7MCHscTANBgkqhkiG9w0BAQsFAAOCAQEASTtdzNrIx9EJzrryTYVY
-QrP4jl3DKeOGy9KN9HTClSuj2BSTFaizpzzxA++rbZs1Du8NxQWdb8kuIahLyUxY
-hC1ZXTdZCC9Ki13aO1kIdXBLNI9QbQwkLukObqj62aWhhW1Bk6fvkbMli6Zmtt16
-Pf/9dPQjKq1H79bz1dArM0vuG4lNjy0RspOoTmfbbRAkY4MApY9gPoC5W9UdHKzB
-wVg/YMgEzAaKXAhgKExM/AGCMdprJFK9btDAJzkU/YLYd00EgEGrUmvwyYpANydP
-l39A1MB3Nkb9rQeyfo32Do4cDbhRZZMlDhWN3984cg5zVHwBFzUOgKZwV/NrRGNZ
-iQ==
+CgKCAQEApmh1Gr6FkunclpaDBiZVU2bswAYYiDj+Qre2jxAhA3GqlrQm9+/TqkOF
+D1ilDfBV3kSxSatiSFhoOGvkEE+gCctdv5Yw0MHY346wiUFX3lAvCr5hMS2tRH7l
+BMW2g9bg1vnj+xw0KSCgAZJFonems1tfvdrLKSlT9xjGj+JWSzP56qeVGeDFYZd6
+6sZCHtc4GeQB1tWJRzLp5iCWlT/X3xXY0EvYjfGS9hd8CQ84z89L2Kfc3AebZ6hy
+ps2eALPflLqSC9ILatdwyBQhDx2dQ+GGvmCCPuMwpOtVabv4DpKbdep8zpYKGt+h
+59DVT5G+IGJmzFE1AI0KTJWWz+tjwQIDAQABo1AwTjAMBgNVHRMEBTADAQH/MB0G
+A1UdDgQWBBQ3OJrozi0N5+YDDsyb7QugsgVxtDAfBgNVHSMEGDAWgBTf3UjHyGMo
+KuvdVKntOGcDzN1K8zANBgkqhkiG9w0BAQsFAAOCAQEAYt0fb4SCbULoyg+HdRZG
+kg2ryDmPaw9CarZnuok01TRppOQ9BOOUuTjl1yi3K6j4pfzQvtiOpduftjJ2scXg
+nfEygsZ4WnzNzV0T/ZsKLho0nS6/OCu0CwghjCLRgU7jrqh8wCQB0x4k1BAP+Yhv
+yiRmqbHXA2pgNWXuW9nVeeT7TgtZg9mP8aXbSeEQUmLFN/WOi7cHD87+AU68YRhf
+LEqnyH1TGNreI+HPbORuG0FtIhM9SvaO4iSHTlAWRcjAfMbYLQukhm9k/QaOhH6l
+9ww+zn7ul8OFe6lCPzXz0ix4udR+m7tkFA4oY6ID+7hn7qzlEgwXmKXNMVXL3WoY
+EA==
-----END CERTIFICATE-----
diff --git a/test/certs/serfclientcert.p12 b/test/certs/serfclientcert.p12
index c5f6912..3d76435 100644
--- a/test/certs/serfclientcert.p12
+++ b/test/certs/serfclientcert.p12
Binary files differ
diff --git a/test/certs/serfclientcert.pem b/test/certs/serfclientcert.pem
index 1f992f3..d2f535c 100644
--- a/test/certs/serfclientcert.pem
+++ b/test/certs/serfclientcert.pem
@@ -3,21 +3,21 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
-LmNvbTAeFw0xNTA5MjAxOTE4NTNaFw0xODA5MTkxOTE4NTNaMIGsMQswCQYDVQQG
+LmNvbTAeFw0xODA0MjkwODUwMzdaFw0yMTA0MjgwODUwMzdaMIGsMQswCQYDVQQG
EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
Q2xpZW50MRQwEgYDVQQDDAtTZXJmIENsaWVudDElMCMGCSqGSIb3DQEJARYWc2Vy
ZmNsaWVudEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAKc1/DkyQaK4+TkMh6eLea7OlcLL17l3p2KuTyWtw+GvZ3z3JrTvIvNwUtI8
-7ObkRcPMcDyDerOnPtNAArMabR6GPhH6UmdY6P07OQ9jnQKC+NRRMjdpTA5lBTVr
-0o0cMSLeN+/pBWHxnPJjnFlJasmk5got5Y0xanyQUcMXyAy2+s4O3FnslWLmB27V
-eKjRpTziID6zOMdIBV//2ylMW8WDK+l1F1d0qq8cF84XhEZYYL/gPRVcrTbIJQjG
-XxnDGTSABGJ8sme5yFB0WjRWXiImnMRLjw5GkIQXf8KXBOK1vWvLQQgom7tyQWN7
-WeOen+Z2FlWoXyWmnXRRKUyg1LMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPbxN
-R/WHSU6de0d3PbmTiLUV2zTVkcdTWRI/EaIvyopt49B2jIWow1Icsqr4L7FkwHZQ
-dC9nooICE7/D1ZHCPXwNn4Kec+tyCjvaG+cdTNTOASWnu79k+5UFk7EiO2HDQbit
-fktuS2C9TY7S0Yvvt/9g9s0j3bAYN4TviQc7IPTAdy0UXHuvvaJX4TlhUyw/1aaS
-vgVjKbKpW9P/r3FrPZP400800ItV5LtrSIqXwj9MWQSlcsYu8IDVuPRbxQPL8mHS
-7IRYlgTz8ZsCbbYUUnBMmBaY/XOMIj3+TdWTx1YOT0sdkA/IaKpDBCrHadLomEhK
-pT9cjnxlou8AkXSITQ==
+ggEBAKL00l2qEHaTWqnkSNkb62Oe1L0YYnNg2ukceNqW8HofCC9LZHZuqx/lQe1p
+LzsUi5Ul276Zi7SbeKtbZv6g1fL9V5Apf5BAOfq/+Ueziy3i3seft8P5C9FnCOvV
+NZUfqTzmWa6FxCVfdQ+w7PCqczqW+F9kU7hjUIPZBwUh1M2oxxan0MZVb1EemMRO
+5MxhFimQfnGbXDcfI9/5E8vvhm9MFgxdW2dkpFImo761O/ppzeMvLIHlOsGAnwQX
+JMGmnxhxcaJaHf6+1p5qDHEgovn2NqjU9Ei9lsLFbekHjAXWcDGltKmLGiuZcAr3
+n0eJLwATtOyS8OiDMilgouUXuwUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAATHj
+cMri3gZhsSdfnRsjrwnfsqe2z7hoV3ysek246b+S+wVCSkM62Hr4aOvIKExdj94i
+bPJzwdcDaOzhvhiwdegeTTwsE17SWBG0/B96e0QRM0ijiZy9/FdxFfcoficDly5m
+fB7DaW2XmF6A4a8Nfl/3+RBiIJyguEQjL4OG84ExqgkmjZoSP77F/dSG40iZEipm
+OloSXVQS8QTFYjonYdBX6+sIJDyAaCwZKXnDvMKZ30iKuN65yRY7yvWkkRVL9YMM
+8VCnYptKDzoaRNv3eu4LxR1T0ZczaJ2OYDyr3c1xEueGYoFHeFA4sUyBEGP8Jm8A
+ra/zM9AjUSXkZUEkXg==
-----END CERTIFICATE-----
diff --git a/test/certs/serfocspresponder.pem b/test/certs/serfocspresponder.pem
new file mode 100644
index 0000000..4d6c8d4
--- /dev/null
+++ b/test/certs/serfocspresponder.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4zCCAsugAwIBAgIDAYa0MA0GCSqGSIb3DQEBCwUAMIGgMQswCQYDVQQGEwJC
+RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
+FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
+EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
+LmNvbTAeFw0xODA0MjkwODUwMzdaFw0zMTA0MjYwODUwMzdaMIGqMQswCQYDVQQG
+EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
+BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
+U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QxJTAjBgkqhkiG9w0BCQEWFnNlcmZz
+ZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQC3iSkUM3L4K2vLMW86QJGY4hsIbLcm1U0HHSvpmEu5LjvnI+KTolZ8eTzrWJ8N
+M2a2gReIhliGsnJEmnjCDTnfCe4GFAFQPMhwU3y3SAeQKRbGEwuqKEB/ZXpMNchb
+PY8M1+2ciP8YKB4ybj/w7+gi7JiIuwTYGQW7vrojkvRZmWu6dPhl8l49HI0lPY63
+huTqNKGZfZQ+JAiHaexiTU57CI8Qg3kZjvveX22yypazS80/TjbkQAuvw/YzGWQF
+7colQgRgsI27XHk2DHRRzZPKFHHry5wE9H44T4V0/mNXQ568Q4Ge2carcNKiZudI
+GaQhr7JIfKPj2H9eVJouPbZpAgMBAAGjGjAYMBYGA1UdJQEB/wQMMAoGCCsGAQUF
+BwMJMA0GCSqGSIb3DQEBCwUAA4IBAQBbhXggo1N0q1fMBJ8qXcmvjAHaJSvF0vB1
+X172HP3G4DmADspo3ef9x8+xHw++hMaA/LSIEWbAqm22Up6/7+EY1/ArIaXyN69v
+yEDZDBJ9Wf3w9qlbbAfi7js0mjI6rkvt4/uTuy+c+ajuCiu/Cm25Jhu6UMMj7b5K
+Erwe25x/92h2hzEzwir25RfkJmqyC14WUd6ExHgqNMKeEYKWcRN/EHrfM0Noel61
+tIUsRFvrHsX1DwJ7NzTZYQiBMF9HVKI/d/LFgCt4SBKyw1tFtXV+EELo69ITDi+x
+QCMthy38ide+c1RUSgZwepPIFwSPcr2yEcy97l4k0Md5NRou1fnw
+-----END CERTIFICATE-----
diff --git a/test/certs/serfrootcacert.pem b/test/certs/serfrootcacert.pem
index cf61c9c..a5cea26 100644
--- a/test/certs/serfrootcacert.pem
+++ b/test/certs/serfrootcacert.pem
@@ -3,23 +3,23 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGzAZBgNVBAsMElRlc3QgU3VpdGUgUm9v
dCBDQTEVMBMGA1UEAwwMU2VyZiBSb290IENBMSUwIwYJKoZIhvcNAQkBFhZzZXJm
-cm9vdGNhQGV4YW1wbGUuY29tMB4XDTE1MDkyMDE5MTg0OVoXDTE4MDkxOTE5MTg0
-OVowga4xCzAJBgNVBAYTAkJFMRAwDgYDVQQIDAdBbnR3ZXJwMREwDwYDVQQHDAhN
+cm9vdGNhQGV4YW1wbGUuY29tMB4XDTE4MDQyOTA4NTAzNFoXDTIxMDQyODA4NTAz
+NFowga4xCzAJBgNVBAYTAkJFMRAwDgYDVQQIDAdBbnR3ZXJwMREwDwYDVQQHDAhN
ZWNoZWxlbjEfMB0GA1UECgwWSW4gU2VyZiB3ZSB0cnVzdCwgSW5jLjEbMBkGA1UE
CwwSVGVzdCBTdWl0ZSBSb290IENBMRUwEwYDVQQDDAxTZXJmIFJvb3QgQ0ExJTAj
BgkqhkiG9w0BCQEWFnNlcmZyb290Y2FAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3
-DQEBAQUAA4IBDwAwggEKAoIBAQDFuK4gqHm89lXVVX7PiHgXAJQRhMd2B1da7HhH
-2684/ELutwspKqsdsfDAQUNI2HkvQdklpcR7D+Q9wr6qLV26/vBS/GGFbLV2EYXu
-ezdlSW+bawKMRzQ++w1nclu9ZczPnYw21R/h7rFrYXChA+iklgNm0ZOmeI/vJ14i
-iT4eh00lQbrf8/jtqVt44h+2PvuBDOE99H1EG2H8Sdwd55S07zv3qx5jDyUgBtp5
-BoAAVRMM0tzyHe4fJvSu8FIyCPxkjvkwPTh0thg5rSD1BUTRhbj5zlvoq5D2WWnW
-zYM32neNBa1qOIQrz5KeBXgTb+jaLJuUxmKnbbssBvO1LD2tAgMBAAGjUDBOMAwG
-A1UdEwQFMAMBAf8wHQYDVR0OBBYEFN1RG5X8WJ0dQ42Qt/wI4nswIexxMB8GA1Ud
-IwQYMBaAFN1RG5X8WJ0dQ42Qt/wI4nswIexxMA0GCSqGSIb3DQEBCwUAA4IBAQBa
-peHLvPD9qy27XfvEmAfshZDC95+QEe7YEncKztP2N4OIAtTf/otOIdTA3bQF2HeC
-Pb5TZU26/l4uPcrsDAYYfHxfZ5ijzp//aL3JfRZA5H5TdIXtTnS0F1QoWdCbcdtg
-SVcuwphz2rTIlQIibMKxKmUY0GMYqLohXAnzWm6ne7m2EluAc8hHAE562/Z3UQsS
-IMWkMHhsJnZCt1qcZ93ZIGuTEcRvVB8TFOV/o5WMK+bW2T9JZ0CP8h96X4YLgfEI
-WrHTiIojvcNeviCC4Wu6AIWTg2Q3uIKBt8xE564QuOLNkvh2RDr3rPsgpI4xE5kf
-3q2PBC1IAhlLRm0qlzzB
+DQEBAQUAA4IBDwAwggEKAoIBAQDbq3/ECY1RPh4C9k0cbwrpGsIts6FaKslI1xZG
+myox882kOGOGB3uTpxUC/oWfgX0GHFId/1S/G6Re4MY/NLd3DIzV+VIsfvG679xW
+5GWgt2+Qw2Kyk08TH1PdSfFBYWjI4c3xAr7GAaOaSg+uvguQbHpOmS1jsoleul9T
+qh9xHWBgFOZhc/tCPNCa7NRVqnUjVcWOd0OhtU/IOi2TF2MbpGYhkLRmkQIwcjty
+SGhzxqBvqNbKom/sfttXN/0MjZGBB4KLeD8yxiWxjoEH8/yXgxOKLSepM+3GmZ8t
+Uzby3E71tz/m+tsIZBTUpK6D5AjbEyl2jQ812t6al6BTjTWXAgMBAAGjUDBOMAwG
+A1UdEwQFMAMBAf8wHQYDVR0OBBYEFN/dSMfIYygq691Uqe04ZwPM3UrzMB8GA1Ud
+IwQYMBaAFN/dSMfIYygq691Uqe04ZwPM3UrzMA0GCSqGSIb3DQEBCwUAA4IBAQAW
+hi0wGKivL1rJInnq4nMvhdSS2cgWXOj7WZaHSWE0lnWH9yxwF07WmYpHC9vJNigk
+pUEQJ0vh6/p2Q0zkHrZApeuojkvWnmlx4gYr3ULetiL8DDFGEOic8GjZW8PAcwAL
+O05LKZeziCNbVrzrXkvllqgNAS9zQ7RklcxNtWT8sLDzeO4vMWcHHfCOnVklD73K
+3O+4C2D94XEdKl+Orr/6PHlapTxSdZapMLbkMGY4ct9KkEZpLyPq0TPk56kQbbSg
+mzbdlSVL8tSsPUVYAHRDVnYDpShKj9WZkey+ROIvY7LAceFQSGtx4xCJ39AynIAA
+WEn+JM3755g6e+Rub7iQ
-----END CERTIFICATE-----
diff --git a/test/certs/serfserver_expired_cert.pem b/test/certs/serfserver_expired_cert.pem
index 56d2392..93a7d47 100644
--- a/test/certs/serfserver_expired_cert.pem
+++ b/test/certs/serfserver_expired_cert.pem
@@ -3,21 +3,21 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
-LmNvbTAeFw0xNTA5MjAxOTE4NTNaFw0xNDA5MjAxOTE4NTNaMIGqMQswCQYDVQQG
+LmNvbTAeFw0xODA0MjkwODUwMzdaFw0xNzA0MjkwODUwMzdaMIGqMQswCQYDVQQG
EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QxJTAjBgkqhkiG9w0BCQEWFnNlcmZz
ZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQDBx5VPS8uSwXFD/sDrfJXSSr+eedVpzXsme9MpOMdY/+Z5GOd94S0Q8RoWIeSo
-ffkIAbcdK0v2EI8Wu82Kio8Y/DBkBiOCe5MsTHzSg+uvC4/FARXUAZfsxqgPQmLc
-99GtPSVkZwbk7gYFnmhJSNM6fRG5vb0t3WJA7+xkzUoL78WK76hlhfAZ0Q6AzlPl
-pMOXAF0YKKhghGo1/vKd8i66o54SLZ4FYkR/LCW4b/hvLZGND75hYR1ujcwI1IyE
-m/geBtF8BfKSqNVsjxDULiWQ2egK6BwtOBd+IyYJOjElQmak0guimykBOIkiv6r5
-Z/np1OXr5v4AWW2flkPYFL1ZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACPTIazy
-1oWLHfuOGE8a3KGOyfsmRG70J+/08noxOb+0kC4/cAVFMvrLQJY0qYINmkeJjch0
-KW4/D4RZis5lVaP4qU+ctCJ/OHnNHtVNza9+RdSzC6K4JWcQjZ+sD1wQ5/rMtoJ9
-yzblrps8BKZvN6a+loVIUXtwHXMNjM7DP5gHNeOA8MGn6vz8cC9Wrmyi+kA52Aap
-EMGwWz5kmIyYX6zq8zngOVh1hGaw0qLEmJpZ485e+ow5YyIUI7EfxyDWIXifId8h
-aFR3A2jDL17gnwR1DvNtyFMNk++1oOdm1II9ueQQdMY55CJgalZUO72qW1HLiagf
-sa1VMFzmN3HpGCY=
+AQC3iSkUM3L4K2vLMW86QJGY4hsIbLcm1U0HHSvpmEu5LjvnI+KTolZ8eTzrWJ8N
+M2a2gReIhliGsnJEmnjCDTnfCe4GFAFQPMhwU3y3SAeQKRbGEwuqKEB/ZXpMNchb
+PY8M1+2ciP8YKB4ybj/w7+gi7JiIuwTYGQW7vrojkvRZmWu6dPhl8l49HI0lPY63
+huTqNKGZfZQ+JAiHaexiTU57CI8Qg3kZjvveX22yypazS80/TjbkQAuvw/YzGWQF
+7colQgRgsI27XHk2DHRRzZPKFHHry5wE9H44T4V0/mNXQ568Q4Ge2carcNKiZudI
+GaQhr7JIfKPj2H9eVJouPbZpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGu6BXtE
+1w706QJaXQoU1DHmcDR4uok18hwU68lG/+yZSXcTknaBANoqR1LKV25X4F1GoanM
+pE+aqsevV8m0KXeZZ0hTYNB3co74oWeouiUhv1iHX3Y/JajXe4udTqLpqeIdKEi/
+ss6olEhrtUxnPPHwD5yqv8DJaLGEC0FEk8yX5/IE97/hBTh7kSD0+kCJpJynzsJr
+ReEg/t+38i5295M1y7cDfLQqY6Jkw1DksBcPbcycHWAmPCyWtUaCyqI+FigzBWcg
+mKm1w7N4YGubejNatTQ3IkFKFLy1LxeiEcmNBWaxNR9OzUBl/Q1SiE5J2vV8IUVN
+aLDHRp/36KyNs3E=
-----END CERTIFICATE-----
diff --git a/test/certs/serfserver_future_cert.pem b/test/certs/serfserver_future_cert.pem
index 4b7524e..805f173 100644
--- a/test/certs/serfserver_future_cert.pem
+++ b/test/certs/serfserver_future_cert.pem
@@ -3,21 +3,21 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
-LmNvbTAeFw0yNTA5MTcxOTE4NTNaFw0yODA5MTYxOTE4NTNaMIGqMQswCQYDVQQG
+LmNvbTAeFw0yODA0MjYwODUwMzdaFw0zMTA0MjYwODUwMzdaMIGqMQswCQYDVQQG
EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QxJTAjBgkqhkiG9w0BCQEWFnNlcmZz
ZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQDBx5VPS8uSwXFD/sDrfJXSSr+eedVpzXsme9MpOMdY/+Z5GOd94S0Q8RoWIeSo
-ffkIAbcdK0v2EI8Wu82Kio8Y/DBkBiOCe5MsTHzSg+uvC4/FARXUAZfsxqgPQmLc
-99GtPSVkZwbk7gYFnmhJSNM6fRG5vb0t3WJA7+xkzUoL78WK76hlhfAZ0Q6AzlPl
-pMOXAF0YKKhghGo1/vKd8i66o54SLZ4FYkR/LCW4b/hvLZGND75hYR1ujcwI1IyE
-m/geBtF8BfKSqNVsjxDULiWQ2egK6BwtOBd+IyYJOjElQmak0guimykBOIkiv6r5
-Z/np1OXr5v4AWW2flkPYFL1ZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEGt3pLx
-BwWeRd19WVsXpOoUMLtRbRUGIJtouFSIwH4XTexqtGvW7iPsMRaeGC9NFRCmJJP/
-Pu10+tpBT7BpW46ObfvLAbDi8r/LlHpt8qajAO7N51/ELZdyDbW29mry+562eF1C
-fVsk6U97hDeqbXpOCgdaFoyIGiQRCCstkCG8kGOOajRlrGfxo8+VaqU877NzWyf9
-fAu+q7SrzWCpOCbcjylICJc1rtNNnLjnSsw1gu9as7PBdI6MJSGID3BxYENDJCPL
-bzZfmCG46De6+MiVI4jJyL7RWjRNjR7zyMdoUEEMXyfn3Oy4H9KCly1icTmMwmz9
-Wy1l/P9tXx5hj4I=
+AQC3iSkUM3L4K2vLMW86QJGY4hsIbLcm1U0HHSvpmEu5LjvnI+KTolZ8eTzrWJ8N
+M2a2gReIhliGsnJEmnjCDTnfCe4GFAFQPMhwU3y3SAeQKRbGEwuqKEB/ZXpMNchb
+PY8M1+2ciP8YKB4ybj/w7+gi7JiIuwTYGQW7vrojkvRZmWu6dPhl8l49HI0lPY63
+huTqNKGZfZQ+JAiHaexiTU57CI8Qg3kZjvveX22yypazS80/TjbkQAuvw/YzGWQF
+7colQgRgsI27XHk2DHRRzZPKFHHry5wE9H44T4V0/mNXQ568Q4Ge2carcNKiZudI
+GaQhr7JIfKPj2H9eVJouPbZpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACIDepxq
+H9gYGGCpvHvTBKeGjCSMZrAlq5PKxUqJIZPcyA1liD2s5IqCCn/fTUMkOK4Po7Ns
+/dgFNdz7NKrmpkm85WzaLpBtPEEuO8lMJehayKwryAKHRTw30Batvd30kvyxctQT
+tumRPoVMRhLZ6VEkk1ljKhut5pOwUg8b26TRSR6P+h3jKZlEoig5te4COpcxhd+6
+QS3XIkGn0PP+41ohK4qYdRPdKbLesDt88MFaHTcht6h/rDB7zc/JIfTPDoTXdKc+
+7QA2KbG1/wul2z6vwA5Hvr3uDVw40bE8jjXJyq50P2jsgYmm8NZIKyN8fMuUUs10
+YZkT876cTSlGptg=
-----END CERTIFICATE-----
diff --git a/test/certs/serfserver_san_nocn_cert.pem b/test/certs/serfserver_san_nocn_cert.pem
index a71c328..29a370e 100644
--- a/test/certs/serfserver_san_nocn_cert.pem
+++ b/test/certs/serfserver_san_nocn_cert.pem
@@ -3,21 +3,21 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
-LmNvbTAeFw0xNTA5MjAxOTE4NTNaFw0yODA5MTYxOTE4NTNaMIGWMQswCQYDVQQG
+LmNvbTAeFw0xODA0MjkwODUwMzdaFw0zMTA0MjYwODUwMzdaMIGWMQswCQYDVQQG
EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
U2VydmVyMSUwIwYJKoZIhvcNAQkBFhZzZXJmc2VydmVyQGV4YW1wbGUuY29tMIIB
-IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwceVT0vLksFxQ/7A63yV0kq/
-nnnVac17JnvTKTjHWP/meRjnfeEtEPEaFiHkqH35CAG3HStL9hCPFrvNioqPGPww
-ZAYjgnuTLEx80oPrrwuPxQEV1AGX7MaoD0Ji3PfRrT0lZGcG5O4GBZ5oSUjTOn0R
-ub29Ld1iQO/sZM1KC+/Fiu+oZYXwGdEOgM5T5aTDlwBdGCioYIRqNf7ynfIuuqOe
-Ei2eBWJEfywluG/4by2RjQ++YWEdbo3MCNSMhJv4HgbRfAXykqjVbI8Q1C4lkNno
-CugcLTgXfiMmCToxJUJmpNILopspATiJIr+q+Wf56dTl6+b+AFltn5ZD2BS9WQID
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt4kpFDNy+CtryzFvOkCRmOIb
+CGy3JtVNBx0r6ZhLuS475yPik6JWfHk861ifDTNmtoEXiIZYhrJyRJp4wg053wnu
+BhQBUDzIcFN8t0gHkCkWxhMLqihAf2V6TDXIWz2PDNftnIj/GCgeMm4/8O/oIuyY
+iLsE2BkFu766I5L0WZlrunT4ZfJePRyNJT2Ot4bk6jShmX2UPiQIh2nsYk1OewiP
+EIN5GY773l9tssqWs0vNP0425EALr8P2MxlkBe3KJUIEYLCNu1x5Ngx0Uc2TyhRx
+68ucBPR+OE+FdP5jV0OevEOBntnGq3DSombnSBmkIa+ySHyj49h/XlSaLj22aQID
AQABoxswGTAXBgNVHREBAf8EDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQAD
-ggEBABiHZ4d4bdXkijuB3Dpz3R4jwPV2jcPL20tr4vSRfRiQ0M49Gd3nyjMTVeCA
-ihK/AJnCJ/Vxgr8XH7Hp/i/7FqXMKrjn2KFTfyqWHHRgvEAMQrQIRXO5BrJNlZLO
-FDwQfIw+Tn+Nq8hJ2+pAckROt013debGVEnQMF/+gAC2QvR+pfwVyV8bixmqYdk7
-2wTfikF+iAeWPBKfzwrytni/8qcfAW0b6AQu6d0EwoIczHmZptM4JaEUr0X77Og4
-Z82lx3HCzUNKcroGQgYYo5wDk0bfQSyzE1gN71YGTcR18ey+uWPHEQZftctnN8p+
-WNUtz4gBNO1JNPpjXAkNlcoWv/I=
+ggEBAKB34499itkwFSI5SZd5Xw6RJbCUrv6D5M2geWjv0EvBq4MP9+eeo4rEtztV
+Eh6PlgdfH15L+UXWgM0kRiP7z8Gx7hVyvbM7QvAfzChkDthzcQhXcPr0Uk7B5fi1
+ZeB6qnwM8IX+RiRoxi590W0vn7O92sRiM52NXjBQ3Fw+eVtyOvbBFmiZ3xPPAIQv
+G90HDg4zoOe4h0y747j9xFwzOmzpLwsmRMrtXio72PSbLHX4cqYhUfkqivQ2HM92
+FzR6zYvmg5NHPVPJiLh6ZC6HppEKOWXfGkQiGUMoaEvpKY+K5NasjOqwQAR/MCsa
+BvnAvOm4IxagKNYTQude+gr37rs=
-----END CERTIFICATE-----
diff --git a/test/certs/serfserver_san_ocsp_cert.pem b/test/certs/serfserver_san_ocsp_cert.pem
new file mode 100644
index 0000000..87da933
--- /dev/null
+++ b/test/certs/serfserver_san_ocsp_cert.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIDAYa0MA0GCSqGSIb3DQEBCwUAMIGgMQswCQYDVQQGEwJC
+RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
+FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
+EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
+LmNvbTAeFw0xODA0MjkwODUwMzdaFw0zMTA0MjYwODUwMzdaMIGqMQswCQYDVQQG
+EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
+BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
+U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QxJTAjBgkqhkiG9w0BCQEWFnNlcmZz
+ZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQC3iSkUM3L4K2vLMW86QJGY4hsIbLcm1U0HHSvpmEu5LjvnI+KTolZ8eTzrWJ8N
+M2a2gReIhliGsnJEmnjCDTnfCe4GFAFQPMhwU3y3SAeQKRbGEwuqKEB/ZXpMNchb
+PY8M1+2ciP8YKB4ybj/w7+gi7JiIuwTYGQW7vrojkvRZmWu6dPhl8l49HI0lPY63
+huTqNKGZfZQ+JAiHaexiTU57CI8Qg3kZjvveX22yypazS80/TjbkQAuvw/YzGWQF
+7colQgRgsI27XHk2DHRRzZPKFHHry5wE9H44T4V0/mNXQ568Q4Ge2carcNKiZudI
+GaQhr7JIfKPj2H9eVJouPbZpAgMBAAGjTDBKMBQGA1UdEQQNMAuCCWxvY2FsaG9z
+dDAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9sb2NhbGhvc3Q6
+MTcwODAwDQYJKoZIhvcNAQELBQADggEBADu9uTPNYByTTLT2dQacpFp7hDDaCR8R
+zQ7g2ts3L+LX9fsUIwDfLEqCDm3/fFl0aNvzQ5m+n2OpuOupK+x4EHpKy1HaBGNx
+h35TqmPCO+HR8JVw6Oh+rDaMU95dsOiDbsDk+BiGq/O1hujlrmcA+ddEhM6xi77w
+e5w/o57hGfJ7h8KqGivuKxhRLzRGyJtZuu5p2kz+J05pGB8hmR5+yNgzROCpgOHQ
+zrnxLQF33GZvnHTzZ/IHIKK7CtwyP5lb9SPorXN9YzNsAPtphNMiTVfhRVleVdDn
+RtU1KysX29DJe2c0bK6wspKefKkFdxdrNcpIDFIgwp4qRx9rTi9OqAU=
+-----END CERTIFICATE-----
diff --git a/test/certs/serfservercert.pem b/test/certs/serfservercert.pem
index 18f2087..b2d7d7e 100644
--- a/test/certs/serfservercert.pem
+++ b/test/certs/serfservercert.pem
@@ -3,21 +3,21 @@
RTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNVBAoM
FkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xFjAUBgNVBAsMDVRlc3QgU3VpdGUgQ0Ex
EDAOBgNVBAMMB1NlcmYgQ0ExITAfBgkqhkiG9w0BCQEWEnNlcmZjYUBleGFtcGxl
-LmNvbTAeFw0xNTA5MjAxOTE4NTNaFw0xODA5MTkxOTE4NTNaMIGqMQswCQYDVQQG
+LmNvbTAeFw0xODA0MjkwODUwMzdaFw0yMTA0MjgwODUwMzdaMIGqMQswCQYDVQQG
EwJCRTEQMA4GA1UECAwHQW50d2VycDERMA8GA1UEBwwITWVjaGVsZW4xHzAdBgNV
BAoMFkluIFNlcmYgd2UgdHJ1c3QsIEluYy4xGjAYBgNVBAsMEVRlc3QgU3VpdGUg
U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QxJTAjBgkqhkiG9w0BCQEWFnNlcmZz
ZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQDBx5VPS8uSwXFD/sDrfJXSSr+eedVpzXsme9MpOMdY/+Z5GOd94S0Q8RoWIeSo
-ffkIAbcdK0v2EI8Wu82Kio8Y/DBkBiOCe5MsTHzSg+uvC4/FARXUAZfsxqgPQmLc
-99GtPSVkZwbk7gYFnmhJSNM6fRG5vb0t3WJA7+xkzUoL78WK76hlhfAZ0Q6AzlPl
-pMOXAF0YKKhghGo1/vKd8i66o54SLZ4FYkR/LCW4b/hvLZGND75hYR1ujcwI1IyE
-m/geBtF8BfKSqNVsjxDULiWQ2egK6BwtOBd+IyYJOjElQmak0guimykBOIkiv6r5
-Z/np1OXr5v4AWW2flkPYFL1ZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI6WJcKh
-NItjHNK5d8crJU+SGXgqQ3nItMEdri23lCALBdxhtPKEdrN4K3r8zZVFdb26qMDa
-c45Ap4xCGYtjV366wQ6W414jFFevaHQBAoBe4m5P41ZXqF9IxxhGOlyhY3vn3KNQ
-N5V7ZEykcFDmRMt+4eAOfnoIhbjkmt2UA+t9Bc+Efb44wcr1QDKcdD/Q8Kg4ktgc
-r5u9zJeZg4nTvCn0mhj939CMalx1TC/13k/sbCLPYA0VMHdo/7JL4LuedSockPu9
-ykn66wmX/epHouRlrtapNY2bC5DO/Q4BPkzwUzK7Br8cw8oe+dM/FmwmgKPyWwHg
-yjlCT0D4mUvHaAU=
+AQC3iSkUM3L4K2vLMW86QJGY4hsIbLcm1U0HHSvpmEu5LjvnI+KTolZ8eTzrWJ8N
+M2a2gReIhliGsnJEmnjCDTnfCe4GFAFQPMhwU3y3SAeQKRbGEwuqKEB/ZXpMNchb
+PY8M1+2ciP8YKB4ybj/w7+gi7JiIuwTYGQW7vrojkvRZmWu6dPhl8l49HI0lPY63
+huTqNKGZfZQ+JAiHaexiTU57CI8Qg3kZjvveX22yypazS80/TjbkQAuvw/YzGWQF
+7colQgRgsI27XHk2DHRRzZPKFHHry5wE9H44T4V0/mNXQ568Q4Ge2carcNKiZudI
+GaQhr7JIfKPj2H9eVJouPbZpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAEaSp7r
+kj3nMUss0UIL3H3J2KWih3jH6TPS/ioS9vo+6uONRilL7Lf2cBIBu0MQ6XHw4Q1k
+e2db8ucHsg/579kek1m7ha5qwVrCd4z7ozAZOc6Lk6oMyadIWiyKOT3pu7FCCprI
+NCbnPON4zgvt0IComP3vRRHDOkhAKSxnEYlJtapTiPI93bn8min3qIoN/YZRpCLp
+Y7pfwfZNe1S9rtIkrCjXRfUKeRgd1ntB0yC44wwqAyFR7X1bcqSL03QcIqeQuSDG
+sZf2PMNnHlKVq+tTfiQ+U4g46zc+f2Ilx/bnT7srADLtOwaYrhE5lZP3uyw+OORS
+kEQ4qqok1Ey/yHE=
-----END CERTIFICATE-----
diff --git a/test/certs/serfservercrl.pem b/test/certs/serfservercrl.pem
index a0c429f..ed04def 100644
--- a/test/certs/serfservercrl.pem
+++ b/test/certs/serfservercrl.pem
@@ -2,13 +2,13 @@
MIICDzCB+DANBgkqhkiG9w0BAQQFADCBoDELMAkGA1UEBhMCQkUxEDAOBgNVBAgM
B0FudHdlcnAxETAPBgNVBAcMCE1lY2hlbGVuMR8wHQYDVQQKDBZJbiBTZXJmIHdl
IHRydXN0LCBJbmMuMRYwFAYDVQQLDA1UZXN0IFN1aXRlIENBMRAwDgYDVQQDDAdT
-ZXJmIENBMSEwHwYJKoZIhvcNAQkBFhJzZXJmY2FAZXhhbXBsZS5jb20XDTE1MDky
-MDE5MTg1M1oXDTE4MDkxOTE5MTg1M1owJjAkAgMBhrQYDzIwMTUwOTIwMTkxODUz
-WjAMMAoGA1UdFQQDCgEAMA0GCSqGSIb3DQEBBAUAA4IBAQBA4ZquCobjlUtQJfJs
-48DKF4qj0L0zWmYl4/SX5dS18UdDOhSB1BQQQPv8eYwFWrIKvNvnj0qq4zzlQXI4
-qOBLv6OQQ4q54EBmaskKfO5A3/QePONWoh3ZB7nvY5pVuic/nIoBLxYSBCo1G5ft
-MoAs1S/pGOlg3awXizdUKKzdjdOu/eR/ZETvYLsmJifIzWkWz9F4F733jv4tJewF
-nLvH+6VOzIDMQHw/uhO3MmL+mKYwWo35RFPUN5mpHwTSoZ6GdYEBEyIma4Bh96RV
-O7RQB0/JaY/zBuqTVPI3ian7U9XfcZ7hmxG3uvyIMqkt/n3WpZVDT9Hj1PQWRQ+u
-qDGi
+ZXJmIENBMSEwHwYJKoZIhvcNAQkBFhJzZXJmY2FAZXhhbXBsZS5jb20XDTE4MDQy
+OTA4NTAzOFoXDTIxMDQyODA4NTAzOFowJjAkAgMBhrQYDzIwMTgwNDI5MDg1MDM4
+WjAMMAoGA1UdFQQDCgEAMA0GCSqGSIb3DQEBBAUAA4IBAQAsqgvpTT/1CGu9irYP
+HbaZXsENTfrLaWDNqXKaJsL5sest3HOc8EwxNRCOPcE+UMG61ZYmzGN89qlnKkfX
+1lQ7B6sxXRKBdi1dnk6NbYhl3YFMZfgcZWpIJPKS7X/Tfw72dUnpmbfmLg9l2FCp
+K7XVDtCQaUwNVONcZg2KjYVcmlcnRb/iGSbJjglGfx5MivlJoMKZc3kcL2nu6L8s
+EIAodjZrghx2oWCVerIgkypPmNsqI6HSvMDxPHQTy2oEgzhXD1u4Q2NOxB7iRRtf
+jVob/ERh3T0M09Xq5pj9E1fHQvxOW39fGimEuwcs8qyIkcVGgAUlYfSWfLsHGUGe
+MP/Q
-----END X509 CRL-----
diff --git a/test/test_ssl.c b/test/test_ssl.c
index 66426f4..5f32803 100644
--- a/test/test_ssl.c
+++ b/test/test_ssl.c
@@ -29,6 +29,12 @@
#include "test_serf.h"
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+#ifndef OPENSSL_NO_OCSP /* requires openssl 0.9.7 or later */
+#include <openssl/ocsp.h>
+#endif
+
#if defined(WIN32) && defined(_DEBUG)
/* Include this file to allow running a Debug build of serf with a Release
build of OpenSSL. */
@@ -168,29 +174,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, "A8:73:BA:89:C5:2C:54:84:1A:2C:E8:04:87:EE:C1:04:48:83:86:F3",
apr_hash_get(kv, "sha1", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Mar 21 13:18:17 2008 GMT",
+ CuAssertStrEquals(tc, "Apr 29 08:50:37 2018 GMT",
apr_hash_get(kv, "notBefore", APR_HASH_KEY_STRING));
- CuAssertStrEquals(tc, "Mar 21 13:18:17 2011 GMT",
+ CuAssertStrEquals(tc, "Apr 26 08:50:37 2031 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 +267,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 +311,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 +2315,360 @@
#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,
+ serf_ssl_certificate_t **signer,
+ serf_ssl_certificate_t **root)
+{
+ test_baton_t *tb = tc->testBaton;
+ apr_status_t status;
+
+ if (cert) {
+ 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);
+ }
+
+ if (issuer) {
+ 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);
+ }
+
+ if (signer) {
+ status = serf_ssl_load_cert_file(
+ signer,
+ get_srcdir_file(tb->pool, "test/certs/serfocspresponder.pem"),
+ tb->pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertPtrNotNull(tc, *signer);
+ }
+
+ if (root) {
+ status = serf_ssl_load_cert_file(
+ root,
+ get_srcdir_file(tb->pool, "test/certs/serfrootcacert.pem"),
+ tb->pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertPtrNotNull(tc, *root);
+ }
+}
+
+static void create_ocsp_response(CuTest *tc,
+ const void **ocsp_response,
+ apr_size_t *ocsp_response_size,
+ serf_ssl_ocsp_request_t *req,
+ serf_ssl_certificate_t *signer,
+ serf_ssl_certificate_t *issuer,
+ serf_ssl_certificate_t *root,
+ EVP_PKEY *pkey,
+ int ignore_nonce,
+ apr_pool_t* pool)
+{
+ /* XXX The following four assignmengs rely on the specific struct
+ definitions in ssl_buckets.c. */
+ X509 *signer_cert = (signer ? *(X509**)signer : NULL);
+ X509 *issuer_cert = (issuer ? *(X509**)issuer : NULL);
+ X509 *root_cert = (root ? *(X509**)root : NULL);
+ OCSP_REQUEST *ocsp_req = *(OCSP_REQUEST**)req;
+ int id_count = OCSP_request_onereq_count(ocsp_req);
+
+ ASN1_TIME *this_update = X509_gmtime_adj(NULL, 0);
+ ASN1_TIME *next_update = X509_time_adj_ex(NULL, 1, 0, NULL);
+ OCSP_BASICRESP *basic = OCSP_BASICRESP_new();
+
+ OCSP_ONEREQ *one_req = NULL;
+ OCSP_CERTID *cid = NULL;
+ OCSP_RESPONSE *rsp = NULL;
+
+ *ocsp_response = NULL;
+ *ocsp_response_size = 0;
+
+ if (id_count != 1 || !this_update || !next_update || !basic)
+ goto cleanup;
+
+ /* Populate and sign the basic response. */
+ one_req = OCSP_request_onereq_get0(ocsp_req, 0);
+ cid = OCSP_onereq_get0_id(one_req);
+ OCSP_basic_add1_status(basic, cid,
+ V_OCSP_CERTSTATUS_GOOD,
+ 0, NULL, this_update, next_update);
+
+ if (!ignore_nonce)
+ OCSP_copy_nonce(basic, ocsp_req);
+
+ if (signer_cert) {
+ STACK_OF(X509) *ca = NULL;
+
+ if (issuer_cert || root_cert) {
+ ca = sk_X509_new_null();
+ if (!ca)
+ goto cleanup;
+
+ if (issuer_cert && !sk_X509_push(ca, issuer_cert)) {
+ sk_X509_free(ca);
+ goto cleanup;
+ }
+ if (root_cert && !sk_X509_push(ca, root_cert)) {
+ sk_X509_free(ca);
+ goto cleanup;
+ }
+ }
+
+ if (!OCSP_basic_sign(basic, signer_cert, pkey,
+ EVP_sha1(), ca, 0)) {
+ sk_X509_free(ca);
+ goto cleanup;
+ }
+
+ sk_X509_free(ca);
+ }
+
+ /* Create the response and convert it to DER form. */
+ rsp = OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, basic);
+ if (rsp) {
+ void *der;
+ int der_len;
+ unsigned char *unused;
+
+ der_len = i2d_OCSP_RESPONSE(rsp, NULL);
+ if (der_len < 0)
+ goto cleanup;
+
+ unused = der = apr_palloc(pool, der_len);
+ der_len = i2d_OCSP_RESPONSE(rsp, &unused); /* unused is incremented */
+ if (der_len < 0)
+ goto cleanup;
+
+ *ocsp_response = der;
+ *ocsp_response_size = der_len;
+ }
+
+ cleanup:
+ ASN1_TIME_free(this_update);
+ ASN1_TIME_free(next_update);
+ OCSP_BASICRESP_free(basic);
+ OCSP_RESPONSE_free(rsp);
+}
+
+static int pkey_password_cb(char *buf, int size, int rwflag, void *u)
+{
+ (void)rwflag;
+ (void)u;
+
+ static const char passphrase[] = "serftest";
+
+ const int passlen = (int)strlen(passphrase);
+ if (size <= passlen)
+ return 0;
+
+ strcpy(buf, passphrase);
+ return passlen;
+}
+
+static apr_status_t verify_ocsp_response(CuTest *tc,
+ int ignore_signer,
+ int invalid_signer,
+ int skip_nonce,
+ int ignore_nonce)
+{
+ test_baton_t *tb = tc->testBaton;
+ serf_ssl_certificate_t *cert = NULL;
+ serf_ssl_certificate_t *issuer = NULL;
+ serf_ssl_certificate_t *signer = NULL;
+ serf_ssl_certificate_t *root = NULL;
+ serf_ssl_ocsp_request_t *req = NULL;
+ const void* ocsp_response = NULL;
+ apr_size_t ocsp_response_size = 0;
+ serf_ssl_ocsp_response_t *rsp = NULL;
+ EVP_PKEY *pkey = NULL;
+ int failures = 0;
+
+ load_ocsp_test_certs(tc, &cert, &issuer, &signer, &root);
+
+ req = serf_ssl_ocsp_request_create(cert, issuer,
+ (skip_nonce ? 0 : 1),
+ tb->pool, tb->pool);
+ if (!req)
+ return APR_EGENERAL;
+
+ if (!ignore_signer) {
+ const char *fname = (
+ invalid_signer
+ ? get_srcdir_file(tb->pool, "test/certs/private/serfrootcakey.pem")
+ : get_srcdir_file(tb->pool, "test/certs/private/serfserverkey.pem"));
+
+ FILE * pkey_file = fopen(fname, "rb");
+ if (pkey_file) {
+ pkey = PEM_read_PrivateKey(pkey_file, NULL, pkey_password_cb, NULL);
+ fclose(pkey_file);
+ }
+ if (!pkey)
+ return APR_EGENERAL;
+ }
+
+ create_ocsp_response(tc, &ocsp_response, &ocsp_response_size, req,
+ (ignore_signer ? NULL
+ : (invalid_signer ? root : signer)),
+ issuer, root, pkey, ignore_nonce, tb->pool);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ if (!ocsp_response || !ocsp_response_size)
+ return APR_EGENERAL;
+
+ rsp = serf_ssl_ocsp_response_parse(ocsp_response, ocsp_response_size,
+ &failures, tb->pool, tb->pool);
+ if (!rsp || failures != 0)
+ return SERF_ERROR_SSL_OCSP_RESPONSE_INVALID;
+ else {
+ serf_bucket_alloc_t *alloc;
+ serf_bucket_t *in_stream;
+ serf_bucket_t *decrypt_bkt;
+ serf_ssl_context_t *ssl_ctx;
+ apr_status_t status;
+
+ alloc = test__create_bucket_allocator(tc, tb->pool);
+ in_stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
+ decrypt_bkt = serf_bucket_ssl_decrypt_create(in_stream, NULL, alloc);
+ ssl_ctx = serf_bucket_ssl_decrypt_context_get(decrypt_bkt);
+
+ status = serf_ssl_trust_cert(ssl_ctx, issuer);
+ if (status == APR_SUCCESS)
+ status = serf_ssl_trust_cert(ssl_ctx, root);
+ if (status == APR_SUCCESS)
+ status = serf_ssl_ocsp_response_verify(ssl_ctx, rsp, req,
+ APR_TIME_C(0),
+ apr_time_from_sec(3600),
+ NULL, NULL, tb->pool);
+ return status;
+ }
+}
+#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, NULL, NULL);
+
+ /* 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, NULL, NULL);
+
+ 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 */
+}
+
+static void test_ssl_ocsp_verify_response(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+ apr_status_t status = verify_ocsp_response(tc, 0, 0, 0, 0);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+#endif /* OPENSSL_NO_OCSP */
+}
+
+static void test_ssl_ocsp_verify_response_no_nonce(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+ apr_status_t status = verify_ocsp_response(tc, 0, 0, 1, 0);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+#endif /* OPENSSL_NO_OCSP */
+}
+
+static void test_ssl_ocsp_verify_response_missing_nonce(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+ apr_status_t status = verify_ocsp_response(tc, 0, 0, 0, 1);
+ CuAssertIntEquals(tc, SERF_ERROR_SSL_OCSP_RESPONSE_INVALID, status);
+#endif /* OPENSSL_NO_OCSP */
+}
+
+static void test_ssl_ocsp_verify_response_ignore_missing_nonce(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+ apr_status_t status = verify_ocsp_response(tc, 0, 0, 1, 1);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+#endif /* OPENSSL_NO_OCSP */
+}
+
+static void test_ssl_ocsp_verify_response_no_signer(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+ apr_status_t status = verify_ocsp_response(tc, 1, 0, 0, 0);
+ CuAssertIntEquals(tc, SERF_ERROR_SSL_OCSP_RESPONSE_INVALID, status);
+#endif /* OPENSSL_NO_OCSP */
+}
+
+static void test_ssl_ocsp_verify_response_wrong_signer(CuTest *tc)
+{
+#ifndef OPENSSL_NO_OCSP
+ apr_status_t status = verify_ocsp_response(tc, 0, 1, 0, 0);
+ CuAssertIntEquals(tc, SERF_ERROR_SSL_OCSP_RESPONSE_INVALID, status);
+#endif /* OPENSSL_NO_OCSP */
+}
+
CuSuite *test_ssl(void)
{
CuSuite *suite = CuSuiteNew();
@@ -2271,6 +2681,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 +2715,13 @@
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);
+ SUITE_ADD_TEST(suite, test_ssl_ocsp_verify_response);
+ SUITE_ADD_TEST(suite, test_ssl_ocsp_verify_response_no_nonce);
+ SUITE_ADD_TEST(suite, test_ssl_ocsp_verify_response_missing_nonce);
+ SUITE_ADD_TEST(suite, test_ssl_ocsp_verify_response_ignore_missing_nonce);
+ SUITE_ADD_TEST(suite, test_ssl_ocsp_verify_response_no_signer);
+ SUITE_ADD_TEST(suite, test_ssl_ocsp_verify_response_wrong_signer);
return suite;
}