blob: 2775a2f62027b1be5d4029300bf193494428d4ca [file] [log] [blame]
/* Copyright 2008 Justin Erenkrantz and Greg Stein
*
* Licensed 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 "serf.h"
#include "serf_bucket_types.h"
/* Access to internal functions, for DER decoding. */
#include "buckets/bucket_private.h"
#include "test_serf.h"
#if defined(WIN32) && defined(_DEBUG)
/* Include this file to allow running a Debug build of serf with a Release
build of OpenSSL. */
#include <openssl/applink.c>
#endif
/* Test setting up the openssl library. */
static void test_ssl_init(CuTest *tc)
{
serf_bucket_t *bkt, *stream;
serf_ssl_context_t *ssl_context;
apr_status_t status;
apr_pool_t *test_pool = tc->testBaton;
serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
NULL);
stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
bkt = serf_bucket_ssl_decrypt_create(stream, NULL,
alloc);
CuAssertTrue(tc, SERF_BUCKET_IS_SSL_DECRYPT(bkt));
ssl_context = serf_bucket_ssl_decrypt_context_get(bkt);
bkt = serf_bucket_ssl_encrypt_create(stream, ssl_context,
alloc);
CuAssertTrue(tc, SERF_BUCKET_IS_SSL_ENCRYPT(bkt));
status = serf_ssl_use_default_certificates(ssl_context);
CuAssertIntEquals(tc, APR_SUCCESS, status);
}
static const char * get_ca_file(apr_pool_t *pool, const char * file)
{
char *srcdir = "";
if (apr_env_get(&srcdir, "srcdir", pool) == APR_SUCCESS) {
return apr_pstrcat(pool, srcdir, "/", file, NULL);
}
else {
return file;
}
}
/* Test that loading a custom CA certificate file works. */
static void test_ssl_load_cert_file(CuTest *tc)
{
serf_ssl_certificate_t *cert = NULL;
apr_pool_t *test_pool = tc->testBaton;
apr_status_t status = serf_ssl_load_cert_file(
&cert, get_ca_file(test_pool, "test/serftestca.pem"), test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
CuAssertPtrNotNull(tc, cert);
}
typedef struct
{
const char *cn;
const char *o;
const char *ou;
const char *l;
const char *st;
const char *c;
const char *e;
} test_dn_t;
typedef struct {
test_dn_t issuer;
test_dn_t subject;
const char *sha1;
const char *notBefore;
const char *notAfter;
/* serial number, subjectAltNames */
} test_cert_t;
static void
validate_dn(CuTest *tc, const test_dn_t *expected, const apr_hash_t *actual)
{
apr_hash_t *dn = (apr_hash_t *)actual;
CuAssertStrEquals(tc, expected->cn,
apr_hash_get(dn, "CN", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->ou,
apr_hash_get(dn, "OU", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->o,
apr_hash_get(dn, "O", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->l,
apr_hash_get(dn, "L", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->st,
apr_hash_get(dn, "ST", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->c,
apr_hash_get(dn, "C", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->e,
apr_hash_get(dn, "E", APR_HASH_KEY_STRING));
}
static void
validate_cert(CuTest *tc, const test_cert_t *expected, const apr_hash_t *actual)
{
const apr_hash_t *subject, *issuer;
apr_hash_t *cert = (apr_hash_t *)actual;
subject = apr_hash_get(cert, "subject", APR_HASH_KEY_STRING);
CuAssertPtrNotNullMsg(tc, "Expected subject", subject);
validate_dn(tc, &expected->subject, subject);
issuer = apr_hash_get(cert, "issuer", APR_HASH_KEY_STRING);
CuAssertPtrNotNullMsg(tc, "Expected issuer", issuer);
validate_dn(tc, &expected->issuer, issuer);
/*
TODO: sha1sum of DER encoded cert for signature.
CuAssertStrEquals(tc, expected->sha1,
apr_hash_get(cert, "sha1", APR_HASH_KEY_STRING));
*/
CuAssertStrEquals(tc, expected->notBefore,
apr_hash_get(cert, "notBefore", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, expected->notAfter,
apr_hash_get(cert, "notAfter", APR_HASH_KEY_STRING));
/* TODO: subjectAltNames */
}
/* Test that reading the subject from a custom CA certificate file works. */
static void test_ssl_cert_subject(CuTest *tc)
{
apr_hash_t *subject;
serf_ssl_certificate_t *cert = NULL;
apr_status_t status;
const test_dn_t exp_subject = { "Serf", "In Serf we trust, Inc.",
"Test Suite", "Mechelen", "Antwerp", "BE", "serf@example.com" };
apr_pool_t *test_pool = tc->testBaton;
status = serf_ssl_load_cert_file(&cert, get_ca_file(test_pool,
"test/serftestca.pem"),
test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
CuAssertPtrNotNull(tc, cert);
subject = serf_ssl_cert_subject(cert, test_pool);
CuAssertPtrNotNull(tc, subject);
validate_dn(tc, &exp_subject, subject);
}
/* Test that reading the issuer from a custom CA certificate file works. */
static void test_ssl_cert_issuer(CuTest *tc)
{
apr_hash_t *issuer;
serf_ssl_certificate_t *cert = NULL;
apr_status_t status;
const test_dn_t exp_issuer = { "Serf", "In Serf we trust, Inc.",
"Test Suite", "Mechelen", "Antwerp", "BE", "serf@example.com" };
apr_pool_t *test_pool = tc->testBaton;
status = serf_ssl_load_cert_file(&cert, get_ca_file(test_pool,
"test/serftestca.pem"),
test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
CuAssertPtrNotNull(tc, cert);
issuer = serf_ssl_cert_issuer(cert, test_pool);
CuAssertPtrNotNull(tc, issuer);
validate_dn(tc, &exp_issuer, issuer);
}
/* 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)
{
apr_hash_t *kv;
serf_ssl_certificate_t *cert = NULL;
apr_array_header_t *san_arr;
apr_status_t status;
apr_pool_t *test_pool = tc->testBaton;
status = serf_ssl_load_cert_file(&cert, get_ca_file(test_pool,
"test/serftestca.pem"),
test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
CuAssertPtrNotNull(tc, cert);
kv = serf_ssl_cert_certificate(cert, test_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",
apr_hash_get(kv, "sha1", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, "Mar 21 13:18:17 2008 GMT",
apr_hash_get(kv, "notBefore", APR_HASH_KEY_STRING));
CuAssertStrEquals(tc, "Mar 21 13:18:17 2011 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);
}
static void test_ssl_load_CA_cert_from_file(CuTest *tc)
{
serf_ssl_certificate_t *cert = NULL;
serf_bucket_t *bkt, *stream;
serf_ssl_context_t *ssl_context;
apr_status_t status;
apr_pool_t *test_pool = tc->testBaton;
serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
NULL);
stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
bkt = serf_bucket_ssl_decrypt_create(stream, NULL, alloc);
ssl_context = serf_bucket_ssl_decrypt_context_get(bkt);
status = serf_ssl_load_CA_cert_from_file(ssl_context,
&cert,
get_ca_file(test_pool, "test/serftestca.pem"),
test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
CuAssertPtrNotNull(tc, cert);
}
static const char *extract_cert_from_pem(const char *pemdata,
apr_size_t *pemlen,
int copy_delimiters,
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;
int delimiter = FALSE;
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)
continue;
state = CERT_BEGIN;
delimiter = TRUE;
if (!copy_delimiters)
continue;
} else if (state == CERT_BEGIN) {
if (strncmp(endcert, data, strlen(endcert)) == 0)
{
state = CERT_FOUND;
delimiter = TRUE;
if (!copy_delimiters)
break;
certdata = apr_pstrcat(pool, certdata, CRLF, NULL);
certlen += 2;
}
}
/* Copy the line to the output buffer. Remove linefeeds from the
base64 DER encoded certificate. */
certdata = apr_pstrcat(pool, certdata, data, NULL);
certlen += len;
switch (found && !delimiter)
{
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;
}
}
if (state == CERT_FOUND) {
*pemlen = certlen;
return certdata;
}
else
return NULL;
}
static void test_ssl_cert_export(CuTest *tc)
{
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;
apr_pool_t *test_pool = tc->testBaton;
status = serf_ssl_load_cert_file(&cert, get_ca_file(test_pool,
"test/serftestca.pem"),
test_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. */
status = apr_file_open(&fp, "test/serftestca.pem",
APR_FOPEN_READ | APR_FOPEN_BINARY,
APR_FPROT_OS_DEFAULT, test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
apr_file_info_get(&file_info, APR_FINFO_SIZE, fp);
pembuf = apr_palloc(test_pool, file_info.size);
status = apr_file_read_full(fp, pembuf, file_info.size, &pemlen);
CuAssertIntEquals(tc, APR_SUCCESS, status);
base64derbuf = serf_ssl_cert_export(cert, test_pool);
CuAssertStrEquals(tc,
extract_cert_from_pem(pembuf, &pemlen, 0, test_pool),
base64derbuf);
}
/****************************************************************************/
/* Test the DER decoding with certificates collected in file
test/certificates.pem.
*/
static apr_status_t read_certificate(serf_bucket_t *bkt,
char *buf,
apr_size_t buf_len,
apr_size_t *read_len)
{
apr_size_t total_read;
apr_status_t status;
const char *delimiter = "=========================";
int acceptable = SERF_NEWLINE_CRLF | SERF_NEWLINE_LF | SERF_NEWLINE_CR;
total_read = 0;
do
{
const char *data;
apr_size_t len;
int found;
status = serf_bucket_readline(bkt, acceptable, &found,
&data, &len);
if (!SERF_BUCKET_READ_ERROR(status))
{
if (len >= sizeof(delimiter) &&
strncmp(delimiter, data, sizeof(delimiter)) == 0)
{
/* end of certificate found, return current data. */
status = APR_SUCCESS;
break;
}
if (total_read + len > buf_len)
{
/* Buffer is not large enough to read all data */
status = SERF_ERROR_ISSUE_IN_TESTSUITE;
}
memcpy(buf + total_read, data, len);
total_read += len;
}
} while(status == APR_SUCCESS);
*read_len = total_read;
return status;
}
const test_cert_t test_certs[] =
{
{ { "Serf CA", "In Serf we trust, Inc.", "Test Suite CA",
"Mechelen", "Antwerp", "BE", "serfca@example.com"},
{ "Serf Server", "In Serf we trust, Inc.", "Test Suite Server",
"Mechelen", "Antwerp", "BE", "serfserver@example.com"},
"", "Apr 18 19:50:11 2013 GMT", "Apr 18 19:50:11 2014 GMT",
},
{ { "Serf Root CA", "In Serf we trust, Inc.", "Test Suite Root CA",
"Mechelen", "Antwerp", "BE", "serfrootca@example.com"},
{ "Serf Root CA", "In Serf we trust, Inc.", "Test Suite Root CA",
"Mechelen", "Antwerp", "BE", "serfrootca@example.com"},
"", "Apr 13 11:19:14 2013 GMT", "Apr 11 11:19:14 2023 GMT",
},
{ { "Belgium Root CA", NULL, NULL, NULL, NULL, "BE", NULL},
{ "Belgium Root CA", NULL, NULL, NULL, NULL, "BE", NULL},
"", "Jan 26 23:00:00 2003 GMT", "Jan 26 23:00:00 2014 GMT",
},
{ { "Belgium Root CA2", NULL, NULL, NULL, NULL, "BE", NULL},
{ "Belgium Root CA2", NULL, NULL, NULL, NULL, "BE", NULL},
"", "Oct 04 10:00:00 2007 GMT", "Dec 15 08:00:00 2021 GMT",
},
};
static void test_macosxssl_DER_decoding(CuTest *tc)
{
#ifdef SERF_HAVE_MACOSXSSL
serf_bucket_t *filebkt;
apr_file_t *fp;
char buf[16384];
apr_size_t len;
apr_status_t status;
int current_cert;
apr_pool_t *test_pool = tc->testBaton;
serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool,
NULL, NULL);
status = apr_file_open(&fp, "test/certificates.pem",
APR_FOPEN_READ | APR_FOPEN_BINARY,
APR_FPROT_OS_DEFAULT, test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
filebkt = serf_bucket_file_create(fp, alloc);
current_cert = 0;
do
{
status = read_certificate(filebkt, buf, sizeof(buf), &len);
CuAssertIntEquals(tc, SERF_BUCKET_READ_ERROR(status), 0);
/* A certificate was read from test/certificates.pem */
if (status == APR_SUCCESS)
{
apr_size_t pemlen;
const char *pemdata = extract_cert_from_pem(buf, &pemlen, 1, test_pool);
serf_ssl_certificate_t *cert;
apr_hash_t *actual;
test_cert_t thiscert = test_certs[current_cert];
/* printf("Certificate found: %.*s\n\n", pemlen, pemdata);**/
status = load_CA_cert_from_buffer(&cert, pemdata, pemlen, test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
status = serf__macosxssl_read_X509_DER_certificate(&actual,
cert->impl_cert,
test_pool);
CuAssertIntEquals(tc, APR_SUCCESS, status);
validate_cert(tc, &thiscert, actual);
current_cert++;
}
} while (status == APR_SUCCESS &&
current_cert < sizeof(test_certs) / sizeof(test_certs[0]));
#endif
}
/* Disable all available SSL implementations, test for no crash. */
static void test_ssl_no_implementations(CuTest *tc)
{
serf_bucket_t *bkt, *stream;
apr_pool_t *test_pool = tc->testBaton;
serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
NULL);
stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
serf_config_disable_bucket_impls(SERF_IMPL_SSL_ALL);
bkt = serf_bucket_ssl_decrypt_create(stream, NULL, alloc);
CuAssertPtrEquals(tc, NULL, bkt);
bkt = serf_bucket_ssl_encrypt_create(stream, NULL, alloc);
CuAssertPtrEquals(tc, NULL, bkt);
serf_config_enable_bucket_impls(SERF_IMPL_SSL_ALL);
}
CuSuite *test_ssl(void)
{
CuSuite *suite = CuSuiteNew();
CuSuite *openssl_suite, *macosxssl_suite;
CuSuiteSetSetupTeardownCallbacks(suite, test_setup, test_teardown);
#ifdef SERF_HAVE_OPENSSL
openssl_suite = CuSuiteNew();
CuSuiteSetSetupTeardownCallbacks(openssl_suite, test_openssl_setup,
test_openssl_teardown);
SUITE_ADD_TEST(openssl_suite, test_ssl_init);
SUITE_ADD_TEST(openssl_suite, test_ssl_load_cert_file);
SUITE_ADD_TEST(openssl_suite, test_ssl_cert_subject);
SUITE_ADD_TEST(openssl_suite, test_ssl_cert_issuer);
SUITE_ADD_TEST(openssl_suite, test_ssl_cert_certificate);
SUITE_ADD_TEST(openssl_suite, test_ssl_load_CA_cert_from_file);
SUITE_ADD_TEST(openssl_suite, test_ssl_cert_export);
SUITE_ADD_TEST(openssl_suite, test_macosxssl_DER_decoding);
CuSuiteAddSuite(suite, openssl_suite);
#endif
#ifdef SERF_HAVE_MACOSXSSL
macosxssl_suite = CuSuiteNew();
CuSuiteSetSetupTeardownCallbacks(macosxssl_suite, test_macosxssl_setup,
test_macosxssl_teardown);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_init);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_load_cert_file);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_subject);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_issuer);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_certificate);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_load_CA_cert_from_file);
SUITE_ADD_TEST(macosxssl_suite, test_ssl_cert_export);
SUITE_ADD_TEST(macosxssl_suite, test_macosxssl_DER_decoding);
CuSuiteAddSuite(suite, macosxssl_suite);
#endif
SUITE_ADD_TEST(suite, test_ssl_no_implementations);
return suite;
}