~apreiml/hare-tls

199e478d9ea97a41eebc7a3388ec948c1780b14f — Armin Preiml 2 months ago 0c69078
limbo test cases progress
4 files changed, 317 insertions(+), 77 deletions(-)

M cmd/limbo/main.ha
M cmd/limbo/parse.ha
M crypto/x509/chain.ha
M crypto/x509/errors.ha
M cmd/limbo/main.ha => cmd/limbo/main.ha +97 -69
@@ 10,62 10,24 @@ use crypto::x509;
use memio;
use time::date;

type testcase = struct {
	id: str,
	conflicts_with: []str,
	features: []str,
	importance: str,
	description: str,
	validation_kind: str,
	trusted_certs: []str,
	untrusted_intermediates: []str,
	peer_certificate: str,
	peer_certificate_key: str,
	expected_result: str,
	validation_time: (date::date | void),
	signature_algorithms: []str,
	key_usage: []str,
	extended_key_usage: []str,
	expected_peer_name: (void | peername),
	expected_peer_names: []peername,
	max_chain_depth: (void | int),
};

fn finish_testcase(tc: *testcase) void = {
	free(tc.id);
	strings::freeall(tc.conflicts_with);
	strings::freeall(tc.features);
	free(tc.importance);
	free(tc.description);
	free(tc.validation_kind);
	strings::freeall(tc.trusted_certs);
	strings::freeall(tc.untrusted_intermediates);
	free(tc.peer_certificate);
	free(tc.peer_certificate_key);
	free(tc.expected_result);
	strings::freeall(tc.signature_algorithms);
	strings::freeall(tc.key_usage);
	strings::freeall(tc.extended_key_usage);
	match (tc.expected_peer_name) {
	case void => void;
	case let pn: peername =>
		finish_peername(&pn);
	};

	for (let pn &.. tc.expected_peer_names) {
		finish_peername(pn);
	};
	free(tc.expected_peer_names);
};

fn finish_peername(pn: *peername) void = {
	free(pn.kind);
	free(pn.value);
};

type peername = struct {
	kind: str,
	value: str,
const skip_features: u16 = feature::HAS_POLICY_CONSTRAINTS
        | feature::HAS_CERT_POLICIES
        | feature::NO_CERT_POLICIES
        | feature::PEDANTIC_PUBLIC_SUFFIX_WILDCARD
        | feature::NAME_CONSTRAINT_DN
        | feature::PEDANTIC_WEBPKI_SUBSCRIBER_KEY
        | feature::PEDANTIC_WEBPKI_EKU
        | feature::PEDANTIC_SERIAL_NUMBER
        | feature::MAX_CHAIN_DEPTH
        | feature::PEDANTIC_RFC5280
        | feature::RFC5280_INCOMPATIBLE_WITH_WEBPKI
        | feature::DENIAL_OF_SERVICE;

type stats = struct {
	success: uint,
	skipped: uint,
	failed: uint,
	total: uint,
};

export fn main() void = {


@@ 112,6 74,8 @@ export fn main() void = {
		fmt::fatalf("Error parsing file: {}", json::strerror(e));
	};

	let stats = stats { ... };

	for (let tc => next(&it)!) {
		match (selecttc) {
		case void => void;


@@ 120,27 84,90 @@ export fn main() void = {
				continue;
			};
		};
		fmt::println("Testcase", tc.id)!;

		run(&tc, dumpcerts);
		const result = run(&tc, dumpcerts);
		finish_testcase(&tc);

		stats.total += 1;
		switch (result) {
		case result::SKIPPED =>
			stats.skipped += 1;
		case result::SUCCESS =>
			stats.success += 1;
		case result::FAILURE =>
			stats.failed += 1;
		};
	};

	fmt::printfln("done! conformant/nonconformant/skipped/total {}/{}/{}/{}.",
		stats.success, stats.failed, stats.skipped, stats.total)!;
};

fn run(tc: *testcase, dumpcerts: bool) void = {
fn run(tc: *testcase, dumpcerts: bool) result = {
	fmt::printf("Running test {} ... ", tc.id)!;

	if (tc.validation_kind == validation_kind::CLIENT) {
		return result::SKIPPED;
	};

	if (tc.features & skip_features != 0) {
		return result::SKIPPED;
	};

	let err: (void | x509::error) = void;
	let testr = match (try_run(tc, dumpcerts)) {
	case let r: result =>
		yield r;
	case x509::critext =>
		// TODO check if test for critext
		yield result::SKIPPED;
	case let e: x509::error =>
		err = e;
		yield result::FAILURE;
	};

	let r = switch (testr) {
	case result::SKIPPED =>
		yield result::SKIPPED;
	case result::SUCCESS, result::FAILURE =>
		yield if (testr == tc.expected_result) {
			yield result::SUCCESS;
		} else {
			yield result::FAILURE;
		};
	};
	fmt::println(strresult(r))!;

	if (r == result::FAILURE) {
		let errstr = match (err) {
		case void =>
			yield "chain built";
		case let e: x509::error =>
			yield x509::strerror(e);
		};

		fmt::printfln("\tterr={}", errstr)!;
	};

	return r;
};

fn try_run(tc: *testcase, dumpcerts: bool) (result | x509::error) = {
	let certbuf = memio::fixed(strings::toutf8(tc.peer_certificate));
	let certs = x509::drain_certs_pem(&certbuf)!;
	let certs = x509::drain_certs_pem(&certbuf)?;
	defer free(certs);
	let peer = certs[0];

	let chain = load_certs(tc.untrusted_intermediates);
	let chain = load_certs(tc.untrusted_intermediates)?;

	let trusted = load_certs(tc.trusted_certs);
	let trusted = load_certs(tc.trusted_certs)?;

	let opts = x509::verifyopts {
		trusted = &trusted,
		chain = &chain,
		date = tc.validation_time,
		keyusage = tc.key_usage,
		// TODO DNS name(s)?
	};

	if (dumpcerts) {


@@ 159,18 186,19 @@ fn run(tc: *testcase, dumpcerts: bool) void = {
		};
	};

	match (x509::verify(&peer, &opts)) {
	case void => void;
	case let e: x509::error =>
		fmt::println("  fail: ", x509::strerror(e))!;
	};
	x509::verify(&peer, &opts)?;

	return result::SUCCESS;

	// TODO if server, check expected_peer_names
	// TODO else, check expected_peer_name
};

fn load_certs(certs: []str) x509::slicestore = {
fn load_certs(certs: []str) (x509::slicestore | x509::error) = {
	let s = x509::new_slicestore([]);
	for (let t .. certs) {
		let certbuf = memio::fixed(strings::toutf8(t));
		let certs = x509::drain_certs_pem(&certbuf)!;
		let certs = x509::drain_certs_pem(&certbuf)?;
		defer free(certs);
		let crt = certs[0];


M cmd/limbo/parse.ha => cmd/limbo/parse.ha +204 -6
@@ 7,6 7,99 @@ use io;
use fs;
use strings;
use time::date;
use crypto::x509::{keyusage};

type result = enum {
	SUCCESS,
	FAILURE,
	SKIPPED,
};

type validation_kind = enum {
	SERVER,
	CLIENT,
};

type feature = enum u16 {
	HAS_POLICY_CONSTRAINTS,
        HAS_CERT_POLICIES,
        NO_CERT_POLICIES,
        PEDANTIC_PUBLIC_SUFFIX_WILDCARD,
        NAME_CONSTRAINT_DN,
        PEDANTIC_WEBPKI_SUBSCRIBER_KEY,
        PEDANTIC_WEBPKI_EKU,
        PEDANTIC_SERIAL_NUMBER,
        MAX_CHAIN_DEPTH,
        PEDANTIC_RFC5280,
        RFC5280_INCOMPATIBLE_WITH_WEBPKI,
        DENIAL_OF_SERVICE
};

fn strresult(r: result) str = {
	switch (r) {
	case result::SUCCESS =>
		return "CONFORMANT";
	case result::FAILURE =>
		return "NON-CONFORMANT";
	case result::SKIPPED =>
		return "SKIPPED";
	};
};

type testcase = struct {
	id: str,
	conflicts_with: []str,
	features: u16,
	importance: str,
	description: str,
	validation_kind: validation_kind,
	trusted_certs: []str,
	untrusted_intermediates: []str,
	peer_certificate: str,
	peer_certificate_key: str,
	expected_result: result,
	validation_time: (date::date | void),
	signature_algorithms: []str,
	key_usage: u16,
	extended_key_usage: []str,
	expected_peer_name: (void | peername),
	expected_peer_names: []peername,
	max_chain_depth: (void | int),
};

fn finish_testcase(tc: *testcase) void = {
	free(tc.id);
	strings::freeall(tc.conflicts_with);
	free(tc.importance);
	free(tc.description);
	strings::freeall(tc.trusted_certs);
	strings::freeall(tc.untrusted_intermediates);
	free(tc.peer_certificate);
	free(tc.peer_certificate_key);
	strings::freeall(tc.signature_algorithms);
	strings::freeall(tc.extended_key_usage);
	match (tc.expected_peer_name) {
	case void => void;
	case let pn: peername =>
		finish_peername(&pn);
	};

	for (let pn &.. tc.expected_peer_names) {
		finish_peername(pn);
	};
	free(tc.expected_peer_names);
};

fn finish_peername(pn: *peername) void = {
	free(pn.kind);
	free(pn.value);
};

type peername = struct {
	kind: str,
	value: str,
};


type iter = struct {
	lex: *json::lexer,


@@ 84,13 177,20 @@ fn next(it: *iter) (testcase | json::error | io::EOF) = {
			case "conflicts_with" =>
				tc.conflicts_with = parse_strarr(it.lex)?;
			case "features" =>
				tc.features = parse_strarr(it.lex)?;
				tc.features = parse_features(it.lex)?;
			case "importance" =>
				tc.importance = strings::dup(json::lex(it.lex)? as str);
			case "description" =>
				tc.description = strings::dup(json::lex(it.lex)? as str);
			case "validation_kind" =>
				tc.validation_kind = strings::dup(json::lex(it.lex)? as str);
				tc.validation_kind = switch (json::lex(it.lex)? as str) {
				case "SERVER" =>
					yield validation_kind::SERVER;
				case "CLIENT" =>
					yield validation_kind::CLIENT;
				case =>
					abort();
				};
			case "trusted_certs" =>
				tc.trusted_certs = parse_strarr(it.lex)?;
			case "untrusted_intermediates" =>


@@ 106,19 206,33 @@ fn next(it: *iter) (testcase | json::error | io::EOF) = {
				};
				let t = t as json::token;
				if (t is str) {
					tc.validation_time = date::from_str(
						date::RFC3339, (t as str))!;
					let dt = t as str;

					if (len(dt) == 29) {
						tc.validation_time = date::from_str(
							"%Y-%m-%dT%H:%M:%S.%N%z", dt)!;
					} else {
						tc.validation_time = date::from_str(
							date::RFC3339, dt)!;
					};
				} else {
					t as json::_null;
				};
			case "signature_algorithms" =>
				tc.signature_algorithms = parse_strarr(it.lex)?;
			case "key_usage" =>
				tc.key_usage = parse_strarr(it.lex)?;
				tc.key_usage = parse_keyusage(it.lex)?;
			case "extended_key_usage" =>
				tc.extended_key_usage = parse_strarr(it.lex)?;
			case "expected_result" =>
				tc.expected_result = strings::dup(json::lex(it.lex)? as str);
				tc.expected_result = switch (json::lex(it.lex)? as str) {
				case "SUCCESS" =>
					yield result::SUCCESS;
				case "FAILURE" =>
					yield result::FAILURE;
				case =>
					abort("unexpected result");
				};
			case "expected_peer_name" =>
				let t = json::lex(it.lex)?;
				if (t is io::EOF) fmt::fatalf("peer name expected");


@@ 237,3 351,87 @@ fn parse_nullablestr(l: *json::lexer) (void | str | json::error) = {
		};
	};
};

fn parse_keyusage(l: *json::lexer) (u16 | json::error) = {
	json::lex(l)? as json::arraystart;

	let ku: u16 = 0;
	for (let t => json::lex(l)?) {
		match (t) {
		case json::comma => void;
		case let s: str =>
			ku |= switch (s) {
			case "digitalSignature" =>
				yield keyusage::DIGITAL_SIGNATURE;
			case "contentCommitment" =>
				yield keyusage::CONTENT_COMMITMENT;
			case "keyEncipherment" =>
				yield keyusage::KEY_ENCIPHERMENT;
			case "dataEncipherment" =>
				yield keyusage::DATA_ENCIPHERMENT;
			case "keyAgreement" =>
				yield keyusage::KEY_AGREEMENT;
			case "keyCertSign" =>
				yield keyusage::KEY_CERT_SIGN;
			case "cRLSign" =>
				yield keyusage::CRL_SIGN;
			case "encipherOnly" =>
				yield keyusage::ENCIPHER_ONLY;
			case "decipherOnly" =>
				yield keyusage::DECIPHER_ONLY;
			case =>
				abort("unknown key usage");
			};
		case json::arrayend =>
			return ku;
		case =>
			fmt::fatal("Expected string array");
		};
	};
	abort();
};

fn parse_features(l: *json::lexer) (u16 | json::error) = {
	json::lex(l)? as json::arraystart;

	let features: u16 = 0;
	for (let t => json::lex(l)?) {
		match (t) {
		case json::comma => void;
		case let s: str =>
			switch (s) {
			case "has-policy-constraints" =>
				yield feature::HAS_POLICY_CONSTRAINTS;
			case "has-cert-policies" =>
				yield feature::HAS_CERT_POLICIES;
			case "no-cert-policies" =>
				yield feature::NO_CERT_POLICIES;
			case "pedantic-public-suffix-wildcard" =>
				yield feature::PEDANTIC_PUBLIC_SUFFIX_WILDCARD;
			case "name-constraint-dn" =>
				yield feature::NAME_CONSTRAINT_DN;
			case "pedantic-webpki-subscriber-key" =>
				yield feature::PEDANTIC_WEBPKI_SUBSCRIBER_KEY;
			case "pedantic-webpki-eku" =>
				yield feature::PEDANTIC_WEBPKI_EKU;
			case "pedantic-serial-number" =>
				yield feature::PEDANTIC_SERIAL_NUMBER;
			case "max-chain-depth" =>
				yield feature::MAX_CHAIN_DEPTH;
			case "pedantic-rfc5280" =>
				yield feature::PEDANTIC_RFC5280;
			case "rfc5280-incompatible-with-webpki" =>
				yield feature::RFC5280_INCOMPATIBLE_WITH_WEBPKI;
			case "denial-of-service" =>
				yield feature::DENIAL_OF_SERVICE;
			case =>
				abort("unknown feature");
			};
		case json::arrayend =>
			return features;
		case =>
			fmt::fatal("Expected string array");
		};
	};
	abort();
};

M crypto/x509/chain.ha => crypto/x509/chain.ha +10 -1
@@ 13,6 13,7 @@ export type verifyopts = struct {
	trusted: *store,
	chain: *store,
	date: (void | date::date),
	keyusage: u16,
};

def MAX_PATHLEN: u16 = 10;


@@ 78,6 79,14 @@ export fn verify(c: *cert, opts: *verifyopts) (void | error) = {
		return expired;
	};

	let ku = match (c.keyusage) {
	case void => yield 0u16;
	case let ku: u16 => yield ku;
	};
	if (ku & opts.keyusage != opts.keyusage) {
		return badusage;
	};

	let it = tpiter {
		opts = opts,
		// TODO all iter?


@@ 157,7 166,7 @@ fn verifysig(c: *cert, p: *cert, at: date::date, pathlen: size) (void | error) =

	match (p.keyusage) {
	case void =>
		yield;
		return notca;
	case let ku: u16 =>
		// printf("KU: {:x}", ku);
		if (ku & keyusage::KEY_CERT_SIGN == 0) {

M crypto/x509/errors.ha => crypto/x509/errors.ha +6 -1
@@ 26,11 26,14 @@ export type critext = !void;

export type maxpath = !void;

export type badusage = !void;

// form asn: badfromat/truncated

// An error which may be returned from an x509 function.
export type error = !(...io::error | badsig | expired | badformat | unknownalgo
	| unsupported | notca | untrusted | truncated | critext | maxpath);
	| unsupported | notca | untrusted | truncated | critext | maxpath
	| badusage);

type _error = !(...asn1::error | ...error);



@@ 84,5 87,7 @@ export fn strerror(e: error) str = {
		return "Trust path could not be found";
	case maxpath =>
		return "Max trust path length exceeded";
	case badusage =>
		return "Bad key usage";
	};
};