| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <apr.h> |
| #include <apr_pools.h> |
| #include <apr_strings.h> |
| #include <apr_env.h> |
| #include <apr_md5.h> |
| |
| #include "serf.h" |
| #include "serf_bucket_types.h" |
| |
| #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) && defined(SERF_HAVE_OPENSSL_APPLINK_C) |
| /* Include this file to allow running a Debug build of serf with a Release |
| build of OpenSSL. Note: his file is not available on OpenSSL 1.1.x. */ |
| #if defined(_MSC_VER) && _MSC_VER >= 1400 |
| #pragma warning(push) |
| #pragma warning(disable: 4152) |
| #endif |
| #include <openssl/applink.c> |
| #if defined(_MSC_VER) && _MSC_VER >= 1400 |
| #pragma warning(pop) |
| #endif |
| #endif |
| |
| /* Test setting up the openssl library. */ |
| static void test_ssl_init(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| serf_bucket_t *decrypt_bkt; |
| serf_bucket_t *encrypt_bkt; |
| serf_bucket_t *in_stream; |
| serf_bucket_t *out_stream; |
| serf_ssl_context_t *ssl_context; |
| apr_status_t status; |
| |
| serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool); |
| |
| in_stream = SERF_BUCKET_SIMPLE_STRING("", alloc); |
| out_stream = SERF_BUCKET_SIMPLE_STRING("", alloc); |
| |
| decrypt_bkt = serf_bucket_ssl_decrypt_create(in_stream, NULL, |
| alloc); |
| ssl_context = serf_bucket_ssl_decrypt_context_get(decrypt_bkt); |
| |
| encrypt_bkt = serf_bucket_ssl_encrypt_create(out_stream, ssl_context, |
| alloc); |
| |
| status = serf_ssl_use_default_certificates(ssl_context); |
| |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| serf_bucket_destroy(decrypt_bkt); |
| serf_bucket_destroy(encrypt_bkt); |
| } |
| |
| |
| /* Test that loading a custom CA certificate file works. */ |
| static void test_ssl_load_cert_file(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| serf_ssl_certificate_t *cert = NULL; |
| |
| apr_status_t 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); |
| } |
| |
| /* Test that reading the subject from a custom CA certificate file works. */ |
| static void test_ssl_cert_subject(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| apr_hash_t *subject; |
| serf_ssl_certificate_t *cert = NULL; |
| 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); |
| |
| subject = serf_ssl_cert_subject(cert, tb->pool); |
| CuAssertPtrNotNull(tc, subject); |
| |
| CuAssertStrEquals(tc, "Serf", |
| apr_hash_get(subject, "CN", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Test Suite", |
| apr_hash_get(subject, "OU", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "In Serf we trust, Inc.", |
| apr_hash_get(subject, "O", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Mechelen", |
| apr_hash_get(subject, "L", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Antwerp", |
| apr_hash_get(subject, "ST", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "BE", |
| apr_hash_get(subject, "C", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "serf@example.com", |
| apr_hash_get(subject, "E", APR_HASH_KEY_STRING)); |
| } |
| |
| /* Test that reading the issuer from a custom CA certificate file works. */ |
| static void test_ssl_cert_issuer(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| apr_hash_t *issuer; |
| serf_ssl_certificate_t *cert = NULL; |
| 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); |
| |
| issuer = serf_ssl_cert_issuer(cert, tb->pool); |
| CuAssertPtrNotNull(tc, issuer); |
| |
| /* TODO: create a new test certificate with different issuer and subject. */ |
| CuAssertStrEquals(tc, "Serf", |
| apr_hash_get(issuer, "CN", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Test Suite", |
| apr_hash_get(issuer, "OU", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "In Serf we trust, Inc.", |
| apr_hash_get(issuer, "O", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Mechelen", |
| apr_hash_get(issuer, "L", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Antwerp", |
| apr_hash_get(issuer, "ST", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "BE", |
| apr_hash_get(issuer, "C", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "serf@example.com", |
| apr_hash_get(issuer, "E", APR_HASH_KEY_STRING)); |
| } |
| |
| /* Test that reading the notBefore,notAfter,sha1 fingerprint and subjectAltNames |
| from a custom CA certificate file works. */ |
| static void test_ssl_cert_certificate(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| 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/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, "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, "Apr 29 08:50:37 2018 GMT", |
| apr_hash_get(kv, "notBefore", APR_HASH_KEY_STRING)); |
| CuAssertStrEquals(tc, "Apr 26 08:50:37 2031 GMT", |
| apr_hash_get(kv, "notAfter", APR_HASH_KEY_STRING)); |
| |
| san_arr = apr_hash_get(kv, "subjectAltName", APR_HASH_KEY_STRING); |
| 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, |
| apr_pool_t *pool) |
| { |
| enum { INIT, CERT_BEGIN, CERT_FOUND } state; |
| serf_bucket_t *pembkt; |
| const char *begincert = "-----BEGIN CERTIFICATE-----"; |
| const char *endcert = "-----END CERTIFICATE-----"; |
| char *certdata = ""; |
| apr_size_t certlen = 0; |
| apr_status_t status = APR_SUCCESS; |
| |
| serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(pool, |
| NULL, NULL); |
| |
| /* Extract the certificate from the .pem file, also remove newlines. */ |
| pembkt = SERF_BUCKET_SIMPLE_STRING(pemdata, alloc); |
| state = INIT; |
| while (state != CERT_FOUND && status != APR_EOF) { |
| const char *data; |
| apr_size_t len; |
| int found; |
| |
| status = serf_bucket_readline(pembkt, SERF_NEWLINE_ANY, &found, |
| &data, &len); |
| if (SERF_BUCKET_READ_ERROR(status)) |
| return NULL; |
| |
| if (state == INIT) { |
| if (strncmp(begincert, data, strlen(begincert)) == 0) |
| state = CERT_BEGIN; |
| } else if (state == CERT_BEGIN) { |
| if (strncmp(endcert, data, strlen(endcert)) == 0) |
| state = CERT_FOUND; |
| else { |
| certdata = apr_pstrcat(pool, certdata, data, NULL); |
| certlen += len; |
| switch (found) { |
| case SERF_NEWLINE_CR: |
| case SERF_NEWLINE_LF: |
| certdata[certlen-1] = '\0'; |
| certlen --; |
| break; |
| case SERF_NEWLINE_CRLF: |
| certdata[certlen-2] = '\0'; |
| certlen-=2; |
| break; |
| } |
| } |
| } |
| } |
| |
| serf_bucket_destroy(pembkt); |
| |
| if (state == CERT_FOUND) |
| return certdata; |
| else |
| 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; |
| 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_export is supposed to be returning. */ |
| 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, 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); |
| } |
| |
| /***************************************************************************** |
| * SSL handshake tests |
| *****************************************************************************/ |
| static const char *server_certs[] = { |
| "serfservercert.pem", |
| "serfcacert.pem", |
| NULL }; |
| |
| static const char *all_server_certs[] = { |
| "serfservercert.pem", |
| "serfcacert.pem", |
| "serfrootcacert.pem", |
| NULL }; |
| |
| static const char *server_key = "private/serfserverkey.pem"; |
| |
| static apr_status_t validate_servercert(const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *subject; |
| subject = serf_ssl_cert_subject(cert, pool); |
| if (strcmp("localhost", |
| apr_hash_get(subject, "CN", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Test Suite Server", |
| apr_hash_get(subject, "OU", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("In Serf we trust, Inc.", |
| apr_hash_get(subject, "O", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Mechelen", |
| apr_hash_get(subject, "L", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Antwerp", |
| apr_hash_get(subject, "ST", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("BE", |
| apr_hash_get(subject, "C", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("serfserver@example.com", |
| apr_hash_get(subject, "E", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t validate_cacert(const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *subject; |
| subject = serf_ssl_cert_subject(cert, pool); |
| if (strcmp("Serf CA", |
| apr_hash_get(subject, "CN", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Test Suite CA", |
| apr_hash_get(subject, "OU", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("In Serf we trust, Inc.", |
| apr_hash_get(subject, "O", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Mechelen", |
| apr_hash_get(subject, "L", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Antwerp", |
| apr_hash_get(subject, "ST", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("BE", |
| apr_hash_get(subject, "C", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("serfca@example.com", |
| apr_hash_get(subject, "E", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t validate_rootcacert(const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *subject; |
| subject = serf_ssl_cert_subject(cert, pool); |
| if (strcmp("Serf Root CA", |
| apr_hash_get(subject, "CN", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Test Suite Root CA", |
| apr_hash_get(subject, "OU", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("In Serf we trust, Inc.", |
| apr_hash_get(subject, "O", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Mechelen", |
| apr_hash_get(subject, "L", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Antwerp", |
| apr_hash_get(subject, "ST", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("BE", |
| apr_hash_get(subject, "C", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("serfrootca@example.com", |
| apr_hash_get(subject, "E", APR_HASH_KEY_STRING)) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t |
| ssl_server_cert_cb_expect_failures(void *baton, int failures, |
| const serf_ssl_certificate_t *cert) |
| { |
| test_baton_t *tb = baton; |
| int expected_failures = *(int *)tb->user_baton; |
| |
| tb->result_flags |= TEST_RESULT_SERVERCERTCB_CALLED; |
| |
| /* We expect an error from the certificate validation function. */ |
| if (failures & expected_failures) |
| return APR_SUCCESS; |
| else |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| static apr_status_t |
| ssl_server_cert_cb_expect_allok(void *baton, int failures, |
| const serf_ssl_certificate_t *cert) |
| { |
| test_baton_t *tb = baton; |
| tb->result_flags |= TEST_RESULT_SERVERCERTCB_CALLED; |
| |
| /* No error expected, certificate is valid. */ |
| if (failures) |
| return REPORT_TEST_SUITE_ERROR(); |
| else |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t |
| ssl_server_cert_cb_reject(void *baton, int failures, |
| const serf_ssl_certificate_t *cert) |
| { |
| test_baton_t *tb = baton; |
| tb->result_flags |= TEST_RESULT_SERVERCERTCB_CALLED; |
| |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| /* Validate that we can connect successfully to an https server. This |
| certificate is not trusted, so a cert validation failure is expected. */ |
| static void test_ssl_handshake(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| static const char *server_cert[] = { "serfservercert.pem", |
| NULL }; |
| |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_cert, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, NULL, |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* This unknown failures is X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, |
| meaning the chain has only the server cert. A good candidate for its |
| own failure code. */ |
| expected_failures = SERF_SSL_CERT_UNKNOWNCA; |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| } |
| |
| /* Validate that connecting to a SSLv2 only server fails. */ |
| static void test_ssl_handshake_nosslv2(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| static const char *server_cert[] = { "serfservercert.pem", |
| NULL }; |
| |
| |
| /* Set up a test context and a https server */ |
| tb->mh = mhInit(); |
| |
| InitMockServers(tb->mh) |
| SetupServer(WithHTTPS, WithPort(30080), |
| WithCertificateFilesPrefix(get_srcdir_file(tb->pool, |
| "test/certs")), |
| WithCertificateKeyFile(server_key), |
| WithCertificateKeyPassPhrase("serftest"), |
| WithCertificateFileArray(server_cert), |
| WithSSLv2) /* SSLv2 only */ |
| EndInit |
| |
| tb->serv_port = mhServerPortNr(tb->mh); |
| tb->serv_host = apr_psprintf(tb->pool, "%s:%d", "localhost", tb->serv_port); |
| tb->serv_url = apr_psprintf(tb->pool, "https://%s", tb->serv_host); |
| |
| status = setup_test_client_https_context(tb, NULL, |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* This unknown failures is X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, |
| meaning the chain has only the server cert. A good candidate for its |
| own failure code. */ |
| expected_failures = SERF_SSL_CERT_UNKNOWNCA; |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssert(tc, "Serf does not disable SSLv2, but it should!", |
| status != APR_SUCCESS); |
| } |
| |
| /* Set up the ssl context with the CA and root CA certificates needed for |
| successful valiation of the server certificate. */ |
| static apr_status_t |
| https_set_root_ca_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| serf_ssl_certificate_t *rootcacert; |
| test_baton_t *tb = setup_baton; |
| apr_status_t status; |
| |
| status = default_https_conn_setup(skt, input_bkt, output_bkt, |
| setup_baton, pool); |
| if (status) |
| return status; |
| |
| status = serf_ssl_load_cert_file(&rootcacert, |
| get_srcdir_file(pool, |
| "test/certs/serfrootcacert.pem"), |
| pool); |
| if (status) |
| return status; |
| status = serf_ssl_trust_cert(tb->ssl_context, rootcacert); |
| if (status) |
| return status; |
| |
| return status; |
| } |
| |
| /* Validate that server certificate validation is ok when we |
| explicitly trust our self-signed root ca. */ |
| static void test_ssl_trust_rootca(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| ssl_server_cert_cb_expect_allok, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| } |
| |
| /* Validate that when the application rejects the cert, the context loop |
| bails out with an error. */ |
| static void test_ssl_application_rejects_cert(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| |
| /* Set up a test context and a https server */ |
| /* The certificate is valid, but we tell serf to reject it. */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| ssl_server_cert_cb_reject, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| /* We expect an error from the certificate validation function. */ |
| CuAssert(tc, "Application told serf the certificate should be rejected," |
| " expected error!", status != APR_SUCCESS); |
| } |
| |
| /* Test for ssl certificate chain callback. */ |
| static apr_status_t |
| cert_chain_cb(void *baton, |
| int failures, |
| int error_depth, |
| const serf_ssl_certificate_t * const * certs, |
| apr_size_t certs_len) |
| { |
| test_baton_t *tb = baton; |
| apr_status_t status; |
| |
| tb->result_flags |= TEST_RESULT_SERVERCERTCHAINCB_CALLED; |
| |
| if (failures) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| if (certs_len != 3) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| status = validate_rootcacert(certs[2], tb->pool); |
| if (status) |
| return status; |
| |
| status = validate_cacert(certs[1], tb->pool); |
| if (status) |
| return status; |
| |
| status = validate_servercert(certs[0], tb->pool); |
| if (status) |
| return status; |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t |
| chain_rootca_callback_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| apr_status_t status; |
| |
| status = https_set_root_ca_conn_setup(skt, input_bkt, output_bkt, |
| setup_baton, pool); |
| if (status) |
| return status; |
| |
| serf_ssl_server_cert_chain_callback_set(tb->ssl_context, |
| ssl_server_cert_cb_expect_allok, |
| cert_chain_cb, |
| tb); |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Make the server return a partial certificate chain (server cert, CA cert), |
| the root CA cert is trusted explicitly in the client. Test the chain |
| callback. */ |
| static void test_ssl_certificate_chain_with_anchor(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| chain_rootca_callback_conn_setup, |
| ssl_server_cert_cb_expect_allok, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCHAINCB_CALLED); |
| } |
| |
| static apr_status_t |
| cert_chain_all_certs_cb(void *baton, |
| int failures, |
| int error_depth, |
| const serf_ssl_certificate_t * const * certs, |
| apr_size_t certs_len) |
| { |
| /* Root CA cert is selfsigned, ignore this 'failure'. */ |
| failures &= ~SERF_SSL_CERT_SELF_SIGNED; |
| |
| return cert_chain_cb(baton, failures, error_depth, certs, certs_len); |
| } |
| |
| static apr_status_t |
| chain_callback_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| apr_status_t status; |
| |
| status = default_https_conn_setup(skt, input_bkt, output_bkt, |
| setup_baton, pool); |
| if (status) |
| return status; |
| |
| serf_ssl_server_cert_chain_callback_set(tb->ssl_context, |
| ssl_server_cert_cb_expect_allok, |
| cert_chain_all_certs_cb, |
| tb); |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Make the server return the complete certificate chain (server cert, CA cert |
| and root CA cert). Test the chain callback. */ |
| static void test_ssl_certificate_chain_all_from_server(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| all_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| chain_callback_conn_setup, |
| ssl_server_cert_cb_expect_allok, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCHAINCB_CALLED); |
| } |
| |
| /* Validate that the ssl handshake succeeds if no application callbacks |
| are set, and the ssl server certificate chains is ok. */ |
| static void test_ssl_no_servercert_callback_allok(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| NULL, /* No server cert callback */ |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| } |
| |
| /* Validate that the ssl handshake fails if no application callbacks |
| are set, and the ssl server certificate chains is NOT ok. */ |
| static void test_ssl_no_servercert_callback_fail(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| NULL, /* default conn setup, |
| no certs */ |
| NULL, /* No server cert callback */ |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| /* We expect an error from the certificate validation function. */ |
| CuAssertIntEquals(tc, SERF_ERROR_SSL_CERT_FAILED, status); |
| } |
| |
| /* Similar to test_connection_large_response, validate reading a large |
| chunked response over SSL. */ |
| static void test_ssl_large_response(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| const char *response; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| NULL, /* No server cert callback */ |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* create large chunked response message */ |
| response = create_large_response_message(tb->pool); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1")) |
| Respond(WithRawData(response, strlen(response))) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* TODO: check the actual response data (duh). */ |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| } |
| |
| /* Similar to test_connection_large_request, validate sending a large |
| chunked request over SSL. */ |
| static void test_ssl_large_request(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| const char *request, *body; |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| NULL, /* No server cert callback */ |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* create large chunked request message */ |
| body = create_large_request_message_body(tb->pool); |
| request = create_large_request_message(tb->pool, body); |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), RawBodyEqualTo(body)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| handler_ctx[0].request = request; |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| } |
| |
| static apr_status_t client_cert_cb(void *data, const char **cert_path) |
| { |
| test_baton_t *tb = data; |
| |
| tb->result_flags |= TEST_RESULT_CLIENT_CERTCB_CALLED; |
| |
| *cert_path = get_srcdir_file(tb->pool, "test/certs/serfclientcert.p12"); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t client_cert_pw_cb(void *data, |
| const char *cert_path, |
| const char **password) |
| { |
| test_baton_t *tb = data; |
| |
| tb->result_flags |= TEST_RESULT_CLIENT_CERTPWCB_CALLED; |
| |
| if (strcmp(cert_path, |
| get_srcdir_file(tb->pool, "test/certs/serfclientcert.p12")) == 0) |
| { |
| *password = "serftest"; |
| return APR_SUCCESS; |
| } |
| |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| static apr_status_t |
| client_cert_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| apr_status_t status; |
| |
| status = https_set_root_ca_conn_setup(skt, input_bkt, output_bkt, |
| setup_baton, pool); |
| if (status) |
| return status; |
| |
| serf_ssl_client_cert_provider_set(tb->ssl_context, |
| client_cert_cb, |
| tb, |
| pool); |
| |
| serf_ssl_client_cert_password_set(tb->ssl_context, |
| client_cert_pw_cb, |
| tb, |
| pool); |
| |
| return APR_SUCCESS; |
| } |
| |
| static void test_ssl_client_certificate(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| |
| /* Set up a test context and a https server */ |
| /* The SSL server uses the complete certificate chain to validate the client |
| certificate. */ |
| setup_test_mock_https_server(tb, server_key, |
| all_server_certs, |
| test_clientcert_optional); |
| status = setup_test_client_https_context(tb, |
| client_cert_conn_setup, |
| NULL, /* No server cert callback */ |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| ConnectionSetup(ClientCertificateIsValid, |
| ClientCertificateCNEqualTo("Serf Client")) |
| |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTCB_CALLED); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTPWCB_CALLED); |
| Verify(tb->mh) |
| CuAssert(tc, ErrorMessage, VerifyConnectionSetupOk); |
| EndVerify |
| } |
| |
| /* Validate that the expired certificate is reported as failure in the |
| callback. */ |
| static void test_ssl_expired_server_cert(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| |
| static const char *expired_server_certs[] = { |
| "serfserver_expired_cert.pem", |
| "serfcacert.pem", |
| "serfrootcacert.pem", |
| NULL }; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| expired_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| NULL, /* default conn setup */ |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| expected_failures = SERF_SSL_CERT_SELF_SIGNED | |
| SERF_SSL_CERT_EXPIRED; |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| |
| } |
| |
| /* Validate that the expired certificate is reported as failure in the |
| callback. */ |
| static void test_ssl_future_server_cert(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| |
| static const char *future_server_certs[] = { |
| "serfserver_future_cert.pem", |
| "serfcacert.pem", |
| "serfrootcacert.pem", |
| NULL }; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| future_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| NULL, /* default conn setup */ |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| expected_failures = SERF_SSL_CERT_SELF_SIGNED | |
| SERF_SSL_CERT_NOTYETVALID; |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| } |
| |
| |
| /* Set up the ssl context with the CA and root CA certificates needed for |
| successful valiation of the server certificate. */ |
| static apr_status_t |
| https_load_crl_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| apr_status_t status; |
| |
| status = https_set_root_ca_conn_setup(skt, input_bkt, output_bkt, |
| setup_baton, pool); |
| if (status) |
| return status; |
| |
| /* Load the certificate revocation list */ |
| status = serf_ssl_add_crl_from_file(tb->ssl_context, |
| get_srcdir_file(pool, |
| "test/certs/serfservercrl.pem"), |
| tb->pool); |
| |
| return status; |
| } |
| |
| /* Logs failures for each depth in tb->user_baton, for later validation. */ |
| /* TODO: use this in place of ssl_server_cert_cb_expect_failures */ |
| static apr_status_t |
| ssl_server_cert_cb_log_failures(void *baton, int failures, |
| const serf_ssl_certificate_t *cert) |
| { |
| test_baton_t *tb = baton; |
| apr_array_header_t *failure_list = (apr_array_header_t *)tb->user_baton; |
| int depth = serf_ssl_cert_depth(cert); |
| int *failures_for_depth; |
| |
| if (!tb->user_baton) { |
| /* make the array big enough to not have to worry about resizing */ |
| tb->user_baton = apr_array_make(tb->pool, 10, sizeof(int)); |
| } |
| failure_list = tb->user_baton; |
| |
| if (depth + 1 > failure_list->nalloc) { |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| failures_for_depth = &APR_ARRAY_IDX(failure_list, depth, int); |
| *failures_for_depth |= failures; |
| |
| tb->result_flags |= TEST_RESULT_SERVERCERTCB_CALLED; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Validate that a CRL file can be loaded and revocation actually works. */ |
| static void test_ssl_revoked_server_cert(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int depth; |
| apr_status_t status; |
| |
| static const char *future_server_certs[] = { |
| "serfservercert.pem", |
| "serfcacert.pem", |
| "serfrootcacert.pem", |
| NULL }; |
| |
| static int expected_failures[] = { |
| SERF_SSL_CERT_REVOKED, |
| SERF_SSL_CERT_UNABLE_TO_GET_CRL, |
| SERF_SSL_CERT_UNABLE_TO_GET_CRL |
| }; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| future_server_certs, |
| test_clientcert_none); |
| |
| /* OpenSSL first checks the revocation status before verifying the rest of |
| certificate. OpenSSL may call the application multiple times per depth, |
| e.g. once to tell that the cert is revoked, and a second time to tell |
| that the certificate itself is valid. |
| We'll have to combine the results of these multiple calls to the callback |
| to get a complete view of the certificate. That's why we use |
| ssl_server_cert_cb_log_failures here, and then later check expected |
| failures for each depth. */ |
| status = setup_test_client_https_context(tb, |
| https_load_crl_conn_setup, |
| ssl_server_cert_cb_log_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| for (depth = 0; depth < 3; depth++) { |
| apr_array_header_t *ary = tb->user_baton; |
| |
| CuAssertIntEquals(tc, expected_failures[depth], |
| APR_ARRAY_IDX(ary, depth, int)); |
| |
| } |
| } |
| |
| /* Test if serf is sets up an SSL tunnel to the proxy and doesn't contact the |
| https server directly. */ |
| static void test_setup_ssltunnel(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| int i; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| |
| /* Set up a test context with a server and a proxy. Serf should send a |
| CONNECT request to the server. */ |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| all_server_certs, |
| test_clientcert_none); |
| CuAssertIntEquals(tc, APR_SUCCESS, setup_test_mock_proxy(tb)); |
| CuAssertIntEquals(tc, APR_SUCCESS, |
| setup_serf_https_context_with_proxy(tb, chain_callback_conn_setup, |
| ssl_server_cert_cb_expect_allok, |
| tb->pool)); |
| |
| Given(tb->mh) |
| RequestsReceivedByServer |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| |
| RequestsReceivedByProxy |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| SetupSSLTunnel |
| EndGiven |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| |
| /* Check that the response were received in the order we sent the requests */ |
| for (i = 0; i < tb->handled_requests->nelts; i++) { |
| int req_nr = APR_ARRAY_IDX(tb->handled_requests, i, int); |
| CuAssertIntEquals(tc, i + 1, req_nr); |
| } |
| } |
| |
| /* Test error if no creds callback */ |
| static void test_ssltunnel_no_creds_cb(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context with a server and a proxy. Serf should send a |
| CONNECT request to the server. */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| CuAssertIntEquals(tc, APR_SUCCESS, setup_test_mock_proxy(tb)); |
| CuAssertIntEquals(tc, APR_SUCCESS, |
| setup_serf_https_context_with_proxy(tb, https_set_root_ca_conn_setup, |
| NULL, /* No server cert cb */ |
| tb->pool)); |
| |
| Given(tb->mh) |
| RequestsReceivedByProxy |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host)) |
| Respond(WithCode(407), WithChunkedBody(""), |
| WithHeader("Proxy-Authentication", |
| "Basic realm=\"Test Suite Proxy\"")) |
| SetupSSLTunnel |
| EndGiven |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* No credentials callback configured. */ |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| CuAssertIntEquals(tc, SERF_ERROR_SSLTUNNEL_SETUP_FAILED, status); |
| } |
| |
| static apr_status_t |
| ssltunnel_basic_authn_callback(char **username, |
| char **password, |
| serf_request_t *request, void *baton, |
| int code, const char *authn_type, |
| const char *realm, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *handler_ctx = baton; |
| test_baton_t *tb = handler_ctx->tb; |
| |
| test__log(TEST_VERBOSE, __FILE__, "ssltunnel_basic_authn_callback\n"); |
| |
| tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; |
| |
| if (strcmp("Basic", authn_type) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| if (code == 401) { |
| if (strcmp(apr_psprintf(pool, "<%s> Test Suite", tb->serv_url), |
| realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serf"; |
| *password = "serftest"; |
| } |
| else if (code == 407) { |
| if (strcmp(apr_psprintf(pool, "<http://localhost:%u> Test Suite Proxy", |
| tb->proxy_port), realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serfproxy"; |
| *password = "serftest"; |
| } else |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| test__log(TEST_VERBOSE, __FILE__, "ssltunnel_basic_authn_callback finished successfully.\n"); |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Test if serf can successfully authenticate to a proxy used for an ssl |
| tunnel. Retry the authentication a few times to test requeueing of the |
| CONNECT request. */ |
| static void ssltunnel_basic_auth(CuTest *tc, int serv_close_conn, |
| int proxy407_close_conn, |
| int proxy200_close_conn) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| int num_requests_sent, num_requests_recvd; |
| apr_status_t status; |
| |
| /* Set up a test context with a server and a proxy. Serf should send a |
| CONNECT request to the server. */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| CuAssertIntEquals(tc, APR_SUCCESS, setup_test_mock_proxy(tb)); |
| CuAssertIntEquals(tc, APR_SUCCESS, |
| setup_serf_https_context_with_proxy(tb, https_set_root_ca_conn_setup, |
| NULL, /* No server cert cb */ |
| tb->pool)); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_BASIC); |
| serf_config_credentials_callback(tb->context, ssltunnel_basic_authn_callback); |
| |
| Given(tb->mh) |
| RequestsReceivedByServer |
| GETRequest(URLEqualTo("/"), HeaderNotSet("Authorization")) |
| Respond(WithCode(401),WithChunkedBody("1"), |
| WithHeader("www-Authenticate", "bAsIc realm=\"Test Suite\""), |
| OnConditionThat(serv_close_conn, WithConnectionCloseHeader)) |
| GETRequest(URLEqualTo("/"), |
| HeaderEqualTo("Authorization", "Basic c2VyZjpzZXJmdGVzdA==")) |
| Respond(WithCode(200),WithChunkedBody("")) |
| RequestsReceivedByProxy |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderNotSet("Proxy-Authorization")) |
| Respond(WithCode(407), WithChunkedBody(""), |
| WithHeader("Proxy-Authenticate", |
| "Basic realm=\"Test Suite Proxy\""), |
| OnConditionThat(proxy407_close_conn, WithConnectionCloseHeader)) |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderEqualTo("Proxy-Authorization", |
| "Basic c2VyZnByb3h5OnNlcmZ0ZXN0")) |
| Respond(WithCode(200), WithChunkedBody(""), |
| /* Don't kill the connection here, just send the header */ |
| OnConditionThat(proxy200_close_conn, |
| WithHeader("Connection", "close"))) |
| SetupSSLTunnel |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* Test that a request is retried and authentication headers are set |
| correctly. */ |
| num_requests_sent = 1; |
| num_requests_recvd = 2; |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| Verify(tb->mh) |
| CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived); |
| EndVerify |
| |
| CuAssertIntEquals(tc, num_requests_recvd, tb->sent_requests->nelts); |
| CuAssertIntEquals(tc, num_requests_recvd, tb->accepted_requests->nelts); |
| CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); |
| |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); |
| } |
| |
| static void test_ssltunnel_basic_auth(CuTest *tc) |
| { |
| /* KeepAlive On for both proxy and server */ |
| ssltunnel_basic_auth(tc, 0, 0, 0); |
| } |
| |
| static void test_ssltunnel_basic_auth_server_has_keepalive_off(CuTest *tc) |
| { |
| /* Add Connection:Close header to server response */ |
| ssltunnel_basic_auth(tc, 1, 0, 0); |
| } |
| |
| static void test_ssltunnel_basic_auth_proxy_has_keepalive_off(CuTest *tc) |
| { |
| /* Add Connection:Close header to proxy 407 response */ |
| ssltunnel_basic_auth(tc, 0, 1, 0); |
| } |
| |
| static void test_ssltunnel_basic_auth_proxy_close_conn_on_200resp(CuTest *tc) |
| { |
| /* Add Connection:Close header to proxy 200 Conn. Establ. response */ |
| ssltunnel_basic_auth(tc, 0, 0, 1); |
| } |
| |
| static apr_status_t |
| basic_authn_callback_2ndtry(char **username, |
| char **password, |
| serf_request_t *request, void *baton, |
| int code, const char *authn_type, |
| const char *realm, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *handler_ctx = baton; |
| test_baton_t *tb = handler_ctx->tb; |
| int secondtry = tb->result_flags & TEST_RESULT_AUTHNCB_CALLED; |
| |
| test__log(TEST_VERBOSE, __FILE__, "ssltunnel_basic_authn_callback\n"); |
| |
| tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; |
| |
| if (strcmp("Basic", authn_type) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| if (code == 401) { |
| if (strcmp(apr_psprintf(pool, "<%s> Test Suite", tb->serv_url), |
| realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serf"; |
| *password = secondtry ? "serftest" : "wrongpwd"; |
| } |
| else if (code == 407) { |
| if (strcmp(apr_psprintf(pool, "<http://localhost:%u> Test Suite Proxy", |
| tb->proxy_port), realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serfproxy"; |
| *password = secondtry ? "serftest" : "wrongpwd"; |
| } else |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| test__log(TEST_VERBOSE, __FILE__, "ssltunnel_basic_authn_callback finished successfully.\n"); |
| |
| return APR_SUCCESS; |
| } |
| |
| |
| /* This test used to make serf crash on Windows when the server aborting the |
| connection resulted in APR_ECONNRESET on the client side. |
| |
| This can be simulated by applying this change to serf__handle_auth_response |
| right after the discard_body call. |
| |
| if (request->conn->completed_responses > 0 && status == APR_EOF) |
| status = APR_ECONNRESET; |
| |
| TODO: create a mock socket or socket bucket wrapper to simulate |
| APR_ECONNRESET. |
| */ |
| static void test_ssltunnel_basic_auth_2ndtry(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| int num_requests_sent, num_requests_recvd; |
| apr_status_t status; |
| |
| /* Set up a test context with a server and a proxy. Serf should send a |
| CONNECT request to the server. */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| CuAssertIntEquals(tc, APR_SUCCESS, setup_test_mock_proxy(tb)); |
| CuAssertIntEquals(tc, APR_SUCCESS, |
| setup_serf_https_context_with_proxy(tb, https_set_root_ca_conn_setup, |
| NULL, /* No server cert cb */ |
| tb->pool)); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_BASIC); |
| serf_config_credentials_callback(tb->context, basic_authn_callback_2ndtry); |
| |
| Given(tb->mh) |
| RequestsReceivedByServer |
| GETRequest(URLEqualTo("/")) |
| Respond(WithCode(200),WithChunkedBody("")) |
| |
| RequestsReceivedByProxy |
| /* Don't close connection when client didn't provide creds */ |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderNotSet("Proxy-Authorization")) |
| Respond(WithCode(407), WithChunkedBody(""), |
| WithHeader("Proxy-Authenticate", |
| "Basic realm=\"Test Suite Proxy\"")) |
| /* serfproxy:wrongpwd fails, close connection. */ |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderNotEqualTo("Proxy-Authorization", |
| "Basic c2VyZnByb3h5OnNlcmZ0ZXN0")) |
| Respond(WithCode(407), WithChunkedBody(""), |
| WithHeader("Proxy-Authenticate", |
| "Basic realm=\"Test Suite Proxy\"")) |
| CloseConnection |
| /* serfproxy:serftest succeeds */ |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderEqualTo("Proxy-Authorization", |
| "Basic c2VyZnByb3h5OnNlcmZ0ZXN0")) |
| Respond(WithCode(200), WithChunkedBody("")) |
| SetupSSLTunnel |
| Expect |
| AllRequestsReceivedInOrder |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* Test that a request is retried and authentication headers are set |
| correctly. */ |
| num_requests_sent = 1; |
| num_requests_recvd = 1; |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests_sent, |
| handler_ctx, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| Verify(tb->mh) |
| CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived); |
| EndVerify |
| |
| CuAssertIntEquals(tc, num_requests_recvd, tb->sent_requests->nelts); |
| CuAssertIntEquals(tc, num_requests_recvd, tb->accepted_requests->nelts); |
| CuAssertIntEquals(tc, num_requests_sent, tb->handled_requests->nelts); |
| |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); |
| } |
| |
| static apr_status_t |
| proxy_digest_authn_callback(char **username, |
| char **password, |
| serf_request_t *request, void *baton, |
| int code, const char *authn_type, |
| const char *realm, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *handler_ctx = baton; |
| test_baton_t *tb = handler_ctx->tb; |
| |
| tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; |
| |
| if (code != 407) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp("Digest", authn_type) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| if (strcmp(apr_psprintf(pool, "<http://localhost:%u> Test Suite Proxy", |
| tb->proxy_port), realm) != 0) |
| return REPORT_TEST_SUITE_ERROR(); |
| |
| *username = "serf"; |
| *password = "serftest"; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Calculate the md5 over INPUT and return as string allocated in POOL */ |
| static const char *simple_md5(char *input, |
| apr_pool_t *pool) |
| { |
| unsigned char digest[APR_MD5_DIGESTSIZE]; |
| const char *hexmap = "0123456789abcdef"; |
| int i; |
| char *result = apr_palloc(pool, 2 * APR_MD5_DIGESTSIZE + 1); |
| |
| if (apr_md5(digest, input, strlen(input))) |
| REPORT_TEST_SUITE_ERROR(); |
| |
| for (i = 0; i < sizeof(digest); i++) { |
| result[i * 2] = hexmap[digest[i] >> 4]; |
| result[i * 2 + 1] = hexmap[digest[i] & 0xF]; |
| } |
| result[APR_MD5_DIGESTSIZE * 2] = 0; |
| return result; |
| } |
| |
| /* Test if serf can successfully authenticate to a proxy used for an ssl |
| tunnel. */ |
| static void test_ssltunnel_digest_auth(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| const char *digest; |
| const char *response_md5; |
| |
| /* Set up a test context with a server and a proxy. Serf should send a |
| CONNECT request to the server. */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| CuAssertIntEquals(tc, APR_SUCCESS, setup_test_mock_proxy(tb)); |
| CuAssertIntEquals(tc, APR_SUCCESS, |
| setup_serf_https_context_with_proxy(tb, https_set_root_ca_conn_setup, |
| NULL, /* No server cert cb */ |
| tb->pool)); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_BASIC | SERF_AUTHN_DIGEST); |
| serf_config_credentials_callback(tb->context, proxy_digest_authn_callback); |
| |
| /* Calculate the proper response. We can't use a hardcoded string as |
| that would make the test fail when the port is in use. */ |
| { |
| const char *ha1 = simple_md5(apr_psprintf(tb->pool, |
| "%s:Test Suite Proxy:%s", |
| "serf", "serftest"), |
| tb->pool); |
| const char *ha2 = simple_md5(apr_psprintf(tb->pool, |
| "CONNECT:%s", tb->serv_host), |
| tb->pool); |
| |
| response_md5 = simple_md5(apr_psprintf(tb->pool, "%s:%s:%s", |
| ha1, |
| "ABCDEF1234567890", |
| ha2), |
| tb->pool); |
| } |
| digest = apr_psprintf(tb->pool, "Digest realm=\"Test Suite Proxy\", " |
| "username=\"serf\", nonce=\"ABCDEF1234567890\", uri=\"localhost:%u\", " |
| "response=\"%s\", opaque=\"myopaque\", " |
| "algorithm=\"MD5\"", tb->serv_port, response_md5); |
| Given(tb->mh) |
| RequestsReceivedByServer |
| GETRequest(URLEqualTo("/test/index.html"), ChunkedBodyEqualTo("1")) |
| Respond(WithCode(200),WithChunkedBody("")) |
| |
| /* Add a Basic header before Digest header, to test that serf uses the most |
| secure authentication scheme first, instead of following the order of |
| the headers. */ |
| /* Use non standard case for Proxy-Authenticate header to test case |
| insensitivity for http headers. */ |
| RequestsReceivedByProxy |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderNotSet("Proxy-Authorization")) |
| Respond(WithCode(407), WithChunkedBody("1"), |
| WithHeader("Proxy-Authenticate", |
| "Basic realm=\"Test Suite Proxy\""), |
| WithHeader("Proxy-Authenticate", "NonExistent blablablabla"), |
| WithHeader("proXy-Authenticate", "Digest " |
| "realm=\"Test Suite Proxy\",nonce=\"ABCDEF1234567890\"," |
| "opaque=\"myopaque\",algorithm=\"MD5\"")) |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderEqualTo("Proxy-Authorization", digest)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| SetupSSLTunnel |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/test/index.html", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_AUTHNCB_CALLED); |
| } |
| |
| /* Minimum tests for Negotiate authentication. If serf is built on Windows or |
| with GSSAPI support, and the user is logged in to a Kerberos realm, this test |
| will initiate a context and send the initial token to the proxy/server. */ |
| static void test_ssltunnel_spnego_authn(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context with a proxy */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_mock_proxy(tb); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| status = setup_test_client_context_with_proxy(tb, NULL, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_NEGOTIATE | |
| SERF_AUTHN_NTLM); |
| serf_config_credentials_callback(tb->context, ssltunnel_basic_authn_callback); |
| |
| Given(tb->mh) |
| RequestsReceivedByProxy |
| HTTPRequest(MethodEqualTo("CONNECT"), |
| URLEqualTo(tb->serv_host), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(407), |
| WithHeader("Proxy-Authenticate", "Negotiate"), |
| WithHeader("Proxy-Authenticate", "Kerberos"), |
| WithHeader("Proxy-Authenticate", "NTLM"), |
| WithHeader("Connection", "close"), |
| WithHeader("Proxy-Connection", "close"), |
| WithHeader("Content-Type", "text/html"), |
| WithBody("<html><body>Authn required</body></html>")) |
| EndGiven |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* Don't check the result, authn will fail. */ |
| run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, tb->pool); |
| } |
| |
| static void test_server_spnego_authn(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context with a server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| serf_config_authn_types(tb->context, SERF_AUTHN_NEGOTIATE | |
| SERF_AUTHN_NTLM); |
| serf_config_credentials_callback(tb->context, ssltunnel_basic_authn_callback); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(401), |
| WithHeader("WWW-Authenticate", "Negotiate"), |
| WithHeader("Content-Type", "text/html"), |
| WithBody("<html><body>Authn required</body></html>")) |
| EndGiven |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| /* Don't check the result, authn will fail. */ |
| run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| } |
| |
| |
| static void test_ssl_renegotiate(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[2]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| ssl_server_cert_cb_expect_allok, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/index1.html"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| SSLRenegotiate |
| GETRequest(URLEqualTo("/index2.html"), ChunkedBodyEqualTo("2"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/index1.html", 1); |
| |
| /* status = run_client_and_mock_servers_loops(tb, 1, handler_ctx, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status);*/ |
| |
| create_new_request(tb, &handler_ctx[1], "GET", "/index2.html", 2); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| /* Check that the requests were sent and reveived by the server */ |
| /* Note: the test server will have received the first request twice, so |
| we can't check for VerifyAllRequestsReceivedInOrder here. */ |
| Verify(tb->mh) |
| CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived); |
| EndVerify |
| CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts); |
| } |
| |
| static void test_ssl_missing_client_certificate(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| |
| /* Set up a test context and a https server */ |
| /* The SSL server uses the complete certificate chain to validate the client |
| certificate. */ |
| setup_test_mock_https_server(tb, server_key, |
| all_server_certs, |
| test_clientcert_mandatory); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| NULL, /* No server cert callback */ |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| ConnectionSetup(ClientCertificateIsValid, |
| ClientCertificateCNEqualTo("Serf Client")) |
| |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| CuAssertIntEquals(tc, SERF_ERROR_SSL_SETUP_FAILED, status); |
| } |
| |
| static apr_status_t handle_response_set_flag(serf_request_t *request, |
| serf_bucket_t *response, |
| void *handler_baton, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *ctx = handler_baton; |
| test_baton_t *tb = ctx->tb; |
| |
| #if 0 |
| /* TODO: this is the expected behahior: if there was an error reading |
| the response while looking for authn headers, serf should pass the |
| response to the application to let it read the error from the response. |
| Not passing the response will make the application think the response |
| get cancelled, and it will requeue it. */ |
| if (!response) |
| return REPORT_TEST_SUITE_ERROR(); |
| #endif |
| |
| tb->result_flags |= TEST_RESULT_HANDLE_RESPONSECB_CALLED; |
| |
| return handle_response(request, response, handler_baton, pool); |
| } |
| |
| /* Test that serf doesn't crash when connecting to a non-HTTP server. */ |
| static void test_connect_to_non_http_server(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context and a http server */ |
| setup_test_mock_server(tb); |
| status = setup_test_client_context(tb, NULL, tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| serf_config_credentials_callback(tb->context, dummy_authn_callback); |
| |
| #define RESPONSE "6EQUJ5 6EQUJ5 hello stranger!\r\n" |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1")) |
| /* Assume that extraterrestrials also use CRLF as line ending symbol. */ |
| Respond(WithRawData(RESPONSE, strlen(RESPONSE))) |
| EndGiven |
| #undef RESPONSE |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| handler_ctx[0].handler = handle_response_set_flag; |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| tb->pool); |
| CuAssertIntEquals(tc, SERF_ERROR_BAD_HTTP_RESPONSE, status); |
| |
| /* Check that the requests were sent and reveived by the server */ |
| Verify(tb->mh) |
| CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived); |
| EndVerify |
| CuAssertIntEquals(tc, num_requests, tb->sent_requests->nelts); |
| CuAssertIntEquals(tc, num_requests, tb->accepted_requests->nelts); |
| /* The response will not have been handled completely, but at least make |
| sure that the handler got called. */ |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_HANDLE_RESPONSECB_CALLED); |
| } |
| |
| static apr_status_t |
| ocsp_response_cb_expect_failures(void *baton, int failures, |
| const serf_ssl_certificate_t *cert) |
| { |
| test_baton_t *tb = baton; |
| int expected_failures = tb->user_baton_l; |
| |
| /* Root CA cert is selfsigned, ignore this 'failure'. */ |
| failures &= ~SERF_SSL_CERT_SELF_SIGNED; |
| |
| /* This callback gets called with both the server certificate and the ocsp |
| response status */ |
| tb->result_flags |= TEST_RESULT_SERVERCERTCB_CALLED; |
| |
| /* We expect an error from the certificate validation function. */ |
| if (!failures) |
| return APR_SUCCESS; |
| if (failures & expected_failures) { |
| tb->result_flags |= TEST_RESULT_OCSP_CHECK_SUCCESSFUL; |
| return APR_SUCCESS; |
| } |
| |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| static void test_ssl_ocsp_response_error_and_override(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| /* Set up a test context, a https server, and a OCSP responder */ |
| setup_test_mock_https_server(tb, server_key, |
| all_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| default_https_conn_setup, |
| ocsp_response_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| tb->enable_ocsp_stapling = 1; |
| |
| InitMockServers(tb->mh) |
| ConfigServerWithID("server", WithOCSPEnabled) |
| EndInit |
| |
| Given(tb->mh) |
| OCSPRequest(MatchAny) |
| Respond(WithOCSPResponseStatus(mhOCSPRespnseStatusInternalError)) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| /* Expect this error */ |
| tb->user_baton_l = SERF_SSL_OCSP_RESPONDER_ERROR; |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| #if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_OCSP_CHECK_SUCCESSFUL); |
| #endif |
| } |
| |
| /* Validate that the subject's CN containing a '\0' byte is reported as failure |
| SERF_SSL_CERT_INVALID_HOST in the callback. */ |
| static void test_ssl_server_cert_with_cn_nul_byte(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| |
| static const char *nul_byte_server_certs[] = { |
| "servercert_cn_nul.pem", |
| "cacert_nul.pem", |
| NULL }; |
| static const char *nul_byte_server_key = "servercert_cn_nul.key"; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, nul_byte_server_key, |
| nul_byte_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| NULL, /* default conn setup */ |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| expected_failures = SERF_SSL_CERT_SELF_SIGNED | |
| SERF_SSL_CERT_INVALID_HOST; /* NUL byte error */ |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| } |
| |
| /* Validate that the subject's SAN containing a '\0' byte is reported as failure |
| SERF_SSL_CERT_INVALID_HOST in the callback. */ |
| static void test_ssl_server_cert_with_san_nul_byte(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| |
| static const char *nul_byte_server_certs[] = { |
| "servercert_san_nul.pem", |
| "cacert_nul.pem", |
| NULL }; |
| static const char *nul_byte_server_key = "servercert_san_nul.key"; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, nul_byte_server_key, |
| nul_byte_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| NULL, /* default conn setup */ |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| expected_failures = SERF_SSL_CERT_SELF_SIGNED | |
| SERF_SSL_CERT_INVALID_HOST; /* NUL byte error */ |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| } |
| |
| /* Validate that the subject's CN and SAN containing a '\0' byte is reported |
| as failure SERF_SSL_CERT_INVALID_HOST in the callback. */ |
| static void test_ssl_server_cert_with_cnsan_nul_byte(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| |
| static const char *nul_byte_server_certs[] = { |
| "servercert_cnsan_nul.pem", |
| "cacert_nul.pem", |
| NULL }; |
| static const char *nul_byte_server_key = "servercert_cnsan_nul.key"; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, nul_byte_server_key, |
| nul_byte_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| NULL, /* default conn setup */ |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| expected_failures = SERF_SSL_CERT_SELF_SIGNED | |
| SERF_SSL_CERT_INVALID_HOST; /* NUL byte error */ |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| } |
| |
| /* Validate a certificate with subjectAltName a DNS entry, but no CN. */ |
| static void test_ssl_server_cert_with_san_and_empty_cb(CuTest *tc) |
| { |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| apr_status_t status; |
| |
| static const char *san_server_certs[] = { |
| "serfserver_san_nocn_cert.pem", |
| "serfcacert.pem", |
| NULL }; |
| |
| /* Set up a test context and a https server */ |
| setup_test_mock_https_server(tb, server_key, |
| san_server_certs, |
| test_clientcert_none); |
| status = setup_test_client_https_context(tb, |
| https_set_root_ca_conn_setup, |
| ssl_server_cert_cb_expect_allok, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); |
| } |
| |
| #ifndef OPENSSL_NO_TLSEXT |
| static apr_status_t http11_select_protocol(void *baton, |
| const char *protocol) |
| { |
| test_baton_t *tb = baton; |
| |
| if (! strcmp(protocol, "http/1.1")) |
| serf_connection_set_framing_type(tb->connection, |
| SERF_CONNECTION_FRAMING_TYPE_HTTP1); |
| else |
| return APR_EGENERAL; /* Failure */ |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t http11_alpn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| apr_status_t status; |
| |
| status = default_https_conn_setup(skt, input_bkt, output_bkt, |
| setup_baton, pool); |
| if (status) |
| return status; |
| |
| status = serf_ssl_negotiate_protocol(tb->ssl_context, "h2,http/1.1", |
| http11_select_protocol, tb); |
| |
| if (!status) { |
| /* Delay writing out the protocol type until we know how */ |
| serf_connection_set_framing_type(tb->connection, |
| SERF_CONNECTION_FRAMING_TYPE_NONE); |
| } |
| |
| return APR_SUCCESS; |
| } |
| #endif /* OPENSSL_NO_TLSEXT */ |
| |
| |
| static void test_ssl_alpn_negotiate(CuTest *tc) |
| { |
| #ifndef OPENSSL_NO_TLSEXT |
| test_baton_t *tb = tc->testBaton; |
| handler_baton_t handler_ctx[1]; |
| const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); |
| int expected_failures; |
| apr_status_t status; |
| static const char *server_cert[] = { "serfservercert.pem", |
| NULL }; |
| |
| /* Set up a test context and a https server */ |
| tb->mh = mhInit(); |
| |
| InitMockServers(tb->mh) |
| SetupServer(WithHTTPS, WithID("server"), WithPort(30080), |
| WithProtocol("http/1.1"), |
| WithCertificateFilesPrefix(get_srcdir_file(tb->pool, |
| "test/certs")), |
| WithCertificateKeyFile(server_key), |
| WithCertificateKeyPassPhrase("serftest"), |
| WithCertificateFileArray(server_cert)) |
| EndInit |
| |
| tb->serv_port = mhServerPortNr(tb->mh); |
| tb->serv_host = apr_psprintf(tb->pool, "%s:%d", "localhost", tb->serv_port); |
| tb->serv_url = apr_psprintf(tb->pool, "https://%s", tb->serv_host); |
| |
| status = setup_test_client_https_context(tb, |
| http11_alpn_setup, |
| ssl_server_cert_cb_expect_failures, |
| tb->pool); |
| CuAssertIntEquals(tc, APR_SUCCESS, status); |
| |
| expected_failures = SERF_SSL_CERT_UNKNOWNCA; |
| tb->user_baton = &expected_failures; |
| |
| Given(tb->mh) |
| GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), |
| HeaderEqualTo("Host", tb->serv_host)) |
| Respond(WithCode(200), WithChunkedBody("")) |
| EndGiven |
| |
| create_new_request(tb, &handler_ctx[0], "GET", "/", 1); |
| |
| run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, |
| handler_ctx, tb->pool); |
| #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) |
| { |
| static const char passphrase[] = "serftest"; |
| const int passlen = (int)strlen(passphrase); |
| |
| (void)rwflag; |
| (void)u; |
| |
| 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(); |
| |
| CuSuiteSetSetupTeardownCallbacks(suite, test_setup, test_teardown); |
| |
| SUITE_ADD_TEST(suite, test_ssl_init); |
| SUITE_ADD_TEST(suite, test_ssl_load_cert_file); |
| SUITE_ADD_TEST(suite, test_ssl_cert_subject); |
| 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); |
| SUITE_ADD_TEST(suite, test_ssl_application_rejects_cert); |
| SUITE_ADD_TEST(suite, test_ssl_certificate_chain_with_anchor); |
| SUITE_ADD_TEST(suite, test_ssl_certificate_chain_all_from_server); |
| SUITE_ADD_TEST(suite, test_ssl_no_servercert_callback_allok); |
| SUITE_ADD_TEST(suite, test_ssl_no_servercert_callback_fail); |
| SUITE_ADD_TEST(suite, test_ssl_large_response); |
| SUITE_ADD_TEST(suite, test_ssl_large_request); |
| SUITE_ADD_TEST(suite, test_ssl_client_certificate); |
| SUITE_ADD_TEST(suite, test_ssl_expired_server_cert); |
| SUITE_ADD_TEST(suite, test_ssl_future_server_cert); |
| SUITE_ADD_TEST(suite, test_ssl_revoked_server_cert); |
| SUITE_ADD_TEST(suite, test_setup_ssltunnel); |
| SUITE_ADD_TEST(suite, test_ssltunnel_no_creds_cb); |
| SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth); |
| SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth_server_has_keepalive_off); |
| SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth_proxy_has_keepalive_off); |
| SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth_proxy_close_conn_on_200resp); |
| SUITE_ADD_TEST(suite, test_ssltunnel_basic_auth_2ndtry); |
| SUITE_ADD_TEST(suite, test_ssltunnel_digest_auth); |
| SUITE_ADD_TEST(suite, test_ssltunnel_spnego_authn); |
| SUITE_ADD_TEST(suite, test_server_spnego_authn); |
| SUITE_ADD_TEST(suite, test_ssl_missing_client_certificate); |
| SUITE_ADD_TEST(suite, test_connect_to_non_http_server); |
| SUITE_ADD_TEST(suite, test_ssl_ocsp_response_error_and_override); |
| SUITE_ADD_TEST(suite, test_ssl_server_cert_with_cn_nul_byte); |
| SUITE_ADD_TEST(suite, test_ssl_server_cert_with_san_nul_byte); |
| SUITE_ADD_TEST(suite, test_ssl_server_cert_with_cnsan_nul_byte); |
| 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; |
| } |