Modified: serf/trunk/test/test_ssl.c URL: http://svn.apache.org/viewvc/serf/trunk/test/test_ssl.c?rev=1830823&r1=1830822&r2=1830823&view=diff ============================================================================== --- serf/trunk/test/test_ssl.c (original) +++ serf/trunk/test/test_ssl.c Thu May 3 13:35:28 2018 @@ -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 @@ static void test_ssl_cert_certificate(Cu 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,17 +267,40 @@ static const char *extract_cert_from_pem return NULL; } -static void test_ssl_cert_export(CuTest *tc) +static const char* load_cert_file_der(CuTest *tc, + const char *path, + apr_pool_t *pool) { - test_baton_t *tb = tc->testBaton; - serf_ssl_certificate_t *cert = NULL; apr_file_t *fp; apr_finfo_t file_info; - const char *base64derbuf; 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; + const char *extractedbuf; + const char *base64derbuf; + apr_status_t status; + status = serf_ssl_load_cert_file(&cert, get_srcdir_file(tb->pool, @@ -273,25 +311,43 @@ static void test_ssl_cert_export(CuTest /* 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); + extractedbuf = load_cert_file_der(tc, + get_srcdir_file(tb->pool, + "test/serftestca.pem"), + tb->pool); + base64derbuf = serf_ssl_cert_export(cert, tb->pool); - 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); + CuAssertStrEquals(tc, extractedbuf, base64derbuf); +} - status = apr_file_read_full(fp, pembuf, file_info.size, &pemlen); +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); - pembuf[file_info.size] = '\0'; + CuAssertPtrNotNull(tc, cert); - base64derbuf = serf_ssl_cert_export(cert, tb->pool); + /* 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); - CuAssertStrEquals(tc, - extract_cert_from_pem(pembuf, tb->pool), - base64derbuf); + base64derbuf = serf_ssl_cert_export2(imported_cert, tb->pool, tb->pool); + CuAssertStrEquals(tc, extractedbuf, base64derbuf); } /***************************************************************************** @@ -2259,6 +2315,360 @@ static void test_ssl_alpn_negotiate(CuTe #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 @@ CuSuite *test_ssl(void) 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 @@ CuSuite *test_ssl(void) 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; }