/*-
 * SSLsplit - transparent SSL/TLS interception
 * https://www.roe.ch/SSLsplit
 *
 * Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "base64.h"
#include "ssl.h"

#include <limits.h>
#include <stdlib.h>
#include <unistd.h>

#include <check.h>

#ifdef __APPLE__
#define DLSUFFIX "dylib"
#else
#define DLSUFFIX "so"
#endif

#define TESTKEY "pki/server.key"
#define TESTCERT "pki/server.crt"
#define TESTCERT2 "pki/rsa.crt"
#define ENGINE "engine/dummy-engine."DLSUFFIX

static void
ssl_setup(void)
{
	if (ssl_init() == -1)
		exit(EXIT_FAILURE);
}

static void
ssl_teardown(void)
{
	ssl_fini();
}

static char wildcard1[] = "*.example.org";
static char wildcard2[] = "www.*.example.org";
static char wildcard3[] = "*.*.org";
static char wildcard4[] = "www*.example.org";
static char wildcard5[] = "*";
static char wildcard6[] = "*.xn--r-1ga.ch";
static char wildcard7[] = "xn--r-1ga*.xn--r-1ga.ch";
static char wildcard8[] = "xn--r-1ga.*.xn--r-1ga.ch";
static char name1[] = "www.example.org";
static char name2[] = "www.example.com";
static char name3[] = "example.org";
static char name4[] = "www.example.org.co.uk";
static char name5[] = "test.www.example.org";
static char name6[] = "www.test.example.org";
static char name7[] = "wwwtest.example.org";
static char name8[] = "ch";
static char name9[] = "www.xn--r-1ga.ch";
static char name10[] = "xn--r-1ga.xn--r-1ga.ch";
static char name11[] = "";

START_TEST(ssl_wildcardify_01)
{
	char *wc = ssl_wildcardify(name1);
	fail_unless(!strcmp(wc, wildcard1), "mismatch for 'www.example.org'");
	free(wc);
}
END_TEST

START_TEST(ssl_wildcardify_02)
{
	char *wc = ssl_wildcardify(name8);
	fail_unless(!strcmp(wc, wildcard5), "mismatch for 'ch'");
	free(wc);
}
END_TEST

START_TEST(ssl_wildcardify_03)
{
	char *wc = ssl_wildcardify(name11);
	fail_unless(!strcmp(wc, wildcard5), "mismatch for ''");
	free(wc);
}
END_TEST

START_TEST(ssl_dnsname_match_01)
{
	fail_unless(
		ssl_dnsname_match(name1, sizeof(name1) - 1,
		                  name1, sizeof(name1) - 1),
		"Hostname does not match itself");
}
END_TEST

START_TEST(ssl_dnsname_match_02)
{
	fail_unless(
		!ssl_dnsname_match(name1, sizeof(name1) - 1,
		                   name2, sizeof(name2) - 1),
		"Hostname matches hostname with different TLD");
}
END_TEST

START_TEST(ssl_dnsname_match_03)
{
	fail_unless(
		ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1,
		                  name1, sizeof(name1) - 1),
		"Regular wildcard does not match");
}
END_TEST

START_TEST(ssl_dnsname_match_04)
{
	fail_unless(
		!ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1,
		                   name2, sizeof(name2) - 1),
		"Regular wildcard matches other TLD");
}
END_TEST

START_TEST(ssl_dnsname_match_05)
{
	fail_unless(
		!ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1,
		                   name3, sizeof(name3) - 1),
		"Regular wildcard matches upper level domain");
}
END_TEST

START_TEST(ssl_dnsname_match_06)
{
	fail_unless(
		!ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1,
		                   name4, sizeof(name4) - 1),
		"Regular wildcard matches despite added suffix");
}
END_TEST

START_TEST(ssl_dnsname_match_07)
{
	fail_unless(
		!ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1,
		                   name5, sizeof(name5) - 1),
		"Regular wildcard matches two elements");
}
END_TEST

START_TEST(ssl_dnsname_match_08)
{
	fail_unless(
		!ssl_dnsname_match(wildcard2, sizeof(wildcard2) - 1,
		                   name6, sizeof(name6) - 1),
		"Wildcard matches in non-leftmost element");
}
END_TEST

START_TEST(ssl_dnsname_match_09)
{
	fail_unless(
		!ssl_dnsname_match(wildcard3, sizeof(wildcard3) - 1,
		                   name5, sizeof(name5) - 1),
		"Multiple wildcard matches");
}
END_TEST

START_TEST(ssl_dnsname_match_10)
{
	fail_unless(
		!ssl_dnsname_match(wildcard4, sizeof(wildcard4) - 1,
		                   name7, sizeof(name7) - 1),
		"Partial label wildcard matches");
}
END_TEST

START_TEST(ssl_dnsname_match_11)
{
	fail_unless(
		!ssl_dnsname_match(wildcard5, sizeof(wildcard5) - 1,
		                   name1, sizeof(name1) - 1),
		"Global wildcard * matches fqdn");
}
END_TEST

START_TEST(ssl_dnsname_match_12)
{
	fail_unless(
		ssl_dnsname_match(wildcard5, sizeof(wildcard5) - 1,
		                  name8, sizeof(name8) - 1),
		"Global wildcard * does not match TLD");
}
END_TEST

START_TEST(ssl_dnsname_match_13)
{
	fail_unless(
		ssl_dnsname_match(wildcard6, sizeof(wildcard6) - 1,
		                  name9, sizeof(name9) - 1),
		"IDN wildcard does not match");
}
END_TEST

START_TEST(ssl_dnsname_match_14)
{
	fail_unless(
		ssl_dnsname_match(wildcard6, sizeof(wildcard6) - 1,
		                  name10, sizeof(name10) - 1),
		"IDN wildcard does not match IDN element");
}
END_TEST

START_TEST(ssl_dnsname_match_15)
{
	fail_unless(
		!ssl_dnsname_match(wildcard7, sizeof(wildcard7) - 1,
		                   name10, sizeof(name10) - 1),
		"Illegal IDN wildcard matches");
}
END_TEST

START_TEST(ssl_dnsname_match_16)
{
	fail_unless(
		!ssl_dnsname_match(wildcard8, sizeof(wildcard8) - 1,
		                   name10, sizeof(name10) - 1),
		"Illegal IDN wildcard matches IDN element");
}
END_TEST

static unsigned char clienthello00[] =
	"\x80\x2b\x01\x00\x02\x00\x12\x00\x00\x00\x10\x07\x00\xc0\x03\x00"
	"\x80\x01\x00\x80\x06\x00\x40\x04\x00\x80\x02\x00\x80\xe0\xc3\x4a"
	"\xc6\xa4\x89\x23\x21\xb1\xbb\x51\xc7\x9c\x06\xa5\xff";
	/* SSL 2.0 */

static unsigned char clienthello01[] =
	"\x80\x67\x01\x03\x00\x00\x4e\x00\x00\x00\x10\x01\x00\x80\x03\x00"
	"\x80\x07\x00\xc0\x06\x00\x40\x02\x00\x80\x04\x00\x80\x00\x00\x39"
	"\x00\x00\x38\x00\x00\x35\x00\x00\x33\x00\x00\x32\x00\x00\x04\x00"
	"\x00\x05\x00\x00\x2f\x00\x00\x16\x00\x00\x13\x00\xfe\xff\x00\x00"
	"\x0a\x00\x00\x15\x00\x00\x12\x00\xfe\xfe\x00\x00\x09\x00\x00\x64"
	"\x00\x00\x62\x00\x00\x03\x00\x00\x06\xa8\xb8\x93\xbb\x90\xe9\x2a"
	"\xa2\x4d\x6d\xcc\x1c\xe7\x2a\x80\x21";
	/* SSL 3.0 in SSL 2.0 record */

static unsigned char clienthello02[] =
	"\x16\x03\x00\x00\x73\x01\x00\x00\x6f\x03\x00\x00\x34\x01\x1e\x67"
	"\x3a\xfa\xce\xd9\x51\xba\xe4\xfc\x64\x95\x03\x82\x63\x0f\xe3\x39"
	"\x6b\xc7\xbd\x2b\xe5\x51\x37\x23\x48\x5b\xfb\x20\xa3\xca\xad\x46"
	"\x95\x5d\x64\xbb\x33\xec\xb5\x12\x91\x21\xa3\x50\xd2\xc0\xc5\xf6"
	"\x67\xc3\xcc\x9e\xc0\x4a\x71\x1b\x92\xdc\x58\x55\x00\x28\x00\x39"
	"\x00\x38\x00\x35\x00\x33\x00\x32\x00\x04\x00\x05\x00\x2f\x00\x16"
	"\x00\x13\xfe\xff\x00\x0a\x00\x15\x00\x12\xfe\xfe\x00\x09\x00\x64"
	"\x00\x62\x00\x03\x00\x06\x01\x00";
	/* SSL 3.0, no TLS extensions */

static unsigned char clienthello03[] =
	"\x16\x03\x01\x00\x9b\x01\x00\x00\x97\x03\x01\x4b\x99\x46\xac\x38"
	"\x08\xbb\xa7\x1c\x9b\xea\x79\xc5\xd6\x70\x3d\xed\x20\x80\x60\xb4"
	"\x7e\xb5\x07\x13\xcf\x9a\x1c\xec\x6f\x64\xe5\x00\x00\x46\xc0\x0a"
	"\xc0\x09\xc0\x07\xc0\x08\xc0\x13\xc0\x14\xc0\x11\xc0\x12\xc0\x04"
	"\xc0\x05\xc0\x02\xc0\x03\xc0\x0e\xc0\x0f\xc0\x0c\xc0\x0d\x00\x2f"
	"\x00\x05\x00\x04\x00\x35\x00\x0a\x00\x09\x00\x03\x00\x08\x00\x06"
	"\x00\x32\x00\x33\x00\x38\x00\x39\x00\x16\x00\x15\x00\x14\x00\x13"
	"\x00\x12\x00\x11\x01\x00\x00\x28\x00\x00\x00\x12\x00\x10\x00\x00"
	"\x0d\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x30\x30\x2e\x34\x00\x0a"
	"\x00\x08\x00\x06\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00";
	/* TLS 1.0, SNI extension with hostname "192.168.100.4";
	 * Note: IP addresses are not legal values */

static unsigned char clienthello04[] =
	"\x16\x03\x01\x00\x6c\x01\x00\x00\x68\x03\x01\x4a\x9d\x49\x75\xb2"
	"\x7e\xf9\xbc\xc3\x76\xac\x19\x78\xfb\x6a\xee\x50\x55\x5e\x35\x4c"
	"\xca\xf2\x21\x15\xf3\x8a\x2a\xfc\xb5\x35\xed\x00\x00\x28\x00\x39"
	"\x00\x38\x00\x35\x00\x16\x00\x13\x00\x0a\x00\x33\x00\x32\x00\x2f"
	"\x00\x07\x00\x05\x00\x04\x00\x15\x00\x12\x00\x09\x00\x14\x00\x11"
	"\x00\x08\x00\x06\x00\x03\x01\x00\x00\x17\x00\x00\x00\x0f\x00\x0d"
	"\x00\x00\x0a\x6b\x61\x6d\x65\x73\x68\x2e\x63\x6f\x6d\x00\x23\x00"
	"\x00";
	/* TLS 1.0, SNI extension with hostname "kamesh.com" */

static unsigned char clienthello05[] =
	"\x16\x03\x03\x01\x7d\x01\x00\x01\x79\x03\x03\x4f\x7f\x27\xd0\x76"
	"\x5f\xc1\x3b\xba\x73\xd5\x07\x8b\xd9\x79\xf9\x51\xd4\xce\x7d\x9a"
	"\xdb\xdf\xf8\x4e\x95\x86\x38\x61\xdd\x84\x2a\x00\x00\xca\xc0\x30"
	"\xc0\x2c\xc0\x28\xc0\x24\xc0\x14\xc0\x0a\xc0\x22\xc0\x21\x00\xa3"
	"\x00\x9f\x00\x6b\x00\x6a\x00\x39\x00\x38\x00\x88\x00\x87\xc0\x19"
	"\xc0\x20\x00\xa7\x00\x6d\x00\x3a\x00\x89\xc0\x32\xc0\x2e\xc0\x2a"
	"\xc0\x26\xc0\x0f\xc0\x05\x00\x9d\x00\x3d\x00\x35\x00\x84\xc0\x12"
	"\xc0\x08\xc0\x1c\xc0\x1b\x00\x16\x00\x13\xc0\x17\xc0\x1a\x00\x1b"
	"\xc0\x0d\xc0\x03\x00\x0a\xc0\x2f\xc0\x2b\xc0\x27\xc0\x23\xc0\x13"
	"\xc0\x09\xc0\x1f\xc0\x1e\x00\xa2\x00\x9e\x00\x67\x00\x40\x00\x33"
	"\x00\x32\x00\x9a\x00\x99\x00\x45\x00\x44\xc0\x18\xc0\x1d\x00\xa6"
	"\x00\x6c\x00\x34\x00\x9b\x00\x46\xc0\x31\xc0\x2d\xc0\x29\xc0\x25"
	"\xc0\x0e\xc0\x04\x00\x9c\x00\x3c\x00\x2f\x00\x96\x00\x41\x00\x07"
	"\xc0\x11\xc0\x07\xc0\x16\x00\x18\xc0\x0c\xc0\x02\x00\x05\x00\x04"
	"\x00\x15\x00\x12\x00\x1a\x00\x09\x00\x14\x00\x11\x00\x19\x00\x08"
	"\x00\x06\x00\x17\x00\x03\x00\xff\x02\x01\x00\x00\x85\x00\x00\x00"
	"\x12\x00\x10\x00\x00\x0d\x64\x61\x6e\x69\x65\x6c\x2e\x72\x6f\x65"
	"\x2e\x63\x68\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x34\x00"
	"\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00"
	"\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00"
	"\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00"
	"\x10\x00\x11\x00\x23\x00\x00\x00\x0d\x00\x22\x00\x20\x06\x01\x06"
	"\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03"
	"\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x01\x01\x00\x0f\x00"
	"\x01\x01";
	/* TLS 1.2, SNI extension with hostname "daniel.roe.ch" */

static unsigned char clienthello06[] =
	"I will start TLS now: "
	"\x16\x03\x03\x01\x7d\x01\x00\x01\x79\x03\x03\x4f\x7f\x27\xd0\x76"
	"\x5f\xc1\x3b\xba\x73\xd5\x07\x8b\xd9\x79\xf9\x51\xd4\xce\x7d\x9a"
	"\xdb\xdf\xf8\x4e\x95\x86\x38\x61\xdd\x84\x2a\x00\x00\xca\xc0\x30"
	"\xc0\x2c\xc0\x28\xc0\x24\xc0\x14\xc0\x0a\xc0\x22\xc0\x21\x00\xa3"
	"\x00\x9f\x00\x6b\x00\x6a\x00\x39\x00\x38\x00\x88\x00\x87\xc0\x19"
	"\xc0\x20\x00\xa7\x00\x6d\x00\x3a\x00\x89\xc0\x32\xc0\x2e\xc0\x2a"
	"\xc0\x26\xc0\x0f\xc0\x05\x00\x9d\x00\x3d\x00\x35\x00\x84\xc0\x12"
	"\xc0\x08\xc0\x1c\xc0\x1b\x00\x16\x00\x13\xc0\x17\xc0\x1a\x00\x1b"
	"\xc0\x0d\xc0\x03\x00\x0a\xc0\x2f\xc0\x2b\xc0\x27\xc0\x23\xc0\x13"
	"\xc0\x09\xc0\x1f\xc0\x1e\x00\xa2\x00\x9e\x00\x67\x00\x40\x00\x33"
	"\x00\x32\x00\x9a\x00\x99\x00\x45\x00\x44\xc0\x18\xc0\x1d\x00\xa6"
	"\x00\x6c\x00\x34\x00\x9b\x00\x46\xc0\x31\xc0\x2d\xc0\x29\xc0\x25"
	"\xc0\x0e\xc0\x04\x00\x9c\x00\x3c\x00\x2f\x00\x96\x00\x41\x00\x07"
	"\xc0\x11\xc0\x07\xc0\x16\x00\x18\xc0\x0c\xc0\x02\x00\x05\x00\x04"
	"\x00\x15\x00\x12\x00\x1a\x00\x09\x00\x14\x00\x11\x00\x19\x00\x08"
	"\x00\x06\x00\x17\x00\x03\x00\xff\x02\x01\x00\x00\x85\x00\x00\x00"
	"\x12\x00\x10\x00\x00\x0d\x64\x61\x6e\x69\x65\x6c\x2e\x72\x6f\x65"
	"\x2e\x63\x68\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x34\x00"
	"\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00"
	"\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00"
	"\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00"
	"\x10\x00\x11\x00\x23\x00\x00\x00\x0d\x00\x22\x00\x20\x06\x01\x06"
	"\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03"
	"\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x01\x01\x00\x0f\x00"
	"\x01\x01";
	/* TLS 1.2, SNI extension with hostname "daniel.roe.ch" */

START_TEST(ssl_tls_clienthello_parse_00)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = (void *)0xDEADBEEF;

	rv = ssl_tls_clienthello_parse(clienthello00,
	                               sizeof(clienthello00) - 1,
	                               0, &ch, &sni);
#ifdef HAVE_SSLV2
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless(sni == NULL, "sni not NULL");
#else /* !HAVE_SSLV2 */
	fail_unless(rv == 1, "rv not 1");
	fail_unless(ch == NULL, "ch not NULL");
	fail_unless(sni == (void*)0xDEADBEEF, "sni modified");
#endif /* !HAVE_SSLV2 */
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_01)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = (void *)0xDEADBEEF;

	rv = ssl_tls_clienthello_parse(clienthello01,
	                               sizeof(clienthello01) - 1,
	                               0, &ch, &sni);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless(sni == NULL, "sni not NULL");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_02)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = (void *)0xDEADBEEF;

	rv = ssl_tls_clienthello_parse(clienthello02,
	                                sizeof(clienthello02) - 1,
	                                0, &ch, &sni);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless(sni == NULL, "sni not NULL");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_03)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = NULL;

	rv = ssl_tls_clienthello_parse(clienthello03,
	                                sizeof(clienthello03) - 1,
	                                0, &ch, &sni);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless(sni && !strcmp(sni, "192.168.100.4"),
	            "sni not '192.168.100.4' but should be");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_04)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = NULL;

	rv = ssl_tls_clienthello_parse(clienthello04,
	                                sizeof(clienthello04) - 1,
	                                0, &ch, &sni);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless(sni && !strcmp(sni, "kamesh.com"),
	            "sni not 'kamesh.com' but should be");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_05)
{
	for (size_t i = 0; i < sizeof(clienthello04) - 1; i++) {
		int rv;
		const unsigned char *ch = NULL;
		char *sni = (void*)0xDEADBEEF;
		ssize_t sz;

		sz = (ssize_t)i;
		rv = ssl_tls_clienthello_parse(clienthello04, sz, 0, &ch, &sni);
		fail_unless(rv == 1, "rv not 1");
		fail_unless(ch != NULL, "ch is NULL");
		fail_unless(sni == (void*)0xDEADBEEF, "sni modified");
	}
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_06)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = NULL;

	rv = ssl_tls_clienthello_parse(clienthello05,
	                                sizeof(clienthello05) - 1,
	                                0, &ch, &sni);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless(sni && !strcmp(sni, "daniel.roe.ch"),
	            "sni not 'daniel.roe.ch' but should be");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_07)
{
	for (size_t i = 0; i < sizeof(clienthello05) - 1; i++) {
		int rv;
		const unsigned char *ch = NULL;
		char *sni = (void*)0xDEADBEEF;
		ssize_t sz;

		sz = (ssize_t)i;
		rv = ssl_tls_clienthello_parse(clienthello05, sz, 0, &ch, &sni);
		fail_unless(rv == 1, "rv not 1");
		fail_unless(ch != NULL, "ch is NULL");
		fail_unless(sni == (void*)0xDEADBEEF, "sni modified");
	}
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_08)
{
	int rv;
	const unsigned char *ch = (void *)0xDEADBEEF;
	char *sni = (void *)0xDEADBEEF;

	rv = ssl_tls_clienthello_parse(clienthello06,
	                                sizeof(clienthello06) - 1,
	                                0, &ch, &sni);
	fail_unless(rv == 1, "rv not 1");
	fail_unless(ch == NULL, "ch not NULL");
	fail_unless(sni == (void*)0xDEADBEEF, "sni modified");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_09)
{
	int rv;
	const unsigned char *ch = NULL;
	char *sni = NULL;

	rv = ssl_tls_clienthello_parse(clienthello06,
	                                sizeof(clienthello06) - 1,
	                                1, &ch, &sni);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless((ch - clienthello06) != 21, "ch does not point to start");
	fail_unless(sni && !strcmp(sni, "daniel.roe.ch"),
	            "sni not 'daniel.roe.ch' but should be");
}
END_TEST

START_TEST(ssl_tls_clienthello_parse_10)
{
	int rv;
	const unsigned char *ch = NULL;

	rv = ssl_tls_clienthello_parse(clienthello06,
	                                sizeof(clienthello06) - 1,
	                                1, &ch, NULL);
	fail_unless(rv == 0, "rv not 0");
	fail_unless(ch != NULL, "ch is NULL");
	fail_unless((ch - clienthello06) != 21, "ch does not point to start");
}
END_TEST

START_TEST(ssl_key_identifier_sha1_01)
{
	X509 *c;
	EVP_PKEY *k;
	unsigned char keyid[SSL_KEY_IDSZ];

	c = ssl_x509_load(TESTCERT);
	fail_unless(!!c, "loading certificate failed");
	k = ssl_key_load(TESTKEY);
	fail_unless(!!k, "loading key failed");

	fail_unless(ssl_key_identifier_sha1(k, keyid) == 0,
	            "ssl_key_identifier_sha1() failed");

	int loc = X509_get_ext_by_NID(c, NID_subject_key_identifier, -1);
	X509_EXTENSION *ext = X509_get_ext(c, loc);
	fail_unless(!!ext, "loading ext failed");
	ASN1_STRING *value = X509_EXTENSION_get_data(ext);
	fail_unless(ASN1_STRING_length(value) - 2 == SSL_KEY_IDSZ,
	             "extension length mismatch");
	fail_unless(!memcmp(ASN1_STRING_get0_data(value) + 2, keyid, SSL_KEY_IDSZ),
	            "key id mismatch");
	EVP_PKEY_free(k);
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_names_01)
{
	X509 *c;
	char **names, **p;

	c = ssl_x509_load(TESTCERT);
	fail_unless(!!c, "loading certificate failed");
	names = ssl_x509_names(c);
	fail_unless(!!names, "parsing names failed");
	fail_unless(!!names[0], "first name");
	fail_unless(!strcmp(names[0], "daniel.roe.ch"), "first name");
	fail_unless(!!names[1], "second name");
	fail_unless(!strcmp(names[1], "daniel.roe.ch"), "second name");
	fail_unless(!!names[2], "third name");
	fail_unless(!strcmp(names[2], "www.roe.ch"), "third name");
	fail_unless(!!names[3], "fourth name");
	fail_unless(!strcmp(names[3], "*.roe.ch"), "fourth name");
	fail_unless(!names[4], "too many names");
	p = names;
	while (*p)
		free(*p++);
	free(names);
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_names_to_str_01)
{
	X509 *c;
	char *names;

	c = ssl_x509_load(TESTCERT);
	fail_unless(!!c, "loading certificate failed");
	names = ssl_x509_names_to_str(c);
	fail_unless(!!names, "no string");
	fail_unless(!strcmp(names,
	            "daniel.roe.ch/daniel.roe.ch/www.roe.ch/*.roe.ch"),
	            "wrong name string");
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_names_to_str_02)
{
	X509 *c;
	char *names;

	c = ssl_x509_load(TESTCERT2);
	fail_unless(!!c, "loading certificate failed");
	names = ssl_x509_names_to_str(c);
	fail_unless(!!names, "no string");
	fail_unless(!strcmp(names, "SSLsplit Root CA"), "wrong name string");
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_subject_01)
{
	X509 *c;
	char *subject;

	c = ssl_x509_load(TESTCERT);
	fail_unless(!!c, "loading certificate failed");
	subject = ssl_x509_subject(c);
	fail_unless(!!subject, "no string");
	fail_unless(!strcmp(subject, "/C=CH/O=SSLsplit Test Certificate/"
	                             "CN=daniel.roe.ch"),
	            "wrong subject string");
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_subject_cn_01)
{
	X509 *c;
	char *cn;
	size_t sz;
	size_t expsz = strlen("daniel.roe.ch") + 1;

	c = ssl_x509_load(TESTCERT);
	fail_unless(!!c, "loading certificate failed");
	cn = ssl_x509_subject_cn(c, &sz);
	fail_unless(!!cn, "no string");
	fail_unless(sz >= expsz, "subject CN size too small");
	fail_unless(!strcmp(cn, "daniel.roe.ch"), "wrong subject CN string");
#if 0
	for (unsigned int i = expsz; i < sz; i++) {
		fail_unless(cn[i] == '\0', "extra byte != 0");
	}
#endif
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_ocsps_01)
{
	X509 *c;
	char **ocsps, **p;

	c = ssl_x509_load(TESTCERT);
	fail_unless(!!c, "loading certificate failed");
	ocsps = ssl_x509_ocsps(c);
	fail_unless(!!ocsps, "parsing OCSP extensions failed");
	fail_unless(!!ocsps[0], "first OCSP");
	fail_unless(!strcmp(ocsps[0], "http://daniel.roe.ch/test/ocsp"),
	                              "first OCSP");
	fail_unless(!ocsps[1], "too many OCSPs");
	p = ocsps;
	while (*p)
		free(*p++);
	free(ocsps);
	X509_free(c);
}
END_TEST

START_TEST(ssl_x509_ocsps_02)
{
	X509 *c;
	char **ocsps;

	c = ssl_x509_load(TESTCERT2);
	fail_unless(!!c, "loading certificate failed");
	ocsps = ssl_x509_ocsps(c);
	fail_unless(!ocsps, "unexpected OCSP extensions");
	X509_free(c);
}
END_TEST

static char ocspreq01[] =
	"MEIwQDA+MDwwOjAJBgUrDgMCGgUABBT4cyABkyiCIhU4JpmIB"
	"ewdDnn8ZgQUbyBZ44kgy35o7xW5BMzM8FTvyTwCAQE=";

START_TEST(ssl_is_ocspreq_01)
{
	unsigned char *buf;
	size_t sz;

	buf = base64_dec(ocspreq01, sizeof(ocspreq01) - 1, &sz);
	fail_unless(!!buf, "failed to base64 decode");
	fail_unless(ssl_is_ocspreq(buf, sz), "is not ocsp req");
}
END_TEST

START_TEST(ssl_features_01)
{
	long vdiff = ((OPENSSL_VERSION_NUMBER ^ SSLeay()) & 0xfffff000L);

	fail_unless(!vdiff, "OpenSSL version mismatch at runtime");
}
END_TEST

START_TEST(ssl_features_02)
{
	int have_threads = 0;
#ifdef OPENSSL_THREADS
	have_threads = 1;
#endif /* OPENSSL_THREADS */
	fail_unless(have_threads, "!OPENSSL_THREADS: no threading support");
}
END_TEST

START_TEST(ssl_key_refcount_inc_01)
{
	EVP_PKEY *key;

	key = ssl_key_load(TESTKEY);
	fail_unless(!!key, "loading key failed");
	ssl_key_refcount_inc(key);
	ssl_key_refcount_inc(key);
	ssl_key_refcount_inc(key);
	EVP_PKEY_free(key);
	/* these must not crash */
	EVP_PKEY_free(key);
	EVP_PKEY_free(key);
	EVP_PKEY_free(key);
}
END_TEST

START_TEST(ssl_x509_refcount_inc_01)
{
	X509 *crt;

	crt = ssl_x509_load(TESTCERT);
	fail_unless(!!crt, "loading certificate failed");
	ssl_x509_refcount_inc(crt);
	ssl_x509_refcount_inc(crt);
	ssl_x509_refcount_inc(crt);
	X509_free(crt);
	/* these must not crash */
	X509_free(crt);
	X509_free(crt);
	X509_free(crt);
}
END_TEST

#ifndef OPENSSL_NO_ENGINE
START_TEST(ssl_engine_01)
{
	char cwd[PATH_MAX];
	char *path;

	fail_unless(getcwd(cwd, sizeof(cwd)) == cwd, "getcwd() failed");
	fail_unless(asprintf(&path, "%s/"ENGINE, cwd) != -1 && !!path,
	            "constructing engine path failed");
	fail_unless(ssl_engine(path) == 0, "loading OpenSSL engine failed");
	free(path);
}
END_TEST
#endif /* !OPENSSL_NO_ENGINE */

Suite *
ssl_suite(void)
{
	Suite *s;
	TCase *tc;

	s = suite_create("ssl");

	tc = tcase_create("ssl_wildcardify");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_wildcardify_01);
	tcase_add_test(tc, ssl_wildcardify_02);
	tcase_add_test(tc, ssl_wildcardify_03);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_dnsname_match");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_dnsname_match_01);
	tcase_add_test(tc, ssl_dnsname_match_02);
	tcase_add_test(tc, ssl_dnsname_match_03);
	tcase_add_test(tc, ssl_dnsname_match_04);
	tcase_add_test(tc, ssl_dnsname_match_05);
	tcase_add_test(tc, ssl_dnsname_match_06);
	tcase_add_test(tc, ssl_dnsname_match_07);
	tcase_add_test(tc, ssl_dnsname_match_08);
	tcase_add_test(tc, ssl_dnsname_match_09);
	tcase_add_test(tc, ssl_dnsname_match_10);
	tcase_add_test(tc, ssl_dnsname_match_11);
	tcase_add_test(tc, ssl_dnsname_match_12);
	tcase_add_test(tc, ssl_dnsname_match_13);
	tcase_add_test(tc, ssl_dnsname_match_14);
	tcase_add_test(tc, ssl_dnsname_match_15);
	tcase_add_test(tc, ssl_dnsname_match_16);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_tls_clienthello_parse");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_tls_clienthello_parse_00);
	tcase_add_test(tc, ssl_tls_clienthello_parse_01);
	tcase_add_test(tc, ssl_tls_clienthello_parse_02);
	tcase_add_test(tc, ssl_tls_clienthello_parse_03);
	tcase_add_test(tc, ssl_tls_clienthello_parse_04);
	tcase_add_test(tc, ssl_tls_clienthello_parse_05);
	tcase_add_test(tc, ssl_tls_clienthello_parse_06);
	tcase_add_test(tc, ssl_tls_clienthello_parse_07);
	tcase_add_test(tc, ssl_tls_clienthello_parse_08);
	tcase_add_test(tc, ssl_tls_clienthello_parse_09);
	tcase_add_test(tc, ssl_tls_clienthello_parse_10);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_key_identifier_sha1");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_key_identifier_sha1_01);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_x509_names");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_x509_names_01);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_x509_names_to_str");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_x509_names_to_str_01);
	tcase_add_test(tc, ssl_x509_names_to_str_02);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_x509_subject");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_x509_subject_01);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_x509_subject_cn");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_x509_subject_cn_01);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_x509_ocsps");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_x509_ocsps_01);
	tcase_add_test(tc, ssl_x509_ocsps_02);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_is_ocspreq");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_is_ocspreq_01);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_features");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_features_01);
	tcase_add_test(tc, ssl_features_02);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_key_refcount_inc");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_key_refcount_inc_01);
	suite_add_tcase(s, tc);

	tc = tcase_create("ssl_x509_refcount_inc");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_x509_refcount_inc_01);
	suite_add_tcase(s, tc);

#ifndef OPENSSL_NO_ENGINE
	tc = tcase_create("ssl_engine");
	tcase_add_checked_fixture(tc, ssl_setup, ssl_teardown);
	tcase_add_test(tc, ssl_engine_01);
	suite_add_tcase(s, tc);
#else /* OPENSSL_NO_ENGINE */
	fprintf(stderr, "ssl: 1 test omitted because OpenSSL has no "
	                "engine support\n");
#endif /* OPENSSL_NO_ENGINE */

	return s;
}

/* vim: set noet ft=c: */
