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;
}