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";
};
};