From eb879d7b501a840c2801ca91e3f06ef6b81d2277 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 27 Feb 2015 14:17:19 +0100 Subject: Make build.sh executable. --- packaging/docker/build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packaging/docker/build.sh diff --git a/packaging/docker/build.sh b/packaging/docker/build.sh old mode 100644 new mode 100755 -- cgit v1.1 From 43811a180ee2163ed2e21c305a9671b31cc37ef6 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 27 Feb 2015 14:55:51 +0100 Subject: Updating documentation to reflect authentication changes --- doc/minimalsystem.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/minimalsystem.txt b/doc/minimalsystem.txt index d0a5cee..9a097e5 100644 --- a/doc/minimalsystem.txt +++ b/doc/minimalsystem.txt @@ -49,7 +49,13 @@ directory, use the directory name with a `/` at the end, for example Running merge ------------- - tools/merge.py --baseurl https://127.0.0.1:8080/ --frontend https://127.0.0.1:8082/ --storage https://127.0.0.1:8081/ --mergedb rel/mergedb --keyfile rel/test/eckey.pem + tools/merge.py --baseurl https://127.0.0.1:8080/ \ + --frontend https://127.0.0.1:8082/ \ + --storage https://127.0.0.1:8081/ \ + --mergedb rel/mergedb \ + --keyfile rel/test/eckey.pem \ + --own-keyname merge-1 \ + --own-keyfile rel/privatekeys/merge-1-private.pem This will read the submitted certificates from the storage node, decide the order, and publish the certificates to the frontend server. -- cgit v1.1 From 056b69cc4891c5ef1ba8da7e0a9369cb069a03e0 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 27 Feb 2015 15:49:48 +0100 Subject: Improve tests-start and tests-stop --- Makefile | 25 +++++++++++++++++++------ tools/halt.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) create mode 100755 tools/halt.py diff --git a/Makefile b/Makefile index 08f6584..b63a11f 100644 --- a/Makefile +++ b/Makefile @@ -45,20 +45,33 @@ tests-prepare: touch rel/tests/machine/machine-1/db/newentries tests-start: - (cd rel ; bin/run_erl -daemon ../test/nodes/frontend-1/ ../test/nodes/frontend-1/log/ "exec bin/erl -config frontend-1") - (cd rel ; bin/run_erl -daemon ../test/nodes/storage-1/ ../test/nodes/storage-1/log/ "exec bin/erl -config storage-1") - sleep 1 + @for node in frontend-1 storage-1; do \ + (cd rel ; bin/run_erl -daemon ../test/nodes/$$node/ ../test/nodes/$$node/log/ "exec bin/erl -config $$node") \ + done + @for i in 1 2 3 4 5 6 7 8 9 10; do \ + echo "waiting for system to start" ; \ + sleep 0.5 ; \ + allstarted=1 ; \ + for testurl in https://127.0.0.1:8080/ https://127.0.0.1:8081/ https://127.0.0.1:8082/; do \ + if curl -s -k $$testurl > /dev/null ; then : ; else allstarted=0 ; fi ; \ + : ; \ + done ; \ + if [ $$allstarted == 1 ]; then break ; fi ; \ + done tests-run: @(cd tools ; python testcase1.py ) || echo "Tests failed" @(cd tools ; python fetchallcerts.py https://127.0.0.1:8080/) || echo "Verification failed" tests-stop: + @for node in frontend-1 storage-1; do \ + ./tools/halt.py ./rel/bin/to_erl test/nodes/$$node/ ; \ + done + +tests-wait: sleep 5 - echo "halt()." | ./rel/bin/to_erl test/nodes/frontend-1/ - echo "halt()." | ./rel/bin/to_erl test/nodes/storage-1/ -tests: tests-prepare tests-start tests-run tests-stop +tests: tests-prepare tests-start tests-run tests-wait tests-stop # Unit testing. check: all diff --git a/tools/halt.py b/tools/halt.py new file mode 100755 index 0000000..cfbf14e --- /dev/null +++ b/tools/halt.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014, NORDUnet A/S. +# See LICENSE for licensing information. + +import argparse +import subprocess +import sys + +parser = argparse.ArgumentParser(description='') +parser.add_argument('toerl') +parser.add_argument('nodedir') +args = parser.parse_args() + +p = subprocess.Popen( + [args.toerl, args.nodedir], + stdin=subprocess.PIPE) +p.communicate("halt().\n") -- cgit v1.1 From 8a7bcb91f440502b24d92842e0c89f2fcdf90c04 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Sun, 8 Mar 2015 00:03:36 +0100 Subject: merge.py: Store certs in hashed directory structure --- tools/merge.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tools/merge.py b/tools/merge.py index c137f4b..1b94581 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -14,6 +14,7 @@ import time import ecdsa import hashlib import urlparse +import os from certtools import build_merkle_tree, create_sth_signature, check_sth_signature, get_eckey_from_file, timing_point, http_request parser = argparse.ArgumentParser(description="") @@ -36,6 +37,8 @@ logorderfile = args.mergedb + "/logorder" own_key = (args.own_keyname, args.own_keyfile) +hashed_dir = True + def parselogrow(row): return base64.b16decode(row) @@ -44,12 +47,26 @@ def get_logorder(): return [parselogrow(row.rstrip()) for row in f] def write_chain(key, value): - f = open(chainsdir + "/" + base64.b16encode(key), "w") + filename = base64.b16encode(key) + if hashed_dir: + path = chainsdir + "/" + filename[0:2] + "/" + filename[2:4] + "/" + filename[4:6] + try: + os.makedirs(path) + except Exception, e: + print e + else: + path = chainsdir + f = open(path + "/" + filename, "w") f.write(value) f.close() def read_chain(key): - f = open(chainsdir + "/" + base64.b16encode(key), "r") + filename = base64.b16encode(key) + path = chainsdir + "/" + filename[0:2] + "/" + filename[2:4] + "/" + filename[4:6] + try: + f = open(path + "/" + filename, "r") + except IOError, e: + f = open(chainsdir + "/" + filename, "r") value = f.read() f.close() return value -- cgit v1.1 From 1f63c850a12797c5efd2db8368ef7219ed555ce2 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Sun, 8 Mar 2015 03:27:59 +0100 Subject: Cache SCT:s --- src/catlfish.erl | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/catlfish.erl b/src/catlfish.erl index 3956eec..2fd9dc7 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -69,10 +69,30 @@ build_mtl(Timestamp, LeafCert) -> entry = TSE}, serialise(MTL). +calc_sct(TimestampedEntry) -> + plop:serialise(plop:spt(list_to_binary([<>, + serialise_signature_type(certificate_timestamp), + serialise(TimestampedEntry)]))). + +get_sct(Hash, TimestampedEntry) -> + case application:get_env(catlfish, sctcache_root_path) of + {ok, RootPath} -> + case perm:readfile(RootPath, Hash) of + Contents when is_binary(Contents) -> + Contents; + noentry -> + SCT = calc_sct(TimestampedEntry), + ok = perm:ensurefile_nosync(RootPath, Hash, SCT), + SCT + end; + _ -> + SCT = calc_sct(TimestampedEntry) + end. + -spec add_chain(binary(), [binary()]) -> nonempty_string(). add_chain(LeafCert, CertChain) -> EntryHash = crypto:hash(sha256, [LeafCert | CertChain]), - TimestampedEntry = + {TimestampedEntry, Hash} = case plop:get(EntryHash) of notfound -> Timestamp = plop:generate_timestamp(), @@ -82,28 +102,27 @@ add_chain(LeafCert, CertChain) -> MTL = #mtl{leaf_version = v1, leaf_type = timestamped_entry, entry = TSE}, + MTLHash = ht:leaf_hash(serialise(MTL)), ok = plop:add( serialise_logentry(Timestamp, LeafCert, CertChain), - ht:leaf_hash(serialise(MTL)), + MTLHash, EntryHash), - TSE; - {_Index, _MTLHash, Entry} -> + {TSE, MTLHash}; + {_Index, MTLHash, Entry} -> <> = Entry, %% TODO: Perform a costly db consistency check against %% unpacked LogEntry (w/ LeafCert and CertChain) - #timestamped_entry{timestamp = Timestamp, - entry_type = x509_entry, - signed_entry = LeafCert} + {#timestamped_entry{timestamp = Timestamp, + entry_type = x509_entry, + signed_entry = LeafCert}, + MTLHash} end, - SCT_sig = - plop:spt(list_to_binary([<>, - serialise_signature_type(certificate_timestamp), - serialise(TimestampedEntry)])), + SCT_sig = get_sct(Hash, TimestampedEntry), {[{sct_version, ?PROTOCOL_VERSION}, {id, base64:encode(plop:get_logid())}, {timestamp, TimestampedEntry#timestamped_entry.timestamp}, {extensions, base64:encode(<<>>)}, - {signature, base64:encode(plop:serialise(SCT_sig))}]}. + {signature, base64:encode(SCT_sig)}]}. -spec serialise_logentry(integer(), binary(), [binary()]) -> binary(). serialise_logentry(Timestamp, LeafCert, CertChain) -> -- cgit v1.1 From e2404caabb5ce3f7dca21cdedddbf744f47e6c3e Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Sun, 8 Mar 2015 12:39:40 +0100 Subject: Test more --- Makefile | 20 +++++++++++++++++++- test/config/frontend-1.config | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d124325..1837422 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,14 @@ tests-start: tests-run: @(cd tools ; python testcase1.py ) || echo "Tests failed" @(cd tools ; python fetchallcerts.py https://127.0.0.1:8080/) || echo "Verification failed" + @(cd tools ; python submitcert.py --store testcerts/cert1.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python submitcert.py --store testcerts/cert2.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python submitcert.py --store testcerts/cert3.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python submitcert.py --store testcerts/cert4.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python submitcert.py --store testcerts/cert5.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + +tests-run2: + @(cd tools ; python verifysct.py --sct-file=../rel/submittedcerts --parallel 1 https://127.0.0.1:8080/) || echo "Verification of SCT:s failed" tests-stop: @for node in $(NODES); do \ @@ -76,7 +84,17 @@ tests-stop: tests-wait: sleep 5 -tests: tests-prepare tests-start tests-run tests-wait tests-stop +tests: + @make tests-prepare + @make tests-start + @make tests-run + @make tests-wait + @make tests-stop + @make tests-wait + @make tests-start + @make tests-run2 + @make tests-wait + @make tests-stop # Unit testing. check: all diff --git a/test/config/frontend-1.config b/test/config/frontend-1.config index e7e8af2..8215027 100644 --- a/test/config/frontend-1.config +++ b/test/config/frontend-1.config @@ -8,6 +8,7 @@ {error_logger_mf_maxfiles, 10}]}, {catlfish, [{known_roots_path, "known_roots"}, + {sctcache_root_path, "tests/machine/machine-1/db/sctcache/"}, {https_servers, [{external_https_api, "127.0.0.1", 8080, v1}, {frontend_https_api, "127.0.0.1", 8082, frontend} -- cgit v1.1 From d1d2185b420d873a97bc78c5e07482accaf574fc Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 19 Mar 2015 17:49:41 +0100 Subject: Add precert handling. --- src/catlfish.erl | 226 ++++++++++++++++++++++++++++++++++------------- src/v1.erl | 61 ++++++------- src/x509.erl | 255 ++++++++++++++++++++++++++++++++++++++--------------- tools/certtools.py | 14 +-- 4 files changed, 389 insertions(+), 167 deletions(-) diff --git a/src/catlfish.erl b/src/catlfish.erl index 2fd9dc7..3b81baa 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -2,7 +2,7 @@ %%% See LICENSE for licensing information. -module(catlfish). --export([add_chain/2, entries/2, entry_and_proof/2]). +-export([add_chain/3, entries/2, entry_and_proof/2]). -export([known_roots/0, update_known_roots/0]). -export([init_cache_table/0]). -include_lib("eunit/include/eunit.hrl"). @@ -21,53 +21,89 @@ -record(timestamped_entry, {timestamp :: integer(), entry_type :: entry_type(), - signed_entry :: binary(), + signed_entry :: signed_x509_entry() | + signed_precert_entry(), extensions = <<>> :: binary()}). -type timestamped_entry() :: #timestamped_entry{}. --spec serialise(mtl() | timestamped_entry()) -> binary(). -serialise(#timestamped_entry{timestamp = Timestamp} = E) -> - list_to_binary( - [<>, - serialise_entry_type(E#timestamped_entry.entry_type), - encode_tls_vector(E#timestamped_entry.signed_entry, 3), - encode_tls_vector(E#timestamped_entry.extensions, 2)]); +-record(signed_x509_entry, {asn1_cert :: binary()}). +-type signed_x509_entry() :: #signed_x509_entry{}. +-record(signed_precert_entry, {issuer_key_hash :: binary(), + tbs_certificate :: binary()}). +-type signed_precert_entry() :: #signed_precert_entry{}. + +-spec serialise(mtl() | timestamped_entry() | + signed_x509_entry() | signed_precert_entry()) -> binary(). +%% @doc Serialise a MerkleTreeLeaf as per RFC6962 Section 3.4. serialise(#mtl{leaf_version = LeafVersion, leaf_type = LeafType, entry = TimestampedEntry}) -> list_to_binary( [serialise_leaf_version(LeafVersion), serialise_leaf_type(LeafType), - serialise(TimestampedEntry)]). + serialise(TimestampedEntry)]); +%% @doc Serialise a TimestampedEntry as per RFC6962 Section 3.4. +serialise(#timestamped_entry{timestamp = Timestamp, + entry_type = EntryType, + signed_entry = SignedEntry, + extensions = Extensions}) -> + list_to_binary( + [<>, + serialise_entry_type(EntryType), + serialise(SignedEntry), + encode_tls_vector(Extensions, 2)]); +%% @doc Serialise an ASN1.Cert as per RFC6962 Section 3.1. +serialise(#signed_x509_entry{asn1_cert = Cert}) -> + encode_tls_vector(Cert, 3); +%% @doc Serialise a PreCert as per RFC6962 Section 3.2. +serialise(#signed_precert_entry{ + issuer_key_hash = IssuerKeyHash, + tbs_certificate = TBSCertificate}) when is_binary(IssuerKeyHash), + size(IssuerKeyHash) == 32 -> + list_to_binary( + [IssuerKeyHash, + encode_tls_vector(TBSCertificate, 3)]). serialise_leaf_version(v1) -> <<0:8>>; serialise_leaf_version(v2) -> <<1:8>>. +deserialise_leaf_version(<<0:8>>) -> + v1; +deserialise_leaf_version(<<1:8>>) -> + v2. serialise_leaf_type(timestamped_entry) -> <<0:8>>. -%% serialise_leaf_type(_) -> -%% <<>>. +deserialise_leaf_type(<<0:8>>) -> + timestamped_entry. serialise_entry_type(x509_entry) -> <<0:16>>; serialise_entry_type(precert_entry) -> <<1:16>>. +deserialise_entry_type(<<0:16>>) -> + x509_entry; +deserialise_entry_type(<<1:16>>) -> + precert_entry. serialise_signature_type(certificate_timestamp) -> <<0:8>>; serialise_signature_type(tree_hash) -> <<1:8>>. +%% deserialise_signature_type(<<0:8>>) -> +%% certificate_timestamp; +%% deserialise_signature_type(<<1:8>>) -> +%% tree_hash. -build_mtl(Timestamp, LeafCert) -> - TSE = #timestamped_entry{timestamp = Timestamp, - entry_type = x509_entry, - signed_entry = LeafCert}, - MTL = #mtl{leaf_version = v1, - leaf_type = timestamped_entry, - entry = TSE}, - serialise(MTL). +%% build_mtl(Timestamp, LeafCert) -> +%% TSE = #timestamped_entry{timestamp = Timestamp, +%% entry_type = x509_entry, +%% signed_entry = LeafCert}, +%% MTL = #mtl{leaf_version = v1, +%% leaf_type = timestamped_entry, +%% entry = TSE}, +%% serialise(MTL). calc_sct(TimestampedEntry) -> plop:serialise(plop:spt(list_to_binary([<>, @@ -86,37 +122,48 @@ get_sct(Hash, TimestampedEntry) -> SCT end; _ -> - SCT = calc_sct(TimestampedEntry) + calc_sct(TimestampedEntry) end. --spec add_chain(binary(), [binary()]) -> nonempty_string(). -add_chain(LeafCert, CertChain) -> +-spec add_chain(binary(), [binary()], normal|precert) -> nonempty_string(). +add_chain(LeafCert, CertChain, Type) -> EntryHash = crypto:hash(sha256, [LeafCert | CertChain]), + EntryType = case Type of + normal -> x509_entry; + precert -> precert_entry + end, {TimestampedEntry, Hash} = case plop:get(EntryHash) of notfound -> Timestamp = plop:generate_timestamp(), - TSE = #timestamped_entry{timestamp = Timestamp, - entry_type = x509_entry, - signed_entry = LeafCert}, - MTL = #mtl{leaf_version = v1, - leaf_type = timestamped_entry, - entry = TSE}, - MTLHash = ht:leaf_hash(serialise(MTL)), - ok = plop:add( - serialise_logentry(Timestamp, LeafCert, CertChain), - MTLHash, - EntryHash), + TSE = timestamped_entry(Timestamp, EntryType, LeafCert, CertChain), + MTLText = serialise(#mtl{leaf_version = v1, + leaf_type = timestamped_entry, + entry = TSE}), + MTLHash = ht:leaf_hash(MTLText), + ExtraData = + case Type of + normal -> CertChain; + precert -> [LeafCert | CertChain] + end, + LogEntry = + list_to_binary( + [encode_tls_vector(MTLText, 4), + encode_tls_vector( + encode_tls_vector( + list_to_binary( + [encode_tls_vector(C, 3) || C <- ExtraData]), + 3), + 4)]), + ok = plop:add(LogEntry, MTLHash, EntryHash), {TSE, MTLHash}; - {_Index, MTLHash, Entry} -> - <> = Entry, - %% TODO: Perform a costly db consistency check against - %% unpacked LogEntry (w/ LeafCert and CertChain) - {#timestamped_entry{timestamp = Timestamp, - entry_type = x509_entry, - signed_entry = LeafCert}, - MTLHash} + {_Index, MTLHash, DBEntry} -> + {MTLText, _ExtraData} = unpack_entry(DBEntry), + MTL = deserialise_mtl(MTLText), + MTLText = serialise(MTL), % verify FIXME: remove + {MTL#mtl.entry, MTLHash} end, + SCT_sig = get_sct(Hash, TimestampedEntry), {[{sct_version, ?PROTOCOL_VERSION}, {id, base64:encode(plop:get_logid())}, @@ -124,15 +171,73 @@ add_chain(LeafCert, CertChain) -> {extensions, base64:encode(<<>>)}, {signature, base64:encode(SCT_sig)}]}. --spec serialise_logentry(integer(), binary(), [binary()]) -> binary(). -serialise_logentry(Timestamp, LeafCert, CertChain) -> - list_to_binary( - [<>, - list_to_binary( - [encode_tls_vector(LeafCert, 3), - encode_tls_vector( - list_to_binary( - [encode_tls_vector(X, 3) || X <- CertChain]), 3)])]). +-spec timestamped_entry(integer(), entry_type(), binary(), binary()) -> + timestamped_entry(). +timestamped_entry(Timestamp, EntryType, LeafCert, CertChain) -> + SignedEntry = + case EntryType of + x509_entry -> + #signed_x509_entry{asn1_cert = LeafCert}; + precert_entry -> + {DetoxedLeafTBSCert, IssuerKeyHash} = + x509:detox(LeafCert, CertChain), + #signed_precert_entry{ + issuer_key_hash = IssuerKeyHash, + tbs_certificate = DetoxedLeafTBSCert} + end, + #timestamped_entry{timestamp = Timestamp, + entry_type = EntryType, + signed_entry = SignedEntry}. + +%% -spec serialise_logentry(integer(), binary(), [binary()]) -> binary(). +%% serialise_logentry(Timestamp, LeafCert, CertChain) -> +%% list_to_binary( +%% [<>, +%% list_to_binary( +%% [encode_tls_vector(LeafCert, 3), +%% encode_tls_vector( +%% list_to_binary( +%% [encode_tls_vector(X, 3) || X <- CertChain]), 3)])]). + +-spec deserialise_mtl(binary()) -> mtl(). +deserialise_mtl(Data) -> + <> = Data, + #mtl{leaf_version = deserialise_leaf_version(LeafVersionBin), + leaf_type = deserialise_leaf_type(LeafTypeBin), + entry = deserialise_timestampedentry(TimestampedEntryBin)}. + +-spec deserialise_timestampedentry(binary()) -> timestamped_entry(). +deserialise_timestampedentry(Data) -> + <> = Data, + EntryType = deserialise_entry_type(EntryTypeBin), + {SignedEntry, ExtensionsBin} = + case EntryType of + x509_entry -> + deserialise_signed_x509_entry(RestData); + precert_entry -> + deserialise_signed_precert_entry(RestData) + end, + {Extensions, <<>>} = decode_tls_vector(ExtensionsBin, 2), + #timestamped_entry{timestamp = Timestamp, + entry_type = EntryType, + signed_entry = SignedEntry, + extensions = Extensions}. + +-spec deserialise_signed_x509_entry(binary()) -> {signed_x509_entry(), binary()}. +deserialise_signed_x509_entry(Data) -> + {E, D} = decode_tls_vector(Data, 3), + {#signed_x509_entry{asn1_cert = E}, D}. + +-spec deserialise_signed_precert_entry(binary()) -> + {signed_precert_entry(), binary()}. +deserialise_signed_precert_entry(Data) -> + <> = Data, + {TBSCertificate, RestData2} = decode_tls_vector(RestData, 3), + {#signed_precert_entry{issuer_key_hash = IssuerKeyHash, + tbs_certificate = TBSCertificate}, + RestData2}. -spec entries(non_neg_integer(), non_neg_integer()) -> list(). entries(Start, End) -> @@ -142,10 +247,9 @@ entries(Start, End) -> entry_and_proof(Index, TreeSize) -> case plop:inclusion_and_entry(Index, TreeSize) of {ok, Entry, Path} -> - {Timestamp, LeafCertVector, CertChainVector} = unpack_entry(Entry), - MTL = build_mtl(Timestamp, LeafCertVector), + {MTL, ExtraData} = unpack_entry(Entry), {[{leaf_input, base64:encode(MTL)}, - {extra_data, base64:encode(CertChainVector)}, + {extra_data, base64:encode(ExtraData)}, {audit_path, [base64:encode(X) || X <- Path]}]}; {notfound, Msg} -> {[{success, false}, @@ -161,20 +265,20 @@ init_cache_table() -> ets:new(?CACHE_TABLE, [set, public, named_table]). %% Private functions. +-spec unpack_entry(binary()) -> {binary(), binary()}. unpack_entry(Entry) -> - <> = Entry, - {LeafCertVector, CertChainVector} = decode_tls_vector(LogEntry, 3), - {Timestamp, LeafCertVector, CertChainVector}. + {MTL, Rest} = decode_tls_vector(Entry, 4), + {ExtraData, <<>>} = decode_tls_vector(Rest, 4), + {MTL, ExtraData}. -spec x_entries([{non_neg_integer(), binary(), binary()}]) -> list(). x_entries([]) -> []; x_entries([H|T]) -> {_Index, _Hash, Entry} = H, - {Timestamp, LeafCertVector, CertChainVector} = unpack_entry(Entry), - MTL = build_mtl(Timestamp, LeafCertVector), - [{[{leaf_input, base64:encode(MTL)}, {extra_data, base64:encode(CertChainVector)}]} | - x_entries(T)]. + {MTL, ExtraData} = unpack_entry(Entry), + [{[{leaf_input, base64:encode(MTL)}, + {extra_data, base64:encode(ExtraData)}]} | x_entries(T)]. -spec encode_tls_vector(binary(), non_neg_integer()) -> binary(). encode_tls_vector(Binary, LengthLen) -> diff --git a/src/v1.erl b/src/v1.erl index 006990d..e672182 100644 --- a/src/v1.erl +++ b/src/v1.erl @@ -9,34 +9,10 @@ %% Public functions, i.e. part of URL. request(post, "ct/v1/add-chain", Input) -> - case (catch mochijson2:decode(Input)) of - {error, E} -> - html("add-chain: bad input:", E); - {struct, [{<<"chain">>, ChainBase64}]} -> - case (catch [base64:decode(X) || X <- ChainBase64]) of - {'EXIT', _} -> - html("add-chain: invalid base64-encoded chain: ", - [ChainBase64]); - [LeafCert | CertChain] -> - Roots = catlfish:known_roots(), - case x509:normalise_chain(Roots, [LeafCert|CertChain]) of - {ok, [Leaf | Chain]} -> - lager:info("adding ~p", - [x509:cert_string(LeafCert)]), - success(catlfish:add_chain(Leaf, Chain)); - {error, Reason} -> - lager:info("rejecting ~p: ~p", - [x509:cert_string(LeafCert), Reason]), - html("add-chain: invalid chain", Reason) - end; - Invalid -> - html("add-chain: chain is not a list: ", [Invalid]) - end; - _ -> html("add-chain: missing input: chain", Input) - end; + add_chain(Input, normal); -request(post, "ct/v1/add-pre-chain", _Input) -> - niy(); +request(post, "ct/v1/add-pre-chain", Input) -> + add_chain(Input, precert); request(get, "ct/v1/get-sth", _Query) -> R = plop:sth(), @@ -130,8 +106,33 @@ html(Text, Input) -> "~p~n" ++ "~n", [Text, Input])}. -niy() -> - html("NIY - Not Implemented Yet|", []). - success(Data) -> {200, [{"Content-Type", "text/json"}], mochijson2:encode(Data)}. + +-spec add_chain(any(), normal|precert) -> any(). +add_chain(Input, Type) -> + case (catch mochijson2:decode(Input)) of + {error, E} -> + html("add-chain: bad input:", E); + {struct, [{<<"chain">>, ChainBase64}]} -> + case (catch [base64:decode(X) || X <- ChainBase64]) of + {'EXIT', _} -> + html("add-chain: invalid base64-encoded chain: ", + [ChainBase64]); + [LeafCert | CertChain] -> + case x509:normalise_chain(catlfish:known_roots(), + [LeafCert|CertChain]) of + {ok, [Leaf | Chain]} -> + lager:info("adding ~p cert ~p", + [Type, x509:cert_string(LeafCert)]), + success(catlfish:add_chain(Leaf, Chain, Type)); + {error, Reason} -> + lager:info("rejecting ~p: ~p", + [x509:cert_string(LeafCert), Reason]), + html("add-chain: invalid chain", Reason) + end; + Invalid -> + html("add-chain: chain is not a list: ", [Invalid]) + end; + _ -> html("add-chain: missing input: chain", Input) + end. diff --git a/src/x509.erl b/src/x509.erl index 5a0e871..43b90b3 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -3,10 +3,10 @@ -module(x509). -export([normalise_chain/2, cert_string/1, read_pemfiles_from_dir/1, - self_signed/1]). - + self_signed/1, detox/2]). -include_lib("public_key/include/public_key.hrl"). -include_lib("eunit/include/eunit.hrl"). +-import(lists, [nth/2, filter/2]). -type reason() :: {chain_too_long | root_unknown | @@ -18,15 +18,49 @@ -spec normalise_chain([binary()], [binary()]) -> {ok, [binary()]} | {error, reason()}. normalise_chain(AcceptableRootCerts, CertChain) -> - case valid_chain_p(AcceptableRootCerts, CertChain, ?MAX_CHAIN_LENGTH) of + case normalise_chain(AcceptableRootCerts, CertChain, ?MAX_CHAIN_LENGTH) of {false, Reason} -> {error, Reason}; {true, Root} -> - [Leaf | Chain] = CertChain, - {ok, [detox_precert(Leaf) | Chain] ++ Root} + {ok, CertChain ++ Root} end. -%%%%%%%%%%%%%%%%%%%% +cert_string(Der) -> + mochihex:to_hex(crypto:hash(sha, Der)). + +-spec read_pemfiles_from_dir(file:filename()) -> [binary()]. +%% @doc Reading certificates from files. Flattening the result -- all +%% certs in all files are returned in a single list. +read_pemfiles_from_dir(Dir) -> + case file:list_dir(Dir) of + {error, enoent} -> + lager:error("directory does not exist: ~p", [Dir]), + []; + {error, Reason} -> + lager:error("unable to read directory ~p: ~p", [Dir, Reason]), + []; + {ok, Filenames} -> + Files = lists:filter( + fun(F) -> + string:equal(".pem", filename:extension(F)) + end, + Filenames), + ders_from_pemfiles(Dir, Files) + end. + +-spec self_signed([binary()]) -> [binary()]. +%% @doc Return a list of certs in L that are self signed. +self_signed(L) -> + lists:filter(fun(Cert) -> signed_by_p(Cert, Cert) end, L). + +%% @doc Return the detoxed cet in LeafDer and the issuer leaf hash. +-spec detox(binary(), [binary()]) -> {binary(), binary()}. +detox(LeafDer, ChainDer) -> + detox_precert(LeafDer, nth(1, ChainDer), nth(2, ChainDer)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Private functions. + %% @doc Verify that the leaf cert or precert has a valid chain back to %% an acceptable root cert. The order of certificates in the second %% argument is: leaf cert in head, chain in tail. Order of first @@ -37,12 +71,12 @@ normalise_chain(AcceptableRootCerts, CertChain) -> %% amongst the acceptable root certs. Otherwise it contains exactly %% one element, a CA cert from the acceptable root certs signing the %% root of the chain. --spec valid_chain_p([binary()], [binary()], integer()) -> - {false, reason()} | {true, list()}. -valid_chain_p(_, _, MaxChainLength) when MaxChainLength =< 0 -> +-spec normalise_chain([binary()], [binary()], integer()) -> + {false, reason()} | {true, list()}. +normalise_chain(_, _, MaxChainLength) when MaxChainLength =< 0 -> %% Chain too long. {false, chain_too_long}; -valid_chain_p(AcceptableRootCerts, [TopCert], MaxChainLength) -> +normalise_chain(AcceptableRootCerts, [TopCert], MaxChainLength) -> %% Check root of chain. case lists:member(TopCert, AcceptableRootCerts) of true -> @@ -58,9 +92,9 @@ valid_chain_p(AcceptableRootCerts, [TopCert], MaxChainLength) -> Root -> {true, [Root]} end end; -valid_chain_p(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> +normalise_chain(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> case signed_by_p(BottomCert, hd(Rest)) of - true -> valid_chain_p(AcceptableRootCerts, Rest, MaxChainLength - 1); + true -> normalise_chain(AcceptableRootCerts, Rest, MaxChainLength - 1); false -> {false, signature_mismatch} end. @@ -90,9 +124,10 @@ encoded_tbs_cert(DerCert) -> PKIXCert, EncodedTBSCert. +-spec extract_verify_data(#'Certificate'{}, binary()) -> {ok, tuple()} | error. +%% @doc Return DER encoded TBScertificate, digest type and signature. %% Code from pubkey_cert:extract_verify_data/2. --spec verifydata_from_cert(#'Certificate'{}, binary()) -> {ok, tuple()} | error. -verifydata_from_cert(Cert, DerCert) -> +extract_verify_data(Cert, DerCert) -> PlainText = encoded_tbs_cert(DerCert), {_, Sig} = Cert#'Certificate'.signature, SigAlgRecord = Cert#'Certificate'.signatureAlgorithm, @@ -114,7 +149,7 @@ verify_sig(Cert, DerCert, % Certificate to verify. tbsCertificate = #'TBSCertificate'{ subjectPublicKeyInfo = IssuerSPKI}}) -> %% Dig out digest, digest type and signature from Cert/DerCert. - case verifydata_from_cert(Cert, DerCert) of + case extract_verify_data(Cert, DerCert) of error -> false; {ok, Tuple} -> verify_sig2(IssuerSPKI, Tuple) end. @@ -159,9 +194,6 @@ signed_by_p(DerCert, IssuerDerCert) when is_binary(DerCert), DerCert, public_key:pkix_decode_cert(IssuerDerCert, plain)). -cert_string(Der) -> - mochihex:to_hex(crypto:hash(sha, Der)). - parsable_cert_p(Der) -> case (catch public_key:pkix_decode_cert(Der, plain)) of #'Certificate'{} -> @@ -175,54 +207,139 @@ parsable_cert_p(Der) -> false end. --spec self_signed([binary()]) -> [binary()]. -self_signed(L) -> - lists:filter(fun(Cert) -> signed_by_p(Cert, Cert) end, L). - -%%%%%%%%%%%%%%%%%%%% -%% Precertificates according to draft-ietf-trans-rfc6962-bis-04. +%% Precerts according to RFC6962. %% Submitted precerts have a special critical poison extension -- OID %% 1.3.6.1.4.1.11129.2.4.3, whose extnValue OCTET STRING contains %% ASN.1 NULL data (0x05 0x00). %% They are signed with either the CA cert that will sign the final -%% cert or Precertificate Signing Certificate directly signed by the +%% cert or a Precertificate Signing Certificate directly signed by the %% CA cert that will sign the final cert. A Precertificate Signing %% Certificate has CA:true and Extended Key Usage: Certificate %% Transparency, OID 1.3.6.1.4.1.11129.2.4.4. -%% A PreCert in a SignedCertificateTimestamp does _not_ contain the -%% poison extension, nor a Precertificate Signing Certificate. This -%% means that we might have to 1) remove poison extensions in leaf -%% certs, 2) remove "poisoned signatures", 3) change issuer and -%% Authority Key Identifier of leaf certs. +%% PreCert in SignedCertificateTimestamp does _not_ contain the poison +%% extension, nor does it have an issuer which is a Precertificate +%% Signing Certificate. This means that we have to 1) remove the +%% poison extension and 2) potentially change issuer and Authority Key +%% Identifier. See RFC6962 Section 3.2. + +%% Changes in draft-ietf-trans-rfc6962-bis-??: TODO. + +-spec detox_precert(binary(), binary(), binary()) -> {binary(), binary()}. +%% @doc Return {DetoxedLeaf, IssuerPubKeyHash} where i) DetoxedLeaf is +%% the tbsCertificate w/o poison and adjusted issuer and authkeyid; +%% and ii) IssuerPubKeyHash is the hash over issuing cert's public +%% key. +detox_precert(LeafDer, ParentDer, GrandParentDer) -> + Leaf = public_key:pkix_decode_cert(LeafDer, plain), + Parent = public_key:pkix_decode_cert(ParentDer, plain), + GrandParent = public_key:pkix_decode_cert(GrandParentDer, plain), + DetoxedLeafTBS = remove_poison_ext(Leaf), + + %% If parent is a precert signing cert, change issuer and + %% potential authority key id to refer to grandparent. + {C, IssuerKeyHash} = + case is_precert_signer(Parent) of + true -> + {set_issuer_and_authkeyid(DetoxedLeafTBS, Parent), + extract_pub_key(GrandParent)}; + false -> + {DetoxedLeafTBS, extract_pub_key(Parent)} + end, + {public_key:pkix_encode('TBSCertificate', C, plain), + crypto:hash(sha256, public_key:pkix_encode( + 'SubjectPublicKeyInfo', IssuerKeyHash, plain))}. + +-spec extract_pub_key(#'Certificate'{}) -> #'SubjectPublicKeyInfo'{}. +extract_pub_key(#'Certificate'{ + tbsCertificate = #'TBSCertificate'{ + subjectPublicKeyInfo = SPKI}}) -> + SPKI. + +-spec set_issuer_and_authkeyid(#'TBSCertificate'{}, #'Certificate'{}) -> + #'TBSCertificate'{}. +%% @doc Return Cert with issuer and AuthorityKeyIdentifier from Parent. +set_issuer_and_authkeyid(TBSCert, + #'Certificate'{ + tbsCertificate = + #'TBSCertificate'{ + issuer = ParentIssuer, + extensions = ParentExtensions}}) -> + case pubkey_cert:select_extension(?'id-ce-authorityKeyIdentifier', + ParentExtensions) of + undefined -> + lager:debug("setting issuer only", []), + TBSCert#'TBSCertificate'{issuer = ParentIssuer}; + ParentAuthKeyExt -> + NewExtensions = + lists:map( + fun(E) -> + case E of + #'Extension'{extnID = + ?'id-ce-authorityKeyIdentifier'} -> + lager:debug("swapping auth key id to ~p", + [ParentAuthKeyExt]), + ParentAuthKeyExt; + Orig -> + Orig + end + end, + TBSCert#'TBSCertificate'.extensions), + TBSCert#'TBSCertificate'{issuer = ParentIssuer, + extensions = NewExtensions} + end. --spec detox_precert([#'Certificate'{}]) -> [#'Certificate'{}]. -detox_precert(CertChain) -> - CertChain. % NYI +-define(CA_POISON_OID, {1,3,6,1,4,1,11129,2,4,4}). + +-spec is_precert_signer(#'Certificate'{}) -> boolean(). +is_precert_signer(#'Certificate'{tbsCertificate = TBSCert}) -> + Extensions = pubkey_cert:extensions_list(TBSCert#'TBSCertificate'.extensions), + %% NOTE: It's OK to look at only the first extension found since + %% "A certificate MUST NOT include more than one instance of a + %% particular extension." --RFC5280 Sect 4.2 + case pubkey_cert:select_extension(?'id-ce-extKeyUsage', Extensions) of + #'Extension'{extnValue = Val} -> + case 'OTP-PUB-KEY':decode('ExtKeyUsageSyntax', Val) of + %% NOTE: We require that the poisoned OID is the + %% _only_ extkeyusage present. RFC6962 Sect 3.1 is not + %% really clear. + {ok, [?CA_POISON_OID]} -> is_ca(TBSCert); + _ -> false + end; + _ -> false + end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec read_pemfiles_from_dir(file:filename()) -> [binary()]. -%% @doc Reading certificates from files. Flattening the result -- all -%% certs in all files are returned in a single list. -read_pemfiles_from_dir(Dir) -> - case file:list_dir(Dir) of - {error, enoent} -> - lager:error("directory does not exist: ~p", [Dir]), - []; - {error, Reason} -> - lager:error("unable to read directory ~p: ~p", [Dir, Reason]), - []; - {ok, Filenames} -> - Files = lists:filter( - fun(F) -> - string:equal(".pem", filename:extension(F)) - end, - Filenames), - ders_from_pemfiles(Dir, Files) +is_ca(#'TBSCertificate'{extensions = Extensions}) -> + case pubkey_cert:select_extension(?'id-ce-basicConstraints', Extensions) of + #'Extension'{critical = true, extnValue = Val} -> + case 'OTP-PUB-KEY':decode('BasicConstraints', Val) of + {ok, {'BasicConstraints', true, _}} -> true; + _ -> false + end; + _ -> false end. +-spec remove_poison_ext(#'Certificate'{}) -> #'TBSCertificate'{}. +remove_poison_ext(#'Certificate'{tbsCertificate = TBSCert}) -> + Extensions = pubkey_cert:extensions_list(TBSCert#'TBSCertificate'.extensions), + SanitisedExtensions = + filter(fun(E) -> not poisoned_leaf_p(E) end, Extensions), + NewTBSCert = TBSCert#'TBSCertificate'{extensions = SanitisedExtensions}, + NewTBSCert. + +-define(LEAF_POISON_OID, {1,3,6,1,4,1,11129,2,4,3}). +-define(LEAF_POISON_VAL, [5,0]). + +poisoned_leaf_p(#'Extension'{extnID = ?LEAF_POISON_OID, + critical = true, + extnValue = ?LEAF_POISON_VAL}) -> + true; +poisoned_leaf_p(_) -> + false. + +%%%% PEM files. ders_from_pemfiles(Dir, Filenames) -> lists:flatten( [ders_from_pemfile(filename:join(Dir, X)) || X <- Filenames]). @@ -297,21 +414,21 @@ valid_cert_test_() -> %% 'OTP-PUB-KEY':Func('OTP-X520countryname', Value0) %% FIXME: This error doesn't make much sense -- is my %% environment borked? - ?_assertMatch({true, _}, valid_chain_p(lists:nth(1, Chains), - lists:nth(1, Chains), 10)), + ?_assertMatch({true, _}, normalise_chain(lists:nth(1, Chains), + lists:nth(1, Chains), 10)), %% Self-signed so fail. ?_assertMatch({false, root_unknown}, - valid_chain_p(KnownRoots, - lists:nth(2, Chains), 10)), + normalise_chain(KnownRoots, + lists:nth(2, Chains), 10)), %% Leaf signed by known CA, pass. - ?_assertMatch({true, _}, valid_chain_p(KnownRoots, - lists:nth(3, Chains), 10)), + ?_assertMatch({true, _}, normalise_chain(KnownRoots, + lists:nth(3, Chains), 10)), %% Proper 3-depth chain with root in KnownRoots, pass. %% Bug CATLFISH-19 --> [info] rejecting "3ee62cb678014c14d22ebf96f44cc899adea72f1": chain_broken %% leaf sha1: 3ee62cb678014c14d22ebf96f44cc899adea72f1 %% leaf Subject: C=KR, O=Government of Korea, OU=Group of Server, OU=\xEA\xB5\x90\xEC\x9C\xA1\xEA\xB3\xBC\xED\x95\x99\xEA\xB8\xB0\xEC\x88\xA0\xEB\xB6\x80, CN=www.berea.ac.kr, CN=haksa.bits.ac.kr - ?_assertMatch({true, _}, valid_chain_p(KnownRoots, - lists:nth(4, Chains), 3)), + ?_assertMatch({true, _}, normalise_chain(KnownRoots, + lists:nth(4, Chains), 3)), %% Verify against self, pass. %% Bug CATLFISH-??, can't handle issuer keytype ECPoint. %% Issuer sha1: 6969562e4080f424a1e7199f14baf3ee58ab6abb @@ -333,21 +450,21 @@ chain_test_() -> chain_test(C0, C1) -> [ %% Root not in chain but in trust store. - ?_assertEqual({true, [C1]}, valid_chain_p([C1], [C0], 10)), - ?_assertEqual({true, [C1]}, valid_chain_p([C1], [C0], 2)), + ?_assertEqual({true, [C1]}, normalise_chain([C1], [C0], 10)), + ?_assertEqual({true, [C1]}, normalise_chain([C1], [C0], 2)), %% Chain too long. - ?_assertMatch({false, chain_too_long}, valid_chain_p([C1], [C0], 1)), + ?_assertMatch({false, chain_too_long}, normalise_chain([C1], [C0], 1)), %% Root in chain and in trust store. - ?_assertEqual({true, []}, valid_chain_p([C1], [C0, C1], 2)), + ?_assertEqual({true, []}, normalise_chain([C1], [C0, C1], 2)), %% Chain too long. - ?_assertMatch({false, chain_too_long}, valid_chain_p([C1], [C0, C1], 1)), + ?_assertMatch({false, chain_too_long}, normalise_chain([C1], [C0, C1], 1)), %% Root not in trust store. - ?_assertMatch({false, root_unknown}, valid_chain_p([], [C0, C1], 10)), + ?_assertMatch({false, root_unknown}, normalise_chain([], [C0, C1], 10)), %% Selfsigned. Actually OK. - ?_assertMatch({true, []}, valid_chain_p([C0], [C0], 10)), - ?_assertMatch({true, []}, valid_chain_p([C0], [C0], 1)), + ?_assertMatch({true, []}, normalise_chain([C0], [C0], 10)), + ?_assertMatch({true, []}, normalise_chain([C0], [C0], 1)), %% Max chain length 0 is not OK. - ?_assertMatch({false, chain_too_long}, valid_chain_p([C0], [C0], 0)) + ?_assertMatch({false, chain_too_long}, normalise_chain([C0], [C0], 0)) ]. %%-spec read_certs(file:filename()) -> [string:string()]. diff --git a/tools/certtools.py b/tools/certtools.py index 222497f..0e639f2 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -6,6 +6,7 @@ import json import base64 import urllib import urllib2 +import ssl import urlparse import struct import sys @@ -79,7 +80,7 @@ def get_root_cert(issuer): return root_cert def get_sth(baseurl): - result = urllib2.urlopen(baseurl + "ct/v1/get-sth").read() + result = urllib2.urlopen(baseurl + "ct/v1/get-sth", context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() return json.loads(result) def get_proof_by_hash(baseurl, hash, tree_size): @@ -87,7 +88,7 @@ def get_proof_by_hash(baseurl, hash, tree_size): params = urllib.urlencode({"hash":base64.b64encode(hash), "tree_size":tree_size}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params).read() + urllib2.urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -98,7 +99,7 @@ def get_consistency_proof(baseurl, tree_size1, tree_size2): params = urllib.urlencode({"first":tree_size1, "second":tree_size2}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-sth-consistency?" + params).read() + urllib2.urlopen(baseurl + "ct/v1/get-sth-consistency?" + params, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() return json.loads(result)["consistency"] except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -121,8 +122,7 @@ def unpack_tls_array(packed_data, length_len): def add_chain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-chain", - json.dumps(submission)).read() + result = urllib2.urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission), context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR", e.code,":", e.read() @@ -140,7 +140,7 @@ def add_chain(baseurl, submission): def get_entries(baseurl, start, end): try: params = urllib.urlencode({"start":start, "end":end}) - result = urllib2.urlopen(baseurl + "ct/v1/get-entries?" + params).read() + result = urllib2.urlopen(baseurl + "ct/v1/get-entries?" + params, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -197,7 +197,7 @@ def http_request(url, data=None, key=None): signature = sk.sign("%s\0%s\0%s" % (method, parsed_url.path, data), hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der) req.add_header('X-Catlfish-Auth', base64.b64encode(signature) + ";key=" + keyname) - result = urllib2.urlopen(req).read() + result = urllib2.urlopen(req, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() return result def get_signature(baseurl, data, key=None): -- cgit v1.1 From 790cd8a7d02fc8c7ac42b4e1e8aa6a15b38a8288 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 20 Mar 2015 17:41:29 +0100 Subject: Formatting; remove debug printouts. --- src/catlfish.erl | 20 ++++---------------- src/x509.erl | 50 +++++++++++++++++--------------------------------- 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/src/catlfish.erl b/src/catlfish.erl index 3b81baa..50811a1 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -91,24 +91,12 @@ serialise_signature_type(certificate_timestamp) -> <<0:8>>; serialise_signature_type(tree_hash) -> <<1:8>>. -%% deserialise_signature_type(<<0:8>>) -> -%% certificate_timestamp; -%% deserialise_signature_type(<<1:8>>) -> -%% tree_hash. - -%% build_mtl(Timestamp, LeafCert) -> -%% TSE = #timestamped_entry{timestamp = Timestamp, -%% entry_type = x509_entry, -%% signed_entry = LeafCert}, -%% MTL = #mtl{leaf_version = v1, -%% leaf_type = timestamped_entry, -%% entry = TSE}, -%% serialise(MTL). calc_sct(TimestampedEntry) -> - plop:serialise(plop:spt(list_to_binary([<>, - serialise_signature_type(certificate_timestamp), - serialise(TimestampedEntry)]))). + plop:serialise( + plop:spt(list_to_binary([<>, + serialise_signature_type(certificate_timestamp), + serialise(TimestampedEntry)]))). get_sct(Hash, TimestampedEntry) -> case application:get_env(catlfish, sctcache_root_path) of diff --git a/src/x509.erl b/src/x509.erl index 43b90b3..e8c4f2b 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -14,6 +14,9 @@ encoding_invalid}. -define(MAX_CHAIN_LENGTH, 10). +-define(LEAF_POISON_OID, {1,3,6,1,4,1,11129,2,4,3}). +-define(LEAF_POISON_VAL, [5,0]). +-define(CA_POISON_OID, {1,3,6,1,4,1,11129,2,4,4}). -spec normalise_chain([binary()], [binary()]) -> {ok, [binary()]} | {error, reason()}. @@ -41,9 +44,7 @@ read_pemfiles_from_dir(Dir) -> []; {ok, Filenames} -> Files = lists:filter( - fun(F) -> - string:equal(".pem", filename:extension(F)) - end, + fun(F) -> string:equal(".pem", filename:extension(F)) end, Filenames), ders_from_pemfiles(Dir, Files) end. @@ -61,6 +62,8 @@ detox(LeafDer, ChainDer) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Private functions. +-spec normalise_chain([binary()], [binary()], integer()) -> + {false, reason()} | {true, list()}. %% @doc Verify that the leaf cert or precert has a valid chain back to %% an acceptable root cert. The order of certificates in the second %% argument is: leaf cert in head, chain in tail. Order of first @@ -71,8 +74,6 @@ detox(LeafDer, ChainDer) -> %% amongst the acceptable root certs. Otherwise it contains exactly %% one element, a CA cert from the acceptable root certs signing the %% root of the chain. --spec normalise_chain([binary()], [binary()], integer()) -> - {false, reason()} | {true, list()}. normalise_chain(_, _, MaxChainLength) when MaxChainLength =< 0 -> %% Chain too long. {false, chain_too_long}; @@ -98,11 +99,11 @@ normalise_chain(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> false -> {false, signature_mismatch} end. +-spec signer(binary(), [binary()]) -> notfound | binary(). %% @doc Return first cert in list signing Cert, or notfound. NOTE: %% This is potentially expensive. It'd be more efficient to search for %% Cert.issuer in a list of Issuer.subject's. If so, maybe make the %% matching somewhat fuzzy unless that too is expensive. --spec signer(binary(), [binary()]) -> notfound | binary(). signer(_Cert, []) -> notfound; signer(Cert, [H|T]) -> @@ -132,7 +133,6 @@ extract_verify_data(Cert, DerCert) -> {_, Sig} = Cert#'Certificate'.signature, SigAlgRecord = Cert#'Certificate'.signatureAlgorithm, SigAlg = SigAlgRecord#'AlgorithmIdentifier'.algorithm, - lager:debug("SigAlg: ~p", [SigAlg]), try {DigestType, _} = public_key:pkix_sign_types(SigAlg), {ok, {PlainText, DigestType, Sig}} @@ -160,10 +160,6 @@ verify_sig2(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> algorithm = #'AlgorithmIdentifier'{algorithm = Alg, parameters = Params}, subjectPublicKey = {0, Key0}} = IssuerSPKI, KeyType = pubkey_cert_records:supportedPublicKeyAlgorithms(Alg), - lager:debug("Alg: ~p", [Alg]), - lager:debug("Params: ~p", [Params]), - lager:debug("KeyType: ~p", [KeyType]), - lager:debug("Key0: ~p", [Key0]), IssuerKey = case KeyType of 'RSAPublicKey' -> @@ -176,12 +172,6 @@ verify_sig2(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> lager:error("NIY: Issuer key type ~p", [KeyType]), false end, - - lager:debug("DigestOrPlainText: ~p", [DigestOrPlainText]), - lager:debug("DigestType: ~p", [DigestType]), - lager:debug("Signature: ~p", [Signature]), - lager:debug("IssuerKey: ~p", [IssuerKey]), - %% Verify the signature. public_key:verify(DigestOrPlainText, DigestType, Signature, IssuerKey). @@ -208,23 +198,23 @@ parsable_cert_p(Der) -> end. %% Precerts according to RFC6962. - +%% %% Submitted precerts have a special critical poison extension -- OID %% 1.3.6.1.4.1.11129.2.4.3, whose extnValue OCTET STRING contains %% ASN.1 NULL data (0x05 0x00). - +%% %% They are signed with either the CA cert that will sign the final %% cert or a Precertificate Signing Certificate directly signed by the %% CA cert that will sign the final cert. A Precertificate Signing %% Certificate has CA:true and Extended Key Usage: Certificate %% Transparency, OID 1.3.6.1.4.1.11129.2.4.4. - +%% %% PreCert in SignedCertificateTimestamp does _not_ contain the poison %% extension, nor does it have an issuer which is a Precertificate %% Signing Certificate. This means that we have to 1) remove the %% poison extension and 2) potentially change issuer and Authority Key %% Identifier. See RFC6962 Section 3.2. - +%% %% Changes in draft-ietf-trans-rfc6962-bis-??: TODO. -spec detox_precert(binary(), binary(), binary()) -> {binary(), binary()}. @@ -282,17 +272,15 @@ set_issuer_and_authkeyid(TBSCert, lager:debug("swapping auth key id to ~p", [ParentAuthKeyExt]), ParentAuthKeyExt; - Orig -> - Orig + _ -> E end end, TBSCert#'TBSCertificate'.extensions), + lager:debug("setting issuer and auth key id", []), TBSCert#'TBSCertificate'{issuer = ParentIssuer, extensions = NewExtensions} end. --define(CA_POISON_OID, {1,3,6,1,4,1,11129,2,4,4}). - -spec is_precert_signer(#'Certificate'{}) -> boolean(). is_precert_signer(#'Certificate'{tbsCertificate = TBSCert}) -> Extensions = pubkey_cert:extensions_list(TBSCert#'TBSCertificate'.extensions), @@ -323,14 +311,10 @@ is_ca(#'TBSCertificate'{extensions = Extensions}) -> -spec remove_poison_ext(#'Certificate'{}) -> #'TBSCertificate'{}. remove_poison_ext(#'Certificate'{tbsCertificate = TBSCert}) -> - Extensions = pubkey_cert:extensions_list(TBSCert#'TBSCertificate'.extensions), - SanitisedExtensions = - filter(fun(E) -> not poisoned_leaf_p(E) end, Extensions), - NewTBSCert = TBSCert#'TBSCertificate'{extensions = SanitisedExtensions}, - NewTBSCert. - --define(LEAF_POISON_OID, {1,3,6,1,4,1,11129,2,4,3}). --define(LEAF_POISON_VAL, [5,0]). + Extensions = + filter(fun(E) -> not poisoned_leaf_p(E) end, + pubkey_cert:extensions_list(TBSCert#'TBSCertificate'.extensions)), + TBSCert#'TBSCertificate'{extensions = Extensions}. poisoned_leaf_p(#'Extension'{extnID = ?LEAF_POISON_OID, critical = true, -- cgit v1.1 From 1812b745e7a3d39b75d295edfa8b9287d2c6a479 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Sat, 21 Mar 2015 01:27:57 +0100 Subject: Add submission and verification of two precerts to the tests. One of them is signed by an ordinare cert, the other by a precertificate --- Makefile | 3 ++ tools/testcerts/pre1.txt | 79 ++++++++++++++++++++++++++++++ tools/testcerts/pre2.txt | 106 ++++++++++++++++++++++++++++++++++++++++ tools/testcerts/roots/root4.pem | 19 +++++++ tools/testcerts/roots/root5.pem | 29 +++++++++++ 5 files changed, 236 insertions(+) create mode 100644 tools/testcerts/pre1.txt create mode 100644 tools/testcerts/pre2.txt create mode 100644 tools/testcerts/roots/root4.pem create mode 100644 tools/testcerts/roots/root5.pem diff --git a/Makefile b/Makefile index 1837422..e1a81fb 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,9 @@ tests-run: @(cd tools ; python submitcert.py --store testcerts/cert3.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" @(cd tools ; python submitcert.py --store testcerts/cert4.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" @(cd tools ; python submitcert.py --store testcerts/cert5.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python submitcert.py --store testcerts/pre1.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python submitcert.py --store testcerts/pre2.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" + @(cd tools ; python merge.py --baseurl https://127.0.0.1:8080/ --frontend https://127.0.0.1:8082/ --storage https://127.0.0.1:8081/ --mergedb ../rel/mergedb --signing https://127.0.0.1:8088/ --own-keyname merge-1 --own-keyfile ../rel/privatekeys/merge-1-private.pem) || echo "Merge failed" tests-run2: @(cd tools ; python verifysct.py --sct-file=../rel/submittedcerts --parallel 1 https://127.0.0.1:8080/) || echo "Verification of SCT:s failed" diff --git a/tools/testcerts/pre1.txt b/tools/testcerts/pre1.txt new file mode 100644 index 0000000..776c38e --- /dev/null +++ b/tools/testcerts/pre1.txt @@ -0,0 +1,79 @@ +Timestamp: 1383337821156 +Leafhash: A4892155FE9929177BCA785A73C15351A3EE2AF6F163DE40C15802BDE0F41302 +-----BEGIN PRECERTIFICATE----- +MIIGqDCCBZCgAwIBAgIQCxvJV1NZEuon0JIojHqH+DANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5EaWdpQ2VydCBTSEEyIFNlY3Vy +ZSBTZXJ2ZXIgQ0EwHhcNMTMxMTAxMDAwMDAwWhcNMTQxMTA2MTIwMDAwWjBkMQswCQYDVQQGEwJV +UzENMAsGA1UECBMEVXRhaDENMAsGA1UEBxMETGVoaTEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x +HjAcBgNVBAMTFWVtYmVkLmN0LmRpZ2ljZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANExEGl1kTCQJNWXQuTH3m4DWx7xh9Tq+EXHlhorVtgUmHLmBPn7FGC3MH51q0MXN6K7 +huQVXa9LRmCdPRNlNPSkWUqpCVTEqBZrTPuAGEs01+XgXsyhP3uwBxWZkkKJ0FJ4tu7RVHXXgmSC ++JQkSgI4MUNuMaIHvWEpEKsmov9kcQZGUTPnwEg90PyVLlbKypRoFM0dynpslh6FUH4OEAuCx4h1 +tsAN2KHk/ajYE0ND+FN0gBf5qXuY+njUEsDaGiAVKgAb16wOk//0xWy4cTWeHnyLObrsZ3F11GVl +8cK1x0dNGxgeVfH6yTB8BJu/2wqaQSAdzf14Cie5D8YUXf0CAwEAAaOCA2swggNnMB8GA1UdIwQY +MBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT8yxF+UXTw/RIW5igB3ZSRrSSkFzAg +BgNVHREEGTAXghVlbWJlZC5jdC5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQW +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjBrBgNVHR8EZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdp +Y2VydC5jb20vc3NjYS1zaGEyLWcxLmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29t +L3NzY2Etc2hhMi1nMS5jcmwwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwBATCCAaQwOgYI +KwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0w +ggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBl +AHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQA +YQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABh +AG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUA +bgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg +AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIA +ZQBmAGUAcgBlAG4AYwBlAC4wfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz +cC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E +aWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADATBgorBgEEAdZ5AgQD +AQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAbHgFxzrmkXjRdQdlHj4Ey2U8rTOetMqjddrXR1DZ +9E12vp8yWB+LkSVASutpgzxNawj/rv1w1ODdJWMTra12R1MnxqoVytSEmbE0gjgxahdWWiV8yTFB +4tMFRHvCCwmIJqhRwjufnRs1q1+9YMxZ6reCG4kg29qgtQhh8V9vCrGfQja/4cBHa6O7w407FPra +b2NIqtJB/47fOdACkVdFjbOVSWielDtTv7QNPi3OUfNwNE/Qqh1k5MOBDP1gif1AFzl5Z7plUos5 +3533VCBjrcOWp8WXUtNlIedlxjarUaTKSRpZVdRzY9ugvou9JLVF1SuDIAXQ3+tN44bjAjERug== +-----END PRECERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgx +MjAwMDBaME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRp +Z2lDZXJ0IFNIQTIgU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83nf36QYSv +x6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bdKpPDkC55gIDvEwRqFDu1 +m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f/ld0Uzs1gN2ujkSYs58O09rg1/RrKatE +p0tYhG2SS4HD2nOLEpdIkARFdRrdNzGXkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJ +TvOX6+guqw9ypzAO+sf0/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQI +MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0 +cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2lj +ZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1oDOGMWh0dHA6Ly9jcmw0LmRpZ2lj +ZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYI +KwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHV +LyjnjUY4tCzhxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB +CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl5TlPHoOlblyY +oiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA8MXW5dRNJ2Srm8c+cftIl7gz +bckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8s +jX7tN8Cp1Tm5gr8ZDOo0rwAhaPitc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopY +JeS4d60tbvVS3bR0j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + diff --git a/tools/testcerts/pre2.txt b/tools/testcerts/pre2.txt new file mode 100644 index 0000000..4c86537 --- /dev/null +++ b/tools/testcerts/pre2.txt @@ -0,0 +1,106 @@ +Timestamp: 1399629239033 +Leafhash: 758B8612DFED6A3321215C0586C0AC9F43137CD2BBF043C86301D66DC7D1205A +-----BEGIN PRECERTIFICATE----- +MIIFFzCCBAGgAwIBAgIgd+115NyVfYOnRINB2wJy2eaQRbJ6j8Zau5IdwBNpmzowCwYJKoZIhvcN +AQELMGYxLDAqBgNVBAMMI1ByZS1jZXJ0aWZpY2F0ZSBTaWduaW5nIENlcnRpZmljYXRlMRAwDgYD +VQQLDAdDQSBUZWFtMRcwFQYDVQQKDA5UQUlXQU4tQ0EgSU5DLjELMAkGA1UEBhMCVFcwHhcNMTQw +NTA5MDk1MzU3WhcNMTQwNTE2MTU1OTU5WjB0MR0wGwYDVQQDDBRjdHRlc3QwNS50d2NhLmNvbS50 +dzELMAkGA1UECwwCUkQxFzAVBgNVBAoMDlRBSVdBTi1DQSBJTkMuMQ8wDQYDVQQHDAZUYWlwZWkx +DzANBgNVBAgMBlRhaXdhbjELMAkGA1UEBhMCVFcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDSgb3MYpsqjkNqcOJHIlEmy8ldCzXtmJfoLfvW1g8JyaGgKR6B98ceg1whThF1tPy8aqJv +fEXGivb+2El1BmxTNvSZ+bOkOT0UsD2hiIgUppD6b/ICWQrIvwrBTNnfJtrwvGD/rygpVTZQoekX +IVdapI95Cfn+36YXqjX7ixgItEx3t/nzOqBxJNI0p52m9l1sowi2/hGmvc/xqC0Cti4m177c8gP0 +u4oKQRJVF2690F748KfzIMcbS7KbDDDVhtWqwgKaRLvqD+gJAUZ1QYEyzDr5Xhhi1O0FXfhyeeCj +mRUJBENmhqElt9C1HugaBsno37JP1AQdsuVg776qQQ1PAgMBAAGjggGlMIIBoTArBgNVHSMEJDAi +gCCVnLtVYCn+QZohG69CSwl1Y2OhEQ7LbPhnh353anz2ezApBgNVHQ4EIgQgt6NL2avrK2PUt8X1 +oG0rd0Wd2ZVDVuJru2T6Z4/eJUEwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2N0dGVzdC50d2Nh +LmNvbS50dy9zc2xzZXJ2ZXIvY3R0ZXN0LmNybDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwID +qDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwJQYDVR0RBB4wHIIUY3R0ZXN0MDUudHdj +YS5jb20udHeHBMCoAckwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRwOi8vY3R0ZXN0 +LnR3Y2EuY29tLnR3L29jc3AwUQYDVR0gBEowSDBGBgdggR4DAQgFMDswIgYIKwYBBQUHAgEWFmh0 +dHA6Ly93d3cudHdjYS5jb20udHcwFQYIKwYBBQUHAgIwCRoHMC4xLjEuMzATBgorBgEEAdZ5AgQD +AQH/BAIFADALBgkqhkiG9w0BAQsDggEBAIkN6er89ss6KAZOH/ZpTPbXhO/J8NNq7vJBxhD4z56R +aRTJpr7Fla9zr8K3aD7bbBUpVeMqER3YA7eeOR8ITBqzMN9SpjdpDlBLcI/6S+7iUVRw4+UvEVqL +0xlCfdxftGLX+T77y7/qqLxyH+QVuSS4sKzTCfspqAaxteK32A5nWKAiJFUI/ise67o3T9f015hR +7rHj+U2AomLQwnyiMg4u3D2mYzK9q7VDGJfKIW6wrFYS/lQsFKyb4sUTyGG9VuzgSDIjCXJag7fs +MZ+/shgsVOTzHUVeHGuKsPcpps0Yvu2W3DybsVoBwtS/vePPnfNfCrDqM9vZCTurvG4KaS4= +-----END PRECERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIEUTCCAzugAwIBAgIEATNR3TALBgkqhkiG9w0BAQswVDELMAkGA1UEBhMCVFcxFzAVBgNVBAoT +DlRBSVdBTi1DQSBJTkMuMRAwDgYDVQQLEwdDQSBUZWFtMRowGAYDVQQDExFSRCBUV0NBIENUVEVT +VCBDQTAeFw0xNDA1MDkwOTQzMjZaFw0xNTA1MDkxNTU5NTlaMGYxLDAqBgNVBAMMI1ByZS1jZXJ0 +aWZpY2F0ZSBTaWduaW5nIENlcnRpZmljYXRlMRAwDgYDVQQLDAdDQSBUZWFtMRcwFQYDVQQKDA5U +QUlXQU4tQ0EgSU5DLjELMAkGA1UEBhMCVFcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCtFIow0xs7VQ42AEck0o+D8pDDOvIclTPJG7j5+wc7lz1wOwbqP8w06Qa/18tg3sdk16dYFg9k +pIeOU7suaWgeHifBjjj9iXTELH4U0RP3HwxlM23WArt9a5OKM5KJlA2T9obppnfsN9fm6ZGX4TTY +JqV8x2vgXSkHhVwxl8wnZoywHlHlgThvVVi+/DzZUD8FIXz2/dPeMtSTfHQ6LqIhee9YMIVgqg/f +tPb5lOhrJEmAl56mJWi1haVYmxZDSa4+1XCJkOxEzQDPpAvIrXVgAQzr6A5jIHZ7VucTEQ5U/9lx +Gckzv6CFDRxYyjSpBZsxML/d4A1P9nKdWcABqO9PAgMBAAGjggEbMIIBFzArBgNVHSMEJDAigCCE +xPSrbrwoBcYxPScQhJ7WOGJB5N3Efkav81dvue7NsjApBgNVHQ4EIgQglZy7VWAp/kGaIRuvQksJ +dWNjoREOy2z4Z4d+d2p89nswPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2N0dGVzdC50d2NhLmNv +bS50dy9zc2xzZXJ2ZXIvY3R0ZXN0LmNybDASBgNVHRMBAf8ECDAGAQH/AgEAMBUGA1UdJQQOMAwG +CisGAQQB1nkCBAQwUQYDVR0gBEowSDBGBgdggR4DAQgFMDswIgYIKwYBBQUHAgEWFmh0dHA6Ly93 +d3cudHdjYS5jb20udHcwFQYIKwYBBQUHAgIwCRoHMC4xLjEuMzALBgkqhkiG9w0BAQsDggEBAN8v +hr/zNUNSSikqAtRVZVgcJTuN3yTlaXX4hMJFAWrbBqJuN++cE6A2BBTkaLpEZajVqPKL5AxL5KWM +dMFNkpF3i0UKbf4vnpfrQprsamDX5tKqPCAOKa8yL82CBkimOCmLx24WN+VtNitYzh/MqspApNM7 +7wCO8ncvFtS4sC1Gj5M9CjVhxKmMe15O4SZr9aZpGP7raT4CE3X95APKX5yyiAVwPcOPdPkfRRLQ +gHko60NbxaeayH5sfWa2dNPEjbOkz0SKaXurV9pzrj/2FZNhgsnRsGIJhx2BLm7FoeUC45RarDJD +YrscJ6DBR83YwJXsaFCyB7l5CP7L13Wr98E= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIEvjCCAqagAwIBAgIQQAEzUd0AAAAAAAAAFzPdqzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJUVzEbMBkGA1UEChMSVFdDQSBSRCBEZXBhcnRtZW50MRAwDgYDVQQLEwdDQSBUZWFtMSQwIgYD +VQQDExtSRCBUV0NBIFJvb3QgQ0EgNDA5NiBTaGEyNTYwHhcNMTQwNTA5MDMyMDUyWhcNMTUwNTA5 +MTU1OTU5WjBUMQswCQYDVQQGEwJUVzEXMBUGA1UEChMOVEFJV0FOLUNBIElOQy4xEDAOBgNVBAsT +B0NBIFRlYW0xGjAYBgNVBAMTEVJEIFRXQ0EgQ1RURVNUIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA6xAMprFXHtOkmXIgL63aTx4S20IzdrcdDTMQvwnyYLBhJf8lWz5qeOY37SaC +4VXILP54qVdML+YBa2BAQfgu6kS+/f73Bp3pSHx8LIHQCt5jdgkNS3OVYK8nQdbWXWeA64bCFdE/ +tlelHSTHtIKXE+v7ug+P5Q/RRBf0Dzi/M1fXTXqXeAga3LaPGPT7o6lZZJh7hp25aJxChIa/1X8x +99sPx/BqO/WHyYKBCU9Ym05yQFel8mpCgzSbqscKTbKPkvm0ejDANX/WCEziJ3IzR5G9kPoL/zYZ +ofIqYJMIYRsQRlD/n1ILnMxwdhN3EFlZ0e5xkyIm9TaCqeCZsdFJWQIDAQABo34wfDArBgNVHSME +JDAigCCwvM16BvA51cl2uO30/ohdOMPVrVBVG5BZ4teNnteYnTApBgNVHQ4EIgQghMT0q268KAXG +MT0nEISe1jhiQeTdxH5Gr/NXb7nuzbIwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C +AQAwDQYJKoZIhvcNAQEFBQADggIBABDkaI3GMCKBfJSfnpmxmiU1ht3cfq/9/hpJSzE6k+of5esV +D3bYW9nnKScCcBy7poeOoc3C7p9fQtsLZbNfhYpG4/Aq0aVYGtZxw/FCWnXi9rUXpSLZh1yW1uV9 +KBj2D8yzGIx99mpHifjjeoCWG0TW/aaHeIolJm2DhkPTLWjd/urN1TP8YqhEiKMHZI3SFWeeH/BV +WJKE5rX8vtLW1FPnlRPE+Z/FAa52qOyN4ie0A9IhNPs3gtc+bnhdcZaHnxkySqUvWxqQxkzAGaiO +VnPlnSlnMCn5I2KOT0XVWYOyU9PP1//V/baDftv7VpT5AOtIaz8mQ6Lp4AIcoPFeU8cgJNZhXgmp +NOv/dW8lWXH6RYxdM7NFmv98Wk3rKLCzOzR6kuXnARKOJghZf4FV+6Fvjgjf/8wLnzhSdCEbyL7A +znkOSKc9wzUcZCxF8aTWtRT8HYIu8diZo0CzPxN8OyDl5mPsYexhguPHOXyLv/EljZ8yCdy/SsgQ +JPzuqKu2a3RD4des15EzbnJOxn4DSeqoUfSfaU/KVfmUKpBEJ3ouD2SLAZ7L+4F6NPCte3HEE2kN +tOmQIwe65htXmLJxDB+dwMjE4dkA2sETaN2dQ9DqpCNkpNxuNdis/uacAAXnDNddPIlR2moCtUx8 ++Y7wlcqBHdmmg5rbFBuBN+esL8J8 +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIFyTCCA7GgAwIBAgIQQAEzK0EAAAAAAAAAFSWxNjANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG +EwJUVzEbMBkGA1UEChMSVFdDQSBSRCBEZXBhcnRtZW50MRAwDgYDVQQLEwdDQSBUZWFtMSQwIgYD +VQQDExtSRCBUV0NBIFJvb3QgQ0EgNDA5NiBTaGEyNTYwHhcNMTMwNjI1MDMwNzIyWhcNMzMwNjI1 +MDMwNzI2WjBiMQswCQYDVQQGEwJUVzEbMBkGA1UEChMSVFdDQSBSRCBEZXBhcnRtZW50MRAwDgYD +VQQLEwdDQSBUZWFtMSQwIgYDVQQDExtSRCBUV0NBIFJvb3QgQ0EgNDA5NiBTaGEyNTYwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2Saqg7eNGISFsG+pQfB/e+VTbpg+KmAAydUrJyCPj +74Gl/MKNeMW6AqUUSiKQq+HTnrHI+I2e85cgAxbSbhXp6utJuOjfsZE5lr7KDkfok9hdMA7YvKuk +y5dLK9Qcvhj4olt3DU0GKdWgKKtMWg4WOx+Wgu50C/TGyeiMx754O09a0YXlDLji84aQbxUWCP+X +hq+LXyGqilcTe+wSVjUHWfJJz8ZeVNCz/WXBn2Sljf614T1AkeU9pTnEkJRd/S+eVNVE8gLiAJSF +/ffHTHGRZoPCTDS26hzSpBAC+va0T4IWvgGJtPNInReXGPeydxHJbsJjwyPQ9n5iclUZmAeKcG7a +Wow/xrU36euBDIp877djj5lbtb0Rq35slDAGLVy/ouLkcrurPZdJGkhcpACMi4sKK98cx/XnzP9o +wV+bDYyYlXSl3tv88CidywHI6VPN6Aio4ipsAOmol1AxbkJ+W9INiQzbdmYXD2v3c0Kvcq4/bZMw +wofoGWGBALF3VYd6aYUnaCHD9gYTPrMHVsMrYDbvlIDkORVL950xvi1SfbRRo36LtYLjupFiJOlP +xS0DxWN6tVarS+1SyHsdEJYKw+b2ty5Sko5JkCedgSXHPhkL2ap3OfHtegSDpIgWL7ydpaoTyD3y +Fev6doCPC6cnHitwBCDpoEqNIm+JK2JZYQIDAQABo3sweTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zArBgNVHSMEJDAigCCwvM16BvA51cl2uO30/ohdOMPVrVBVG5BZ4teNnteYnTAp +BgNVHQ4EIgQgsLzNegbwOdXJdrjt9P6IXTjD1a1QVRuQWeLXjZ7XmJ0wDQYJKoZIhvcNAQELBQAD +ggIBAGSVKdVIynZnTpFaK3F2jjaC2oaJ1L8CA6e5OjWi6WUshKG4kJzLASD/U8XQXA8rVSuWShmz +B7ccm4fy1SmnSvsi4JA5mSDqcIOmwZmIYBAd/+8QK/sijzyO2MNPpqSupuhWxAakHSG8/3Leij2Q +P2GEXejDq3ewtS/gT1ZVI/ZSlIYxChsKZ3UEkl4XhUhL8fir/5Z+g6WdTFvXUB3wc/JA/MZ+h5Nu +BsrnrTlxet0vu3UlyOELsF5pMe1WGayR2A56LRL3UKhjFrUJSCTYMBiqAMS3Fsvk+RXttPYtcpiB +uheX8M/X8g2WTLOklS9/QYy1VmIWZcrfExHrMxQ8FCrxMfQn8fNlkMADmcRbQYeVHHZGx7MQEjBw +py45jzcPudJTx8Ccz6r0YSxoumC9reS0hASQ/NdXh6vcWfT8qsqYohL/k9J0PbfgJuIExAStIs+Y +nn4N7HgNftijy+l0sS//rMhVcofUaJzhJcbUe4TX/SL8ZHFkSkhUSPdDd1DR+r1IWKDKd/2FxMn3 ++oKBVsjPdL0HBwwHFQja8TBb5E3vYo4XKKEOGIuFa7NcSq0pF7pK85K0XIypAwgJCXffWP9SynDo +eK+ZbSOZNOCvH67ZRUQnWo1nZds+6OplhSpWkYDYN834wXEU4zbHRvtymCbIeMZzAXzdsJM2i3zy +7bTu +-----END CERTIFICATE----- + diff --git a/tools/testcerts/roots/root4.pem b/tools/testcerts/roots/root4.pem new file mode 100644 index 0000000..3fdb770 --- /dev/null +++ b/tools/testcerts/roots/root4.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/tools/testcerts/roots/root5.pem b/tools/testcerts/roots/root5.pem new file mode 100644 index 0000000..096fd18 --- /dev/null +++ b/tools/testcerts/roots/root5.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFyTCCA7GgAwIBAgIQQAEzK0EAAAAAAAAAFSWxNjANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG +EwJUVzEbMBkGA1UEChMSVFdDQSBSRCBEZXBhcnRtZW50MRAwDgYDVQQLEwdDQSBUZWFtMSQwIgYD +VQQDExtSRCBUV0NBIFJvb3QgQ0EgNDA5NiBTaGEyNTYwHhcNMTMwNjI1MDMwNzIyWhcNMzMwNjI1 +MDMwNzI2WjBiMQswCQYDVQQGEwJUVzEbMBkGA1UEChMSVFdDQSBSRCBEZXBhcnRtZW50MRAwDgYD +VQQLEwdDQSBUZWFtMSQwIgYDVQQDExtSRCBUV0NBIFJvb3QgQ0EgNDA5NiBTaGEyNTYwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2Saqg7eNGISFsG+pQfB/e+VTbpg+KmAAydUrJyCPj +74Gl/MKNeMW6AqUUSiKQq+HTnrHI+I2e85cgAxbSbhXp6utJuOjfsZE5lr7KDkfok9hdMA7YvKuk +y5dLK9Qcvhj4olt3DU0GKdWgKKtMWg4WOx+Wgu50C/TGyeiMx754O09a0YXlDLji84aQbxUWCP+X +hq+LXyGqilcTe+wSVjUHWfJJz8ZeVNCz/WXBn2Sljf614T1AkeU9pTnEkJRd/S+eVNVE8gLiAJSF +/ffHTHGRZoPCTDS26hzSpBAC+va0T4IWvgGJtPNInReXGPeydxHJbsJjwyPQ9n5iclUZmAeKcG7a +Wow/xrU36euBDIp877djj5lbtb0Rq35slDAGLVy/ouLkcrurPZdJGkhcpACMi4sKK98cx/XnzP9o +wV+bDYyYlXSl3tv88CidywHI6VPN6Aio4ipsAOmol1AxbkJ+W9INiQzbdmYXD2v3c0Kvcq4/bZMw +wofoGWGBALF3VYd6aYUnaCHD9gYTPrMHVsMrYDbvlIDkORVL950xvi1SfbRRo36LtYLjupFiJOlP +xS0DxWN6tVarS+1SyHsdEJYKw+b2ty5Sko5JkCedgSXHPhkL2ap3OfHtegSDpIgWL7ydpaoTyD3y +Fev6doCPC6cnHitwBCDpoEqNIm+JK2JZYQIDAQABo3sweTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zArBgNVHSMEJDAigCCwvM16BvA51cl2uO30/ohdOMPVrVBVG5BZ4teNnteYnTAp +BgNVHQ4EIgQgsLzNegbwOdXJdrjt9P6IXTjD1a1QVRuQWeLXjZ7XmJ0wDQYJKoZIhvcNAQELBQAD +ggIBAGSVKdVIynZnTpFaK3F2jjaC2oaJ1L8CA6e5OjWi6WUshKG4kJzLASD/U8XQXA8rVSuWShmz +B7ccm4fy1SmnSvsi4JA5mSDqcIOmwZmIYBAd/+8QK/sijzyO2MNPpqSupuhWxAakHSG8/3Leij2Q +P2GEXejDq3ewtS/gT1ZVI/ZSlIYxChsKZ3UEkl4XhUhL8fir/5Z+g6WdTFvXUB3wc/JA/MZ+h5Nu +BsrnrTlxet0vu3UlyOELsF5pMe1WGayR2A56LRL3UKhjFrUJSCTYMBiqAMS3Fsvk+RXttPYtcpiB +uheX8M/X8g2WTLOklS9/QYy1VmIWZcrfExHrMxQ8FCrxMfQn8fNlkMADmcRbQYeVHHZGx7MQEjBw +py45jzcPudJTx8Ccz6r0YSxoumC9reS0hASQ/NdXh6vcWfT8qsqYohL/k9J0PbfgJuIExAStIs+Y +nn4N7HgNftijy+l0sS//rMhVcofUaJzhJcbUe4TX/SL8ZHFkSkhUSPdDd1DR+r1IWKDKd/2FxMn3 ++oKBVsjPdL0HBwwHFQja8TBb5E3vYo4XKKEOGIuFa7NcSq0pF7pK85K0XIypAwgJCXffWP9SynDo +eK+ZbSOZNOCvH67ZRUQnWo1nZds+6OplhSpWkYDYN834wXEU4zbHRvtymCbIeMZzAXzdsJM2i3zy +7bTu +-----END CERTIFICATE----- -- cgit v1.1 From 3f9f38468dd9f43a7a71768dbe84dd40723c30c5 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Mon, 23 Mar 2015 15:22:15 +0100 Subject: Add spec's for most functions. NOTE: We're not dialyzer clean yet. --- src/catlfish.erl | 11 +---------- src/x509.erl | 9 ++++++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/catlfish.erl b/src/catlfish.erl index 50811a1..2e5ffd4 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -87,6 +87,7 @@ deserialise_entry_type(<<0:16>>) -> deserialise_entry_type(<<1:16>>) -> precert_entry. +-spec serialise_signature_type(certificate_timestamp|tree_hash) -> binary(). serialise_signature_type(certificate_timestamp) -> <<0:8>>; serialise_signature_type(tree_hash) -> @@ -177,16 +178,6 @@ timestamped_entry(Timestamp, EntryType, LeafCert, CertChain) -> entry_type = EntryType, signed_entry = SignedEntry}. -%% -spec serialise_logentry(integer(), binary(), [binary()]) -> binary(). -%% serialise_logentry(Timestamp, LeafCert, CertChain) -> -%% list_to_binary( -%% [<>, -%% list_to_binary( -%% [encode_tls_vector(LeafCert, 3), -%% encode_tls_vector( -%% list_to_binary( -%% [encode_tls_vector(X, 3) || X <- CertChain]), 3)])]). - -spec deserialise_mtl(binary()) -> mtl(). deserialise_mtl(Data) -> < {ok, CertChain ++ Root} end. +-spec cert_string(binary()) -> string(). cert_string(Der) -> mochihex:to_hex(crypto:hash(sha, Der)). @@ -54,8 +55,8 @@ read_pemfiles_from_dir(Dir) -> self_signed(L) -> lists:filter(fun(Cert) -> signed_by_p(Cert, Cert) end, L). -%% @doc Return the detoxed cet in LeafDer and the issuer leaf hash. -spec detox(binary(), [binary()]) -> {binary(), binary()}. +%% @doc Return the detoxed cet in LeafDer and the issuer leaf hash. detox(LeafDer, ChainDer) -> detox_precert(LeafDer, nth(1, ChainDer), nth(2, ChainDer)). @@ -117,6 +118,7 @@ signer(Cert, [H|T]) -> signer(Cert, T) end. +-spec encoded_tbs_cert(binary()) -> binary(). %% Code from pubkey_cert:encoded_tbs_cert/1. encoded_tbs_cert(DerCert) -> {ok, PKIXCert} = @@ -299,6 +301,7 @@ is_precert_signer(#'Certificate'{tbsCertificate = TBSCert}) -> _ -> false end. +-spec is_ca(#'TBSCertificate'{}) -> binary(). is_ca(#'TBSCertificate'{extensions = Extensions}) -> case pubkey_cert:select_extension(?'id-ce-basicConstraints', Extensions) of #'Extension'{critical = true, extnValue = Val} -> @@ -316,6 +319,7 @@ remove_poison_ext(#'Certificate'{tbsCertificate = TBSCert}) -> pubkey_cert:extensions_list(TBSCert#'TBSCertificate'.extensions)), TBSCert#'TBSCertificate'{extensions = Extensions}. +-spec poisoned_leaf_p(binary()) -> boolean(). poisoned_leaf_p(#'Extension'{extnID = ?LEAF_POISON_OID, critical = true, extnValue = ?LEAF_POISON_VAL}) -> @@ -324,10 +328,12 @@ poisoned_leaf_p(_) -> false. %%%% PEM files. +-spec ders_from_pemfiles(string(), [string()]) -> [binary()]. ders_from_pemfiles(Dir, Filenames) -> lists:flatten( [ders_from_pemfile(filename:join(Dir, X)) || X <- Filenames]). +-spec ders_from_pemfile(string()) -> [binary()]. ders_from_pemfile(Filename) -> lager:debug("reading PEM from ~s", [Filename]), PemBins = pems_from_file(Filename), @@ -339,6 +345,7 @@ ders_from_pemfile(Filename) -> end, [der_from_pem(X) || X <- Pems]. +-spec der_from_pem(binary()) -> binary(). der_from_pem(Pem) -> case Pem of {_Type, Der, not_encrypted} -> -- cgit v1.1 From 1075b68f287e957cd73c8cdb9517293b4c920eec Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 16 Mar 2015 10:06:42 +0100 Subject: Add submission of precerts --- tools/certtools.py | 23 +++++++++++++++++++++++ tools/submitcert.py | 8 +++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tools/certtools.py b/tools/certtools.py index 0e639f2..b0a1c97 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -61,6 +61,10 @@ def get_certs_from_string(s): f = cStringIO.StringIO(s) return get_pemlike_from_file(f, "CERTIFICATE") +def get_precerts_from_string(s): + f = cStringIO.StringIO(s) + return get_pemlike_from_file(f, "PRECERTIFICATE") + def get_eckey_from_file(keyfile): keys = get_pemlike(keyfile, "EC PRIVATE KEY") assert len(keys) == 1 @@ -137,6 +141,24 @@ def add_chain(baseurl, submission): print "========================" raise e +def add_prechain(baseurl, submission): + try: + result = urllib2.urlopen(baseurl + "ct/v1/add-pre-chain", + json.dumps(submission)).read() + return json.loads(result) + except urllib2.HTTPError, e: + print "ERROR", e.code,":", e.read() + if e.code == 400: + return None + sys.exit(1) + except ValueError, e: + print "==== FAILED REQUEST ====" + print submission + print "======= RESPONSE =======" + print result + print "========================" + raise e + def get_entries(baseurl, start, end): try: params = urllib.urlencode({"start":start, "end":end}) @@ -586,5 +608,6 @@ def verify_consistency_proof(consistency_proof, first, second, oldhash_input): def verify_inclusion_proof(inclusion_proof, index, treesize, leafhash): chain = zip([(index, 0)] + nodes_for_index(index, treesize), [leafhash] + inclusion_proof) + assert len(nodes_for_index(index, treesize)) == len(inclusion_proof) (_, hash) = reduce(lambda e1, e2: combine_two_hashes(e1, e2, treesize), chain) return hash diff --git a/tools/submitcert.py b/tools/submitcert.py index 9f0be67..1c79544 100755 --- a/tools/submitcert.py +++ b/tools/submitcert.py @@ -44,10 +44,16 @@ sth = get_sth(baseurl) def submitcert((certfile, cert)): timing = timing_point() certchain = get_certs_from_string(cert) + precerts = get_precerts_from_string(cert) + assert len(precerts) == 0 or len(precerts) == 1 + precert = precerts[0] if precerts else None timing_point(timing, "readcerts") try: - result = add_chain(baseurl, {"chain":map(base64.b64encode, certchain)}) + if precert: + result = add_prechain(baseurl, {"chain":map(base64.b64encode, [precert] + certchain)}) + else: + result = add_chain(baseurl, {"chain":map(base64.b64encode, certchain)}) except SystemExit: print "EXIT:", certfile select.select([], [], [], 1.0) -- cgit v1.1 From 15d5d6fd5cffdea185d18fbd4feb62afa23b9d12 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Wed, 18 Mar 2015 01:03:18 +0100 Subject: Added validatestore.py --- tools/certtools.py | 20 ++ tools/precerttools.py | 102 ++++++ tools/rfc2459.py | 927 +++++++++++++++++++++++++++++++++++++++++++++++++ tools/validatestore.py | 96 +++++ 4 files changed, 1145 insertions(+) create mode 100644 tools/precerttools.py create mode 100644 tools/rfc2459.py create mode 100755 tools/validatestore.py diff --git a/tools/certtools.py b/tools/certtools.py index b0a1c97..1436863 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -289,6 +289,18 @@ def pack_mtl(timestamp, leafcert): merkle_tree_leaf = version + leaf_type + timestamped_entry return merkle_tree_leaf +def pack_mtl_precert(timestamp, cleanedcert, issuer_key_hash): + entry_type = struct.pack(">H", 1) + extensions = "" + assert len(issuer_key_hash) == 32 + + timestamped_entry = struct.pack(">Q", timestamp) + entry_type + \ + issuer_key_hash + tls_array(cleanedcert, 3) + tls_array(extensions, 2) + version = struct.pack(">b", 0) + leaf_type = struct.pack(">b", 0) + merkle_tree_leaf = version + leaf_type + timestamped_entry + return merkle_tree_leaf + def unpack_mtl(merkle_tree_leaf): version = merkle_tree_leaf[0:1] leaf_type = merkle_tree_leaf[1:2] @@ -375,6 +387,14 @@ def get_hash_from_certfile(cert): return base64.b16decode(line[len("Leafhash: "):]) return None +def get_timestamp_from_certfile(cert): + for line in cert.split("\n"): + if line.startswith("-----"): + return None + if line.startswith("Timestamp: "): + return int(line[len("Timestamp: "):]) + return None + def get_proof(store, tree_size, n): hash = get_hash_from_certfile(get_one_cert(store, n)) return get_proof_by_hash(args.baseurl, hash, tree_size) diff --git a/tools/precerttools.py b/tools/precerttools.py new file mode 100644 index 0000000..13ac572 --- /dev/null +++ b/tools/precerttools.py @@ -0,0 +1,102 @@ +# Copyright (c) 2014, NORDUnet A/S. +# See LICENSE for licensing information. + +import sys +import hashlib +import rfc2459 +from pyasn1.type import univ, tag +from pyasn1.codec.der import encoder, decoder + +def cleanextensions(extensions): + result = rfc2459.Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)) + for idx in range(len(extensions)): + extension = extensions.getComponentByPosition(idx) + if extension.getComponentByName("extnID") == univ.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3"): + pass + else: + result.setComponentByPosition(len(result), extension) + return result + +def decode_any(anydata, asn1Spec=None): + (wrapper, _) = decoder.decode(anydata) + (data, _) = decoder.decode(wrapper, asn1Spec=asn1Spec) + return data + +def get_subject(cert): + (asn1,rest) = decoder.decode(cert, asn1Spec=rfc2459.Certificate()) + assert rest == '' + tbsCertificate = asn1.getComponentByName("tbsCertificate") + subject = tbsCertificate.getComponentByName("subject") + extensions = tbsCertificate.getComponentByName("extensions") + keyid_wrapper = get_extension(extensions, rfc2459.id_ce_subjectKeyIdentifier) + keyid = decode_any(keyid_wrapper, asn1Spec=rfc2459.KeyIdentifier()) + return (subject, keyid) + +def cleanprecert(precert, issuer=None): + (asn1,rest) = decoder.decode(precert, asn1Spec=rfc2459.Certificate()) + assert rest == '' + tbsCertificate = asn1.getComponentByName("tbsCertificate") + + extensions = tbsCertificate.getComponentByName("extensions") + tbsCertificate.setComponentByName("extensions", cleanextensions(extensions)) + + if issuer: + (issuer_subject, keyid) = get_subject(issuer) + tbsCertificate.setComponentByName("issuer", issuer_subject) + authkeyid = rfc2459.AuthorityKeyIdentifier() + authkeyid.setComponentByName("keyIdentifier", + rfc2459.KeyIdentifier(str(keyid)).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) + authkeyid_wrapper = univ.OctetString(encoder.encode(authkeyid)) + authkeyid_wrapper2 = encoder.encode(authkeyid_wrapper) + set_extension(extensions, rfc2459.id_ce_authorityKeyIdentifier, authkeyid_wrapper2) + return encoder.encode(tbsCertificate) + +def get_extension(extensions, id): + for idx in range(len(extensions)): + extension = extensions.getComponentByPosition(idx) + if extension.getComponentByName("extnID") == id: + return extension.getComponentByName("extnValue") + return None + +def set_extension(extensions, id, value): + result = rfc2459.Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)) + for idx in range(len(extensions)): + extension = extensions.getComponentByPosition(idx) + if extension.getComponentByName("extnID") == id: + extension.setComponentByName("extnValue", value) + +def get_cert_key_hash(cert): + (asn1,rest) = decoder.decode(cert, asn1Spec=rfc2459.Certificate()) + assert rest == '' + tbsCertificate = asn1.getComponentByName("tbsCertificate") + key = encoder.encode(tbsCertificate.getComponentByName("subjectPublicKeyInfo")) + hash = hashlib.sha256() + hash.update(key) + return hash.digest() + +def printcert(cert, outfile=sys.stdout): + (asn1,rest) = decoder.decode(cert, asn1Spec=rfc2459.Certificate()) + assert rest == '' + print >>outfile, asn1.prettyPrint() + +def printtbscert(cert, outfile=sys.stdout): + (asn1,rest) = decoder.decode(cert, asn1Spec=rfc2459.TBSCertificate()) + assert rest == '' + print >>outfile, asn1.prettyPrint() + +ext_key_usage_precert_signing_cert = univ.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") + +def get_ext_key_usage(cert): + (asn1,rest) = decoder.decode(cert, asn1Spec=rfc2459.Certificate()) + assert rest == '' + tbsCertificate = asn1.getComponentByName("tbsCertificate") + extensions = tbsCertificate.getComponentByName("extensions") + for idx in range(len(extensions)): + extension = extensions.getComponentByPosition(idx) + if extension.getComponentByName("extnID") == rfc2459.id_ce_extKeyUsage: + ext_key_usage_wrapper_binary = extension.getComponentByName("extnValue") + (ext_key_usage_wrapper, _) = decoder.decode(ext_key_usage_wrapper_binary) + (ext_key_usage, _) = decoder.decode(ext_key_usage_wrapper)#, asn1Spec=rfc2459.ExtKeyUsageSyntax()) + return list(ext_key_usage) + return [] + diff --git a/tools/rfc2459.py b/tools/rfc2459.py new file mode 100644 index 0000000..0ce9c6d --- /dev/null +++ b/tools/rfc2459.py @@ -0,0 +1,927 @@ +# Copyright (c) 2005-2013, Ilya Etingof +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * 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 HOLDERS 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. +# +# X.509 message syntax +# +# ASN.1 source from: +# http://www.trl.ibm.com/projects/xml/xss4j/data/asn1/grammars/x509.asn +# http://www.ietf.org/rfc/rfc2459.txt +# +# Sample captures from: +# http://wiki.wireshark.org/SampleCaptures/ +# +from pyasn1.type import tag,namedtype,namedval,univ,constraint,char,useful + +MAX = 64 # XXX ? + +# +# PKIX1Explicit88 +# + +# Upper Bounds +ub_name = univ.Integer(32768) +ub_common_name = univ.Integer(64) +ub_locality_name = univ.Integer(128) +ub_state_name = univ.Integer(128) +ub_organization_name = univ.Integer(64) +ub_organizational_unit_name = univ.Integer(64) +ub_title = univ.Integer(64) +ub_match = univ.Integer(128) +ub_emailaddress_length = univ.Integer(128) +ub_common_name_length = univ.Integer(64) +ub_country_name_alpha_length = univ.Integer(2) +ub_country_name_numeric_length = univ.Integer(3) +ub_domain_defined_attributes = univ.Integer(4) +ub_domain_defined_attribute_type_length = univ.Integer(8) +ub_domain_defined_attribute_value_length = univ.Integer(128) +ub_domain_name_length = univ.Integer(16) +ub_extension_attributes = univ.Integer(256) +ub_e163_4_number_length = univ.Integer(15) +ub_e163_4_sub_address_length = univ.Integer(40) +ub_generation_qualifier_length = univ.Integer(3) +ub_given_name_length = univ.Integer(16) +ub_initials_length = univ.Integer(5) +ub_integer_options = univ.Integer(256) +ub_numeric_user_id_length = univ.Integer(32) +ub_organization_name_length = univ.Integer(64) +ub_organizational_unit_name_length = univ.Integer(32) +ub_organizational_units = univ.Integer(4) +ub_pds_name_length = univ.Integer(16) +ub_pds_parameter_length = univ.Integer(30) +ub_pds_physical_address_lines = univ.Integer(6) +ub_postal_code_length = univ.Integer(16) +ub_surname_length = univ.Integer(40) +ub_terminal_id_length = univ.Integer(24) +ub_unformatted_address_length = univ.Integer(180) +ub_x121_address_length = univ.Integer(16) + +class UniversalString(char.UniversalString): pass +class BMPString(char.BMPString): pass +class UTF8String(char.UTF8String): pass + +id_pkix = univ.ObjectIdentifier('1.3.6.1.5.5.7') +id_pe = univ.ObjectIdentifier('1.3.6.1.5.5.7.1') +id_qt = univ.ObjectIdentifier('1.3.6.1.5.5.7.2') +id_kp = univ.ObjectIdentifier('1.3.6.1.5.5.7.3') +id_ad = univ.ObjectIdentifier('1.3.6.1.5.5.7.48') + +id_qt_cps = univ.ObjectIdentifier('1.3.6.1.5.5.7.2.1') +id_qt_unotice = univ.ObjectIdentifier('1.3.6.1.5.5.7.2.2') + +id_ad_ocsp = univ.ObjectIdentifier('1.3.6.1.5.5.7.48.1') +id_ad_caIssuers = univ.ObjectIdentifier('1.3.6.1.5.5.7.48.2') + +class AttributeValue(univ.Any): pass + +class AttributeType(univ.ObjectIdentifier): pass + +class AttributeTypeAndValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('value', AttributeValue()) + ) + +class Attribute(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('vals', univ.SetOf(componentType=AttributeValue())) + ) + +id_at = univ.ObjectIdentifier('2.5.4') +id_at_name = univ.ObjectIdentifier('2.5.4.41') +id_at_sutname = univ.ObjectIdentifier('2.5.4.4') +id_at_givenName = univ.ObjectIdentifier('2.5.4.42') +id_at_initials = univ.ObjectIdentifier('2.5.4.43') +id_at_generationQualifier = univ.ObjectIdentifier('2.5.4.44') + +class X520name(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_name))) + ) + +id_at_commonName = univ.ObjectIdentifier('2.5.4.3') + +class X520CommonName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_common_name))) + ) + +id_at_localityName = univ.ObjectIdentifier('2.5.4.7') + +class X520LocalityName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_locality_name))) + ) + +id_at_stateOrProvinceName = univ.ObjectIdentifier('2.5.4.8') + +class X520StateOrProvinceName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_state_name))) + ) + +id_at_organizationName = univ.ObjectIdentifier('2.5.4.10') + +class X520OrganizationName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organization_name))) + ) + +id_at_organizationalUnitName = univ.ObjectIdentifier('2.5.4.11') + +class X520OrganizationalUnitName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_organizational_unit_name))) + ) + +id_at_title = univ.ObjectIdentifier('2.5.4.12') + +class X520Title(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_title))) + ) + +id_at_dnQualifier = univ.ObjectIdentifier('2.5.4.46') + +class X520dnQualifier(char.PrintableString): pass + +id_at_countryName = univ.ObjectIdentifier('2.5.4.6') + +class X520countryName(char.PrintableString): + subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(2, 2) + +pkcs_9 = univ.ObjectIdentifier('1.2.840.113549.1.9') + +emailAddress = univ.ObjectIdentifier('1.2.840.113549.1.9.1') + +class Pkcs9email(char.IA5String): + subtypeSpec = char.IA5String.subtypeSpec + constraint.ValueSizeConstraint(1, ub_emailaddress_length) + +# ---- + +class DSAPrivateKey(univ.Sequence): + """PKIX compliant DSA private key structure""" + componentType = namedtype.NamedTypes( + namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('v1', 0)))), + namedtype.NamedType('p', univ.Integer()), + namedtype.NamedType('q', univ.Integer()), + namedtype.NamedType('g', univ.Integer()), + namedtype.NamedType('public', univ.Integer()), + namedtype.NamedType('private', univ.Integer()) + ) + +# ---- + +class RelativeDistinguishedName(univ.SetOf): + componentType = AttributeTypeAndValue() + +class RDNSequence(univ.SequenceOf): + componentType = RelativeDistinguishedName() + +class Name(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('', RDNSequence()) + ) + +class DirectoryString(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX + ) + +# certificate and CRL specific structures begin here + +class AlgorithmIdentifier(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', univ.ObjectIdentifier()), + namedtype.OptionalNamedType('parameters', univ.Any()) + ) + +class Extension(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('extnID', univ.ObjectIdentifier()), + namedtype.DefaultedNamedType('critical', univ.Boolean('False')), + namedtype.NamedType('extnValue', univ.Any()) + ) + +class Extensions(univ.SequenceOf): + componentType = Extension() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + +class SubjectPublicKeyInfo(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', AlgorithmIdentifier()), + namedtype.NamedType('subjectPublicKey', univ.BitString()) + ) + +class UniqueIdentifier(univ.BitString): pass + +class Time(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('utcTime', useful.UTCTime()), + namedtype.NamedType('generalTime', useful.GeneralizedTime()) + ) + +class Validity(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('notBefore', Time()), + namedtype.NamedType('notAfter', Time()) + ) + +class CertificateSerialNumber(univ.Integer): pass + +class Version(univ.Integer): + namedValues = namedval.NamedValues( + ('v1', 0), ('v2', 1), ('v3', 2) + ) + +class TBSCertificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.DefaultedNamedType('version', Version('v1').subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('serialNumber', CertificateSerialNumber()), + namedtype.NamedType('signature', AlgorithmIdentifier()), + namedtype.NamedType('issuer', Name()), + namedtype.NamedType('validity', Validity()), + namedtype.NamedType('subject', Name()), + namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()), + namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) + ) + +class Certificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('tbsCertificate', TBSCertificate()), + namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), + namedtype.NamedType('signatureValue', univ.BitString()) + ) + +# CRL structures + +class RevokedCertificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('userCertificate', CertificateSerialNumber()), + namedtype.NamedType('revocationDate', Time()), + namedtype.OptionalNamedType('crlEntryExtensions', Extensions()) + ) + +class TBSCertList(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('version', Version()), + namedtype.NamedType('signature', AlgorithmIdentifier()), + namedtype.NamedType('issuer', Name()), + namedtype.NamedType('thisUpdate', Time()), + namedtype.OptionalNamedType('nextUpdate', Time()), + namedtype.OptionalNamedType('revokedCertificates', univ.SequenceOf(componentType=RevokedCertificate())), + namedtype.OptionalNamedType('crlExtensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + ) + +class CertificateList(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('tbsCertList', TBSCertList()), + namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), + namedtype.NamedType('signature', univ.BitString()) + ) + +# Algorithm OIDs and parameter structures + +pkcs_1 = univ.ObjectIdentifier('1.2.840.113549.1.1') +rsaEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.1') +md2WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.2') +md5WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.4') +sha1WithRSAEncryption = univ.ObjectIdentifier('1.2.840.113549.1.1.5') +id_dsa_with_sha1 = univ.ObjectIdentifier('1.2.840.10040.4.3') + +class Dss_Sig_Value(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('r', univ.Integer()), + namedtype.NamedType('s', univ.Integer()) + ) + +dhpublicnumber = univ.ObjectIdentifier('1.2.840.10046.2.1') + +class ValidationParms(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('seed', univ.BitString()), + namedtype.NamedType('pgenCounter', univ.Integer()) + ) + +class DomainParameters(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('p', univ.Integer()), + namedtype.NamedType('g', univ.Integer()), + namedtype.NamedType('q', univ.Integer()), + namedtype.NamedType('j', univ.Integer()), + namedtype.OptionalNamedType('validationParms', ValidationParms()) + ) + +id_dsa = univ.ObjectIdentifier('1.2.840.10040.4.1') + +class Dss_Parms(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('p', univ.Integer()), + namedtype.NamedType('q', univ.Integer()), + namedtype.NamedType('g', univ.Integer()) + ) + +# x400 address syntax starts here + +teletex_domain_defined_attributes = univ.Integer(6) + +class TeletexDomainDefinedAttribute(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))), + namedtype.NamedType('value', char.TeletexString()) + ) + +class TeletexDomainDefinedAttributes(univ.SequenceOf): + componentType = TeletexDomainDefinedAttribute() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, ub_domain_defined_attributes) + +terminal_type = univ.Integer(23) + +class TerminalType(univ.Integer): + subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueSizeConstraint(0, ub_integer_options) + namedValues = namedval.NamedValues( + ('telex', 3), + ('teletelex', 4), + ('g3-facsimile', 5), + ('g4-facsimile', 6), + ('ia5-terminal', 7), + ('videotex', 8) + ) + +class PresentationAddress(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('pSelector', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('sSelector', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('tSelector', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('nAddresses', univ.SetOf(componentType=univ.OctetString()).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3), subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + ) + +extended_network_address = univ.Integer(22) + +class E163_4_address(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('number', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_number_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('sub-address', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_e163_4_sub_address_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))) + ) + +class ExtendedNetworkAddress(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('e163-4-address', E163_4_address()), + namedtype.NamedType('psap-address', PresentationAddress().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) + ) + +class PDSParameter(univ.Set): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('printable-string', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length))), + namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length))) + ) + +local_postal_attributes = univ.Integer(21) + +class LocalPostalAttributes(PDSParameter): pass + +class UniquePostalName(PDSParameter): pass + +unique_postal_name = univ.Integer(20) + +poste_restante_address = univ.Integer(19) + +class PosteRestanteAddress(PDSParameter): pass + +post_office_box_address = univ.Integer(18) + +class PostOfficeBoxAddress(PDSParameter): pass + +street_address = univ.Integer(17) + +class StreetAddress(PDSParameter): pass + +class UnformattedPostalAddress(univ.Set): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('printable-address', univ.SequenceOf(componentType=char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_parameter_length)).subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_pds_physical_address_lines)))), + namedtype.OptionalNamedType('teletex-string', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_unformatted_address_length))) + ) + +physical_delivery_office_name = univ.Integer(10) + +class PhysicalDeliveryOfficeName(PDSParameter): pass + +physical_delivery_office_number = univ.Integer(11) + +class PhysicalDeliveryOfficeNumber(PDSParameter): pass + +extension_OR_address_components = univ.Integer(12) + +class ExtensionORAddressComponents(PDSParameter): pass + +physical_delivery_personal_name = univ.Integer(13) + +class PhysicalDeliveryPersonalName(PDSParameter): pass + +physical_delivery_organization_name = univ.Integer(14) + +class PhysicalDeliveryOrganizationName(PDSParameter): pass + +extension_physical_delivery_address_components = univ.Integer(15) + +class ExtensionPhysicalDeliveryAddressComponents(PDSParameter): pass + +unformatted_postal_address = univ.Integer(16) + +postal_code = univ.Integer(9) + +class PostalCode(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('numeric-code', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length))), + namedtype.NamedType('printable-code', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_postal_code_length))) + ) + +class PhysicalDeliveryCountryName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length, ub_country_name_numeric_length))), + namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length))) + ) + +class PDSName(char.PrintableString): + subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_pds_name_length) + +physical_delivery_country_name = univ.Integer(8) + +class TeletexOrganizationalUnitName(char.TeletexString): + subtypeSpec = char.TeletexString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length) + +pds_name = univ.Integer(7) + +teletex_organizational_unit_names = univ.Integer(5) + +class TeletexOrganizationalUnitNames(univ.SequenceOf): + componentType = TeletexOrganizationalUnitName() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organizational_units) + +teletex_personal_name = univ.Integer(4) + +class TeletexPersonalName(univ.Set): + componentType = namedtype.NamedTypes( + namedtype.NamedType('surname', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('given-name', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('initials', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('generation-qualifier', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) + ) + +teletex_organization_name = univ.Integer(3) + +class TeletexOrganizationName(char.TeletexString): + subtypeSpec = char.TeletexString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organization_name_length) + +teletex_common_name = univ.Integer(2) + +class TeletexCommonName(char.TeletexString): + subtypeSpec = char.TeletexString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_common_name_length) + +class CommonName(char.PrintableString): + subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_common_name_length) + +common_name = univ.Integer(1) + +class ExtensionAttribute(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('extension-attribute-type', univ.Integer().subtype(subtypeSpec=constraint.ValueSizeConstraint(0, ub_extension_attributes), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('extension-attribute-value', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))) + ) + +class ExtensionAttributes(univ.SetOf): + componentType = ExtensionAttribute() + subtypeSpec = univ.SetOf.subtypeSpec + constraint.ValueSizeConstraint(1, ub_extension_attributes) + +class BuiltInDomainDefinedAttribute(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_type_length))), + namedtype.NamedType('value', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_defined_attribute_value_length))) + ) + +class BuiltInDomainDefinedAttributes(univ.SequenceOf): + componentType = BuiltInDomainDefinedAttribute() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, ub_domain_defined_attributes) + +class OrganizationalUnitName(char.PrintableString): + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organizational_unit_name_length) + +class OrganizationalUnitNames(univ.SequenceOf): + componentType = OrganizationalUnitName() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organizational_units) + +class PersonalName(univ.Set): + componentType = namedtype.NamedTypes( + namedtype.NamedType('surname', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_surname_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('given-name', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_given_name_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('initials', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_initials_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('generation-qualifier', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_generation_qualifier_length), explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) + ) + +class NumericUserIdentifier(char.NumericString): + subtypeSpec = char.NumericString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_numeric_user_id_length) + +class OrganizationName(char.PrintableString): + subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_organization_name_length) + +class PrivateDomainName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('numeric', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length))), + namedtype.NamedType('printable', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, ub_domain_name_length))) + ) + +class TerminalIdentifier(char.PrintableString): + subtypeSpec = char.PrintableString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_terminal_id_length) + +class X121Address(char.NumericString): + subtypeSpec = char.NumericString.subtypeSpec + constraint.ValueSizeConstraint(1, ub_x121_address_length) + +class NetworkAddress(X121Address): pass + +class AdministrationDomainName(univ.Choice): + tagSet = univ.Choice.tagSet.tagExplicitly( + tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 2) + ) + componentType = namedtype.NamedTypes( + namedtype.NamedType('numeric', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length))), + namedtype.NamedType('printable', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(0, ub_domain_name_length))) + ) + +class CountryName(univ.Choice): + tagSet = univ.Choice.tagSet.tagExplicitly( + tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 1) + ) + componentType = namedtype.NamedTypes( + namedtype.NamedType('x121-dcc-code', char.NumericString().subtype(subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_numeric_length, ub_country_name_numeric_length))), + namedtype.NamedType('iso-3166-alpha2-code', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(ub_country_name_alpha_length, ub_country_name_alpha_length))) + ) + +class BuiltInStandardAttributes(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('country-name', CountryName()), + namedtype.OptionalNamedType('administration-domain-name', AdministrationDomainName()), + namedtype.OptionalNamedType('network-address', NetworkAddress().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('terminal-identifier', TerminalIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('private-domain-name', PrivateDomainName().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('organization-name', OrganizationName().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + namedtype.OptionalNamedType('numeric-user-identifier', NumericUserIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))), + namedtype.OptionalNamedType('personal-name', PersonalName().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))), + namedtype.OptionalNamedType('organizational-unit-names', OrganizationalUnitNames().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))) + ) + +class ORAddress(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('built-in-standard-attributes', BuiltInStandardAttributes()), + namedtype.OptionalNamedType('built-in-domain-defined-attributes', BuiltInDomainDefinedAttributes()), + namedtype.OptionalNamedType('extension-attributes', ExtensionAttributes()) + ) + +# +# PKIX1Implicit88 +# + +id_ce_invalidityDate = univ.ObjectIdentifier('2.5.29.24') + +class InvalidityDate(useful.GeneralizedTime): pass + +id_holdinstruction_none = univ.ObjectIdentifier('2.2.840.10040.2.1') +id_holdinstruction_callissuer = univ.ObjectIdentifier('2.2.840.10040.2.2') +id_holdinstruction_reject = univ.ObjectIdentifier('2.2.840.10040.2.3') + +holdInstruction = univ.ObjectIdentifier('2.2.840.10040.2') + +id_ce_holdInstructionCode = univ.ObjectIdentifier('2.5.29.23') + +class HoldInstructionCode(univ.ObjectIdentifier): pass + +id_ce_cRLReasons = univ.ObjectIdentifier('2.5.29.21') + +class CRLReason(univ.Enumerated): + namedValues = namedval.NamedValues( + ('unspecified', 0), + ('keyCompromise', 1), + ('cACompromise', 2), + ('affiliationChanged', 3), + ('superseded', 4), + ('cessationOfOperation', 5), + ('certificateHold', 6), + ('removeFromCRL', 8) + ) + +id_ce_cRLNumber = univ.ObjectIdentifier('2.5.29.20') + +class CRLNumber(univ.Integer): + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(0, MAX) + +class BaseCRLNumber(CRLNumber): pass + +id_kp_serverAuth = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.1.1') +id_kp_clientAuth = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.2') +id_kp_codeSigning = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.3') +id_kp_emailProtection = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.4') +id_kp_ipsecEndSystem = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.5') +id_kp_ipsecTunnel = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.6') +id_kp_ipsecUser = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.7') +id_kp_timeStamping = univ.ObjectIdentifier('1.3.6.1.5.5.7.3.8') +id_pe_authorityInfoAccess = univ.ObjectIdentifier('1.3.6.1.5.5.7.1.1') +id_ce_extKeyUsage = univ.ObjectIdentifier('2.5.29.37') + +class KeyPurposeId(univ.ObjectIdentifier): pass + +class ExtKeyUsageSyntax(univ.SequenceOf): + componentType = KeyPurposeId() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +class ReasonFlags(univ.BitString): + namedValues = namedval.NamedValues( + ('unused', 0), + ('keyCompromise', 1), + ('cACompromise', 2), + ('affiliationChanged', 3), + ('superseded', 4), + ('cessationOfOperation', 5), + ('certificateHold', 6) + ) + + +class SkipCerts(univ.Integer): + subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueSizeConstraint(0, MAX) + +id_ce_policyConstraints = univ.ObjectIdentifier('2.5.29.36') + +class PolicyConstraints(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('requireExplicitPolicy', SkipCerts().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('inhibitPolicyMapping', SkipCerts().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + ) + +id_ce_basicConstraints = univ.ObjectIdentifier('2.5.29.19') + +class BasicConstraints(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('cA', univ.Boolean(False)), + namedtype.OptionalNamedType('pathLenConstraint', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, MAX))) + ) + +id_ce_subjectDirectoryAttributes = univ.ObjectIdentifier('2.5.29.9') + +class SubjectDirectoryAttributes(univ.SequenceOf): + componentType = Attribute() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +class EDIPartyName(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('nameAssigner', DirectoryString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('partyName', DirectoryString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))) + ) + +class AnotherName(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type-id', univ.ObjectIdentifier()), + namedtype.NamedType('value', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) + ) + +class GeneralName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('otherName', AnotherName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('rfc822Name', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.NamedType('dNSName', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.NamedType('x400Address', ORAddress().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + namedtype.NamedType('directoryName', Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))), + namedtype.NamedType('ediPartyName', EDIPartyName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))), + namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))), + namedtype.NamedType('iPAddress', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))), + namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))) + ) + +class GeneralNames(univ.SequenceOf): + componentType = GeneralName() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +class AccessDescription(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('accessMethod', univ.ObjectIdentifier()), + namedtype.NamedType('accessLocation', GeneralName()) + ) + +class AuthorityInfoAccessSyntax(univ.SequenceOf): + componentType = AccessDescription() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +id_ce_deltaCRLIndicator = univ.ObjectIdentifier('2.5.29.27') + +class DistributionPointName(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('fullName', GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.NamedType('nameRelativeToCRLIssuer', RelativeDistinguishedName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + ) + +class DistributionPoint(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('distributionPoint', DistributionPointName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('reasons', ReasonFlags().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('cRLIssuer', GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))) + ) +class BaseDistance(univ.Integer): + subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint(0, MAX) + +id_ce_cRLDistributionPoints = univ.ObjectIdentifier('2.5.29.31') + +class CRLDistPointsSyntax(univ.SequenceOf): + componentType = DistributionPoint + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) +id_ce_issuingDistributionPoint = univ.ObjectIdentifier('2.5.29.28') + +class IssuingDistributionPoint(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('distributionPoint', DistributionPointName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.NamedType('onlyContainsUserCerts', univ.Boolean(False).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.NamedType('onlyContainsCACerts', univ.Boolean(False).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('onlySomeReasons', ReasonFlags().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + namedtype.NamedType('indirectCRL', univ.Boolean(False).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))) + ) + +class GeneralSubtree(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('base', GeneralName()), + namedtype.NamedType('minimum', BaseDistance(0).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('maximum', BaseDistance().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + ) + +class GeneralSubtrees(univ.SequenceOf): + componentType = GeneralSubtree() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +id_ce_nameConstraints = univ.ObjectIdentifier('2.5.29.30') + +class NameConstraints(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('permittedSubtrees', GeneralSubtrees().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), + namedtype.OptionalNamedType('excludedSubtrees', GeneralSubtrees().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + ) + + +class DisplayText(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('visibleString', char.VisibleString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 200))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 200))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 200))) + ) + +class NoticeReference(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('organization', DisplayText()), + namedtype.NamedType('noticeNumbers', univ.SequenceOf(componentType=univ.Integer())) + ) + +class UserNotice(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('noticeRef', NoticeReference()), + namedtype.OptionalNamedType('explicitText', DisplayText()) + ) + +class CPSuri(char.IA5String): pass + +class PolicyQualifierId(univ.ObjectIdentifier): + subtypeSpec = univ.ObjectIdentifier.subtypeSpec + constraint.SingleValueConstraint(id_qt_cps, id_qt_unotice) + +class CertPolicyId(univ.ObjectIdentifier): pass + +class PolicyQualifierInfo(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('policyQualifierId', PolicyQualifierId()), + namedtype.NamedType('qualifier', univ.Any()) + ) + +id_ce_certificatePolicies = univ.ObjectIdentifier('2.5.29.32') + +class PolicyInformation(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('policyIdentifier', CertPolicyId()), + namedtype.OptionalNamedType('policyQualifiers', univ.SequenceOf(componentType=PolicyQualifierInfo()).subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) + ) + +class CertificatePolicies(univ.SequenceOf): + componentType = PolicyInformation() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +id_ce_policyMappings = univ.ObjectIdentifier('2.5.29.33') + +class PolicyMapping(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('issuerDomainPolicy', CertPolicyId()), + namedtype.NamedType('subjectDomainPolicy', CertPolicyId()) + ) + +class PolicyMappings(univ.SequenceOf): + componentType = PolicyMapping() + subtypeSpec = univ.SequenceOf.subtypeSpec + constraint.ValueSizeConstraint(1, MAX) + +id_ce_privateKeyUsagePeriod = univ.ObjectIdentifier('2.5.29.16') + +class PrivateKeyUsagePeriod(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('notBefore', useful.GeneralizedTime().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('notAfter', useful.GeneralizedTime().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))) + ) + +id_ce_keyUsage = univ.ObjectIdentifier('2.5.29.15') + +class KeyUsage(univ.BitString): + namedValues = namedval.NamedValues( + ('digitalSignature', 0), + ('nonRepudiation', 1), + ('keyEncipherment', 2), + ('dataEncipherment', 3), + ('keyAgreement', 4), + ('keyCertSign', 5), + ('cRLSign', 6), + ('encipherOnly', 7), + ('decipherOnly', 8) + ) + +id_ce = univ.ObjectIdentifier('2.5.29') + +id_ce_authorityKeyIdentifier = univ.ObjectIdentifier('2.5.29.35') + +class KeyIdentifier(univ.OctetString): pass + +id_ce_subjectKeyIdentifier = univ.ObjectIdentifier('2.5.29.14') + +class SubjectKeyIdentifier(KeyIdentifier): pass + +class AuthorityKeyIdentifier(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('keyIdentifier', KeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.OptionalNamedType('authorityCertIssuer', GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('authorityCertSerialNumber', CertificateSerialNumber().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))) + ) + +id_ce_certificateIssuer = univ.ObjectIdentifier('2.5.29.29') + +class CertificateIssuer(GeneralNames): pass + +id_ce_subjectAltName = univ.ObjectIdentifier('2.5.29.17') + +class SubjectAltName(GeneralNames): pass + +id_ce_issuerAltName = univ.ObjectIdentifier('2.5.29.18') + +class IssuerAltName(GeneralNames): pass diff --git a/tools/validatestore.py b/tools/validatestore.py new file mode 100755 index 0000000..74963e0 --- /dev/null +++ b/tools/validatestore.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, NORDUnet A/S. +# See LICENSE for licensing information. + +import argparse +import urllib2 +import urllib +import json +import base64 +import sys +import struct +import hashlib +import itertools +from certtools import * +try: + from precerttools import * +except ImportError: + pass +import os +import signal +import select +import zipfile +import traceback + +parser = argparse.ArgumentParser(description='') +parser.add_argument('--store', default=None, metavar="dir", help='Get certificates from directory dir') +parser.add_argument('--parallel', type=int, default=1, metavar="n", help="Number of parallel workers") +args = parser.parse_args() + +from multiprocessing import Pool + +certfilepath = args.store + +if certfilepath[-1] == "/": + certfiles = [certfilepath + filename for filename in sorted(os.listdir(certfilepath)) if os.path.isfile(certfilepath + filename)] +else: + certfiles = [certfilepath] + +def submitcert((certfile, cert)): + try: + certchain = get_certs_from_string(cert) + if len(certchain) == 0: + return True + precerts = get_precerts_from_string(cert) + hash = get_hash_from_certfile(cert) + timestamp = get_timestamp_from_certfile(cert) + assert len(precerts) == 0 or len(precerts) == 1 + precert = precerts[0] if precerts else None + if precert: + if ext_key_usage_precert_signing_cert in get_ext_key_usage(certchain[0]): + issuer_key_hash = get_cert_key_hash(certchain[1]) + issuer = certchain[1] + else: + issuer_key_hash = get_cert_key_hash(certchain[0]) + issuer = None + cleanedcert = cleanprecert(precert, issuer=issuer) + mtl = pack_mtl_precert(timestamp, cleanedcert, issuer_key_hash) + leaf_hash = get_leaf_hash(mtl) + else: + mtl = pack_mtl(timestamp, certchain[0]) + leaf_hash = get_leaf_hash(mtl) + if leaf_hash == hash: + return True + else: + print certfile, repr(leaf_hash), repr(hash), precert != None + return None + except Exception, e: + print certfile + traceback.print_exc() + raise e + +def get_all_certificates(certfiles): + for certfile in certfiles: + if certfile.endswith(".zip"): + zf = zipfile.ZipFile(certfile) + for name in zf.namelist(): + yield (name, zf.read(name)) + zf.close() + else: + yield (certfile, open(certfile).read()) + +p = Pool(args.parallel, lambda: signal.signal(signal.SIGINT, signal.SIG_IGN)) + +certs = get_all_certificates(certfiles) + +try: + for result in p.imap_unordered(submitcert, certs): + if result == None: + print "error" + p.terminate() + p.join() + sys.exit(1) +except KeyboardInterrupt: + p.terminate() + p.join() -- cgit v1.1 From 0a76e4d080a8349456d04434dcb2d4b381eb8ec4 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Wed, 18 Mar 2015 14:27:18 +0100 Subject: Added precert handling for SCT calculation --- tools/certtools.py | 20 +++++++++++++++----- tools/submitcert.py | 38 +++++++++++++++++++++++++++++--------- tools/verifysct.py | 17 ++++++++++++++--- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/tools/certtools.py b/tools/certtools.py index 1436863..cc423af 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -257,7 +257,7 @@ def create_sth_signature(tree_size, timestamp, root_hash, baseurl, key=None): return create_signature(baseurl, tree_head, key=key) -def check_sct_signature(baseurl, leafcert, sct): +def check_sct_signature(baseurl, signed_entry, sct, precert=False): publickey = base64.decodestring(publickeys[baseurl]) calculated_logid = hashlib.sha256(publickey).digest() received_logid = base64.decodestring(sct["id"]) @@ -271,9 +271,12 @@ def check_sct_signature(baseurl, leafcert, sct): version = struct.pack(">b", sct["sct_version"]) signature_type = struct.pack(">b", 0) timestamp = struct.pack(">Q", sct["timestamp"]) - entry_type = struct.pack(">H", 0) + if precert: + entry_type = struct.pack(">H", 1) + else: + entry_type = struct.pack(">H", 0) signed_struct = version + signature_type + timestamp + \ - entry_type + tls_array(leafcert, 3) + \ + entry_type + signed_entry + \ tls_array(base64.decodestring(sct["extensions"]), 2) check_signature(baseurl, signature, signed_struct) @@ -292,15 +295,22 @@ def pack_mtl(timestamp, leafcert): def pack_mtl_precert(timestamp, cleanedcert, issuer_key_hash): entry_type = struct.pack(">H", 1) extensions = "" - assert len(issuer_key_hash) == 32 timestamped_entry = struct.pack(">Q", timestamp) + entry_type + \ - issuer_key_hash + tls_array(cleanedcert, 3) + tls_array(extensions, 2) + pack_precert(cleanedcert, issuer_key_hash) + tls_array(extensions, 2) version = struct.pack(">b", 0) leaf_type = struct.pack(">b", 0) merkle_tree_leaf = version + leaf_type + timestamped_entry return merkle_tree_leaf +def pack_precert(cleanedcert, issuer_key_hash): + assert len(issuer_key_hash) == 32 + + return issuer_key_hash + tls_array(cleanedcert, 3) + +def pack_cert(cert): + return tls_array(cert, 3) + def unpack_mtl(merkle_tree_leaf): version = merkle_tree_leaf[0:1] leaf_type = merkle_tree_leaf[1:2] diff --git a/tools/submitcert.py b/tools/submitcert.py index 1c79544..2e8cc33 100755 --- a/tools/submitcert.py +++ b/tools/submitcert.py @@ -13,6 +13,11 @@ import struct import hashlib import itertools from certtools import * +from certtools import * +try: + from precerttools import * +except ImportError: + pass import os import signal import select @@ -51,8 +56,20 @@ def submitcert((certfile, cert)): try: if precert: + if ext_key_usage_precert_signing_cert in get_ext_key_usage(certchain[0]): + issuer_key_hash = get_cert_key_hash(certchain[1]) + issuer = certchain[1] + else: + issuer_key_hash = get_cert_key_hash(certchain[0]) + issuer = None + cleanedcert = cleanprecert(precert, issuer=issuer) + signed_entry = pack_precert(cleanedcert, issuer_key_hash) + leafcert = cleanedcert result = add_prechain(baseurl, {"chain":map(base64.b64encode, [precert] + certchain)}) else: + signed_entry = pack_cert(certchain[0]) + leafcert = certchain[0] + issuer_key_hash = None result = add_chain(baseurl, {"chain":map(base64.b64encode, certchain)}) except SystemExit: print "EXIT:", certfile @@ -67,7 +84,7 @@ def submitcert((certfile, cert)): try: if args.check_sct: - check_sct_signature(baseurl, certchain[0], result) + check_sct_signature(baseurl, signed_entry, result, precert=precert) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", certfile, e @@ -81,7 +98,7 @@ def submitcert((certfile, cert)): if lookup_in_log: - merkle_tree_leaf = pack_mtl(result["timestamp"], certchain[0]) + merkle_tree_leaf = pack_mtl(result["timestamp"], leafcert) leaf_hash = get_leaf_hash(merkle_tree_leaf) @@ -119,7 +136,7 @@ def submitcert((certfile, cert)): print "and submitted chain has length", len(submittedcertchain) timing_point(timing, "lookup") - return ((certchain[0], result), timing["deltatimes"]) + return ((leafcert, issuer_key_hash, result), timing["deltatimes"]) def get_ncerts(certfiles): n = 0 @@ -142,9 +159,12 @@ def get_all_certificates(certfiles): else: yield (certfile, open(certfile).read()) -def save_sct(sct, sth): +def save_sct(sct, sth, leafcert, issuer_key_hash): sctlog = open(args.sct_file, "a") - json.dump({"leafcert": base64.b64encode(leafcert), "sct": sct, "sth": sth}, sctlog) + sctentry = {"leafcert": base64.b64encode(leafcert), "sct": sct, "sth": sth} + if issuer_key_hash: + sctentry["issuer_key_hash"] = base64.b64encode(issuer_key_hash) + json.dump(sctentry, sctlog) sctlog.write("\n") sctlog.close() @@ -163,8 +183,8 @@ certs = get_all_certificates(certfiles) (result, timing) = submitcert(certs.next()) if result != None: nsubmitted += 1 - (leafcert, sct) = result - save_sct(sct, sth) + (leafcert, issuer_key_hash, sct) = result + save_sct(sct, sth, leafcert, issuer_key_hash) if args.pre_warm: select.select([], [], [], 3.0) @@ -181,8 +201,8 @@ try: sys.exit(1) if result != None: nsubmitted += 1 - (leafcert, sct) = result - save_sct(sct, sth) + (leafcert, issuer_key_hash, sct) = result + save_sct(sct, sth, leafcert, issuer_key_hash) deltatime = datetime.datetime.now() - starttime deltatime_f = deltatime.seconds + deltatime.microseconds / 1000000.0 rate = nsubmitted / deltatime_f diff --git a/tools/verifysct.py b/tools/verifysct.py index 699a0ad..27ab4c9 100755 --- a/tools/verifysct.py +++ b/tools/verifysct.py @@ -34,8 +34,16 @@ def verifysct(sctentry): timing = timing_point() leafcert = base64.b64decode(sctentry["leafcert"]) + if "issuer_key_hash" in sctentry: + issuer_key_hash = base64.b64decode(sctentry["issuer_key_hash"]) + else: + issuer_key_hash = None try: - check_sct_signature(baseurl, leafcert, sctentry["sct"]) + if issuer_key_hash: + signed_entry = pack_precert(leafcert, issuer_key_hash) + else: + signed_entry = pack_cert(leafcert) + check_sct_signature(baseurl, signed_entry, sctentry["sct"], precert=issuer_key_hash) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", e @@ -47,7 +55,10 @@ def verifysct(sctentry): print "ERROR: bad signature" return (None, None) - merkle_tree_leaf = pack_mtl(sctentry["sct"]["timestamp"], leafcert) + if issuer_key_hash: + merkle_tree_leaf = pack_mtl_precert(sctentry["sct"]["timestamp"], leafcert, issuer_key_hash) + else: + merkle_tree_leaf = pack_mtl(sctentry["sct"]["timestamp"], leafcert) leaf_hash = get_leaf_hash(merkle_tree_leaf) @@ -76,7 +87,7 @@ def verifysct(sctentry): p = Pool(args.parallel, lambda: signal.signal(signal.SIGINT, signal.SIG_IGN)) sctfile = open(args.sct_file) -scts = [json.loads(row) for row in sctfile] +scts = (json.loads(row) for row in sctfile) nverified = 0 lastprinted = 0 -- cgit v1.1 From 47e16395eeb7c1825d507a31df9bc89f9b24c10d Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 19 Mar 2015 10:57:11 +0100 Subject: Fix signature calculation in test --- tools/testcase1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testcase1.py b/tools/testcase1.py index 73613fb..dc62d17 100755 --- a/tools/testcase1.py +++ b/tools/testcase1.py @@ -70,7 +70,8 @@ def do_add_chain(chain): except ValueError, e: print_error("%s", e) try: - check_sct_signature(baseurl, chain[0], result) + signed_entry = pack_cert(chain[0]) + check_sct_signature(baseurl, signed_entry, result) except AssertionError, e: print_error("%s", e) except ecdsa.keys.BadSignatureError, e: -- cgit v1.1 From 26c5e8f248454f62329361a4504c78820e24b649 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 23 Mar 2015 09:32:55 +0100 Subject: Support multiple baseurls in test --- tools/testcase1.py | 113 +++++++++++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/tools/testcase1.py b/tools/testcase1.py index dc62d17..7b3229d 100755 --- a/tools/testcase1.py +++ b/tools/testcase1.py @@ -14,7 +14,7 @@ import hashlib import itertools from certtools import * -baseurl = "https://127.0.0.1:8080/" +baseurls = ["https://127.0.0.1:8080/"] certfiles = ["testcerts/cert1.txt", "testcerts/cert2.txt", "testcerts/cert3.txt", "testcerts/cert4.txt", "testcerts/cert5.txt"] @@ -51,7 +51,7 @@ def assert_equal(actual, expected, name, quiet=False, nodata=False): elif not quiet: print_success("%s was correct", name) -def print_and_check_tree_size(expected): +def print_and_check_tree_size(expected, baseurl): global failures sth = get_sth(baseurl) try: @@ -61,9 +61,9 @@ def print_and_check_tree_size(expected): except ecdsa.keys.BadSignatureError, e: print_error("bad STH signature") tree_size = sth["tree_size"] - assert_equal(tree_size, expected, "tree size") + assert_equal(tree_size, expected, "tree size", quiet=True) -def do_add_chain(chain): +def do_add_chain(chain, baseurl): global failures try: result = add_chain(baseurl, {"chain":map(base64.b64encode, chain)}) @@ -75,11 +75,12 @@ def do_add_chain(chain): except AssertionError, e: print_error("%s", e) except ecdsa.keys.BadSignatureError, e: + print e print_error("bad SCT signature") print_success("signature check succeeded") return result -def get_and_validate_proof(timestamp, chain, leaf_index, nentries): +def get_and_validate_proof(timestamp, chain, leaf_index, nentries, baseurl): cert = chain[0] merkle_tree_leaf = pack_mtl(timestamp, cert) leaf_hash = get_leaf_hash(merkle_tree_leaf) @@ -87,31 +88,31 @@ def get_and_validate_proof(timestamp, chain, leaf_index, nentries): proof = get_proof_by_hash(baseurl, leaf_hash, sth["tree_size"]) leaf_index = proof["leaf_index"] inclusion_proof = [base64.b64decode(e) for e in proof["audit_path"]] - assert_equal(leaf_index, leaf_index, "leaf_index") - assert_equal(len(inclusion_proof), nentries, "audit_path length") + assert_equal(leaf_index, leaf_index, "leaf_index", quiet=True) + assert_equal(len(inclusion_proof), nentries, "audit_path length", quiet=True) calc_root_hash = verify_inclusion_proof(inclusion_proof, leaf_index, sth["tree_size"], leaf_hash) root_hash = base64.b64decode(sth["sha256_root_hash"]) - assert_equal(root_hash, calc_root_hash, "verified root hash", nodata=True) - get_and_check_entry(timestamp, chain, leaf_index) + assert_equal(root_hash, calc_root_hash, "verified root hash", nodata=True, quiet=True) + get_and_check_entry(timestamp, chain, leaf_index, baseurl) -def get_and_validate_consistency_proof(sth1, sth2, size1, size2): +def get_and_validate_consistency_proof(sth1, sth2, size1, size2, baseurl): consistency_proof = [base64.decodestring(entry) for entry in get_consistency_proof(baseurl, size1, size2)] (old_treehead, new_treehead) = verify_consistency_proof(consistency_proof, size1, size2, sth1) #print repr(sth1), repr(old_treehead) #print repr(sth2), repr(new_treehead) - assert_equal(old_treehead, sth1, "sth1", nodata=True) - assert_equal(new_treehead, sth2, "sth2", nodata=True) + assert_equal(old_treehead, sth1, "sth1", nodata=True, quiet=True) + assert_equal(new_treehead, sth2, "sth2", nodata=True, quiet=True) -def get_and_check_entry(timestamp, chain, leaf_index): +def get_and_check_entry(timestamp, chain, leaf_index, baseurl): entries = get_entries(baseurl, leaf_index, leaf_index) assert_equal(len(entries), 1, "get_entries", quiet=True) fetched_entry = entries["entries"][0] merkle_tree_leaf = pack_mtl(timestamp, chain[0]) leaf_input = base64.decodestring(fetched_entry["leaf_input"]) - assert_equal(leaf_input, merkle_tree_leaf, "entry", nodata=True) + assert_equal(leaf_input, merkle_tree_leaf, "entry", nodata=True, quiet=True) extra_data = base64.decodestring(fetched_entry["extra_data"]) certchain = decode_certificate_chain(extra_data) @@ -119,7 +120,7 @@ def get_and_check_entry(timestamp, chain, leaf_index): for (submittedcert, fetchedcert, i) in zip(submittedcertchain, certchain, itertools.count(1)): - assert_equal(fetchedcert, submittedcert, "cert %d in chain" % (i,)) + assert_equal(fetchedcert, submittedcert, "cert %d in chain" % (i,), quiet=True) if len(certchain) == len(submittedcertchain) + 1: last_issuer = get_cert_info(submittedcertchain[-1])["issuer"] @@ -137,106 +138,116 @@ def get_and_check_entry(timestamp, chain, leaf_index): len(submittedcertchain)) def merge(): - return subprocess.call(["./merge.py", "--baseurl", "https://127.0.0.1:8080/", "--frontend", "https://127.0.0.1:8082/", "--storage", "https://127.0.0.1:8081/", "--mergedb", "../rel/mergedb", "--signing", "https://127.0.0.1:8088/", "--own-keyname", "merge-1", "--own-keyfile", "../rel/privatekeys/merge-1-private.pem"]) + return subprocess.call(["./merge.py", "--baseurl", "https://127.0.0.1:8080/", + "--frontend", "https://127.0.0.1:8082/", "--storage", "https://127.0.0.1:8081/", + "--mergedb", "../rel/mergedb", "--signing", "https://127.0.0.1:8088/", + "--own-keyname", "merge-1", "--own-keyfile", "../rel/privatekeys/merge-1-private.pem"]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -print_and_check_tree_size(0) +for baseurl in baseurls: + print_and_check_tree_size(0, baseurl) testgroup("cert1") -result1 = do_add_chain(cc1) +result1 = do_add_chain(cc1, baseurls[0]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) size_sth = {} -print_and_check_tree_size(1) -size_sth[1] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"]) +for baseurl in baseurls: + print_and_check_tree_size(1, baseurl) +size_sth[1] = base64.b64decode(get_sth(baseurls[0])["sha256_root_hash"]) -result2 = do_add_chain(cc1) +result2 = do_add_chain(cc1, baseurls[0]) assert_equal(result2["timestamp"], result1["timestamp"], "timestamp") mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -print_and_check_tree_size(1) -size1_v2_sth = base64.b64decode(get_sth(baseurl)["sha256_root_hash"]) +for baseurl in baseurls: + print_and_check_tree_size(1, baseurl) +size1_v2_sth = base64.b64decode(get_sth(baseurls[0])["sha256_root_hash"]) assert_equal(size_sth[1], size1_v2_sth, "sth", nodata=True) # TODO: add invalid cert and check that it generates an error # and that treesize still is 1 -get_and_validate_proof(result1["timestamp"], cc1, 0, 0) +get_and_validate_proof(result1["timestamp"], cc1, 0, 0, baseurls[0]) testgroup("cert2") -result3 = do_add_chain(cc2) +result3 = do_add_chain(cc2, baseurls[0]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -print_and_check_tree_size(2) -size_sth[2] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"]) +for baseurl in baseurls: + print_and_check_tree_size(2, baseurl) +size_sth[2] = base64.b64decode(get_sth(baseurls[0])["sha256_root_hash"]) -get_and_validate_proof(result1["timestamp"], cc1, 0, 1) -get_and_validate_proof(result3["timestamp"], cc2, 1, 1) +get_and_validate_proof(result1["timestamp"], cc1, 0, 1, baseurls[0]) +get_and_validate_proof(result3["timestamp"], cc2, 1, 1, baseurls[0]) testgroup("cert3") -result4 = do_add_chain(cc3) +result4 = do_add_chain(cc3, baseurls[0]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -print_and_check_tree_size(3) -size_sth[3] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"]) +for baseurl in baseurls: + print_and_check_tree_size(3, baseurl) +size_sth[3] = base64.b64decode(get_sth(baseurls[0])["sha256_root_hash"]) -get_and_validate_proof(result1["timestamp"], cc1, 0, 2) -get_and_validate_proof(result3["timestamp"], cc2, 1, 2) -get_and_validate_proof(result4["timestamp"], cc3, 2, 1) +get_and_validate_proof(result1["timestamp"], cc1, 0, 2, baseurls[0]) +get_and_validate_proof(result3["timestamp"], cc2, 1, 2, baseurls[0]) +get_and_validate_proof(result4["timestamp"], cc3, 2, 1, baseurls[0]) testgroup("cert4") -result5 = do_add_chain(cc4) +result5 = do_add_chain(cc4, baseurls[0]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -print_and_check_tree_size(4) -size_sth[4] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"]) +for baseurl in baseurls: + print_and_check_tree_size(4, baseurl) +size_sth[4] = base64.b64decode(get_sth(baseurls[0])["sha256_root_hash"]) -get_and_validate_proof(result1["timestamp"], cc1, 0, 2) -get_and_validate_proof(result3["timestamp"], cc2, 1, 2) -get_and_validate_proof(result4["timestamp"], cc3, 2, 2) -get_and_validate_proof(result5["timestamp"], cc4, 3, 2) +get_and_validate_proof(result1["timestamp"], cc1, 0, 2, baseurls[0]) +get_and_validate_proof(result3["timestamp"], cc2, 1, 2, baseurls[0]) +get_and_validate_proof(result4["timestamp"], cc3, 2, 2, baseurls[0]) +get_and_validate_proof(result5["timestamp"], cc4, 3, 2, baseurls[0]) testgroup("cert5") -result6 = do_add_chain(cc5) +result6 = do_add_chain(cc5, baseurls[0]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -print_and_check_tree_size(5) -size_sth[5] = base64.b64decode(get_sth(baseurl)["sha256_root_hash"]) +for baseurl in baseurls: + print_and_check_tree_size(5, baseurl) +size_sth[5] = base64.b64decode(get_sth(baseurls[0])["sha256_root_hash"]) -get_and_validate_proof(result1["timestamp"], cc1, 0, 3) -get_and_validate_proof(result3["timestamp"], cc2, 1, 3) -get_and_validate_proof(result4["timestamp"], cc3, 2, 3) -get_and_validate_proof(result5["timestamp"], cc4, 3, 3) -get_and_validate_proof(result6["timestamp"], cc5, 4, 1) +get_and_validate_proof(result1["timestamp"], cc1, 0, 3, baseurls[0]) +get_and_validate_proof(result3["timestamp"], cc2, 1, 3, baseurls[0]) +get_and_validate_proof(result4["timestamp"], cc3, 2, 3, baseurls[0]) +get_and_validate_proof(result5["timestamp"], cc4, 3, 3, baseurls[0]) +get_and_validate_proof(result6["timestamp"], cc5, 4, 1, baseurls[0]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) for first_size in range(1, 5): for second_size in range(first_size + 1, 6): - get_and_validate_consistency_proof(size_sth[first_size], size_sth[second_size], first_size, second_size) + get_and_validate_consistency_proof(size_sth[first_size], size_sth[second_size], first_size, second_size, baseurls[0]) print "-------" if failures: -- cgit v1.1 From 510ecc5702ea36395addc733789756ce8075517e Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 23 Mar 2015 09:36:24 +0100 Subject: Make timing printouts optional in merge.py --- tools/merge.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/merge.py b/tools/merge.py index 1b94581..dd8de07 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -26,6 +26,7 @@ parser.add_argument("--signing", metavar="url", help="Base URL for signing serve parser.add_argument("--own-keyname", metavar="keyname", help="The key name of the merge node", required=True) parser.add_argument("--own-keyfile", metavar="keyfile", help="The file containing the private key of the merge node", required=True) parser.add_argument("--nomerge", action='store_true', help="Don't actually do merge") +parser.add_argument("--timing", action='store_true', help="Print timing information") args = parser.parse_args() ctbaseurl = args.baseurl @@ -244,7 +245,8 @@ check_sth_signature(ctbaseurl, sth) timing_point(timing, "build sth") -print timing["deltatimes"] +if args.timing: + print timing["deltatimes"] print "root hash", base64.b16encode(root_hash) @@ -280,4 +282,5 @@ for frontendnode in frontendnodes: print "send sth:", sendsthresult sys.exit(1) timing_point(timing, "send sth") - print timing["deltatimes"] + if args.timing: + print timing["deltatimes"] -- cgit v1.1 From d645194006bf3c81372073af9784f7d993096444 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 23 Mar 2015 16:12:13 +0100 Subject: Generate config from master config. Verify responses in merge.py. --- .gitignore | 1 + Makefile | 76 +++++----- test/catlfish-test-local-1.cfg | 17 +++ test/catlfish-test-local-merge.cfg | 8 + test/catlfish-test-local-signing.cfg | 12 ++ test/catlfish-test.cfg | 19 +++ test/config/frontend-1.config | 61 -------- test/config/signing-1.config | 35 ----- test/config/storage-1.config | 39 ----- tools/certtools.py | 43 +++++- tools/compileconfig.py | 283 +++++++++++++++++++++++++++++++++++ tools/merge.py | 92 ++++++------ tools/testcase1.py | 12 +- 13 files changed, 467 insertions(+), 231 deletions(-) create mode 100644 test/catlfish-test-local-1.cfg create mode 100644 test/catlfish-test-local-merge.cfg create mode 100644 test/catlfish-test-local-signing.cfg create mode 100644 test/catlfish-test.cfg delete mode 100644 test/config/frontend-1.config delete mode 100644 test/config/signing-1.config delete mode 100644 test/config/storage-1.config create mode 100755 tools/compileconfig.py diff --git a/.gitignore b/.gitignore index 17278c0..c139839 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.beam +test/test.mk diff --git a/Makefile b/Makefile index e1a81fb..8223a4e 100644 --- a/Makefile +++ b/Makefile @@ -16,38 +16,34 @@ release: all cp storage_node.config rel cp storage_node_httpd.conf rel mkdir rel/catlfish - mkdir rel/db - mkdir rel/mergedb - mkdir rel/mergedb/chains - touch rel/mergedb/logorder - printf "0" > rel/db/treesize cp -r webroot rel/catlfish test -d rel/catlfish/webroot/log || mkdir rel/catlfish/webroot/log +-include test/test.mk + tests-prepare: + rm -r rel/mergedb || true + mkdir rel/mergedb + mkdir rel/mergedb/chains + touch rel/mergedb/logorder rm -r rel/known_roots || true mkdir rel/known_roots cp tools/testcerts/roots/* rel/known_roots - - mkdir -p test/nodes/frontend-1/log - mkdir -p test/nodes/storage-1/log - mkdir -p test/nodes/storage-2/log - mkdir -p test/nodes/signing-1/log - cp test/config/frontend-1.config rel - cp test/config/storage-1.config rel - cp test/config/signing-1.config rel cp -r test/config/privatekeys rel cp -r test/config/publickeys rel rm -r rel/tests || true - mkdir -p rel/tests/machine/machine-1/db - printf "0" > rel/tests/machine/machine-1/db/treesize - mkdir -p rel/tests/machine/machine-2/db - printf "0" > rel/tests/machine/machine-2/db/treesize - touch rel/tests/machine/machine-1/db/index - touch rel/tests/machine/machine-1/db/newentries - -NODES=frontend-1 storage-1 signing-1 -TESTURLS=https://127.0.0.1:8080/ https://127.0.0.1:8081/ https://127.0.0.1:8082/ https://127.0.0.1:8088/ + @for machine in $(MACHINES); do \ + tools/compileconfig.py --config=test/catlfish-test.cfg --localconfig test/catlfish-test-local-$$machine.cfg ; \ + mkdir -p rel/tests/machine/machine-$$machine/db ; \ + printf "0" > rel/tests/machine/machine-$$machine/db/treesize ; \ + touch rel/tests/machine/machine-$$machine/db/index ; \ + touch rel/tests/machine/machine-$$machine/db/newentries ; \ + done + tools/compileconfig.py --config=test/catlfish-test.cfg --localconfig test/catlfish-test-local-signing.cfg + @for node in $(NODES); do \ + mkdir -p test/nodes/$$node/log ; \ + cp test/config/$$node.config rel ; \ + done tests-start: @for node in $(NODES); do \ @@ -57,45 +53,49 @@ tests-start: echo "waiting for system to start" ; \ sleep 0.5 ; \ allstarted=1 ; \ + notstarted= ; \ for testurl in $(TESTURLS); do \ - if curl -s -k $$testurl > /dev/null ; then : ; else allstarted=0 ; fi ; \ + if curl -s -k https://$$testurl > /dev/null ; then : ; else allstarted=0 ; notstarted="$$testurl $$notstarted" ; fi ; \ : ; \ done ; \ - if [ $$allstarted -eq 1 ]; then break ; fi ; \ + if [ $$allstarted -eq 1 ]; then break ; \ + elif [ $$i -eq 10 ]; then echo Not started: $$notstarted ; fi ; \ done tests-run: - @(cd tools ; python testcase1.py ) || echo "Tests failed" - @(cd tools ; python fetchallcerts.py https://127.0.0.1:8080/) || echo "Verification failed" - @(cd tools ; python submitcert.py --store testcerts/cert1.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python submitcert.py --store testcerts/cert2.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python submitcert.py --store testcerts/cert3.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python submitcert.py --store testcerts/cert4.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python submitcert.py --store testcerts/cert5.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python submitcert.py --store testcerts/pre1.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python submitcert.py --store testcerts/pre2.txt --check-sct --sct-file=../rel/submittedcerts https://127.0.0.1:8080/) || echo "Submission failed" - @(cd tools ; python merge.py --baseurl https://127.0.0.1:8080/ --frontend https://127.0.0.1:8082/ --storage https://127.0.0.1:8081/ --mergedb ../rel/mergedb --signing https://127.0.0.1:8088/ --own-keyname merge-1 --own-keyfile ../rel/privatekeys/merge-1-private.pem) || echo "Merge failed" + @(cd rel && python ../tools/testcase1.py ) || (echo "Tests failed" ; false) + @(cd rel && python ../tools/fetchallcerts.py $(BASEURL)) || (echo "Verification failed" ; false) + @(cd rel && rm -f submittedcerts) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert1.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert2.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert3.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert4.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert5.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre1.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre2.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) + @(cd rel && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg || (echo "Merge failed" ; false) tests-run2: - @(cd tools ; python verifysct.py --sct-file=../rel/submittedcerts --parallel 1 https://127.0.0.1:8080/) || echo "Verification of SCT:s failed" + @(cd rel ; python ../tools/verifysct.py --sct-file=submittedcerts --parallel 1 $(BASEURL)) || echo "Verification of SCT:s failed" tests-stop: @for node in $(NODES); do \ - ./tools/halt.py ./rel/bin/to_erl test/nodes/$$node/ ; \ + ./tools/halt.py to_erl test/nodes/$$node/ ; \ done tests-wait: sleep 5 tests: + tools/compileconfig.py --config=test/catlfish-test.cfg --testmakefile=test/test.mk --machines 1 @make tests-prepare @make tests-start - @make tests-run + @make tests-run || (make tests-stop ; false) @make tests-wait @make tests-stop @make tests-wait @make tests-start - @make tests-run2 + @make tests-run2 || (make tests-stop ; false) @make tests-wait @make tests-stop diff --git a/test/catlfish-test-local-1.cfg b/test/catlfish-test-local-1.cfg new file mode 100644 index 0000000..5e9a593 --- /dev/null +++ b/test/catlfish-test-local-1.cfg @@ -0,0 +1,17 @@ +localnodes: + - frontend-1 + - storage-1 + +paths: + configdir: test/config/ + knownroots: known_roots + https_certfile: catlfish/webroot/certs/webcert.pem + https_keyfile: catlfish/webroot/keys/webkey.pem + https_cacertfile: catlfish/webroot/certs/webcert.pem + db: tests/machine/machine-1/db/ + publickeys: publickeys + logpublickey: test/eckey-public.pem + privatekeys: privatekeys + +#options: +# - sctcaching diff --git a/test/catlfish-test-local-merge.cfg b/test/catlfish-test-local-merge.cfg new file mode 100644 index 0000000..b7f5009 --- /dev/null +++ b/test/catlfish-test-local-merge.cfg @@ -0,0 +1,8 @@ +nodename: merge-1 + +paths: + mergedb: ../rel/mergedb + https_cacertfile: catlfish/webroot/certs/webcert.pem + publickeys: publickeys + logpublickey: test/eckey-public.pem + privatekeys: privatekeys diff --git a/test/catlfish-test-local-signing.cfg b/test/catlfish-test-local-signing.cfg new file mode 100644 index 0000000..047c02b --- /dev/null +++ b/test/catlfish-test-local-signing.cfg @@ -0,0 +1,12 @@ +localnodes: + - signing-1 + +paths: + configdir: test/config/ + https_certfile: catlfish/webroot/certs/webcert.pem + https_keyfile: catlfish/webroot/keys/webkey.pem + https_cacertfile: catlfish/webroot/certs/webcert.pem + publickeys: publickeys + logpublickey: test/eckey-public.pem + logprivatekey: test/eckey.pem + privatekeys: privatekeys diff --git a/test/catlfish-test.cfg b/test/catlfish-test.cfg new file mode 100644 index 0000000..7a4bb18 --- /dev/null +++ b/test/catlfish-test.cfg @@ -0,0 +1,19 @@ +baseurl: https://127.0.0.1:8080/ + +frontendnodes: + - name: frontend-1 + publicaddress: 127.0.0.1:8080 + address: 127.0.0.1:8082 + +storagenodes: + - name: storage-1 + address: 127.0.0.1:8081 + +signingnodes: + - name: signing-1 + address: 127.0.0.1:8088 + +mergenodes: + - name: merge-1 + +storage-quorum-size: 1 diff --git a/test/config/frontend-1.config b/test/config/frontend-1.config deleted file mode 100644 index 8215027..0000000 --- a/test/config/frontend-1.config +++ /dev/null @@ -1,61 +0,0 @@ -%% catlfish configuration file (-*- erlang -*-) - -[{sasl, - [{sasl_error_logger, false}, - {errlog_type, error}, - {error_logger_mf_dir, "log"}, - {error_logger_mf_maxbytes, 10485760}, % 10 MB - {error_logger_mf_maxfiles, 10}]}, - {catlfish, - [{known_roots_path, "known_roots"}, - {sctcache_root_path, "tests/machine/machine-1/db/sctcache/"}, - {https_servers, - [{external_https_api, "127.0.0.1", 8080, v1}, - {frontend_https_api, "127.0.0.1", 8082, frontend} - ]}, - {https_certfile, "catlfish/webroot/certs/webcert.pem"}, - {https_keyfile, "catlfish/webroot/keys/webkey.pem"}, - {https_cacertfile, "catlfish/webroot/certs/webcert.pem"} - ]}, - {lager, - [{handlers, - [{lager_console_backend, info}, - {lager_file_backend, [{file, "frontend-1-error.log"}, {level, error}]}, - {lager_file_backend, [{file, "frontend-1-debug.log"}, {level, debug}]}, - {lager_file_backend, [{file, "frontend-1-console.log"}, {level, info}]} - ]} - ]}, - {plop, - [{entry_root_path, "tests/machine/machine-1/db/certentries/"}, - {index_path, "tests/machine/machine-1/db/index"}, - {entryhash_root_path, "tests/machine/machine-1/db/entryhash/"}, - {treesize_path, "tests/machine/machine-1/db/treesize"}, - {indexforhash_root_path, "tests/machine/machine-1/db/certindex/"}, - {sth_path, "tests/machine/machine-1/db/sth"}, - {storage_nodes, ["https://127.0.0.1:8081/ct/storage/"]}, - {storage_nodes_quorum, 1}, - {publickey_path, "publickeys"}, - {services, [ht]}, - {log_public_key, "test/eckey-public.pem"}, - {own_key, {"frontend-1", "privatekeys/frontend-1-private.pem"}}, - {signing_node, "https://127.0.0.1:8088/ct/signing/"}, - {allowed_clients, [{"/ct/frontend/sendentry", ["merge-1"]}, - {"/ct/frontend/sendlog", ["merge-1"]}, - {"/ct/frontend/sendsth", ["merge-1"]}, - {"/ct/frontend/currentposition", ["merge-1"]}, - {"/ct/frontend/missingentries", ["merge-1"]}, - {"/ct/v1/add-chain", noauth}, - {"/ct/v1/add-pre-chain", noauth}, - {"/ct/v1/get-sth", noauth}, - {"/ct/v1/get-sth-consistency", noauth}, - {"/ct/v1/get-proof-by-hash", noauth}, - {"/ct/v1/get-entries", noauth}, - {"/ct/v1/get-entry-and-proof", noauth}, - {"/ct/v1/get-roots", noauth} - ]}, - {allowed_servers, [{"/ct/storage/sendentry", ["storage-1"]}, - {"/ct/storage/entrycommitted", ["storage-1"]}, - {"/ct/signing/sct", ["signing-1"]}, - {"/ct/signing/sth", ["signing-1"]} - ]} - ]}]. diff --git a/test/config/signing-1.config b/test/config/signing-1.config deleted file mode 100644 index a11bdeb..0000000 --- a/test/config/signing-1.config +++ /dev/null @@ -1,35 +0,0 @@ -%% catlfish configuration file (-*- erlang -*-) - -[{sasl, - [{sasl_error_logger, false}, - {errlog_type, error}, - {error_logger_mf_dir, "log"}, - {error_logger_mf_maxbytes, 10485760}, % 10 MB - {error_logger_mf_maxfiles, 10}]}, - {catlfish, - [{known_roots_path, "known_roots"}, - {https_servers, - [{signing_https_api, "127.0.0.1", 8088, signing} - ]}, - {https_certfile, "catlfish/webroot/certs/webcert.pem"}, - {https_keyfile, "catlfish/webroot/keys/webkey.pem"}, - {https_cacertfile, "catlfish/webroot/certs/webcert.pem"} - ]}, - {lager, - [{handlers, - [{lager_console_backend, info}, - {lager_file_backend, [{file, "signing-1-error.log"}, {level, error}]}, - {lager_file_backend, [{file, "signing-1-debug.log"}, {level, debug}]}, - {lager_file_backend, [{file, "signing-1-console.log"}, {level, info}]} - ]} - ]}, - {plop, - [{publickey_path, "publickeys"}, - {services, [sign]}, - {log_private_key, "test/eckey.pem"}, - {log_public_key, "test/eckey-public.pem"}, - {own_key, {"signing-1", "privatekeys/signing-1-private.pem"}}, - {allowed_clients, [{"/ct/signing/sct", ["frontend-1"]}, - {"/ct/signing/sth", ["merge-1"]} - ]} - ]}]. diff --git a/test/config/storage-1.config b/test/config/storage-1.config deleted file mode 100644 index 005a8ad..0000000 --- a/test/config/storage-1.config +++ /dev/null @@ -1,39 +0,0 @@ -%% catlfish configuration file (-*- erlang -*-) - -[{sasl, - [{sasl_error_logger, false}, - {errlog_type, error}, - {error_logger_mf_dir, "log"}, - {error_logger_mf_maxbytes, 10485760}, % 10 MB - {error_logger_mf_maxfiles, 10}]}, - {catlfish, - [{https_servers, - [{storage_https_api, "127.0.0.1", 8081, storage} - ]}, - {https_certfile, "catlfish/webroot/certs/webcert.pem"}, - {https_keyfile, "catlfish/webroot/keys/webkey.pem"}, - {https_cacertfile, "catlfish/webroot/certs/webcert.pem"} - ]}, - {lager, - [{handlers, - [{lager_console_backend, info}, - {lager_file_backend, [{file, "storage-1-error.log"}, {level, error}]}, - {lager_file_backend, [{file, "storage-1-debug.log"}, {level, debug}]}, - {lager_file_backend, [{file, "storage-1-console.log"}, {level, info}]} - ]} - ]}, - {plop, - [{entry_root_path, "tests/machine/machine-1/db/certentries/"}, - {index_path, "tests/machine/machine-1/db/index"}, - {newentries_path, "tests/machine/machine-1/db/newentries"}, - {entryhash_root_path, "tests/machine/machine-1/db/entryhash/"}, - {treesize_path, "tests/machine/machine-1/db/treesize"}, - {indexforhash_root_path, "tests/machine/machine-1/db/certindex/"}, - {publickey_path, "publickeys"}, - {own_key, {"storage-1", "privatekeys/storage-1-private.pem"}}, - {allowed_clients, [{"/ct/storage/sendentry", ["frontend-1"]}, - {"/ct/storage/entrycommitted", ["frontend-1"]}, - {"/ct/storage/fetchnewentries", ["merge-1"]}, - {"/ct/storage/getentry", noauth} - ]} -]}]. diff --git a/tools/certtools.py b/tools/certtools.py index cc423af..939d9f1 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -70,6 +70,11 @@ def get_eckey_from_file(keyfile): assert len(keys) == 1 return keys[0] +def get_public_key_from_file(keyfile): + keys = get_pemlike(keyfile, "PUBLIC KEY") + assert len(keys) == 1 + return keys[0] + def get_root_cert(issuer): accepted_certs = \ json.loads(open("googlelog-accepted-certs.txt").read())["certificates"] @@ -84,7 +89,7 @@ def get_root_cert(issuer): return root_cert def get_sth(baseurl): - result = urllib2.urlopen(baseurl + "ct/v1/get-sth", context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() + result = urllib2.urlopen(baseurl + "ct/v1/get-sth").read() return json.loads(result) def get_proof_by_hash(baseurl, hash, tree_size): @@ -92,7 +97,7 @@ def get_proof_by_hash(baseurl, hash, tree_size): params = urllib.urlencode({"hash":base64.b64encode(hash), "tree_size":tree_size}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() + urllib2.urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -103,7 +108,7 @@ def get_consistency_proof(baseurl, tree_size1, tree_size2): params = urllib.urlencode({"first":tree_size1, "second":tree_size2}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-sth-consistency?" + params, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() + urllib2.urlopen(baseurl + "ct/v1/get-sth-consistency?" + params).read() return json.loads(result)["consistency"] except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -126,7 +131,7 @@ def unpack_tls_array(packed_data, length_len): def add_chain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission), context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() + result = urllib2.urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission)).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR", e.code,":", e.read() @@ -162,7 +167,7 @@ def add_prechain(baseurl, submission): def get_entries(baseurl, start, end): try: params = urllib.urlencode({"start":start, "end":end}) - result = urllib2.urlopen(baseurl + "ct/v1/get-entries?" + params, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() + result = urllib2.urlopen(baseurl + "ct/v1/get-entries?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -205,7 +210,26 @@ def check_signature(baseurl, signature, data): vk.verify(unpacked_signature, data, hashfunc=hashlib.sha256, sigdecode=ecdsa.util.sigdecode_der) -def http_request(url, data=None, key=None): +def parse_auth_header(authheader): + splittedheader = authheader.split(";") + (signature, rawoptions) = (splittedheader[0], splittedheader[1:]) + options = dict([(e.partition("=")[0], e.partition("=")[2]) for e in rawoptions]) + return (base64.b64decode(signature), options) + +def check_auth_header(authheader, expected_key, publickeydir, data, path): + if expected_key == None: + return True + (signature, options) = parse_auth_header(authheader) + keyname = options.get("key") + if keyname != expected_key: + raise Exception("Response claimed to come from %s, expected %s" % (keyname, expected_key)) + publickey = get_public_key_from_file(publickeydir + "/" + keyname + ".pem") + vk = ecdsa.VerifyingKey.from_der(publickey) + vk.verify(signature, "%s\0%s\0%s" % ("REPLY", path, data), hashfunc=hashlib.sha256, + sigdecode=ecdsa.util.sigdecode_der) + return True + +def http_request(url, data=None, key=None, verifynode=None, publickeydir="."): req = urllib2.Request(url, data) (keyname, keyfile) = key privatekey = get_eckey_from_file(keyfile) @@ -219,8 +243,11 @@ def http_request(url, data=None, key=None): signature = sk.sign("%s\0%s\0%s" % (method, parsed_url.path, data), hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der) req.add_header('X-Catlfish-Auth', base64.b64encode(signature) + ";key=" + keyname) - result = urllib2.urlopen(req, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)).read() - return result + result = urllib2.urlopen(req) + authheader = result.info().get('X-Catlfish-Auth') + data = result.read() + check_auth_header(authheader, verifynode, publickeydir, data, parsed_url.path) + return data def get_signature(baseurl, data, key=None): try: diff --git a/tools/compileconfig.py b/tools/compileconfig.py new file mode 100755 index 0000000..30424c5 --- /dev/null +++ b/tools/compileconfig.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python + +# Copyright (c) 2014, NORDUnet A/S. +# See LICENSE for licensing information. + +import argparse +import sys +import yaml +import re + +class Symbol(str): + pass + +clean_string = re.compile(r'^[-.:_/A-Za-z0-9 ]*$') +clean_symbol = re.compile(r'^[_A-Za-z0-9]*$') + +def quote_erlang_string(s): + if clean_string.match(s): + return '"' + s + '"' + else: + return "[" + ",".join([str(ord(c)) for c in s]) + "]" + +def quote_erlang_symbol(s): + if clean_symbol.match(s): + return s + elif clean_string.match(s): + return "'" + s + "'" + else: + print >>sys.stderr, "Cannot generate symbol", s + sys.exit(1) + +def gen_erlang(term, level=1): + indent = " " * level + separator = ",\n" + indent + if isinstance(term, Symbol): + return quote_erlang_symbol(term) + elif isinstance(term, basestring): + return quote_erlang_string(term) + elif isinstance(term, int): + return str(term) + elif isinstance(term, tuple): + tuplecontents = [gen_erlang(e, level=level+1) for e in term] + if "\n" not in "".join(tuplecontents): + separator = ", " + return "{" + separator.join(tuplecontents) + "}" + elif isinstance(term, list): + listcontents = [gen_erlang(e, level=level+1) for e in term] + return "[" + separator.join(listcontents) + "]" + else: + print "unknown type", type(term) + sys.exit(1) + +saslconfig = [(Symbol("sasl_error_logger"), Symbol("false")), + (Symbol("errlog_type"), Symbol("error")), + (Symbol("error_logger_mf_dir"), "log"), + (Symbol("error_logger_mf_maxbytes"), 10485760), + (Symbol("error_logger_mf_maxfiles"), 10), + ] + +def parse_address(address): + parsed_address = address.split(":") + if len(parsed_address) != 2: + print >>sys.stderr, "Invalid address format", address + sys.exit(1) + return (parsed_address[0], int(parsed_address[1])) + +def get_node_config(nodename, config): + nodetype = None + nodeconfig = None + for t in ["frontendnodes", "storagenodes", "signingnodes"]: + for node in config[t]: + if node["name"] == nodename: + nodetype = t + nodeconfig = node + if nodeconfig == None: + print >>sys.stderr, "Cannot find config for node", nodename + sys.exit(1) + return (nodetype, nodeconfig) + +def gen_https_servers(nodetype, nodeconfig): + if nodetype == "frontendnodes": + (publichost, publicport) = parse_address(nodeconfig["publicaddress"]) + (host, port) = parse_address(nodeconfig["address"]) + return [(Symbol("external_https_api"), publichost, publicport, Symbol("v1")), + (Symbol("frontend_https_api"), host, port, Symbol("frontend"))] + elif nodetype == "storagenodes": + (host, port) = parse_address(nodeconfig["address"]) + return [(Symbol("storage_https_api"), host, port, Symbol("storage"))] + elif nodetype == "signingnodes": + (host, port) = parse_address(nodeconfig["address"]) + return [(Symbol("signing_https_api"), host, port, Symbol("signing"))] + +def allowed_clients_frontend(mergenodenames): + return [ + ("/ct/frontend/sendentry", mergenodenames), + ("/ct/frontend/sendlog", mergenodenames), + ("/ct/frontend/sendsth", mergenodenames), + ("/ct/frontend/currentposition", mergenodenames), + ("/ct/frontend/missingentries", mergenodenames), + ] + +def allowed_clients_public(): + noauth = Symbol("noauth") + return [ + ("/ct/v1/add-chain", noauth), + ("/ct/v1/add-pre-chain", noauth), + ("/ct/v1/get-sth", noauth), + ("/ct/v1/get-sth-consistency", noauth), + ("/ct/v1/get-proof-by-hash", noauth), + ("/ct/v1/get-entries", noauth), + ("/ct/v1/get-entry-and-proof", noauth), + ("/ct/v1/get-roots", noauth), + ] + +def allowed_clients_signing(frontendnodenames, mergenodenames): + return [ + ("/ct/signing/sct", frontendnodenames), + ("/ct/signing/sth", mergenodenames), + ] + +def allowed_clients_storage(frontendnodenames, mergenodenames): + return [ + ("/ct/storage/sendentry", frontendnodenames), + ("/ct/storage/entrycommitted", frontendnodenames), + ("/ct/storage/fetchnewentries", mergenodenames), + ("/ct/storage/getentry", mergenodenames), + ] + +def allowed_servers_frontend(signingnodenames, storagenodenames): + return [ + ("/ct/storage/sendentry", storagenodenames), + ("/ct/storage/entrycommitted", storagenodenames), + ("/ct/signing/sct", signingnodenames), + ] + +def gen_config(nodename, config, localconfig): + print "generating config for", nodename + paths = localconfig["paths"] + options = localconfig.get("options", []) + + configfile = open(paths["configdir"] + nodename + ".config", "w") + print >>configfile, "%% catlfish configuration file (-*- erlang -*-)" + + (nodetype, nodeconfig) = get_node_config(nodename, config) + https_servers = gen_https_servers(nodetype, nodeconfig) + + catlfishconfig = [] + plopconfig = [] + + if nodetype == "frontendnodes": + catlfishconfig.append((Symbol("known_roots_path"), localconfig["paths"]["knownroots"])) + if "sctcaching" in options: + catlfishconfig.append((Symbol("sctcache_root_path"), paths["db"] + "sctcache/")) + + catlfishconfig += [ + (Symbol("https_servers"), https_servers), + (Symbol("https_certfile"), paths["https_certfile"]), + (Symbol("https_keyfile"), paths["https_keyfile"]), + (Symbol("https_cacertfile"), paths["https_cacertfile"]), + ] + + lagerconfig = [ + (Symbol("handlers"), [ + (Symbol("lager_console_backend"), Symbol("info")), + (Symbol("lager_file_backend"), [(Symbol("file"), nodename + "-error.log"), (Symbol("level"), Symbol("error"))]), + (Symbol("lager_file_backend"), [(Symbol("file"), nodename + "-debug.log"), (Symbol("level"), Symbol("debug"))]), + (Symbol("lager_file_backend"), [(Symbol("file"), nodename + "-console.log"), (Symbol("level"), Symbol("info"))]), + ]) + ] + + if nodetype in ("frontendnodes", "storagenodes"): + plopconfig += [ + (Symbol("entry_root_path"), paths["db"] + "certentries/"), + ] + if nodetype == "frontendnodes": + plopconfig += [ + (Symbol("index_path"), paths["db"] + "index"), + ] + elif nodetype == "storagenodes": + plopconfig += [ + (Symbol("newentries_path"), paths["db"] + "newentries"), + ] + if nodetype in ("frontendnodes", "storagenodes"): + plopconfig += [ + (Symbol("entryhash_root_path"), paths["db"] + "entryhash/"), + (Symbol("indexforhash_root_path"), paths["db"] + "certindex/"), + ] + if nodetype == "frontendnodes": + plopconfig += [ + (Symbol("sth_path"), paths["db"] + "sth"), + ] + + signingnode = config["signingnodes"][0] + mergenodenames = [node["name"] for node in config["mergenodes"]] + storagenodeaddresses = ["https://%s/ct/storage/" % node["address"] for node in config["storagenodes"]] + frontendnodenames = [node["name"] for node in config["frontendnodes"]] + + allowed_clients = [] + allowed_servers = [] + + if nodetype == "frontendnodes": + storagenodenames = [node["name"] for node in config["storagenodes"]] + plopconfig.append((Symbol("storage_nodes"), storagenodeaddresses)) + plopconfig.append((Symbol("storage_nodes_quorum"), config["storage-quorum-size"])) + services = [Symbol("ht")] + allowed_clients += allowed_clients_frontend(mergenodenames) + allowed_clients += allowed_clients_public() + allowed_servers += allowed_servers_frontend([signingnode["name"]], storagenodenames) + elif nodetype == "storagenodes": + allowed_clients += allowed_clients_storage(frontendnodenames, mergenodenames) + services = [] + elif nodetype == "signingnodes": + allowed_clients += allowed_clients_signing(frontendnodenames, mergenodenames) + services = [Symbol("sign")] + + plopconfig += [ + (Symbol("publickey_path"), paths["publickeys"]), + (Symbol("services"), services), + ] + if nodetype == "signingnodes": + plopconfig.append((Symbol("log_private_key"), paths["logprivatekey"])) + plopconfig += [ + (Symbol("log_public_key"), paths["logpublickey"]), + (Symbol("own_key"), (nodename, "%s/%s-private.pem" % (paths["privatekeys"], nodename))), + ] + if nodetype == "frontendnodes": + plopconfig.append((Symbol("signing_node"), "https://%s/ct/signing/" % signingnode["address"])) + plopconfig += [ + (Symbol("allowed_clients"), allowed_clients), + (Symbol("allowed_servers"), allowed_servers), + ] + + erlangconfig = [ + (Symbol("sasl"), saslconfig), + (Symbol("catlfish"), catlfishconfig), + (Symbol("lager"), lagerconfig), + (Symbol("plop"), plopconfig), + ] + + print >>configfile, gen_erlang(erlangconfig) + ".\n" + + configfile.close() + + +def gen_testmakefile(config, testmakefile, machines): + configfile = open(testmakefile, "w") + frontendnodenames = [node["name"] for node in config["frontendnodes"]] + storagenodenames = [node["name"] for node in config["storagenodes"]] + signingnodename = [node["name"] for node in config["signingnodes"]] + + frontendnodeaddresses = [node["publicaddress"] for node in config["frontendnodes"]] + storagenodeaddresses = [node["address"] for node in config["storagenodes"]] + signingnodeaddresses = [node["address"] for node in config["signingnodes"]] + + print >>configfile, "NODES=" + " ".join(frontendnodenames+storagenodenames+signingnodename) + print >>configfile, "MACHINES=" + " ".join([str(e) for e in range(1, machines+1)]) + print >>configfile, "TESTURLS=" + " ".join(frontendnodeaddresses+storagenodeaddresses+signingnodeaddresses) + print >>configfile, "BASEURL=" + config["baseurl"] + + configfile.close() + + +def main(): + parser = argparse.ArgumentParser(description="") + parser.add_argument('--config', help="System configuration", required=True) + parser.add_argument('--localconfig', help="Local configuration") + parser.add_argument("--testmakefile", metavar="file", help="Generate makefile variables for test") + parser.add_argument("--machines", type=int, metavar="n", help="Number of machines") + args = parser.parse_args() + + config = yaml.load(open(args.config)) + if args.testmakefile and args.machines: + gen_testmakefile(config, args.testmakefile, args.machines) + elif args.localconfig: + localconfig = yaml.load(open(args.localconfig)) + localnodes = localconfig["localnodes"] + for localnode in localnodes: + gen_config(localnode, config, localconfig) + else: + print >>sys.stderr, "Nothing to do" + sys.exit(1) + +main() diff --git a/tools/merge.py b/tools/merge.py index dd8de07..75e72ae 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -15,28 +15,31 @@ import ecdsa import hashlib import urlparse import os +import yaml from certtools import build_merkle_tree, create_sth_signature, check_sth_signature, get_eckey_from_file, timing_point, http_request parser = argparse.ArgumentParser(description="") -parser.add_argument("--baseurl", metavar="url", help="Base URL for CT server", required=True) -parser.add_argument("--frontend", action="append", metavar="url", help="Base URL for frontend server", required=True) -parser.add_argument("--storage", action="append", metavar="url", help="Base URL for storage server", required=True) -parser.add_argument("--mergedb", metavar="dir", help="Merge database directory", required=True) -parser.add_argument("--signing", metavar="url", help="Base URL for signing server", required=True) -parser.add_argument("--own-keyname", metavar="keyname", help="The key name of the merge node", required=True) -parser.add_argument("--own-keyfile", metavar="keyfile", help="The file containing the private key of the merge node", required=True) +parser.add_argument('--config', help="System configuration", required=True) +parser.add_argument('--localconfig', help="Local configuration", required=True) parser.add_argument("--nomerge", action='store_true', help="Don't actually do merge") parser.add_argument("--timing", action='store_true', help="Print timing information") args = parser.parse_args() -ctbaseurl = args.baseurl -frontendnodes = args.frontend -storagenodes = args.storage +config = yaml.load(open(args.config)) +localconfig = yaml.load(open(args.localconfig)) -chainsdir = args.mergedb + "/chains" -logorderfile = args.mergedb + "/logorder" +ctbaseurl = config["baseurl"] +frontendnodes = config["frontendnodes"] +storagenodes = config["storagenodes"] +paths = localconfig["paths"] +mergedb = paths["mergedb"] -own_key = (args.own_keyname, args.own_keyfile) +signingnode = config["signingnodes"][0] + +chainsdir = mergedb + "/chains" +logorderfile = mergedb + "/logorder" + +own_key = (localconfig["nodename"], "%s/%s-private.pem" % (paths["privatekeys"], localconfig["nodename"])) hashed_dir = True @@ -77,9 +80,9 @@ def add_to_logorder(key): f.write(base64.b16encode(key) + "\n") f.close() -def get_new_entries(baseurl): +def get_new_entries(node, baseurl): try: - result = http_request(baseurl + "ct/storage/fetchnewentries", key=own_key) + result = http_request(baseurl + "ct/storage/fetchnewentries", key=own_key, verifynode=node, publickeydir=paths["publickeys"]) parsed_result = json.loads(result) if parsed_result.get(u"result") == u"ok": return [base64.b64decode(entry) for entry in parsed_result[u"entries"]] @@ -89,10 +92,10 @@ def get_new_entries(baseurl): print "ERROR: fetchnewentries", e.read() sys.exit(1) -def get_entries(baseurl, hashes): +def get_entries(node, baseurl, hashes): try: params = urllib.urlencode({"hash":[base64.b64encode(hash) for hash in hashes]}, doseq=True) - result = http_request(baseurl + "ct/storage/getentry?" + params, key=own_key) + result = http_request(baseurl + "ct/storage/getentry?" + params, key=own_key, verifynode=node, publickeydir=paths["publickeys"]) parsed_result = json.loads(result) if parsed_result.get(u"result") == u"ok": entries = dict([(base64.b64decode(entry["hash"]), base64.b64decode(entry["entry"])) for entry in parsed_result[u"entries"]]) @@ -105,9 +108,9 @@ def get_entries(baseurl, hashes): print "ERROR: getentry", e.read() sys.exit(1) -def get_curpos(baseurl): +def get_curpos(node, baseurl): try: - result = http_request(baseurl + "ct/frontend/currentposition", key=own_key) + result = http_request(baseurl + "ct/frontend/currentposition", key=own_key, verifynode=node, publickeydir=paths["publickeys"]) parsed_result = json.loads(result) if parsed_result.get(u"result") == u"ok": return parsed_result[u"position"] @@ -117,10 +120,10 @@ def get_curpos(baseurl): print "ERROR: currentposition", e.read() sys.exit(1) -def sendlog(baseurl, submission): +def sendlog(node, baseurl, submission): try: result = http_request(baseurl + "ct/frontend/sendlog", - json.dumps(submission), key=own_key) + json.dumps(submission), key=own_key, verifynode=node, publickeydir=paths["publickeys"]) return json.loads(result) except urllib2.HTTPError, e: print "ERROR: sendlog", e.read() @@ -133,10 +136,11 @@ def sendlog(baseurl, submission): print "========================" raise e -def sendentry(baseurl, entry, hash): +def sendentry(node, baseurl, entry, hash): try: result = http_request(baseurl + "ct/frontend/sendentry", - json.dumps({"entry":base64.b64encode(entry), "treeleafhash":base64.b64encode(hash)}), key=own_key) + json.dumps({"entry":base64.b64encode(entry), "treeleafhash":base64.b64encode(hash)}), key=own_key, + verifynode=node, publickeydir=paths["publickeys"]) return json.loads(result) except urllib2.HTTPError, e: print "ERROR: sendentry", e.read() @@ -149,10 +153,10 @@ def sendentry(baseurl, entry, hash): print "========================" raise e -def sendsth(baseurl, submission): +def sendsth(node, baseurl, submission): try: result = http_request(baseurl + "ct/frontend/sendsth", - json.dumps(submission), key=own_key) + json.dumps(submission), key=own_key, verifynode=node, publickeydir=paths["publickeys"]) return json.loads(result) except urllib2.HTTPError, e: print "ERROR: sendsth", e.read() @@ -165,9 +169,9 @@ def sendsth(baseurl, submission): print "========================" raise e -def get_missingentries(baseurl): +def get_missingentries(node, baseurl): try: - result = http_request(baseurl + "ct/frontend/missingentries", key=own_key) + result = http_request(baseurl + "ct/frontend/missingentries", key=own_key, verifynode=node, publickeydir=paths["publickeys"]) parsed_result = json.loads(result) if parsed_result.get(u"result") == u"ok": return parsed_result[u"entries"] @@ -193,10 +197,10 @@ new_entries = set() entries_to_fetch = {} for storagenode in storagenodes: - print "getting new entries from", storagenode - new_entries_per_node[storagenode] = set(get_new_entries(storagenode)) - new_entries.update(new_entries_per_node[storagenode]) - entries_to_fetch[storagenode] = [] + print "getting new entries from", storagenode["name"] + new_entries_per_node[storagenode["name"]] = set(get_new_entries(storagenode["name"], "https://%s/" % storagenode["address"])) + new_entries.update(new_entries_per_node[storagenode["name"]]) + entries_to_fetch[storagenode["name"]] = [] timing_point(timing, "get new entries") @@ -209,16 +213,16 @@ if args.nomerge: for hash in new_entries: for storagenode in storagenodes: - if hash in new_entries_per_node[storagenode]: - entries_to_fetch[storagenode].append(hash) + if hash in new_entries_per_node[storagenode["name"]]: + entries_to_fetch[storagenode["name"]].append(hash) break added_entries = 0 for storagenode in storagenodes: - print "getting", len(entries_to_fetch[storagenode]), "entries from", storagenode - for chunk in chunks(entries_to_fetch[storagenode], 100): - entries = get_entries(storagenode, chunk) + print "getting", len(entries_to_fetch[storagenode["name"]]), "entries from", storagenode["name"] + for chunk in chunks(entries_to_fetch[storagenode["name"]], 100): + entries = get_entries(storagenode["name"], "https://%s/" % storagenode["address"], chunk) for hash in chunk: entry = entries[hash] write_chain(hash, entry) @@ -235,7 +239,7 @@ root_hash = tree[-1][0] timestamp = int(time.time() * 1000) tree_head_signature = create_sth_signature(tree_size, timestamp, - root_hash, args.signing, key=own_key) + root_hash, "https://%s/" % signingnode["address"], key=own_key) sth = {"tree_size": tree_size, "timestamp": timestamp, "sha256_root_hash": base64.b64encode(root_hash), @@ -251,14 +255,16 @@ if args.timing: print "root hash", base64.b16encode(root_hash) for frontendnode in frontendnodes: + nodeaddress = "https://%s/" % frontendnode["address"] + nodename = frontendnode["name"] timing = timing_point() - print "distributing for node", frontendnode - curpos = get_curpos(frontendnode) + print "distributing for node", nodename + curpos = get_curpos(nodename, nodeaddress) timing_point(timing, "get curpos") print "current position", curpos entries = [base64.b64encode(entry) for entry in logorder[curpos:]] for chunk in chunks(entries, 1000): - sendlogresult = sendlog(frontendnode, {"start": curpos, "hashes": chunk}) + sendlogresult = sendlog(nodename, nodeaddress, {"start": curpos, "hashes": chunk}) if sendlogresult["result"] != "ok": print "sendlog:", sendlogresult sys.exit(1) @@ -267,17 +273,17 @@ for frontendnode in frontendnodes: sys.stdout.flush() timing_point(timing, "sendlog") print "log sent" - missingentries = get_missingentries(frontendnode) + missingentries = get_missingentries(nodename, nodeaddress) timing_point(timing, "get missing") print "missing entries:", len(missingentries) for missingentry in missingentries: hash = base64.b64decode(missingentry) - sendentryresult = sendentry(frontendnode, read_chain(hash), hash) + sendentryresult = sendentry(nodename, nodeaddress, read_chain(hash), hash) if sendentryresult["result"] != "ok": print "send sth:", sendentryresult sys.exit(1) timing_point(timing, "send missing") - sendsthresult = sendsth(frontendnode, sth) + sendsthresult = sendsth(nodename, nodeaddress, sth) if sendsthresult["result"] != "ok": print "send sth:", sendsthresult sys.exit(1) diff --git a/tools/testcase1.py b/tools/testcase1.py index 7b3229d..4502b56 100755 --- a/tools/testcase1.py +++ b/tools/testcase1.py @@ -15,9 +15,9 @@ import itertools from certtools import * baseurls = ["https://127.0.0.1:8080/"] -certfiles = ["testcerts/cert1.txt", "testcerts/cert2.txt", - "testcerts/cert3.txt", "testcerts/cert4.txt", - "testcerts/cert5.txt"] +certfiles = ["../tools/testcerts/cert1.txt", "../tools/testcerts/cert2.txt", + "../tools/testcerts/cert3.txt", "../tools/testcerts/cert4.txt", + "../tools/testcerts/cert5.txt"] cc1 = get_certs_from_file(certfiles[0]) cc2 = get_certs_from_file(certfiles[1]) @@ -138,10 +138,8 @@ def get_and_check_entry(timestamp, chain, leaf_index, baseurl): len(submittedcertchain)) def merge(): - return subprocess.call(["./merge.py", "--baseurl", "https://127.0.0.1:8080/", - "--frontend", "https://127.0.0.1:8082/", "--storage", "https://127.0.0.1:8081/", - "--mergedb", "../rel/mergedb", "--signing", "https://127.0.0.1:8088/", - "--own-keyname", "merge-1", "--own-keyfile", "../rel/privatekeys/merge-1-private.pem"]) + return subprocess.call(["../tools/merge.py", "--config", "../test/catlfish-test.cfg", + "--localconfig", "../test/catlfish-test-local-merge.cfg"]) mergeresult = merge() assert_equal(mergeresult, 0, "merge", quiet=True) -- cgit v1.1 From 842b07ef461483fcc297cc56e128918ddd273932 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 23 Mar 2015 16:24:13 +0100 Subject: Fix typo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8223a4e..b54bc50 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ tests-run: @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert5.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre1.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre2.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg || (echo "Merge failed" ; false) + @(cd rel && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg) || (echo "Merge failed" ; false) tests-run2: @(cd rel ; python ../tools/verifysct.py --sct-file=submittedcerts --parallel 1 $(BASEURL)) || echo "Verification of SCT:s failed" -- cgit v1.1 From 999bffa05d2f99cb33e14f50ed17b2da331b887b Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 24 Mar 2015 12:38:14 +0100 Subject: Clarify that 0.test.pem is not a valid #'OTPCertificate'{}. Also some cosmetic changes. --- src/x509.erl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/x509.erl b/src/x509.erl index eae1468..c3a829f 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -400,36 +400,36 @@ valid_cert_test_() -> fun({KnownRoots, Chains}) -> [ %% Self-signed but verified against itself so pass. - %% Not a valid OTPCertificate: - %% {error,{asn1,{invalid_choice_tag,{22,<<"US">>}}}} - %% 'OTP-PUB-KEY':Func('OTP-X520countryname', Value0) - %% FIXME: This error doesn't make much sense -- is my - %% environment borked? - ?_assertMatch({true, _}, normalise_chain(lists:nth(1, Chains), - lists:nth(1, Chains), 10)), + %% Note that this certificate is rejected by the + %% stricter OTP-PKIX.asn1 specification generating + %% #'OTPCertificate'{}. The error is + %% {invalid_choice_tag,{22,<<"US">>}}}} in + %% 'OTP-PUB-KEY':Func('OTP-X520countryname', Value0). + ?_assertMatch({true, _}, normalise_chain(nth(1, Chains), + nth(1, Chains), 10)), %% Self-signed so fail. ?_assertMatch({false, root_unknown}, normalise_chain(KnownRoots, - lists:nth(2, Chains), 10)), + nth(2, Chains), 10)), %% Leaf signed by known CA, pass. ?_assertMatch({true, _}, normalise_chain(KnownRoots, - lists:nth(3, Chains), 10)), + nth(3, Chains), 10)), %% Proper 3-depth chain with root in KnownRoots, pass. %% Bug CATLFISH-19 --> [info] rejecting "3ee62cb678014c14d22ebf96f44cc899adea72f1": chain_broken %% leaf sha1: 3ee62cb678014c14d22ebf96f44cc899adea72f1 %% leaf Subject: C=KR, O=Government of Korea, OU=Group of Server, OU=\xEA\xB5\x90\xEC\x9C\xA1\xEA\xB3\xBC\xED\x95\x99\xEA\xB8\xB0\xEC\x88\xA0\xEB\xB6\x80, CN=www.berea.ac.kr, CN=haksa.bits.ac.kr ?_assertMatch({true, _}, normalise_chain(KnownRoots, - lists:nth(4, Chains), 3)), + nth(4, Chains), 3)), %% Verify against self, pass. %% Bug CATLFISH-??, can't handle issuer keytype ECPoint. %% Issuer sha1: 6969562e4080f424a1e7199f14baf3ee58ab6abb - ?_assertMatch(true, signed_by_p(hd(lists:nth(5, Chains)), - hd(lists:nth(5, Chains)))), + ?_assertMatch(true, signed_by_p(hd(nth(5, Chains)), + hd(nth(5, Chains)))), %% Unsupported signature algorithm MD2-RSA, fail. %% Signature Algorithm: md2WithRSAEncryption %% CA cert with sha1 96974cd6b663a7184526b1d648ad815cf51e801a - ?_assertMatch(false, signed_by_p(hd(lists:nth(6, Chains)), - hd(lists:nth(6, Chains)))) + ?_assertMatch(false, signed_by_p(hd(nth(6, Chains)), + hd(nth(6, Chains)))) ] end}. chain_test_() -> -- cgit v1.1 From cbf3f563074e4f9a9024db0c0393aa4e6ba97087 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 25 Mar 2015 09:53:34 +0100 Subject: Store rejected certificates. Not storing the full chain, which would be even more useful. No rate limiting, which would be good. Also, reorganise some in x509.erl and add tests. --- src/x509.erl | 146 +++++++++----- ...01.9ed5072acb40d74aa5034b4525e4db56e2733ed0.pem | 52 +++++ ...02.8094ee90e2725c8ebde18bb83dd3cabe246ecb2b.pem | 62 ++++++ ...03.842456568ed7904347aa89ab777da4943ba1a7d5.pem | 213 +++++++++++++++++++++ ...04.3ee62cb678014c14d22ebf96f44cc899adea72f1.pem | 50 +++++ ...05.6969562e4080f424a1e7199f14baf3ee58ab6abb.pem | 13 ++ ...06.96974cd6b663a7184526b1d648ad815cf51e801a.pem | 49 +++++ ...07.cb0d9182ec62dfef2f233441335f32667a5ce85b.pem | 89 +++++++++ ...08.97eea3ff4bc293adb9de14a8fcf915804b4f026a.pem | 87 +++++++++ ...09.29dcb4c215b563e71d615cae5f5a57dbfc2c2871.pem | 35 ++++ ...10.2cf11ca183130b3ea882cbe2b620cc83bc8e4a6a.pem | 38 ++++ ...11.7c2d41564b256f4115e646f71387aa9e1aaa0f56.pem | 80 ++++++++ ...12.41b4b3980ab6389afe5647353b5abe882870b032.pem | 73 +++++++ ...13.9e862686af81aa013267c2b5fd098720734bc93b.pem | 69 +++++++ .../5.96974cd6b663a7184526b1d648ad815cf51e801a.pem | 49 ----- 15 files changed, 1004 insertions(+), 101 deletions(-) create mode 100644 test/testdata/chains/001.9ed5072acb40d74aa5034b4525e4db56e2733ed0.pem create mode 100644 test/testdata/chains/002.8094ee90e2725c8ebde18bb83dd3cabe246ecb2b.pem create mode 100644 test/testdata/chains/003.842456568ed7904347aa89ab777da4943ba1a7d5.pem create mode 100644 test/testdata/chains/004.3ee62cb678014c14d22ebf96f44cc899adea72f1.pem create mode 100644 test/testdata/chains/005.6969562e4080f424a1e7199f14baf3ee58ab6abb.pem create mode 100644 test/testdata/chains/006.96974cd6b663a7184526b1d648ad815cf51e801a.pem create mode 100644 test/testdata/chains/007.cb0d9182ec62dfef2f233441335f32667a5ce85b.pem create mode 100644 test/testdata/chains/008.97eea3ff4bc293adb9de14a8fcf915804b4f026a.pem create mode 100644 test/testdata/chains/009.29dcb4c215b563e71d615cae5f5a57dbfc2c2871.pem create mode 100644 test/testdata/chains/010.2cf11ca183130b3ea882cbe2b620cc83bc8e4a6a.pem create mode 100644 test/testdata/chains/011.7c2d41564b256f4115e646f71387aa9e1aaa0f56.pem create mode 100644 test/testdata/chains/012.41b4b3980ab6389afe5647353b5abe882870b032.pem create mode 100644 test/testdata/chains/013.9e862686af81aa013267c2b5fd098720734bc93b.pem delete mode 100644 test/testdata/chains/5.96974cd6b663a7184526b1d648ad815cf51e801a.pem diff --git a/src/x509.erl b/src/x509.erl index c3a829f..1b0db5e 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -127,37 +127,55 @@ encoded_tbs_cert(DerCert) -> PKIXCert, EncodedTBSCert. --spec extract_verify_data(#'Certificate'{}, binary()) -> {ok, tuple()} | error. -%% @doc Return DER encoded TBScertificate, digest type and signature. -%% Code from pubkey_cert:extract_verify_data/2. -extract_verify_data(Cert, DerCert) -> - PlainText = encoded_tbs_cert(DerCert), - {_, Sig} = Cert#'Certificate'.signature, - SigAlgRecord = Cert#'Certificate'.signatureAlgorithm, - SigAlg = SigAlgRecord#'AlgorithmIdentifier'.algorithm, - try - {DigestType, _} = public_key:pkix_sign_types(SigAlg), - {ok, {PlainText, DigestType, Sig}} - catch - error:function_clause -> - lager:debug("signature algorithm not supported: ~p", [SigAlg]), +-spec decode_cert(binary()) -> #'Certificate'{} | error. +decode_cert(Der) -> + case (catch public_key:pkix_decode_cert(Der, plain)) of + #'Certificate'{} = Cert -> + Cert; + {'EXIT', Reason} -> + lager:info("invalid certificate: ~p: ~p", [cert_string(Der), Reason]), + dump_unparsable_cert(Der), + error; + Unknown -> + lager:info("unknown error decoding cert: ~p: ~p", + [cert_string(Der), Unknown]), error end. -%% @doc Verify that Cert/DerCert is signed by Issuer. --spec verify_sig(#'Certificate'{}, binary(), #'Certificate'{}) -> boolean(). -verify_sig(Cert, DerCert, % Certificate to verify. - #'Certificate'{ % Issuer. - tbsCertificate = #'TBSCertificate'{ - subjectPublicKeyInfo = IssuerSPKI}}) -> - %% Dig out digest, digest type and signature from Cert/DerCert. - case extract_verify_data(Cert, DerCert) of - error -> false; - {ok, Tuple} -> verify_sig2(IssuerSPKI, Tuple) +parsable_cert_p(Der) -> + case decode_cert(Der) of + error -> + false; + _ -> + true end. -verify_sig2(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> - %% Dig out issuer key from issuer cert. +%% @doc Is Cert signed by Issuer? Only verify that the signature +%% matches and don't check things like Cert.issuer == Issuer.subject. +-spec signed_by_p(binary(), binary()) -> boolean(). +signed_by_p(SubjectDer, IssuerDer) -> + SubjectCert = decode_cert(SubjectDer), + IssuerCert = decode_cert(IssuerDer), + + case {SubjectCert, IssuerCert} of + {#'Certificate'{}, + #'Certificate'{tbsCertificate = + #'TBSCertificate'{subjectPublicKeyInfo = + IssuerSPKI}}} -> + %% Dig out digest, digest type and signature from subject cert and + %% verify signature. + case extract_verify_data(decode_cert(SubjectDer), SubjectDer) of + error -> + false; + {ok, SubjectData} -> + verify_sig(IssuerSPKI, SubjectData) + end; + _ -> + false + end. + +verify_sig(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> + %% Dig out alg, params and key from issuer. #'SubjectPublicKeyInfo'{ algorithm = #'AlgorithmIdentifier'{algorithm = Alg, parameters = Params}, subjectPublicKey = {0, Key0}} = IssuerSPKI, @@ -170,33 +188,29 @@ verify_sig2(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> Point = #'ECPoint'{point = Key0}, ECParams = public_key:der_decode('EcpkParameters', Params), {Point, ECParams}; - _ -> % FIXME: 'DSAPublicKey' + _ -> % FIXME: 'DSAPublicKey' lager:error("NIY: Issuer key type ~p", [KeyType]), false end, %% Verify the signature. public_key:verify(DigestOrPlainText, DigestType, Signature, IssuerKey). -%% @doc Is Cert signed by Issuer? Only verify that the signature -%% matches and don't check things like Cert.issuer == Issuer.subject. --spec signed_by_p(binary(), binary()) -> boolean(). -signed_by_p(DerCert, IssuerDerCert) when is_binary(DerCert), - is_binary(IssuerDerCert) -> - verify_sig(public_key:pkix_decode_cert(DerCert, plain), - DerCert, - public_key:pkix_decode_cert(IssuerDerCert, plain)). - -parsable_cert_p(Der) -> - case (catch public_key:pkix_decode_cert(Der, plain)) of - #'Certificate'{} -> - true; - {'EXIT', Reason} -> - lager:info("invalid certificate: ~p: ~p", [cert_string(Der), Reason]), - false; - Unknown -> - lager:info("unknown error decoding cert: ~p: ~p", - [cert_string(Der), Unknown]), - false +-spec extract_verify_data(#'Certificate'{}, binary()) -> {ok, tuple()} | error. +%% @doc Return DER encoded TBScertificate, digest type and signature. +%% Code from pubkey_cert:extract_verify_data/2. +extract_verify_data(Cert, DerCert) -> + PlainText = encoded_tbs_cert(DerCert), + {_, Sig} = Cert#'Certificate'.signature, + SigAlgRecord = Cert#'Certificate'.signatureAlgorithm, + SigAlg = SigAlgRecord#'AlgorithmIdentifier'.algorithm, + try + {DigestType, _} = public_key:pkix_sign_types(SigAlg), + {ok, {PlainText, DigestType, Sig}} + catch + error:function_clause -> + lager:debug("~p: signature algorithm not supported: ~p", + [cert_string(DerCert), SigAlg]), + error end. %% Precerts according to RFC6962. @@ -367,18 +381,18 @@ pems_from_file(Filename) -> Pems. -spec dump_unparsable_cert(binary()) -> ok | {error, atom()} | not_logged. -dump_unparsable_cert(CertDer) -> +dump_unparsable_cert(Der) -> case application:get_env(catlfish, rejected_certs_path) of {ok, Directory} -> {NowMegaSec, NowSec, NowMicroSec} = now(), Filename = filename:join(Directory, io_lib:format("~p:~p.~p", - [cert_string(CertDer), + [cert_string(Der), NowMegaSec * 1000 * 1000 + NowSec, NowMicroSec])), - lager:debug("dumping cert to ~p~n", [Filename]), - file:write_file(Filename, CertDer); + lager:info("dumping cert to ~p~n", [Filename]), + file:write_file(Filename, Der); _ -> not_logged end. @@ -429,7 +443,35 @@ valid_cert_test_() -> %% Signature Algorithm: md2WithRSAEncryption %% CA cert with sha1 96974cd6b663a7184526b1d648ad815cf51e801a ?_assertMatch(false, signed_by_p(hd(nth(6, Chains)), - hd(nth(6, Chains)))) + hd(nth(6, Chains)))), + + %% Supposedly problematic chains from Google Aviator, fatal. + %% 00459972: asn1: syntax error: sequence truncated + ?_assertMatch({true, _}, normalise_chain(nth(7, Chains), + nth(7, Chains), 10)), + %% 1402673: x509: RSA modulus is not a positive number + ?_assertMatch({true, _}, normalise_chain(nth(8, Chains), + nth(8, Chains), 10)), + %% 1345105: asn1: syntax error: IA5String contains invalid character + ?_assertMatch({true, _}, normalise_chain(nth(9, Chains), + nth(9, Chains), 10)), + %% 1557693: asn1: structure error: integer too large + ?_assertMatch({true, _}, normalise_chain(nth(10, Chains), + nth(10, Chains), 10)), + + %% Supposedly problematic chains from Google Aviator, non-fatal. + %% 16800: x509: negative serial number + %% a.pem + ?_assertMatch({true, _}, normalise_chain(nth(11, Chains), + nth(11, Chains), 10)), + %% 22487: x509: unhandled critical extension ([2 5 29 32]) + %% b.pem + ?_assertMatch({true, _}, normalise_chain(nth(12, Chains), + nth(12, Chains), 10)), + %% 5198: x509: certificate contained IP address of length 8 + %% c.pem + ?_assertMatch({true, _}, normalise_chain(nth(13, Chains), + nth(13, Chains), 10)) ] end}. chain_test_() -> diff --git a/test/testdata/chains/001.9ed5072acb40d74aa5034b4525e4db56e2733ed0.pem b/test/testdata/chains/001.9ed5072acb40d74aa5034b4525e4db56e2733ed0.pem new file mode 100644 index 0000000..0c8847b --- /dev/null +++ b/test/testdata/chains/001.9ed5072acb40d74aa5034b4525e4db56e2733ed0.pem @@ -0,0 +1,52 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1167666620 (0x45992dbc) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=California, L=Sunnyvale, O=HTTPS Management Certificate for SonicWALL (self-signed), OU=HTTPS Management Certificate for SonicWALL (self-signed), CN=192.168.168.168 + Validity + Not Before: Jan 1 00:00:01 1970 GMT + Not After : Jan 19 03:14:07 2038 GMT + Subject: C=US, ST=California, L=Sunnyvale, O=HTTPS Management Certificate for SonicWALL (self-signed), OU=HTTPS Management Certificate for SonicWALL (self-signed), CN=192.168.168.168 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d0:e3:96:0a:8c:4c:c6:86:72:99:f6:c4:3d:d9: + 87:56:12:65:1b:5b:ab:a7:fc:23:0f:d7:d3:66:a8: + eb:ff:04:6b:53:6a:e3:75:5e:c4:bd:f7:41:10:2c: + 4e:47:f6:ab:4a:99:79:f0:30:8a:9d:71:a2:5f:a5: + 11:79:5e:c9:85:28:1e:dd:17:c6:41:e9:94:60:ac: + 2f:cd:1b:7f:10:60:0c:9c:4a:be:11:00:10:83:70: + 44:df:b6:b3:81:ff:64:26:83:63:b1:30:7e:60:9f: + 15:26:41:f6:7b:23:aa:0a:54:4f:ad:9c:6f:25:d6: + 3a:e3:f1:7d:3f:28:22:c5:d9 + Exponent: 65537 (0x10001) + Signature Algorithm: md5WithRSAEncryption + cd:2a:0b:e6:e9:f6:cb:cd:5f:8c:cd:7d:21:25:21:69:33:dd: + a3:a2:e9:25:4b:c4:56:51:c9:00:26:57:53:42:f2:27:50:d0: + 71:61:a8:c1:56:40:1e:5a:65:b1:79:35:b0:44:50:57:5f:87: + e9:c4:14:8d:5e:8f:3e:b9:11:9b:4f:91:99:06:82:68:17:53: + 5e:88:3c:4c:e7:2e:46:80:88:fc:6c:a9:b2:23:65:71:d0:f7: + df:22:53:0e:0c:4b:90:d2:ee:49:b8:c8:c3:e9:66:5d:72:a5: + 91:21:f0:c1:9d:b9:2f:38:14:da:d5:15:0f:fc:f4:0b:64:29: + b1:6f +-----BEGIN CERTIFICATE----- +MIIDJTCCAo6gAwIBAgIERZktvDANBgkqhkiG9w0BAQQFADCB1jELMAkGA1UEBhYC +VVMxEzARBgNVBAgWCkNhbGlmb3JuaWExEjAQBgNVBAcWCVN1bm55dmFsZTFBMD8G +A1UEChY4SFRUUFMgTWFuYWdlbWVudCBDZXJ0aWZpY2F0ZSBmb3IgU29uaWNXQUxM +IChzZWxmLXNpZ25lZCkxQTA/BgNVBAsWOEhUVFBTIE1hbmFnZW1lbnQgQ2VydGlm +aWNhdGUgZm9yIFNvbmljV0FMTCAoc2VsZi1zaWduZWQpMRgwFgYDVQQDFg8xOTIu +MTY4LjE2OC4xNjgwHhcNNzAwMTAxMDAwMDAxWhcNMzgwMTE5MDMxNDA3WjCB1jEL +MAkGA1UEBhYCVVMxEzARBgNVBAgWCkNhbGlmb3JuaWExEjAQBgNVBAcWCVN1bm55 +dmFsZTFBMD8GA1UEChY4SFRUUFMgTWFuYWdlbWVudCBDZXJ0aWZpY2F0ZSBmb3Ig +U29uaWNXQUxMIChzZWxmLXNpZ25lZCkxQTA/BgNVBAsWOEhUVFBTIE1hbmFnZW1l +bnQgQ2VydGlmaWNhdGUgZm9yIFNvbmljV0FMTCAoc2VsZi1zaWduZWQpMRgwFgYD +VQQDFg8xOTIuMTY4LjE2OC4xNjgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB +ANDjlgqMTMaGcpn2xD3Zh1YSZRtbq6f8Iw/X02ao6/8Ea1Nq43VexL33QRAsTkf2 +q0qZefAwip1xol+lEXleyYUoHt0XxkHplGCsL80bfxBgDJxKvhEAEINwRN+2s4H/ +ZCaDY7EwfmCfFSZB9nsjqgpUT62cbyXWOuPxfT8oIsXZAgMBAAEwDQYJKoZIhvcN +AQEEBQADgYEAzSoL5un2y81fjM19ISUhaTPdo6LpJUvEVlHJACZXU0LyJ1DQcWGo +wVZAHlplsXk1sERQV1+H6cQUjV6PPrkRm0+RmQaCaBdTXog8TOcuRoCI/GypsiNl +cdD33yJTDgxLkNLuSbjIw+lmXXKlkSHwwZ25LzgU2tUVD/z0C2QpsW8= +-----END CERTIFICATE----- diff --git a/test/testdata/chains/002.8094ee90e2725c8ebde18bb83dd3cabe246ecb2b.pem b/test/testdata/chains/002.8094ee90e2725c8ebde18bb83dd3cabe246ecb2b.pem new file mode 100644 index 0000000..36ea7fd --- /dev/null +++ b/test/testdata/chains/002.8094ee90e2725c8ebde18bb83dd3cabe246ecb2b.pem @@ -0,0 +1,62 @@ +% Self-signed. + +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=flimsytest + Validity + Not Before: May 4 10:17:19 2014 GMT + Not After : May 4 10:17:19 2015 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=flimsytest + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c5:1e:c3:c1:9a:26:e8:64:7f:dd:1c:05:5a:e0: + 9a:87:cc:d1:d4:f5:30:95:62:73:79:56:a8:8e:8e: + eb:12:7b:cb:8d:5e:5f:eb:3b:12:c9:c4:7d:fe:ad: + 85:c5:89:81:63:2f:3c:dc:a1:b6:ee:7c:7b:42:9d: + 6d:69:81:a4:c7:34:0e:85:f0:f3:ee:5f:34:92:a1: + 01:bb:f6:f6:c1:6a:e8:c6:cf:7f:44:8d:b7:9d:62: + d5:9a:7a:22:bc:f2:d4:e3:fa:03:e9:b1:ca:01:f0: + db:84:33:9f:64:60:f3:f8:7a:5b:f0:e3:9d:4e:b2: + 21:a1:49:a8:d9:e5:e8:7f:f5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 7C:05:0C:BA:09:58:C2:DE:46:7F:ED:39:5B:87:B2:28:8B:99:D7:28 + X509v3 Authority Key Identifier: + keyid:7C:05:0C:BA:09:58:C2:DE:46:7F:ED:39:5B:87:B2:28:8B:99:D7:28 + + Signature Algorithm: sha256WithRSAEncryption + 59:47:3b:91:85:21:40:31:af:82:bf:57:21:c3:46:07:eb:14: + bf:be:ec:f8:98:d1:0e:51:0b:eb:2c:44:8a:95:d0:e9:43:04: + 56:43:c5:10:41:76:2e:6c:f3:0a:9b:e4:5f:15:f5:2e:38:17: + dd:f6:f7:9e:5f:ed:f7:b2:76:b2:c2:55:da:48:73:e4:54:dc: + 3b:7e:b8:88:33:27:83:67:34:c8:a4:e7:b2:c7:20:51:0e:9f: + f6:b8:f3:a5:73:e2:b2:fc:5e:cf:82:43:6b:0e:73:fa:ef:ce: + 5d:46:f8:de:54:6c:b1:96:17:be:1c:f9:c4:49:cb:8d:ee:0a: + da:32 +-----BEGIN CERTIFICATE----- +MIICpTCCAg6gAwIBAgIBADANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMRMwEQYDVQQDDApmbGltc3l0ZXN0MB4XDTE0MDUwNDEwMTcxOVoXDTE1 +MDUwNDEwMTcxOVowWjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZmxp +bXN5dGVzdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxR7DwZom6GR/3RwF +WuCah8zR1PUwlWJzeVaojo7rEnvLjV5f6zsSycR9/q2FxYmBYy883KG27nx7Qp1t +aYGkxzQOhfDz7l80kqEBu/b2wWroxs9/RI23nWLVmnoivPLU4/oD6bHKAfDbhDOf +ZGDz+Hpb8OOdTrIhoUmo2eXof/UCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB +hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE +FHwFDLoJWMLeRn/tOVuHsiiLmdcoMB8GA1UdIwQYMBaAFHwFDLoJWMLeRn/tOVuH +siiLmdcoMA0GCSqGSIb3DQEBCwUAA4GBAFlHO5GFIUAxr4K/VyHDRgfrFL++7PiY +0Q5RC+ssRIqV0OlDBFZDxRBBdi5s8wqb5F8V9S44F932955f7feydrLCVdpIc+RU +3Dt+uIgzJ4NnNMik57LHIFEOn/a486Vz4rL8Xs+CQ2sOc/rvzl1G+N5UbLGWF74c ++cRJy43uCtoy +-----END CERTIFICATE----- diff --git a/test/testdata/chains/003.842456568ed7904347aa89ab777da4943ba1a7d5.pem b/test/testdata/chains/003.842456568ed7904347aa89ab777da4943ba1a7d5.pem new file mode 100644 index 0000000..7d86862 --- /dev/null +++ b/test/testdata/chains/003.842456568ed7904347aa89ab777da4943ba1a7d5.pem @@ -0,0 +1,213 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 09:48:b1:a9:3b:25:1d:0d:b1:05:10:59:e2:c2:68:0a + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA + Validity + Not Before: Oct 22 12:00:01 2013 GMT + Not After : May 3 12:00:00 2016 GMT + Subject: C=US, ST=Massachusetts, L=Walpole, O=The Tor Project, Inc., CN=*.torproject.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b7:23:39:ed:c8:68:85:27:e5:81:0e:9c:00:0c: + fa:e2:25:2a:6d:07:c8:75:1a:47:aa:f0:53:49:b9: + 62:17:52:57:c0:d1:19:40:7c:d1:0e:bb:ce:42:1b: + ba:d4:cc:6c:49:5a:f0:aa:4f:4a:ab:0a:fc:54:a1: + 49:78:4b:58:1e:87:bf:95:15:da:34:7a:fc:fc:f1: + 8b:c4:1a:2c:c3:00:b8:b4:f9:a0:70:a4:47:a2:67: + 2c:56:6b:52:d3:ea:e7:44:66:85:87:e0:d7:99:30: + a2:c9:84:cc:fa:8b:6b:73:43:70:ae:6d:a5:35:f9: + 17:8f:03:bc:14:fe:d1:a0:99:40:b9:dd:28:6c:d5: + 86:22:48:a4:42:5d:7d:37:3a:f5:bd:62:e3:11:b2: + 87:3a:78:0a:15:05:0e:d9:8a:f4:c4:59:15:1b:c3: + 16:5e:19:69:50:5e:da:16:b0:ff:ed:64:7a:61:b0: + 87:95:2e:68:3f:8f:0e:a4:c9:97:ec:70:41:d5:02: + ac:a5:81:83:09:ce:54:b2:4a:aa:ba:76:fd:87:34: + 9a:49:13:15:7a:9d:50:3d:41:4b:ec:20:bc:20:e2: + eb:87:fb:9d:dc:b2:4d:08:1b:f0:85:a8:58:47:85: + e8:a1:db:88:56:4b:55:1f:e9:b8:7e:b8:71:bc:91: + 17:c7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Authority Key Identifier: + keyid:51:68:FF:90:AF:02:07:75:3C:CC:D9:65:64:62:A2:12:B8:59:72:3B + + X509v3 Subject Key Identifier: + 82:26:08:F1:13:29:55:34:14:B4:8F:80:1D:71:B8:60:DA:4B:41:CC + X509v3 Subject Alternative Name: + DNS:*.torproject.org, DNS:torproject.org + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 CRL Distribution Points: + + Full Name: + URI:http://crl3.digicert.com/sha2-ha-server-g1.crl + + Full Name: + URI:http://crl4.digicert.com/sha2-ha-server-g1.crl + + X509v3 Certificate Policies: + Policy: 2.16.840.1.114412.1.1 + CPS: https://www.digicert.com/CPS + + Authority Information Access: + OCSP - URI:http://ocsp.digicert.com + CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2HighAssuranceServerCA.crt + + X509v3 Basic Constraints: critical + CA:FALSE + Signature Algorithm: sha256WithRSAEncryption + 6f:70:71:7e:80:11:d0:aa:60:09:61:3a:e9:a9:4b:42:34:8f: + ab:74:63:d0:d6:8b:58:83:1e:04:d7:aa:99:85:df:64:52:0c: + 2e:83:d7:3e:ca:0a:3d:2e:c4:6a:6a:9f:5a:04:c4:8e:29:82: + 9c:e4:c6:c7:5f:56:bd:aa:41:18:14:ec:25:0c:dd:b8:23:20: + a5:01:5f:8c:3e:40:95:50:ab:cd:95:9f:59:23:40:b4:6f:5b: + db:b2:5e:8b:e8:cb:5b:d0:60:35:e5:e8:c5:e7:f0:53:e9:0d: + fc:b0:df:38:3e:67:96:a7:99:db:60:9d:19:00:ab:2b:93:2f: + dc:4c:e4:bf:5f:12:b7:13:b1:66:1e:ca:fa:8b:f3:87:88:68: + 4a:d5:e5:9b:1c:a3:c0:77:aa:53:83:b4:d3:dd:50:e5:ab:2b: + 2c:f0:4f:ad:ed:d7:24:b8:0a:c4:7a:45:63:9b:2f:28:a7:2e: + f9:37:8c:64:cc:48:6e:44:c7:4f:ab:bd:b6:b8:e9:c7:b1:8c: + 57:bc:f3:80:f7:a4:4a:b9:f4:e4:17:02:63:7b:fc:55:9b:f8: + 3b:be:53:76:dc:81:01:78:a9:bb:50:ea:7a:92:c2:11:19:3a: + 3a:6f:ec:98:af:67:f3:54:e5:71:a5:79:cc:36:46:c9:ed:63: + 52:fd:9b:52 +-----BEGIN CERTIFICATE----- +MIIFXTCCBEWgAwIBAgIQCUixqTslHQ2xBRBZ4sJoCjANBgkqhkiG9w0BAQsFADBw +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz +dXJhbmNlIFNlcnZlciBDQTAeFw0xMzEwMjIxMjAwMDFaFw0xNjA1MDMxMjAwMDBa +MHIxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMRAwDgYDVQQH +EwdXYWxwb2xlMR4wHAYDVQQKExVUaGUgVG9yIFByb2plY3QsIEluYy4xGTAXBgNV +BAMMECoudG9ycHJvamVjdC5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC3IzntyGiFJ+WBDpwADPriJSptB8h1Gkeq8FNJuWIXUlfA0RlAfNEOu85C +G7rUzGxJWvCqT0qrCvxUoUl4S1geh7+VFdo0evz88YvEGizDALi0+aBwpEeiZyxW +a1LT6udEZoWH4NeZMKLJhMz6i2tzQ3CubaU1+RePA7wU/tGgmUC53Shs1YYiSKRC +XX03OvW9YuMRsoc6eAoVBQ7ZivTEWRUbwxZeGWlQXtoWsP/tZHphsIeVLmg/jw6k +yZfscEHVAqylgYMJzlSySqq6dv2HNJpJExV6nVA9QUvsILwg4uuH+53csk0IG/CF +qFhHheih24hWS1Uf6bh+uHG8kRfHAgMBAAGjggHvMIIB6zAfBgNVHSMEGDAWgBRR +aP+QrwIHdTzM2WVkYqISuFlyOzAdBgNVHQ4EFgQUgiYI8RMpVTQUtI+AHXG4YNpL +QcwwKwYDVR0RBCQwIoIQKi50b3Jwcm9qZWN0Lm9yZ4IOdG9ycHJvamVjdC5vcmcw +DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1 +BgNVHR8EbjBsMDSgMqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1o +YS1zZXJ2ZXItZzEuY3JsMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v +c2hhMi1oYS1zZXJ2ZXItZzEuY3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCow +KAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgYMGCCsG +AQUFBwEBBHcwdTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t +ME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl +cnRTSEEySGlnaEFzc3VyYW5jZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMA0G +CSqGSIb3DQEBCwUAA4IBAQBvcHF+gBHQqmAJYTrpqUtCNI+rdGPQ1otYgx4E16qZ +hd9kUgwug9c+ygo9LsRqap9aBMSOKYKc5MbHX1a9qkEYFOwlDN24IyClAV+MPkCV +UKvNlZ9ZI0C0b1vbsl6L6Mtb0GA15ejF5/BT6Q38sN84PmeWp5nbYJ0ZAKsrky/c +TOS/XxK3E7FmHsr6i/OHiGhK1eWbHKPAd6pTg7TT3VDlqyss8E+t7dckuArEekVj +my8opy75N4xkzEhuRMdPq722uOnHsYxXvPOA96RKufTkFwJje/xVm/g7vlN23IEB +eKm7UOp6ksIRGTo6b+yYr2fzVOVxpXnMNkbJ7WNS/ZtS +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 04:e1:e7:a4:dc:5c:f2:f3:6d:c0:2b:42:b8:5d:15:9f + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA + Validity + Not Before: Oct 22 12:00:00 2013 GMT + Not After : Oct 22 12:00:00 2028 GMT + Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b6:e0:2f:c2:24:06:c8:6d:04:5f:d7:ef:0a:64: + 06:b2:7d:22:26:65:16:ae:42:40:9b:ce:dc:9f:9f: + 76:07:3e:c3:30:55:87:19:b9:4f:94:0e:5a:94:1f: + 55:56:b4:c2:02:2a:af:d0:98:ee:0b:40:d7:c4:d0: + 3b:72:c8:14:9e:ef:90:b1:11:a9:ae:d2:c8:b8:43: + 3a:d9:0b:0b:d5:d5:95:f5:40:af:c8:1d:ed:4d:9c: + 5f:57:b7:86:50:68:99:f5:8a:da:d2:c7:05:1f:a8: + 97:c9:dc:a4:b1:82:84:2d:c6:ad:a5:9c:c7:19:82: + a6:85:0f:5e:44:58:2a:37:8f:fd:35:f1:0b:08:27: + 32:5a:f5:bb:8b:9e:a4:bd:51:d0:27:e2:dd:3b:42: + 33:a3:05:28:c4:bb:28:cc:9a:ac:2b:23:0d:78:c6: + 7b:e6:5e:71:b7:4a:3e:08:fb:81:b7:16:16:a1:9d: + 23:12:4d:e5:d7:92:08:ac:75:a4:9c:ba:cd:17:b2: + 1e:44:35:65:7f:53:25:39:d1:1c:0a:9a:63:1b:19: + 92:74:68:0a:37:c2:c2:52:48:cb:39:5a:a2:b6:e1: + 5d:c1:dd:a0:20:b8:21:a2:93:26:6f:14:4a:21:41: + c7:ed:6d:9b:f2:48:2f:f3:03:f5:a2:68:92:53:2f: + 5e:e3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:0 + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + Authority Information Access: + OCSP - URI:http://ocsp.digicert.com + + X509v3 CRL Distribution Points: + + Full Name: + URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl + + X509v3 Certificate Policies: + Policy: X509v3 Any Policy + CPS: https://www.digicert.com/CPS + + X509v3 Subject Key Identifier: + 51:68:FF:90:AF:02:07:75:3C:CC:D9:65:64:62:A2:12:B8:59:72:3B + X509v3 Authority Key Identifier: + keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3 + + Signature Algorithm: sha256WithRSAEncryption + 18:8a:95:89:03:e6:6d:df:5c:fc:1d:68:ea:4a:8f:83:d6:51: + 2f:8d:6b:44:16:9e:ac:63:f5:d2:6e:6c:84:99:8b:aa:81:71: + 84:5b:ed:34:4e:b0:b7:79:92:29:cc:2d:80:6a:f0:8e:20:e1: + 79:a4:fe:03:47:13:ea:f5:86:ca:59:71:7d:f4:04:96:6b:d3: + 59:58:3d:fe:d3:31:25:5c:18:38:84:a3:e6:9f:82:fd:8c:5b: + 98:31:4e:cd:78:9e:1a:fd:85:cb:49:aa:f2:27:8b:99:72:fc: + 3e:aa:d5:41:0b:da:d5:36:a1:bf:1c:6e:47:49:7f:5e:d9:48: + 7c:03:d9:fd:8b:49:a0:98:26:42:40:eb:d6:92:11:a4:64:0a: + 57:54:c4:f5:1d:d6:02:5e:6b:ac:ee:c4:80:9a:12:72:fa:56: + 93:d7:ff:bf:30:85:06:30:bf:0b:7f:4e:ff:57:05:9d:24:ed: + 85:c3:2b:fb:a6:75:a8:ac:2d:16:ef:7d:79:27:b2:eb:c2:9d: + 0b:07:ea:aa:85:d3:01:a3:20:28:41:59:43:28:d2:81:e3:aa: + f6:ec:7b:3b:77:b6:40:62:80:05:41:45:01:ef:17:06:3e:de: + c0:33:9b:67:d3:61:2e:72:87:e4:69:fc:12:00:57:40:1e:70: + f5:1e:c9:b4 +-----BEGIN CERTIFICATE----- +MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3Vy +YW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2 +4C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMIC +Kq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1 +itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn +4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0X +sh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcft +bZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEA +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +NAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +dC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIG +BFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ +UzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7D +aQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwd +aOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNH +E+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly +/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zu +xICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF +0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0Ae +cPUeybQ= +-----END CERTIFICATE----- diff --git a/test/testdata/chains/004.3ee62cb678014c14d22ebf96f44cc899adea72f1.pem b/test/testdata/chains/004.3ee62cb678014c14d22ebf96f44cc899adea72f1.pem new file mode 100644 index 0000000..2affd7a --- /dev/null +++ b/test/testdata/chains/004.3ee62cb678014c14d22ebf96f44cc899adea72f1.pem @@ -0,0 +1,50 @@ +SHA1 Fingerprint=3E:E6:2C:B6:78:01:4C:14:D2:2E:BF:96:F4:4C:C8:99:AD:EA:72:F1 +Timestamp: 1364288520513 +Leafhash: F1BB1CD704EDFE2A37AB1FFD4EFB9E523F9F7227E945B06C18F52BF270729314 +-----BEGIN CERTIFICATE----- +MIIE0jCCA7qgAwIBAgIEAP3y5DANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJLUjEcMBoGA1UE +CgwTR292ZXJubWVudCBvZiBLb3JlYTENMAsGA1UECwwER1BLSTEUMBIGA1UEAwwLQ0ExMzQwNDAw +MDEwHhcNMTExMTAxMDQ1ODAwWhcNMTQwMjAxMDQ1NzU5WjCBmjELMAkGA1UEBhMCS1IxHDAaBgNV +BAoME0dvdmVybm1lbnQgb2YgS29yZWExGDAWBgNVBAsMD0dyb3VwIG9mIFNlcnZlcjEeMBwGA1UE +CwwV6rWQ7Jyh6rO87ZWZ6riw7Iig67aAMRgwFgYDVQQDDA93d3cuYmVyZWEuYWMua3IxGTAXBgNV +BAMMEGhha3NhLmJpdHMuYWMua3IwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALLf+nRKv05O +fUAfobmxtuMuh+ZLXZeBM141nN0K8H3KN3BiqYqOONe7bbAreiz0p42Ul4rhL1FOwwNcvVf4nXBo +wfu+Hiwp3WrD8ef8CJc3LywzJyq5fQ4MT0xYQYu1J4icv1SXacvXbSPwHK3Brt2JHxOpfzeVnh4T +TAYRV3ZfAgMBAAGjggHrMIIB5zBnBggrBgEFBQcBAQRbMFkwVwYIKwYBBQUHMAKGS2xkYXA6Ly9s +ZGFwLmVwa2kuZ28ua3I6Mzg5L2NuPUdQS0lSb290Q0Esb3U9R1BLSSxvPUdvdmVybm1lbnQgb2Yg +S29yZWEsYz1LUjCBhgYDVR0jBH8wfYAU+nIEA5n96tt8UN2+5XKk0nclFcihU6RRME8xCzAJBgNV +BAYTAktSMRwwGgYDVQQKDBNHb3Zlcm5tZW50IG9mIEtvcmVhMQ0wCwYDVQQLDARHUEtJMRMwEQYD +VQQDDApHUEtJUm9vdENBghBH/vYAAgeG2AGSNf+eSiACMB0GA1UdDgQWBBSM7j+3kdc2M6QCR5H+ +xdNdfh2cEzALBgNVHQ8EBAMCBaAwDAYDVR0TBAUwAwEB/zAoBgNVHSUEITAfBggrBgEFBQcDAQYI +KwYBBQUHAwIGCWCGSAGG+EIEATB8BgNVHR8EdTBzMHGgb6BthmtsZGFwOi8vbGRhcC5lcGtpLmdv +LmtyOjM4OS9vdT1kcDFwMjA1NTYsb3U9Q1JMLG91PUdQS0ksbz1Hb3Zlcm5tZW50IG9mIEtvcmVh +LGM9a3I/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDARBglghkgBhvhCAQEEBAMCBsAwDQYJKoZI +hvcNAQEFBQADggEBAC14Ht+BTjUGTgeG0Q5C5Bdj2RqewI4asd8UPTkXf0N+Tg7VhR3f1bfyHnsX +Zx8Dbdzij0dMD7NlMur5I1LKYTKMYruAxEPULLMhp9qsQX2i91t8s+uRYxcPWqK96DoRoeLJCpmQ +D338GwoUsy+vy43K4urJnCLnEe/ZtWFD+XMIux89T7DglieBA4+PkUhsD3QA0Pd+l15Kx2RFh4os +fX3IfKundxzJ0jQ4OzyeV/2NjyRb2GZQlJUuA9On+8EobU4nwDKJCv3MmsjlFFUa3TQk/n3JusRl +Iwu3vNPWc3mqoWbF61oF/0aNPmsf17vLRCDOfDsEcufz1ZPQa02dpDY= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQR/72AAIHhtgBkjX/nkogAjANBgkqhkiG9w0BAQUFADBPMQswCQYDVQQG +EwJLUjEcMBoGA1UECgwTR292ZXJubWVudCBvZiBLb3JlYTENMAsGA1UECwwER1BLSTETMBEGA1UE +AwwKR1BLSVJvb3RDQTAeFw0wODA2MDkxNDA5MjFaFw0xODA2MDkxNDA5MjFaMFAxCzAJBgNVBAYT +AktSMRwwGgYDVQQKDBNHb3Zlcm5tZW50IG9mIEtvcmVhMQ0wCwYDVQQLDARHUEtJMRQwEgYDVQQD +DAtDQTEzNDA0MDAwMTCCASEwDQYJKoZIhvcNAQEBBQADggEOADCCAQkCggEAZvDlMT1PwNhEkeB5 +WvvyCrQXf10ah2jWNDq3A86IEHOVRB3sNoABgkCHue70jIa/EI9PRpdoouPYdR+DJPkFS9QLizlg +krPCNQhJqr7vuXQd/JV2OFhKhsrlIrKZaB1FU0ndJmzezZUZZxBfsBz6LAjRZn4EVPPqQY+DR7fS +rgh8h6yGPMhMtV8aADTpMkLmnfSjYJKsY4NTYheBsXQ7kr2d3CK5a7Sn3Nze4TvC05DyctpTWPJN +yFOx8Ahyi0dVg77mNNx4uPXQhlip4n4pV4ibLlVw+O9E9/7lUDG31yH/wgSl4ukwcQjHHXI2dadv +P2M63tjdHXfZVHBHY3IgKwIDAQABo4IBNDCCATAwHwYDVR0jBBgwFoAUFmcy9GheaDFH2+3szmEu +miRGxH0wHQYDVR0OBBYEFPpyBAOZ/erbfFDdvuVypNJ3JRXIMA4GA1UdDwEB/wQEAwIBBjBPBgNV +HSAESDBGMAwGCiqDGoaNIQUDAQMwDAYKKoMaho0hBQMBATAMBgoqgxqGjSEFAwEHMAwGCiqDGoaN +IQUDAQkwDAYKKoMaho0hBQMBBTASBgNVHRMBAf8ECDAGAQH/AgEAMHkGA1UdHwRyMHAwbqBsoGqG +aGxkYXA6Ly9jZW4uZGlyLmdvLmtyOjM4OS9jbj1HUEtJUm9vdENBLG91PUdQS0ksbz1Hb3Zlcm5t +ZW50IG9mIEtvcmVhLGM9S1I/YXV0aG9yaXR5UmV2b2NhdGlvbmxpc3Q7YmluYXJ5MA0GCSqGSIb3 +DQEBBQUAA4IBAQAhagazxtMY+p+i1F/OyJJ0kwZU8PrKISJUZMpBxMaZpfCzUWSnaO9Ha6SPnqm8 +gE71ZJV+KUj6ll6YL3VExaGU2YPpNUzbo4mFuTP5QBo+d18sEZAIsKPAG2ZXw1wUBx51jduMBWGY +o43JFS+XPlrxrYULPobprudrqTt+EffG++hey18VBk/mPubyovFlMZ74esV96IenJvGxMNhsS+U+ +RIE1QoLDscJrlenmjctbowNZ8pq91MJw6V8OG0w9ELVQMt98uidzU2fzF4W0XxHiIlZBtp6imOZx +Q+xtCiJd0/S/jpEoHBU9ZEJrBRolRMdvf5Oh2qTLeowZU17RtC8T +-----END CERTIFICATE----- diff --git a/test/testdata/chains/005.6969562e4080f424a1e7199f14baf3ee58ab6abb.pem b/test/testdata/chains/005.6969562e4080f424a1e7199f14baf3ee58ab6abb.pem new file mode 100644 index 0000000..e9d0c33 --- /dev/null +++ b/test/testdata/chains/005.6969562e4080f424a1e7199f14baf3ee58ab6abb.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- diff --git a/test/testdata/chains/006.96974cd6b663a7184526b1d648ad815cf51e801a.pem b/test/testdata/chains/006.96974cd6b663a7184526b1d648ad815cf51e801a.pem new file mode 100644 index 0000000..4b0bcf5 --- /dev/null +++ b/test/testdata/chains/006.96974cd6b663a7184526b1d648ad815cf51e801a.pem @@ -0,0 +1,49 @@ +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + ca:f6:c1:f5:70:e2:e9:03:6b:73:fc:06:92:1c:2a:f1 + Signature Algorithm: md2WithRSAEncryption + Issuer: L=Internet, O=VeriSign, Inc., OU=VeriSign Individual Software Publishers CA + Validity + Not Before: Apr 9 00:00:00 1996 GMT + Not After : Jan 7 23:59:59 2004 GMT + Subject: L=Internet, O=VeriSign, Inc., OU=VeriSign Individual Software Publishers CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c1:7a:ca:65:a7:2d:d5:0f:4f:6c:47:32:f8:73: + 77:86:e5:3d:f2:6f:79:90:b5:de:4f:d2:14:79:33: + 4b:b2:0e:46:fd:88:72:2a:c2:fc:64:9e:61:8f:6b: + c0:5b:e8:f0:11:04:da:7a:ba:72:f6:ec:5d:af:fb: + f1:97:f1:14:d2:28:e3:28:85:5c:7b:bd:8a:a2:7f: + c2:33:b5:6d:0b:92:78:0b:38:71:74:85:7f:3d:ba: + b9:2f:ef:be:27:48:0d:38:22:c9:56:30:8d:77:fa: + 5d:2c:5a:9c:97:ce:70:30:e9:51:5f:a6:8b:e4:95: + 96:a5:a0:17:77:f1:93:b8:29 + Exponent: 65537 (0x10001) + Signature Algorithm: md2WithRSAEncryption + c0:8f:9f:d7:7f:0e:26:f7:47:fd:65:7b:63:92:06:46:cc:e0: + 5e:4b:81:58:b3:97:ec:05:18:13:15:d6:97:10:2a:7f:f8:56: + ad:d8:0e:7a:bf:27:48:45:07:9f:25:22:ac:70:12:06:69:37: + 80:58:58:d8:8a:34:5a:07:c6:27:da:c6:fb:9e:b1:7d:64:20: + 80:4f:f4:a1:15:7b:19:61:af:c6:ab:c0:26:65:fe:8c:07:87: + 3f:1a:c4:8e:b2:40:44:10:7e:2c:61:7a:fe:ad:a3:c8:be:cc: + bd:5e:aa:af:ac:c5:bf:11:67:58:2a:32:f3:25:dd:b6:74:43: + c0:7c +-----BEGIN CERTIFICATE----- +MIICQTCCAaoCEQDK9sH1cOLpA2tz/AaSHCrxMA0GCSqGSIb3DQEBAgUAMGExETAP +BgNVBAcTCEludGVybmV0MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEzMDEGA1UE +CxMqVmVyaVNpZ24gSW5kaXZpZHVhbCBTb2Z0d2FyZSBQdWJsaXNoZXJzIENBMB4X +DTk2MDQwOTAwMDAwMFoXDTA0MDEwNzIzNTk1OVowYTERMA8GA1UEBxMISW50ZXJu +ZXQxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTMwMQYDVQQLEypWZXJpU2lnbiBJ +bmRpdmlkdWFsIFNvZnR3YXJlIFB1Ymxpc2hlcnMgQ0EwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAMF6ymWnLdUPT2xHMvhzd4blPfJveZC13k/SFHkzS7IORv2I +cirC/GSeYY9rwFvo8BEE2nq6cvbsXa/78ZfxFNIo4yiFXHu9iqJ/wjO1bQuSeAs4 +cXSFfz26uS/vvidIDTgiyVYwjXf6XSxanJfOcDDpUV+mi+SVlqWgF3fxk7gpAgMB +AAEwDQYJKoZIhvcNAQECBQADgYEAwI+f138OJvdH/WV7Y5IGRszgXkuBWLOX7AUY +ExXWlxAqf/hWrdgOer8nSEUHnyUirHASBmk3gFhY2Io0WgfGJ9rG+56xfWQggE/0 +oRV7GWGvxqvAJmX+jAeHPxrEjrJARBB+LGF6/q2jyL7MvV6qr6zFvxFnWCoy8yXd +tnRDwHw= +-----END CERTIFICATE----- diff --git a/test/testdata/chains/007.cb0d9182ec62dfef2f233441335f32667a5ce85b.pem b/test/testdata/chains/007.cb0d9182ec62dfef2f233441335f32667a5ce85b.pem new file mode 100644 index 0000000..2fca29e --- /dev/null +++ b/test/testdata/chains/007.cb0d9182ec62dfef2f233441335f32667a5ce85b.pem @@ -0,0 +1,89 @@ +-----BEGIN CERTIFICATE----- +MIIFXjCCBEagAwIBAgICBx0wDQYJKoZIhvcNAQEFBQAwUDELMAkGA1UEBhMCREUxDzANBgNVBAoM +BkdBRCBFRzERMA8GA1UECwwIVlIgSURFTlQxHTAbBgNVBAMMFFZSIElERU5UIFNTTCBDQSAyMDA5 +MB4XDTEzMDQwNDA4NDcxNFoXDTE0MDUwNDIxNTk1OVowgZUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI +DAZCQVlFUk4xETAPBgNVBAcMCE1VRU5DSEVOMSwwKgYDVQQKDCNERVVUU0NIRVIgR0VOT1NTRU5T +Q0hBRlRTLVZFUkxBRyBFRzEYMBYGA1UECwwPRElBTE9HTUFSS0VUSU5HMRowGAYDVQQDDBFXV1cu +R0VOTy1MT0dJTi5ERTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbXt0nKG39wuCkg +XkxtxNnvsAFxlLjJdbp9JpXJOtPSGqTK2c8x5jGCgsD4krDAcbBzVSWTMCkpwyL4Wq+pwhgTcZX2 +Ozuipsj5vGNNtSWgx46y8qOKaaQZAaJhXkuIH3uSXOqYz7iUymDyXUrw08itQJLCvMkY0Sici5sZ +XNX7tZS91ltLjq/oOFE945Do6DmDrMqIkqf1aQJ+Z2eXoEvoeoZf6dEFxWK39M0fmLhEsyf1K7Nu +6f4Eea/UUDdnNOV7Szs1O8zPzpb53rXvbyfWLWZ1sOsZkUo6tItGuQFWqj8x2Z+m8GjVpgh/hHsd +HY0wGUXzjpChLcV2S/84Kz0CAwEAAaOCAfowggH2MGYGCCsGAQUFBwEBBFowWDBWBggrBgEFBQcw +AYZKaHR0cDovL29jc3AudnItaWRlbnQuZGUvZ3Rub2NzcC9PQ1NQUmVzcG9uZGVyL1ZSJTIwSWRl +bnQlMjBTU0wlMjBDQSUyMDIwMDkwgZIGA1UdIwSBijCBh4AiUFJPRC5HVE4uRVhTU0xDQS5TSUdH +RU5SUy4wMDAwMTYwMKFepFwwWjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkdBRCBlRzERMA8GA1UE +CxMIVlIgSURFTlQxJzAlBgNVBAMTHlZSIElERU5UIEVYVEVSTkFMIFJPT1QgQ0EgMjAwOYIBAjCB +sgYDVR0fBIGqMIGnME2gS6BJhkdodHRwOi8vd3d3LnZyLWlkZW50LmRlL2d0bmNybC9DUkxSZXNw +b25kZXIvVlIlMjBJZGVudCUyMFNTTCUyMENBJTIwMjAwOTBWolSkUjBQMQswCQYDVQQGEwJERTEP +MA0GA1UECgwGR0FEIEVHMREwDwYDVQQLDAhWUiBJREVOVDEdMBsGA1UEAwwUVlIgSURFTlQgU1NM +IENBIDIwMDkwDgYDVR0PAQH/BAQDAgWgMB0GA1UdDgQWBBQygVXLjfIZAVbUPQH/WYD7yYA+0TAT +BgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOCAQEAJdYUSflPqF47cCpCb3E7TKhQ +YAouGo3/0nEecjgaCBkV3Z5O0KkNqKctpxH+1JO6rrT8O8+qZph+MCxYl3YVXDyrqXVdq2bMbN/h +rUx2WCJYyz0g71rSJyBFALwSDSTh6fLEBQmuG45MOcvCe/rwwM8qI/C/PAmSzuxwCQ53mdpmiNP5 +5IJzhHjzGTB0hLM1VCGhDWhz4gCcR0gOok3dT1S4wxln5TUTSC2r97oe6olmLWtwG5g+svKtov/b +7YDRSY4bwuIpY95GHMArmsIc3ceNQe5QKsBsEYgZPAh6yti2mcPIoVQltKJrQk4YcXAhOI+eeX5e +e6zSirwDBbDMsQ== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIEZzCCA1GgAwIBAgIBAjALBgkqhkiG9w0BAQUwWjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkdB +RCBlRzERMA8GA1UECxMIVlIgSURFTlQxJzAlBgNVBAMTHlZSIElERU5UIEVYVEVSTkFMIFJPT1Qg +Q0EgMjAwOTAeFw0wOTA5MDExMzQzNThaFw0xNjA4MDUxNzMwNDNaMFAxCzAJBgNVBAYTAkRFMQ8w +DQYDVQQKDAZHQUQgRUcxETAPBgNVBAsMCFZSIElERU5UMR0wGwYDVQQDDBRWUiBJREVOVCBTU0wg +Q0EgMjAwOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkkUOLwvOHMwNf7OCp9V688 +5K+YHKD74mswf3p0y8QcmA5g/6jbExXbfLH8X2iuKDbFJGJ6lEoFnyK+ZEE4aHIIlsZXjBWVqSuT +mA2QXTMCX4lQB0uTEMbkp8l9VJZOcwnp56/422+B+V/PQE4IWTbbIa46MVpiS0LpYU5dyzdlwScF +yV8uH1IRhJkC05/7o4MrsqxsN9ZU9KI2ezo6oyJwEEfpbsqsGm50f8wwHtIzuNMnOkg6ZLIp1+bw +ezclGnpwaYAcAUUbJLjD1BjXDlopVR7trlgrrCH/ZJ5/x1z/Dxq4wXXeY6SY7MAAECg6o4fmSeK+ +hE2pdY7qNzDJWJMCAwEAAaOCAUQwggFAMGYGCCsGAQUFBwEBBFowWDBWBggrBgEFBQcwAYZKaHR0 +cDovL29jc3AudnItaWRlbnQuZGUvZ3Rub2NzcC9PQ1NQUmVzcG9uZGVyL1ZSJTIwSWRlbnQlMjBT +U0wlMjBDQSUyMDIwMDkwLgYDVR0jBCcwJYAjUFJPRC5HVE4uRVhST09UQ0EuU0lHR0VOUlMuMDAw +MDE2MDAwDwYDVR0TAQH/BAUwAwEB/zBYBgNVHR8EUTBPME2gS6BJhkdodHRwOi8vd3d3LnZyLWlk +ZW50LmRlL2d0bmNybC9DUkxSZXNwb25kZXIvVlIlMjBJZGVudCUyMFNTTCUyMENBJTIwMjAwOTAO +BgNVHQ8BAf8EBAMCAYYwKwYDVR0OBCQEIlBST0QuR1ROLkVYU1NMQ0EuU0lHR0VOUlMuMDAwMDE2 +MDAwCwYJKoZIhvcNAQEFA4IBAQARXS47O/pO2QPLWV5bBey+x8Qc6EnkdzNyZRbniAa4ZRwoNFWH +ZUuBO9BpyJ5Ej2MLpERrVdD62N8r1HCBWDI53dotZz2CrfLTxHWhpXE11G/f48aTi57pIS/Wi1iX +IJNF67jf5q6WJrIcDkFJaOP/Sv4k3AGNdYBGtVHqOQf/zm/VEmCOsSLxndd8ql+1WDL0eNXXbQRM +l7HgOy7UH9xU/uzd2cg2peTKs6IhUy/Xmt3+ogJm8dxP8r4Cr7EqQGvMV7qclkrLqaVa1LOPBa3j +XWO4s7U1YXmdUIGAW/BAf6PRh/mjX9AEX5RpvrIJ8rjqzTgn1tQl+tyW6g8wrHlE +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIETDCCA7WgAwIBAgIEBydInjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJVUzEYMBYGA1UE +ChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIElu +Yy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEdsb2JhbCBSb290MB4XDTA5MDgwNTE3MzEzMVoX +DTE2MDgwNTE3MzA0M1owWjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkdBRCBlRzERMA8GA1UECxMI +VlIgSURFTlQxJzAlBgNVBAMTHlZSIElERU5UIEVYVEVSTkFMIFJPT1QgQ0EgMjAwOTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKeQvkjveaO0Rz7TBwrFMa/4zNg8spAAZA1dJy9vjbee +BK1jB8+wUD7/N2MMGmTM4wsot9962nyYsoss3OcJAHjpU2gfgtYydz+qEheA4P1SxUuyY9l2AT/S +HKkLOB7uKrR9nMWYau9e8Z/rbniwDuN0RO3qwThS1xy5glViyWZZH7N8kMuqsWrlesq6Gg5q06yt +3xFzt0+zFyAKjcbBnHxMZ97Ll56lhsZ1e4frbuT5uH/AsB8zq6moqXGgfrKOGgrX40xwDSBP6pDM +EEQhMFBpuvsn8zqaGzy4zDyq2sODe88f0UF97Svt8SE6lS2TmbrJzILGgYt88QCEQZNh70UCAwEA +AaOCAX4wggF6MBIGA1UdEwEB/wQIMAYBAf8CAQEwUwYDVR0gBEwwSjBIBgkrBgEEAbE+AQAwOzA5 +BggrBgEFBQcCARYtaHR0cDovL2N5YmVydHJ1c3Qub21uaXJvb3QuY29tL3JlcG9zaXRvcnkuY2Zt +MA4GA1UdDwEB/wQEAwIBhjCBiQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQK +Ew9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5j +LjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDwwOqA4 +oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmww +LAYDVR0OBCUEI1BST0QuR1ROLkVYUk9PVENBLlNJR0dFTlJTLjAwMDAxNjAwMA0GCSqGSIb3DQEB +BQUAA4GBAIBqVFa9Y7EtnJTRyiLS5ShQM+3BBSJIOz+mxxv3ir7/AAK66yop5aKcUVlvx9kJQ+O5 +nbqhSQlyqsYCJLyH1Ay2LOV/Jjc1vHDbpGEhsup+24tPM9+kubQazh+8xgHgZN2JxCFHpqYurwPI +JTJ0IpQNX3EiqFgfd4IuiY4u+Y3j +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/008.97eea3ff4bc293adb9de14a8fcf915804b4f026a.pem b/test/testdata/chains/008.97eea3ff4bc293adb9de14a8fcf915804b4f026a.pem new file mode 100644 index 0000000..6b88b0c --- /dev/null +++ b/test/testdata/chains/008.97eea3ff4bc293adb9de14a8fcf915804b4f026a.pem @@ -0,0 +1,87 @@ +-----BEGIN CERTIFICATE----- +MIIGxTCCBa2gAwIBAgIQCxFta0HqQmeJFiuJSXo7gDANBgkqhkiG9w0BAQUFADBmMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSUw +IwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBDQS0zMB4XDTEzMDkxMzAwMDAwMFoXDTE0 +MDkxODEyMDAwMFowgYExCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlXaXNjb25zaW4xDzANBgNVBAcT +BlJhY2luZTEgMB4GA1UEChMXVHdpbiBEaXNjLCBJbmNvcnBvcmF0ZWQxEjAQBgNVBAsTCUNvcnBv +cmF0ZTEXMBUGA1UEAwwOKi50d2luZGlzYy5jb20wggEhMA0GCSqGSIb3DQEBAQUAA4IBDgAwggEJ +AoIBALVeexMOuUpRrjhs9hfQm7zHVr30YUPmm1aa/LWjC795NwdTdhUYQzEqBjUagD5WaqBcrJET +1oZ3ygeTVuxj3CYbsDd1ysgtR0Dku0FCKbpQylZQY/6Ez3VH/0cSMb0kr53sY9ftd+n+h3e8lEvR +KDUEYKfALY8rmCdqTSNIIXSJpWHjfL9IHsCvzrOjwHukPnpKmVMXfCAqptiHBUzdihddnM0LhnLb +vQott6KqcLXm84TxoC0ORdbulfpKAMwG1xNUD6DBONEVS7KjIGxJrNqHFongvSCSDsYQU271scen +eVoESGxMFDzJr2QYe0CCq6COF/Q4hRwHZPpxiNBQv/cCAwEAAaOCA1IwggNOMB8GA1UdIwQYMBaA +FFDqc4nbKfsQj57lASDU3nmZSIP3MB0GA1UdDgQWBBT8YtDCALtftoQt43QQ7Qoj2fMPRjAnBgNV +HREEIDAegg4qLnR3aW5kaXNjLmNvbYIMdHdpbmRpc2MuY29tMA4GA1UdDwEB/wQEAwIFoDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwYQYDVR0fBFowWDAqoCigJoYkaHR0cDovL2NybDMu +ZGlnaWNlcnQuY29tL2NhMy1nMjQuY3JsMCqgKKAmhiRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v +Y2EzLWcyNC5jcmwwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwBATCCAaQwOgYIKwYBBQUH +AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggr +BgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp +AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMA +ZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAg +AHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAA +dwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBl +ACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUA +cgBlAG4AYwBlAC4wewYIKwYBBQUHAQEEbzBtMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp +Y2VydC5jb20wRQYIKwYBBQUHMAKGOWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy +dEhpZ2hBc3N1cmFuY2VDQS0zLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBBQUAA4IBAQA1 +qAE+6nL92rvilZrGKwgPPl8AHzxAg8Ghlex5FTIUfqj+1GvCHpK3g1RhgNiZ8PTqKDeQlt8D9smr +HXhq9Tzc4KbWuXhPojsEZuWc0mX2zhRJEB8MpJq0cmgtoeq/oPIhICY+5DcyUhVzUSusd24068Ps +QUbBTeBq5taXRKKoI2M6fPONWLJaLwapConAzx5VSr8avcoWF35H0Xt+9LuZIioktmlqD+0cd0np +JBacoVkM6MSyLHxXGZymF9BbQkWlrnD/mGBufUZuP5XHynSG2iRA9EGS2X/5i+3/4qOE9S8vmoA6 +DEXE2FCEteD9gJRi5BBFCmEIUSzFTl1Efhk5 +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIGWDCCBUCgAwIBAgIQCl8RTQNbF5EX0u/UA4w/OzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA4MDQwMjEyMDAw +MFoXDTIyMDQwMzAwMDAwMFowZjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgQ0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdRCPge+yLt +Yb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcvKEZmBMcqeSZ6mdWOw21P +oF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fm +XcCabH4JnudSREoQOiPkm7YDr6ictFuf1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6R +lgWYw7If48hl66l7XaAszPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp49 +6+ynaF4d32duXvsCAwEAAaOCAvowggL2MA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0wggG5 +MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNv +bS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUA +cwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABp +AHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkA +QwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ +AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwA +aQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBk +ACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMBIGA1UdEwEB/wQIMAYB +Af8CAQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j +b20wgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 +SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAWgBSxPsNpA/i/RwHU +mCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTeeZlIg/cwDQYJKoZIhvcNAQEFBQAD +ggEBAB7ipUiebNtTOA/vphoqrOIDQ+2avD6OdRvw/S4iWawTwGHi5/rpmc2HCXVUKL9GYNy+USyS +8xuRfDEIcOI3ucFbqL2jCwD7GhX9A61YasXHJJlIR0YxHpLvtF9ONMeQvzHB+LGEhtCcAarfilYG +zjrpDq6XdF3XcZpCdF/ejUN83ulV7WkAywXgemFhM9EZTfkI7qA5xSU1tyvED7Ld8aW3DiTEJiiN +eXf1L/BXunwH1OH8zVowV36GEEfdMR/X/KLCvzB8XSSq6PmuX2p0ws5rs0bYIb4p1I5eFdZCSucy +b6Sxa1GDWL4/bcf72gMhy2oWGU4K8K2Eyl2Us1p292E= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/009.29dcb4c215b563e71d615cae5f5a57dbfc2c2871.pem b/test/testdata/chains/009.29dcb4c215b563e71d615cae5f5a57dbfc2c2871.pem new file mode 100644 index 0000000..5da5ef8 --- /dev/null +++ b/test/testdata/chains/009.29dcb4c215b563e71d615cae5f5a57dbfc2c2871.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAm6gAwIBAgIDCIjoMA0GCSqGSIb3DQEBBAUAMIHEMQswCQYDVQQGEwJaQTEVMBMGA1UE +CBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xHTAbBgNVBAoTFFRoYXd0ZSBDb25z +dWx0aW5nIGNjMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMRkwFwYD +VQQDExBUaGF3dGUgU2VydmVyIENBMSYwJAYJKoZIhvcNAQkBFhdzZXJ2ZXItY2VydHNAdGhhd3Rl +LmNvbTAeFw0wMjAxMjUxMzQ2MjFaFw0wMzAxMjUxMzQ2MjFaMIGiMQswCQYDVQQGEwJERTEMMAoG +A1UECBMDTlJXMQ8wDQYDVQQHEwZBYWNoZW4xRzBFBgNVBAoWPnRlYW0gaW4gbWVkaWFzIGdlc2Vs +bHNjaGFmdCBm/HIgbXVsdGltZWRpYWxlIGtvbW11bmlrYXRpb24gbWJIMQ8wDQYDVQQLEwZBYWNo +ZW4xGjAYBgNVBAMTEWltYWlsLmlubWVkaWFzLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQC73OnDMoVrurUCrgOmbBnGZKoNLRV++LPbSlxH1joI0WlheRr/bkxf3oyfgJWWFSiltkAaj5M2 +ODWQbZ9sJSUW/54A3r90oHVuu4RxjMU66GwuiZXr8zNMzkpBhSAtrCJPCHJ0tYh7PLvjHSAugvu2 +9DDLrjXoHtu33EATi1ny9wIDAQABoyUwIzATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8E +AjAAMA0GCSqGSIb3DQEBBAUAA4GBAJcYvfKP2JNdn3m4wRg+uWxGgXc1vgcDNNtRLlFTo7zvXMTa +FZQ3Wx6KPRkdZFCWIm29mVlUJ7r9EaaPlEuJAh0FLmWGlTsxYB0jtOKBC3WwEOa5ZAwrz965rAxD +P98UK9+WZ/jqynERXcQvJcxn0lcMFr1d9fnQTXLNtQueM6BJ +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE +AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j +b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u +c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG +A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 +ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl +/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 +1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J +GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ +GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/010.2cf11ca183130b3ea882cbe2b620cc83bc8e4a6a.pem b/test/testdata/chains/010.2cf11ca183130b3ea882cbe2b620cc83bc8e4a6a.pem new file mode 100644 index 0000000..4200803 --- /dev/null +++ b/test/testdata/chains/010.2cf11ca183130b3ea882cbe2b620cc83bc8e4a6a.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDgTCCAuqgAwIBAgIQIsWFzBD0GDkG+p8oIxaD6DANBgkqhkiG9w0BAQUFADCBzjELMAkGA1UE +BhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQK +ExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBE +aXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkB +FhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29tMB4XDTA5MDgwNjAwMDAwMFoXDTEyMDgyMzIzNTk1 +OVowgYQxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhLZW50dWNreTETMBEGA1UEBxMKTG91aXN2aWxs +ZTEWMBQGA1UEChMNSEVQQXJ0cywgSW5jLjEbMBkGA1UECxMSV2ViIEFkbWluaXN0cmF0aW9uMRgw +FgYDVQQDEw93d3cuSEVQQXJ0cy5jb20wgaAwDQYJKoZIhvcNAQEBBQADgY4AMIGKAoGAVk8R3cq8 +FFr39vPzHBRM7vffaz/XHSZu9prSRkzw8u99qsby9gDPVWougR6a0osPf4t9HwNVPirJohX2IXz/ +Tka8I4Ba7fcWWOImQQXvMF756Qsg0iDkFdPo7jgWYFrcxtHMzgRAQSUKE6gZRNx6xJ4W/B1djQS7 +JT3VVtTpvmUCBQCkj/mHo4GmMIGjMAwGA1UdEwEB/wQCMAAwQAYDVR0fBDkwNzA1oDOgMYYvaHR0 +cDovL2NybC50aGF3dGUuY29tL1RoYXd0ZVNlcnZlclByZW1pdW1DQS5jcmwwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29j +c3AudGhhd3RlLmNvbTANBgkqhkiG9w0BAQUFAAOBgQDMJdmHnzSMrQ/zDpgwelwk4E1vwieSxGJW +3ELtaA5uU15CaVNKvu9Zk0aPXVD+JEWNjXO2ZXs0xLBgKyeMMUZUL4CCASCQsaAKyvsi0wMi2l2K +5v6VzfOnwKLevRDlLV7t++r0QWutXtbU85/Hq5ba2orUYiNauv5v9CK6s4IaAg== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE +AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl +ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU +VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ +cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 +aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh +Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ +qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm +SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf +8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t +UCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/011.7c2d41564b256f4115e646f71387aa9e1aaa0f56.pem b/test/testdata/chains/011.7c2d41564b256f4115e646f71387aa9e1aaa0f56.pem new file mode 100644 index 0000000..9bc2446 --- /dev/null +++ b/test/testdata/chains/011.7c2d41564b256f4115e646f71387aa9e1aaa0f56.pem @@ -0,0 +1,80 @@ +-----BEGIN CERTIFICATE----- +MIIFpjCCBI6gAwIBAgIIjK0t3AAANcYwDQYJKoZIhvcNAQEFBQAwXjELMAkGA1UEBhMCS1IxEjAQ +BgNVBAoMCUNyb3NzQ2VydDEVMBMGA1UECwwMQWNjcmVkaXRlZENBMSQwIgYDVQQDDBtDcm9zc0Nl +cnQgQ2xhc3MgMSBTZXJ2ZXIgQ0EwHhcNMTIxMTA5MDc0NjAwWhcNMTMxMTExMTQ1OTU5WjBwMQsw +CQYDVQQGEwJLUjESMBAGA1UECgwJQ3Jvc3NDZXJ0MRUwEwYDVQQLDAxBY2NyZWRpdGVkQ0ExDDAK +BgNVBAsMA1NTTDEMMAoGA1UECwwDMDAxMRowGAYDVQQDDBF3d3cuZ2V0ZmlsZS5jby5rcjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ6VG9Dvx5t1iL1rjyfX36/yqj2+xfpxtDaXueoD +qRoMSJBmVZiio2VX/mfcbSTUJH3XoEjBkPzjKDMouvCBF8AhGPUX6HnaeTRtxejTdoqYP4SaxT7Q +2CUJFtqQ4PI+QOyA4DMTUNMNFv3ZRemrJKk0YluL+sNcy12su58QNmAmsVjjTVmQtPs+mE28E2vP +yJ2Ze472wYnJOs2giWo16ewahd0swoeowcVRFOnRMXCXIsgiMn0Bv97As7eS4xv1dFmKfEmNCQPm ++X4qmLZFlIw9SGO1TPS/wDblXvsqb6VH2JnL7qv+j2cn3frv/T57UFnSLTXGIKrb13Lo6nmjah0C +AwEAAaOCAlQwggJQMIGPBgNVHSMEgYcwgYSAFG24/5Jcsb/pSXgxfIB1wGIQS704oWikZjBkMQsw +CQYDVQQGEwJLUjENMAsGA1UECgwES0lTQTEuMCwGA1UECwwlS29yZWEgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkgQ2VudHJhbDEWMBQGA1UEAwwNS0lTQSBSb290Q0EgMYICJ4cwHQYDVR0OBBYEFLCn +RDny9eLbgtg6lumkKmesw5TzMA4GA1UdDwEB/wQEAwIFoDB7BgNVHSAEdDByMHAGCSqDGoyaRAUE +BjBjMC0GCCsGAQUFBwIBFiFodHRwOi8vZ2NhLmNyb3NzY2VydC5jb20vY3BzLmh0bWwwMgYIKwYB +BQUHAgIwJh4kx3QAIMd4yZ3BHLKUACDG+cEcvIQAIMd4yZ3BHMeFssiy5AAuMH8GA1UdHwR4MHYw +dKByoHCGbmxkYXA6Ly9zc2xkaXIuY3Jvc3NjZXJ0LmNvbTozODkvY249czFkcDZwMSxvdT1jcmxk +cCxvdT1BY2NyZWRpdGVkQ0Esbz1Dcm9zc0NlcnQsYz1LUj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25M +aXN0MIGOBggrBgEFBQcBAQSBgTB/MH0GCCsGAQUFBzAChnFsZGFwOi8vc3NsLmNyb3NzY2VydC5j +b206Mzg5L2NuPUNyb3NzQ2VydCBDbGFzcyAxIFNlcnZlciBDQSxvdT1BY2NyZWRpdGVkQ0Esbz1D +cm9zc0NlcnQsYz1LUj9jQUNlcnRpZmljYXRlO2JpbmFyeTANBgkqhkiG9w0BAQUFAAOCAQEA1t7F +EWpwXWm4TULUIHbV1HhDLXqjav1ybE7KzTye3/bTGpvzzy7DdMs0ppgLSXOT2ADhE4bWabh1VVAE +eqniqsg0iui5IpFquA6lKHOmqyh+IoCOza83ovx7f+Ku8M6enYcDgaWindw88eWl0dYsukp5p2F9 +GSFnk+b3SAdAZ5gDvp0BFZ0dBEV0Bn8rQFFiju740WEaEobGFBTfhXDE+Znk2x5UErwyxqEO2R3i +I7Og79SXTy9Pygmin+A5KD+2yiJqxhDKuck2nmLmNSja81tO3CyLagfiNewCefg05dUExXTwFncY +eiRkIBn8s1lL9zhzY55XoduTw40BR7rn4w== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIGHjCCBQagAwIBAgICJ4cwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCS1IxDTALBgNVBAoM +BEtJU0ExLjAsBgNVBAsMJUtvcmVhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENlbnRyYWwxFjAU +BgNVBAMMDUtJU0EgUm9vdENBIDEwHhcNMTAwNzMwMDcxMTM0WhcNMjAwNzMwMDcxMTM0WjBeMQsw +CQYDVQQGEwJLUjESMBAGA1UECgwJQ3Jvc3NDZXJ0MRUwEwYDVQQLDAxBY2NyZWRpdGVkQ0ExJDAi +BgNVBAMMG0Nyb3NzQ2VydCBDbGFzcyAxIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAODF0o/q0AUTtbuc2AL8q4SZ2PP0MPfYGs8sWCnIOnQGdY4BOfK85CKgZGaiE14W +E/AZvxYuSSIkuy4lH4k6+MPbiKTk1UGLLdM5Xi0NYLR9/zs2OrfwFKEpjH0mSBXjoZk8ioqDRED6 +kkkzt9WOHIRqz7RqmJP9typp7NQVRKwQWP77Ny2yJlevlz0l2rhdd+4F2HNgDNjXxuda2Ivis1Ws +O6LlrS/KFbz9o9QJ0yy7k7nCVISwPABPWZTz/Zzxd2fNGhPQF4dV9uOnIfi1sKbvWabTPJctPB54 +3mtH3tpEnlpOsaGxegEGsUqYBFHx6IvvfDDbE4pm+pg4AeZ6nRMCAwEAAaOCAt4wggLaMIGOBgNV +HSMEgYYwgYOAFL+2J9gDWnZlTGEBQVYx5Yt7OtnMoWikZjBkMQswCQYDVQQGEwJLUjENMAsGA1UE +CgwES0lTQTEuMCwGA1UECwwlS29yZWEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgQ2VudHJhbDEW +MBQGA1UEAwwNS0lTQSBSb290Q0EgMYIBBDAdBgNVHQ4EFgQUbbj/klyxv+lJeDF8gHXAYhBLvTgw +DgYDVR0PAQH/BAQDAgEGMIIBLgYDVR0gBIIBJTCCASEwggEdBgRVHSAAMIIBEzAwBggrBgEFBQcC +ARYkaHR0cDovL3d3dy5yb290Y2Eub3Iua3IvcmNhL2Nwcy5odG1sMIHeBggrBgEFBQcCAjCB0R6B +zsd0ACDHeMmdwRyylAAgrPXHeMd4yZ3BHMeFssiy5AAoAFQAaABpAHMAIABjAGUAcgB0AGkAZgBp +AGMAYQB0AGUAIABpAHMAIABhAGMAYwByAGUAZABpAHQAZQBkACAAdQBuAGQAZQByACAARQBsAGUA +YwB0AHIAbwBuAGkAYwAgAFMAaQBnAG4AYQB0AHUAcgBlACAAQQBjAHQAIABvAGYAIAB0AGgAZQAg +AFIAZQBwAHUAYgBsAGkAYwAgAG8AZgAgAEsAbwByAGUAYQApMDMGA1UdEQQsMCqgKAYJKoMajJpE +CgEBoBswGQwX7ZWc6rWt7KCE7J6Q7J247KadKOyjvCkwEgYDVR0TAQH/BAgwBgEB/wIBADAMBgNV +HSQEBTADgAEAMIGOBgNVHR8EgYYwgYMwgYCgfqB8hnpsZGFwOi8vZGlyLmNyb3NzY2VydC5jb206 +Mzg5L0NOPUtJU0EtUm9vdENBLTEsT1U9S29yZWEtQ2VydGlmaWNhdGlvbi1BdXRob3JpdHktQ2Vu +dHJhbCxPPUtJU0EsQz1LUj9hdXRob3JpdHlSZXZvY2F0aW9uTGlzdDANBgkqhkiG9w0BAQUFAAOC +AQEAfAovYTiiuBdEs42+wvBYT/+aVm6C2G4/Udk1Uo3JcMbCtpvHH+7cUvRXjNH6nCYXBcjnFCD1 +Zv17WL6hEfVa3WYJhQWSQXyadOp9pmpRFf1APuCtYq/JnV/uevkxoYmYXXzvT8teTK7BacEqg8/w +DzsHkk+xw6eXCgB5ul6fOBRHJEKPmWKHSgp0o5C+3pTi5siicEL+rHPQUzb/cPBBlhfOXrkMc5Vt +14oM8N5xfBZBgxX3fEFrj2vXhR8dYPsrqm7D+87YDUqN4kP637k7wWm74RtXAAcIp/m0iQ00OjDh +cBAEMLT/e9ObEcEK6l5nGfzZK/duSvq3PnAjWFznzw== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJLUjENMAsGA1UECgwE +S0lTQTEuMCwGA1UECwwlS29yZWEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgQ2VudHJhbDEWMBQG +A1UEAwwNS0lTQSBSb290Q0EgMTAeFw0wNTA4MjQwODA1NDZaFw0yNTA4MjQwODA1NDZaMGQxCzAJ +BgNVBAYTAktSMQ0wCwYDVQQKDARLSVNBMS4wLAYDVQQLDCVLb3JlYSBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSBDZW50cmFsMRYwFAYDVQQDDA1LSVNBIFJvb3RDQSAxMIIBIDANBgkqhkiG9w0BAQEF +AAOCAQ0AMIIBCAKCAQEAvATk+hM58DSWIGtsaLv623f/J/es7C/n/fB/bW+MKs0lCVsk9KFo/Cjs +ySXirO3eyDOE9bClCTqnsUdIxcxPjHmc+QZXfd3uOPbPFLKc6tPAXXdi8EcNuRpAU1xkcK8IWsD3 +z3X5bI1kKB4g/rcbGdNaZoNy4rCbvdMlFQ0yb2Q3lIVGyHK+d9VuHygvx2nt54OJM1jT3qC/QOhD +UO7cTWu8peqmyGGO9cNkrwYV3CmLP3WMvHFE2/yttRcdbYmDz8Yzvb9Fov4Kn6MRXw+5H5wawkbM +nChmn3AmPC7fqoD+jMUECSVPzZNHPDfqAmeS/vwiJFys0izgXAEzisEZ2wIBA6MyMDAwHQYDVR0O +BBYEFL+2J9gDWnZlTGEBQVYx5Yt7OtnMMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBABOvUQveimpb5poKyLGQSk6hAp3MiNKrZr097LuxQpVqslxa/6FjZJapaBV/JV6K+KRzwYCK +hQoOUugy50X4TmWAkZl0Q+VFnUkq8JSV3enhMNITbslOsXflBM+tWh6UCVrXPAgcrnrpFDLBRa3S +JkhyrKhB2vAhhzle3/xk/2F0KpzZm4tfwjeT2KM3LzuTa7IbB6d/CVDv0zq+IWuKkDsnSlFOa56c +h534eJAx7REnxqhZvvwYC/uOfi5C4e3nCSG9uRPFVmf0JqZCQ5BEVLRxm3bkGhKsGigA35vB1fjb +XKP4krG9tNT5UNkAAk/bg9ART6RCVmE6fhMy04Qfybo= +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/012.41b4b3980ab6389afe5647353b5abe882870b032.pem b/test/testdata/chains/012.41b4b3980ab6389afe5647353b5abe882870b032.pem new file mode 100644 index 0000000..c72fd6e --- /dev/null +++ b/test/testdata/chains/012.41b4b3980ab6389afe5647353b5abe882870b032.pem @@ -0,0 +1,73 @@ +-----BEGIN CERTIFICATE----- +MIIFVTCCBD2gAwIBAgIUKt2G2CQP6ZyX6+/O9gHZcFqZRikwDQYJKoZIhvcNAQEFBQAwUDELMAkG +A1UEBhMCS1IxHDAaBgNVBAoME0dvdmVybm1lbnQgb2YgS29yZWExDTALBgNVBAsMBEdQS0kxFDAS +BgNVBAMMC0NBMTM0MTAwMDMxMB4XDTEyMDEzMTAzMDIzOFoXDTE0MDUwMTE0NTk1OVowgYAxCzAJ +BgNVBAYTAktSMRwwGgYDVQQKDBNHb3Zlcm5tZW50IG9mIEtvcmVhMRgwFgYDVQQLDA9Hcm91cCBv +ZiBTZXJ2ZXIxHjAcBgNVBAsMFeq1kOycoeqzvO2Vmeq4sOyIoOu2gDEZMBcGA1UEAwwQYm1yaS5r +b3JlYS5hYy5rcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKuV73B+Gx1+DsUtIF5o +pF0B8vcMD1WibgWMRlgw0GG4zWhKxTIztNQ9pTctUdN4mrKvtf/rDCqDGXT9sagyipJY8OUukykA +hwqFVmfrceLbxZEGnF34HqAZrCOV/rH7S1qD0AZqnzfM7R8unXor43GPzdu5Uhf4NbUwYOpjeYm/ +vm48cx467kUjpLAAPsvg9E4Pgx7dvnwLuuDfnhcCKXTj3I2PrA1MDc32rpi8SNm/bbtO8Ze5Zb6H +o/dAiiLYd3rn/gyvvHKoZfCO/CeKI4u7MONyh8HWMxlpfPG3XOcINo61RMPq6d/hzKnwAh3zRy11 +J+EBJZO/SHhHnmVV3YcCAwEAAaOCAfQwggHwMHkGA1UdIwRyMHCAFI5G+A2eeHaizBrkD1F/UtdN +nFsboVSkUjBQMQswCQYDVQQGEwJLUjEcMBoGA1UECgwTR292ZXJubWVudCBvZiBLb3JlYTENMAsG +A1UECwwER1BLSTEUMBIGA1UEAwwLR1BLSVJvb3RDQTGCAicZMB0GA1UdDgQWBBRavyyJ3U49seIB +OfL702AETmd9ljAOBgNVHQ8BAf8EBAMCBaAwbQYDVR0gAQH/BGMwYTBfBgoqgxqGjSEFAwEJMFEw +KgYIKwYBBQUHAgEWHmh0dHA6Ly93d3cuZXBraS5nby5rci9jcHMuaHRtbDAjBggrBgEFBQcCAjAX +GhVFZHVjYXRpb24gQ2VydGlmaWNhdGUwGwYDVR0RBBQwEoIQYm1yaS5rb3JlYS5hYy5rcjAxBgNV +HRIEKjAooCYGCSqDGoyaRAoBAaAZMBcMFeq1kOycoeqzvO2Vmeq4sOyIoOu2gDCBhAYDVR0fBH0w +ezB5oHegdYZzbGRhcDovL2xkYXAuZXBraS5nby5rcjozODkvY249Y3JsMXAxZHAxMSxvdT1DUkws +b3U9R1BLSSxvPUdvdmVybm1lbnQgb2YgS29yZWEsYz1rcj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25M +aXN0O2JpbmFyeTANBgkqhkiG9w0BAQUFAAOCAQEAr5A5ISBwFUftV3M2/0T7FR77+Zli/wMtHjVd +i2KkvDtv3jotmtDLKNqsyhYTVtas7y8HtRdH/GGFNdG2wY+EKGZjI2tsHtMgZ0jb5xCCh8DMONsy +ACSTOlGp3eR1Y/1ER8yolR7jm67nFyNSAp0vjSCprXExQ9Q8UIqrm/6iYG6N08W7Or0l9qAT4Q5N +VNx068Jx+UF6Wj10gYCsbCG7YvEunPTLkldLxL3MeDoyFU3wx23MDnWYEr/EeLPZo7DyrOg++9Oq +ixNY1wuJr+WgfjGQjotiSGB6Bgy8pBZRSihGKGNbi6pp+r4UZWu+W53LL4qXoRbDBqRiXZ44C4Z0 +Ng== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgICJxkwDQYJKoZIhvcNAQELBQAwUDELMAkGA1UEBhMCS1IxHDAaBgNVBAoM +E0dvdmVybm1lbnQgb2YgS29yZWExDTALBgNVBAsMBEdQS0kxFDASBgNVBAMMC0dQS0lSb290Q0Ex +MB4XDTExMTIxNTA2MDAxM1oXDTIxMTIxNTA2MDAxM1owUDELMAkGA1UEBhMCS1IxHDAaBgNVBAoM +E0dvdmVybm1lbnQgb2YgS29yZWExDTALBgNVBAsMBEdQS0kxFDASBgNVBAMMC0NBMTM0MTAwMDMx +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwxGkCUA8iQHQdsTjMpV7zYjb3sBAvF/Q +K7OhhCfMGcUZVfh4z1A7X94Lxfu6CeyFn2KF2wy+AsCUs1xG+AqXB/y/zB9QPp1lZAEJotSyKbhQ +cJUNG+YwsdeEV8PIy2TvKmGjT6J+8G/RtRVA2I/lpOYcuFxS7ipu8kx78FHS9NyXiYGWPKxjemWs +VgYrfwjkcIt1mAt30nZEvcO9LuFSlldtGSEir6lZjLv9Igb/K2ayHmmnSB5i7y2DOzYKF6o1GnNF +c0fdK5VCuoyX4puQeDcSv3rVR6kwIL9c5HuC1czrD86cVY9kqe5qQUPeuvNd0gfG75mDv26yAMbm +Sx+LeQIDAQABo4IBnzCCAZsweAYDVR0jBHEwb4AUeAPrDIym01V1pIe069GaZg9Mc4uhVKRSMFAx +CzAJBgNVBAYTAktSMRwwGgYDVQQKDBNHb3Zlcm5tZW50IG9mIEtvcmVhMQ0wCwYDVQQLDARHUEtJ +MRQwEgYDVQQDDAtHUEtJUm9vdENBMYIBATAdBgNVHQ4EFgQUjkb4DZ54dqLMGuQPUX9S102cWxsw +DgYDVR0PAQH/BAQDAgEGME8GA1UdIARIMEYwDAYKKoMaho0hBQMBAzAMBgoqgxqGjSEFAwEBMAwG +CiqDGoaNIQUDAQcwDAYKKoMaho0hBQMBCTAMBgoqgxqGjSEFAwEFMBIGA1UdEwEB/wQIMAYBAf8C +AQAwDwYDVR0kAQH/BAUwA4ABADB6BgNVHR8EczBxMG+gbaBrhmlsZGFwOi8vY2VuLmRpci5nby5r +cjozODkvY249R1BLSVJvb3RDQTEsb3U9R1BLSSxvPUdvdmVybm1lbnQgb2YgS29yZWEsYz1LUj9h +dXRob3JpdHlSZXZvY2F0aW9ubGlzdDtiaW5hcnkwDQYJKoZIhvcNAQELBQADggEBAH22zMoINn+l +mZeGtxjvbSIzT8xvKH8VNw0KifIjqBbRS48duCctrCS5YGXkksNcDyAofKc1I0YyteeFJQtVGYXB +05NN10i/IwklDdOSCfsWGBprYoFG/dBaEt4cSh/cgTQYxQWYmPhxYPUDF24yIVJSUvt1heZnSBP8 +vHayUa5Cvyyh8NibORHyGRZ0183cJrpqjDgw80Y/YgD7CMxw6P/rRw9vx1c0pbhhp68uc1jrYvKN +xlfJrt/aGCm/sSxAPnbTUOtgBG22ghWnzamTtQingsgJiKF7GCDXeTRkt2GQgkHarm7vbZykMHmq +8w1dYdrwkPFb8E5ejajxn30Uyyo= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJLUjEcMBoGA1UECgwT +R292ZXJubWVudCBvZiBLb3JlYTENMAsGA1UECwwER1BLSTEUMBIGA1UEAwwLR1BLSVJvb3RDQTEw +HhcNMTEwODAzMDY1MjMwWhcNMzEwODAzMDY1MjMwWjBQMQswCQYDVQQGEwJLUjEcMBoGA1UECgwT +R292ZXJubWVudCBvZiBLb3JlYTENMAsGA1UECwwER1BLSTEUMBIGA1UEAwwLR1BLSVJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCh/m8EBbDJhGQyN2+g5dTlsgjtaRKqhgj3 +gkYKBgtuXsXkaTVxbf99AvbN3QE8+WCIaPJUd0091UGmLzaBVyW4ct+iUNrX/FXyzjafbNbbl1nf +HhaZhkiOTVQhmY5zuj96evEtJMevnxe6iRADOPWnqp+CxT2IzcSFkQCq7L2qn8hU2/LpXUvnAYgl +JZi8t6Ef+r03P1r8dA5OzZ8yV3qhD1R1wsNQtCzMgwcErFRZhFZYuxpfmS5y0fZW0seeTjcdxHiR +3whYI5U6AI7DjdWIrT9Cd9ByV4aevkBhqkePPIYGmUPXnnqCkdHdnzkMH0WP9TBhD2jTXZKdcFtT +yEJrAgMBAAGjQjBAMB0GA1UdDgQWBBR4A+sMjKbTVXWkh7Tr0ZpmD0xzizAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEARGJWATwo81x7UEQugNbicL8I +WXoV51SZVH3kz49fNUjVoq1n2yzfaMddlblbflDNObp/68DxTlSXCeqFHkgi/WvyVHERRECXnF0W +eeelI+Q8XdF3IJZLT3u5Ss0VAB2loCuC+4hBWSRQu2WZu2Yks9eBN0x6NmtopRmnf2d6VrcFA+WO +gUeTjXiDkG52IaPw0w1uTfmRw5epky5idyY2bfJ1JeVUINMJnOWpgLkOH3xxakoD8F1Fbi6C3t7M +mKupojUq/toUDms6zTk3DIkcwd7PALNWL5U8TxNLoroTHSf/lzaOv3o9KDRa0FQo58bPI7MdbRWE +4F3mS/ZIrnv7jQ== +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/013.9e862686af81aa013267c2b5fd098720734bc93b.pem b/test/testdata/chains/013.9e862686af81aa013267c2b5fd098720734bc93b.pem new file mode 100644 index 0000000..08b7b4e --- /dev/null +++ b/test/testdata/chains/013.9e862686af81aa013267c2b5fd098720734bc93b.pem @@ -0,0 +1,69 @@ +-----BEGIN CERTIFICATE----- +MIIGFTCCBP2gAwIBAgIDAuH+MA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNVBAYTAlVTMRcwFQYDVQQK +Ew5DeWJlcnRydXN0IEluYzERMA8GA1UECxMIU2VydmljZXMxDDAKBgNVBAsTA1BLSTEnMCUGA1UE +AxMeQ3liZXJ0cnVzdCBQdWJsaWMgSXNzdWluZyBDQSAxMB4XDTEzMDkwMzE3NDkwMloXDTE0MDkw +MzE3NDkwMlowgdMxEzARBgoJkiaJk/IsZAEZFgNnb3YxEjAQBgoJkiaJk/IsZAEZFgJ2YTElMCMG +A1UECAwcV2VzdCBWaXJnaW5pYSxEQ1w9dmEsRENcPWdvdjEXMBUGA1UEBxMORmFsbGluZyBXYXRl +cnMxKjAoBgNVBAoTIVVTIERlcGFydG1lbnQgb2YgVmV0ZXJhbnMgQWZmYWlyczEjMCEGA1UECxMa +Q2FwaXRvbCBSZWdpb24gRGF0YSBDZW50ZXIxFzAVBgNVBAMTDnd3dy5wYXkudmEuZ292MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1g8bd2bmPCPClPcM85qxhvGKKt/k2FxsipOyGec +imRM4V3weC3mKvUBOEJmQuLm2B81NTVJmEFBgxIlho09HGDxpMj8B+JGDe0VHkjvJgf6xod4a5gC +jHaKSkCv/DsIWeyi/9SIJlTUgc8MdAXU5PMCYUvOD8IEq6Q9L6g28g9FDeFGboDKCgtRSsLaEGoX +QV4VqTu6aBT1Ecrhy0IdcvE9O+LRCAvZh/z3+k0tpkA/C6gdH5mBiALjGbpqRbwCmXC04RkJG9r2 +WBA+cMXK1vhM7PrsdcJhqLvDTM3aqGhYxkR/8ixuYsHn7QML/AKXeEXrXD6phi4Foik8gO1Q8QID +AQABo4ICUjCCAk4wEwYDVR0RBAwwCocIAAAAAP///wAwgdMGCCsGAQUFBwEBBIHGMIHDMEAGCCsG +AQUFBzAChjRodHRwOi8vYWlhMS5jb20tc3Ryb25nLWlkLm5ldC9DQS9DVC1QVUJMSUMtSUNBLTEu +cDdjMH8GCCsGAQUFBzAChnNsZGFwOi8vZGlyMS5jb20tc3Ryb25nLWlkLm5ldC9jbj1DeWJlcnRy +dXN0IFB1YmxpYyBJc3N1aW5nIENBIDEsb3U9UEtJLG91PVNlcnZpY2VzLG89Q3liZXJ0cnVzdCwg +Yz1VUz9jQUNlcnRpZmljYXRlMA4GA1UdDwEB/wQEAwIFoDAjBgNVHSUEHDAaBgRVHSUABggrBgEF +BQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUVJyBRgoWyv9g+eqKRQp1XE6y3oowgesGA1UdHwSB +4zCB4DA8oDqgOIY2aHR0cDovL2NkcDEuY29tLXN0cm9uZy1pZC5uZXQvQ0RQL0NULVBVQkxJQy1J +LUNBLTEuY3JsMIGfoIGcoIGZhoGWbGRhcDovL2RpcjEuc3NwLXN0cm9uZy1pZC5uZXQvY24lM2RD +eWJlcnRydXN0JTIwUHVibGljJTIwSXNzdWluZyUyMENBJTIwMSxvdSUzZFBLSSxvdSUzZFNlcnZp +Y2VzLG8lM2RDeWJlcnRydXN0JTIwSW5jLGMlM2RVUz9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0 +MB0GA1UdDgQWBBSgzZLv1sNwrXLn8if9Dk6qpxQUFjANBgkqhkiG9w0BAQUFAAOCAQEAn5xdVJ99 +9WnC+NPfnb7TAobQzKKnsuQu0AfHXFLaLkgVGePG7uBo+W3dtWWLk45YO8ae5hOAcPhhnarYIQir +5TaPYpSoZvzOu/bRw8ZajPQsuGNv0PbGPhqLP8MzGJxlCHeWkG44MPSvRpbuh5IrWsI7eqpInUPq +azJMUyC+D+HhqW9GHyAfWsZyR/NlNFN2R3mx2EbTkflfD1vGWPhyqW7i33nh2MKMiuQMe2rUO5iL +ZsLA/JaIshWp4vjFQLo/a8lrcUNan5gs45sO6ibTK6Vpr03KpVg3GVa4PsfdGiY6PLPtyK0WIF2O +ETUkNcR0xhFmuK850LNDorhyzUdCiw== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIEUzCCA7ygAwIBAgIEBycUnzANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJVUzEYMBYGA1UE +ChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIElu +Yy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEdsb2JhbCBSb290MB4XDTA3MDcxMTE4MTYxMFoX +DTE3MDcxMTE4MTUyMFowcDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkN5YmVydHJ1c3QgSW5jMREw +DwYDVQQLEwhTZXJ2aWNlczEMMAoGA1UECxMDUEtJMScwJQYDVQQDEx5DeWJlcnRydXN0IFB1Ymxp +YyBJc3N1aW5nIENBIDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCybxFtLjT4sEpg +TGaXge5SLQp13Uq5uznggVhoT0+deGB550ttPPeGAC4hSEnGjeMhvKszR+r1/biw0NpGCxMR7wAM +nwFW0cBZIgjWNdoHfooFyGweAqB+SQMvJUNibbHKw1RQH2CpQoGwDk8wNfpP04My+xwnYKaqZ3FR +IexAUNzeoCVBzIIDruXgmEmmF2eUtWy9VhWFvNxHLJjVGYDL3Ai9AciKntwV71YwR1XZqga6/Fxq +fdWDzltCANQhBE/yoI+PQTemTJ0bnsHmL0XszBdKE5CFZgVebQu9ssVomVu8SEnnDJFx0fj1EhT8 +9+mDTzae6Zd7Npe1B3BB1XllAgMBAAGjggFvMIIBazASBgNVHRMBAf8ECDAGAQH/AgEAMFMGA1Ud +IARMMEowSAYJKwYBBAGxPgEAMDswOQYIKwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmly +b290LmNvbS9yZXBvc2l0b3J5LmNmbTAOBgNVHQ8BAf8EBAMCAcYwgYkGA1UdIwSBgTB/oXmkdzB1 +MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3li +ZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEdsb2JhbCBS +b290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vd3d3LnB1YmxpYy10cnVzdC5jb20vY2dp +LWJpbi9DUkwvMjAxOC9jZHAuY3JsMB0GA1UdDgQWBBRUnIFGChbK/2D56opFCnVcTrLeijANBgkq +hkiG9w0BAQUFAAOBgQAPZ5XZrnS2sqq3+uErzpLVkpRbQRWUUdCxMtN7RLL3HKc8xmXRJ3lhMslE +AgDk+xr5QxLOuUa2EqTnMFlwvh9J81O3+uZACBZqWkb1db2ThW49WnlO7Nok78Rmb6ZQvTTNjsCx +GYKcu9v6m4Z6AK2Y6vyc+jTx/WL+jlTJMkOahg== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + + diff --git a/test/testdata/chains/5.96974cd6b663a7184526b1d648ad815cf51e801a.pem b/test/testdata/chains/5.96974cd6b663a7184526b1d648ad815cf51e801a.pem deleted file mode 100644 index 4b0bcf5..0000000 --- a/test/testdata/chains/5.96974cd6b663a7184526b1d648ad815cf51e801a.pem +++ /dev/null @@ -1,49 +0,0 @@ -Certificate: - Data: - Version: 1 (0x0) - Serial Number: - ca:f6:c1:f5:70:e2:e9:03:6b:73:fc:06:92:1c:2a:f1 - Signature Algorithm: md2WithRSAEncryption - Issuer: L=Internet, O=VeriSign, Inc., OU=VeriSign Individual Software Publishers CA - Validity - Not Before: Apr 9 00:00:00 1996 GMT - Not After : Jan 7 23:59:59 2004 GMT - Subject: L=Internet, O=VeriSign, Inc., OU=VeriSign Individual Software Publishers CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (1024 bit) - Modulus: - 00:c1:7a:ca:65:a7:2d:d5:0f:4f:6c:47:32:f8:73: - 77:86:e5:3d:f2:6f:79:90:b5:de:4f:d2:14:79:33: - 4b:b2:0e:46:fd:88:72:2a:c2:fc:64:9e:61:8f:6b: - c0:5b:e8:f0:11:04:da:7a:ba:72:f6:ec:5d:af:fb: - f1:97:f1:14:d2:28:e3:28:85:5c:7b:bd:8a:a2:7f: - c2:33:b5:6d:0b:92:78:0b:38:71:74:85:7f:3d:ba: - b9:2f:ef:be:27:48:0d:38:22:c9:56:30:8d:77:fa: - 5d:2c:5a:9c:97:ce:70:30:e9:51:5f:a6:8b:e4:95: - 96:a5:a0:17:77:f1:93:b8:29 - Exponent: 65537 (0x10001) - Signature Algorithm: md2WithRSAEncryption - c0:8f:9f:d7:7f:0e:26:f7:47:fd:65:7b:63:92:06:46:cc:e0: - 5e:4b:81:58:b3:97:ec:05:18:13:15:d6:97:10:2a:7f:f8:56: - ad:d8:0e:7a:bf:27:48:45:07:9f:25:22:ac:70:12:06:69:37: - 80:58:58:d8:8a:34:5a:07:c6:27:da:c6:fb:9e:b1:7d:64:20: - 80:4f:f4:a1:15:7b:19:61:af:c6:ab:c0:26:65:fe:8c:07:87: - 3f:1a:c4:8e:b2:40:44:10:7e:2c:61:7a:fe:ad:a3:c8:be:cc: - bd:5e:aa:af:ac:c5:bf:11:67:58:2a:32:f3:25:dd:b6:74:43: - c0:7c ------BEGIN CERTIFICATE----- -MIICQTCCAaoCEQDK9sH1cOLpA2tz/AaSHCrxMA0GCSqGSIb3DQEBAgUAMGExETAP -BgNVBAcTCEludGVybmV0MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEzMDEGA1UE -CxMqVmVyaVNpZ24gSW5kaXZpZHVhbCBTb2Z0d2FyZSBQdWJsaXNoZXJzIENBMB4X -DTk2MDQwOTAwMDAwMFoXDTA0MDEwNzIzNTk1OVowYTERMA8GA1UEBxMISW50ZXJu -ZXQxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTMwMQYDVQQLEypWZXJpU2lnbiBJ -bmRpdmlkdWFsIFNvZnR3YXJlIFB1Ymxpc2hlcnMgQ0EwgZ8wDQYJKoZIhvcNAQEB -BQADgY0AMIGJAoGBAMF6ymWnLdUPT2xHMvhzd4blPfJveZC13k/SFHkzS7IORv2I -cirC/GSeYY9rwFvo8BEE2nq6cvbsXa/78ZfxFNIo4yiFXHu9iqJ/wjO1bQuSeAs4 -cXSFfz26uS/vvidIDTgiyVYwjXf6XSxanJfOcDDpUV+mi+SVlqWgF3fxk7gpAgMB -AAEwDQYJKoZIhvcNAQECBQADgYEAwI+f138OJvdH/WV7Y5IGRszgXkuBWLOX7AUY -ExXWlxAqf/hWrdgOer8nSEUHnyUirHASBmk3gFhY2Io0WgfGJ9rG+56xfWQggE/0 -oRV7GWGvxqvAJmX+jAeHPxrEjrJARBB+LGF6/q2jyL7MvV6qr6zFvxFnWCoy8yXd -tnRDwHw= ------END CERTIFICATE----- -- cgit v1.1 From 575d810afbcfcca99f701f3ea42de79bf6e283d1 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 25 Mar 2015 09:53:54 +0100 Subject: Add rel to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 17278c0..3684b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.beam +rel -- cgit v1.1 From 94282d502072f894f3168ef8c2c7527fe4a69e52 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 25 Mar 2015 09:55:03 +0100 Subject: Add tools/fetchacert.py, fetching exactly one chain from a log. Also move a piece of common code from fetchallcerts.py to certtools.py. --- tools/certtools.py | 10 ++++++++++ tools/fetchacert.py | 22 ++++++++++++++++++++++ tools/fetchallcerts.py | 10 ---------- 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100755 tools/fetchacert.py diff --git a/tools/certtools.py b/tools/certtools.py index 0e639f2..ffd631c 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -588,3 +588,13 @@ def verify_inclusion_proof(inclusion_proof, index, treesize, leafhash): chain = zip([(index, 0)] + nodes_for_index(index, treesize), [leafhash] + inclusion_proof) (_, hash) = reduce(lambda e1, e2: combine_two_hashes(e1, e2, treesize), chain) return hash + +def extract_original_entry(entry): + leaf_input = base64.decodestring(entry["leaf_input"]) + (leaf_cert, timestamp, issuer_key_hash) = unpack_mtl(leaf_input) + extra_data = base64.decodestring(entry["extra_data"]) + if issuer_key_hash != None: + (precert, extra_data) = extract_precertificate(extra_data) + leaf_cert = precert + certchain = decode_certificate_chain(extra_data) + return ([leaf_cert] + certchain, timestamp, issuer_key_hash) diff --git a/tools/fetchacert.py b/tools/fetchacert.py new file mode 100755 index 0000000..82ea7c1 --- /dev/null +++ b/tools/fetchacert.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse +import base64 +from certtools import * + +parser = argparse.ArgumentParser(description='') +parser.add_argument('baseurl', help="Base URL for CT server") +parser.add_argument('index', type=int, help="Index for entry to fetch") +args = parser.parse_args() + +rawentries = get_entries(args.baseurl, args.index, args.index)["entries"] +entry = extract_original_entry(rawentries[0]) +(chain, _timestamp, _issuer_key_hash) = entry +s = "" +for cert in chain: + s += "-----BEGIN CERTIFICATE-----\n" + s += base64.encodestring(cert).rstrip() + "\n" + s += "-----END CERTIFICATE-----\n" + s += "\n" +print s diff --git a/tools/fetchallcerts.py b/tools/fetchallcerts.py index 398c563..e0ea92f 100755 --- a/tools/fetchallcerts.py +++ b/tools/fetchallcerts.py @@ -24,16 +24,6 @@ parser.add_argument('--store', default=None, metavar="dir", help='Store certific parser.add_argument('--write-sth', action='store_true', help='Write STH') args = parser.parse_args() -def extract_original_entry(entry): - leaf_input = base64.decodestring(entry["leaf_input"]) - (leaf_cert, timestamp, issuer_key_hash) = unpack_mtl(leaf_input) - extra_data = base64.decodestring(entry["extra_data"]) - if issuer_key_hash != None: - (precert, extra_data) = extract_precertificate(extra_data) - leaf_cert = precert - certchain = decode_certificate_chain(extra_data) - return ([leaf_cert] + certchain, timestamp, issuer_key_hash) - def get_entries_wrapper(baseurl, start, end): fetched_entries = 0 while start + fetched_entries < (end + 1): -- cgit v1.1 From 4702c16d704d009819e203a5dfa5913c55e8f756 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 25 Mar 2015 11:20:56 +0100 Subject: Document (some of) the Python dependencies needed for merge. --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a012f3d..47bbd93 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,26 @@ catlfish is a Certificate Transparency log server (RFC 6962). # Requirements -A compiled plop application in ../plop +In order to compile catlfish, the following software packages are +needed: -A compiled https://github.com/basho/lager (for logging) in ../lager -A compiled https://github.com/mochi/mochiweb (for web server functionality) in ../mochiweb -A compiled https://github.com/benoitc/hackney.git (http client) in ../hackney +- A compiled https://git.nordu.net/plop.git application in ../plop + +- A compiled https://github.com/basho/lager (for logging) in ../lager + +- A compiled https://github.com/mochi/mochiweb (for web server + functionality) in ../mochiweb + +- A compiled https://github.com/benoitc/hackney.git (http client) in + ../hackney Note: hackney is dependent on rebar, but doesn't include one. You can use the rebar from lager by adding "REBAR=../lager/rebar" to the make command line, or install rebar yourself. +In order to perform merge operations, the following software packages +are needed: python-ecdsa, python-yaml + # Start $ (cd rel ; bin/erl -config catlfish) -- cgit v1.1 From b3e0e91b7e7e50e5f907e1872751a1a82a2f6351 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 26 Mar 2015 13:20:42 +0100 Subject: [Docker] Use newer mochiweb, lager and hackney. --- packaging/docker/catlfish-dev/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/docker/catlfish-dev/Dockerfile b/packaging/docker/catlfish-dev/Dockerfile index 0326aea..8849734 100644 --- a/packaging/docker/catlfish-dev/Dockerfile +++ b/packaging/docker/catlfish-dev/Dockerfile @@ -7,13 +7,13 @@ RUN apt-get install -qq \ WORKDIR /opt -RUN git clone -b v2.9.2 https://github.com/mochi/mochiweb +RUN git clone -b v2.12.2 https://github.com/mochi/mochiweb RUN make -C mochiweb -RUN git clone -b 2.1.0 https://github.com/basho/lager +RUN git clone -b 2.1.1 https://github.com/basho/lager RUN make -C lager -RUN git clone -b 1.0.6-ndn-3 https://github.com/NORDUnet/hackney.git +RUN git clone -b 1.1.0 https://github.com/benoitc/hackney.git RUN make -C hackney REBAR=../lager/rebar RUN git clone https://git.nordu.net/plop.git -- cgit v1.1 From d0e9ab32bab65d1f57e946427c53e52903bb26f7 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 26 Mar 2015 13:21:28 +0100 Subject: Cosmetic changes. --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b54bc50..a5de5f3 100644 --- a/Makefile +++ b/Makefile @@ -3,14 +3,10 @@ build all: clean: -rm ebin/*.beam release: all - test ! -f rel/db/treesize || \ - test $$(cat rel/db/treesize) = 0 && \ - rm -rf rel rm -rf rel mkdir rel ./makerelease.erl - (cd rel; \ - ln -s ../../plop/test .) + (cd rel; ln -s ../../plop/test .) cp httpd_props.conf rel cp catlfish.config rel cp storage_node.config rel -- cgit v1.1 From 3c03b0a40abb4fb74ed344e3a7d0f89fbdb061d7 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 26 Mar 2015 13:21:52 +0100 Subject: [Make] db/treesize isn't used anymore. --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index a5de5f3..cd445e3 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,6 @@ tests-prepare: @for machine in $(MACHINES); do \ tools/compileconfig.py --config=test/catlfish-test.cfg --localconfig test/catlfish-test-local-$$machine.cfg ; \ mkdir -p rel/tests/machine/machine-$$machine/db ; \ - printf "0" > rel/tests/machine/machine-$$machine/db/treesize ; \ touch rel/tests/machine/machine-$$machine/db/index ; \ touch rel/tests/machine/machine-$$machine/db/newentries ; \ done -- cgit v1.1 From d0a85087278f46b60ea3ada2e9d39bb4e82fad85 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 26 Mar 2015 14:11:38 +0100 Subject: Don't apt-get -qq. Set frontend 'noninteractive' first. --- packaging/docker/base-debian:jessie/Dockerfile | 3 ++- packaging/docker/catlfish-dev/Dockerfile | 3 ++- packaging/docker/erlang/Dockerfile | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packaging/docker/base-debian:jessie/Dockerfile b/packaging/docker/base-debian:jessie/Dockerfile index 1c248c0..6a30a45 100644 --- a/packaging/docker/base-debian:jessie/Dockerfile +++ b/packaging/docker/base-debian:jessie/Dockerfile @@ -1,4 +1,5 @@ FROM debian:jessie RUN apt-get update -RUN apt-get install -qq supervisor +RUN echo 'debconf debconf/frontend select noninteractive' | debconf-set-selections +RUN apt-get install -y -q supervisor RUN mkdir -p /var/log/supervisor diff --git a/packaging/docker/catlfish-dev/Dockerfile b/packaging/docker/catlfish-dev/Dockerfile index 8849734..cbfc285 100644 --- a/packaging/docker/catlfish-dev/Dockerfile +++ b/packaging/docker/catlfish-dev/Dockerfile @@ -1,6 +1,7 @@ FROM erlang RUN apt-get update -RUN apt-get install -qq \ +RUN echo 'debconf debconf/frontend select noninteractive' | debconf-set-selections +RUN apt-get install -y -q \ gcc \ git \ make diff --git a/packaging/docker/erlang/Dockerfile b/packaging/docker/erlang/Dockerfile index 2212df6..c33a22b 100644 --- a/packaging/docker/erlang/Dockerfile +++ b/packaging/docker/erlang/Dockerfile @@ -1,6 +1,7 @@ FROM base RUN apt-get update -RUN apt-get install -qq \ +RUN echo 'debconf debconf/frontend select noninteractive' | debconf-set-selections +RUN apt-get install -y -q \ erlang-base \ erlang-crypto \ erlang-dev \ -- cgit v1.1 From f38b28bfe90ac9c35c8b2edcf296720e189f7083 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 26 Mar 2015 14:45:38 +0100 Subject: Update docker docu a bit. --- packaging/docker/README | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packaging/docker/README b/packaging/docker/README index 24e7e1b..0a75c10 100644 --- a/packaging/docker/README +++ b/packaging/docker/README @@ -1,14 +1,16 @@ Requirements: - lack of expectations regarding security -- docker doesn't verify downloaded images -- a 64-bit debian or ubuntu system +- a 64-bit Linux system - lxc-docker version 1.3 or later -Build a docker image with catlfish: +Build a docker image with catlfish. Note that you will have to cd into +this directory, catlfish/packaging/docker, in order for docker to find +the appropriate docker files. $ ./build.sh -The resulting image can be run in interactive mode by: +Run the resulting image in interactive mode. $ docker run -it --rm catlfish /bin/bash -- cgit v1.1 From db1c5cf0eeba9b47a3f89bebc1abd9985ee9fef7 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 26 Mar 2015 23:59:30 +0100 Subject: Provide function for calculating entryhash from entry --- Makefile | 2 +- src/catlfish.erl | 25 +++++++++++++++++++++++++ tools/compileconfig.py | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cd445e3..58a1160 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ build all: - erl -pa ../lager/ebin -make + ./make.erl clean: -rm ebin/*.beam release: all diff --git a/src/catlfish.erl b/src/catlfish.erl index 2e5ffd4..ed75495 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -5,6 +5,7 @@ -export([add_chain/3, entries/2, entry_and_proof/2]). -export([known_roots/0, update_known_roots/0]). -export([init_cache_table/0]). +-export([entryhash_from_entry/1]). -include_lib("eunit/include/eunit.hrl"). -define(PROTOCOL_VERSION, 0). @@ -243,6 +244,30 @@ init_cache_table() -> end, ets:new(?CACHE_TABLE, [set, public, named_table]). +deserialise_extra_data(ExtraData) -> + case decode_tls_vector(ExtraData, 3) of + {E, <<>>} -> + [E]; + {E, Rest} -> + [E | deserialise_extra_data(Rest)] + end. + +entryhash_from_entry(Entry) -> + {MTLText, ExtraDataPacked} = unpack_entry(Entry), + {ExtraData, <<>>} = decode_tls_vector(ExtraDataPacked, 3), + MTL = deserialise_mtl(MTLText), + TimestampedEntry = MTL#mtl.entry, + Chain = deserialise_extra_data(ExtraData), + Data = + case TimestampedEntry#timestamped_entry.entry_type of + x509_entry -> + SignedEntry = TimestampedEntry#timestamped_entry.signed_entry, + [SignedEntry#signed_x509_entry.asn1_cert | Chain]; + precert_entry -> + Chain + end, + crypto:hash(sha256, Data). + %% Private functions. -spec unpack_entry(binary()) -> {binary(), binary()}. unpack_entry(Entry) -> diff --git a/tools/compileconfig.py b/tools/compileconfig.py index 30424c5..e3d9ee0 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -188,6 +188,8 @@ def gen_config(nodename, config, localconfig): if nodetype == "frontendnodes": plopconfig += [ (Symbol("sth_path"), paths["db"] + "sth"), + (Symbol("entryhash_from_entry"), + (Symbol("catlfish"), Symbol("entryhash_from_entry"))), ] signingnode = config["signingnodes"][0] -- cgit v1.1 From 0fffec2394f2d1962b44eaea4786f9ad69dea901 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 27 Mar 2015 11:38:24 +0100 Subject: Add make.erl. --- make.erl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 make.erl diff --git a/make.erl b/make.erl new file mode 100755 index 0000000..4ebdf74 --- /dev/null +++ b/make.erl @@ -0,0 +1,18 @@ +#!/usr/bin/env escript +%% -*- erlang -*- + +main(_) -> + LagerPath = "../lager/ebin", + case code:add_path(LagerPath) of + true -> + ok; + {error, bad_directory} -> + io:format("Could not add path ~p~n", [LagerPath]), + halt(1) + end, + case make:all() of + up_to_date -> + ok; + error -> + halt(1) + end. -- cgit v1.1 From 6cd3c80c61711cabf9e308da1963b56f6dd49cfd Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 27 Mar 2015 03:08:06 +0100 Subject: Handle multiple signing nodes --- tools/compileconfig.py | 7 ++++--- tools/merge.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tools/compileconfig.py b/tools/compileconfig.py index e3d9ee0..52f10e8 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -192,7 +192,8 @@ def gen_config(nodename, config, localconfig): (Symbol("catlfish"), Symbol("entryhash_from_entry"))), ] - signingnode = config["signingnodes"][0] + signingnodes = config["signingnodes"] + signingnodeaddresses = ["https://%s/ct/signing/" % node["address"] for node in config["signingnodes"]] mergenodenames = [node["name"] for node in config["mergenodes"]] storagenodeaddresses = ["https://%s/ct/storage/" % node["address"] for node in config["storagenodes"]] frontendnodenames = [node["name"] for node in config["frontendnodes"]] @@ -207,7 +208,7 @@ def gen_config(nodename, config, localconfig): services = [Symbol("ht")] allowed_clients += allowed_clients_frontend(mergenodenames) allowed_clients += allowed_clients_public() - allowed_servers += allowed_servers_frontend([signingnode["name"]], storagenodenames) + allowed_servers += allowed_servers_frontend([node["name"] for node in signingnodes], storagenodenames) elif nodetype == "storagenodes": allowed_clients += allowed_clients_storage(frontendnodenames, mergenodenames) services = [] @@ -226,7 +227,7 @@ def gen_config(nodename, config, localconfig): (Symbol("own_key"), (nodename, "%s/%s-private.pem" % (paths["privatekeys"], nodename))), ] if nodetype == "frontendnodes": - plopconfig.append((Symbol("signing_node"), "https://%s/ct/signing/" % signingnode["address"])) + plopconfig.append((Symbol("signing_nodes"), signingnodeaddresses)) plopconfig += [ (Symbol("allowed_clients"), allowed_clients), (Symbol("allowed_servers"), allowed_servers), diff --git a/tools/merge.py b/tools/merge.py index 75e72ae..e6fae24 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -34,7 +34,7 @@ storagenodes = config["storagenodes"] paths = localconfig["paths"] mergedb = paths["mergedb"] -signingnode = config["signingnodes"][0] +signingnodes = config["signingnodes"] chainsdir = mergedb + "/chains" logorderfile = mergedb + "/logorder" @@ -238,8 +238,13 @@ tree_size = len(logorder) root_hash = tree[-1][0] timestamp = int(time.time() * 1000) -tree_head_signature = create_sth_signature(tree_size, timestamp, - root_hash, "https://%s/" % signingnode["address"], key=own_key) +for signingnode in signingnodes: + try: + tree_head_signature = create_sth_signature(tree_size, timestamp, + root_hash, "https://%s/" % signingnode["address"], key=own_key) + break + except urllib2.URLError: + pass sth = {"tree_size": tree_size, "timestamp": timestamp, "sha256_root_hash": base64.b64encode(root_hash), -- cgit v1.1 From e436e8ee9fb3239baa6bd926bf5ef1bc501734b6 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 27 Mar 2015 16:56:19 +0100 Subject: Fix typo in makerelease.erl. Conflicts: makerelease.erl --- makerelease.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makerelease.erl b/makerelease.erl index f72fdb6..16e5f7f 100755 --- a/makerelease.erl +++ b/makerelease.erl @@ -1,7 +1,7 @@ #!/usr/bin/env escript %% -*- erlang -*- -main(_) -> +main([DestDir]) -> {ok, Conf} = file:consult("reltool.config"), {ok, Spec} = reltool:get_target_spec(Conf), - ok = reltool:eval_target_spec(Spec, code:root_dir(), "rel"). + ok = reltool:eval_target_spec(Spec, code:root_dir(), DestDir). -- cgit v1.1 From 1481cc600aee9f8a8b9308fc5cb99bb93ec1898d Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 27 Mar 2015 17:02:10 +0100 Subject: Revert "Fix typo in makerelease.erl." This reverts commit e436e8ee9fb3239baa6bd926bf5ef1bc501734b6. --- makerelease.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makerelease.erl b/makerelease.erl index 16e5f7f..f72fdb6 100755 --- a/makerelease.erl +++ b/makerelease.erl @@ -1,7 +1,7 @@ #!/usr/bin/env escript %% -*- erlang -*- -main([DestDir]) -> +main(_) -> {ok, Conf} = file:consult("reltool.config"), {ok, Spec} = reltool:get_target_spec(Conf), - ok = reltool:eval_target_spec(Spec, code:root_dir(), DestDir). + ok = reltool:eval_target_spec(Spec, code:root_dir(), "rel"). -- cgit v1.1 From 2d8d55bb9b6672ebe829b185beb05d4a399167f5 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 27 Mar 2015 17:33:35 +0100 Subject: Allow local administrator to override IP address and port for web server Closes CATLFISH-30 --- test/catlfish-test-local-1.cfg | 7 +++++++ test/catlfish-test-local-signing.cfg | 3 +++ test/catlfish-test.cfg | 10 +++++----- tools/certkeys.py | 4 ++++ tools/compileconfig.py | 21 +++++++++++++++------ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/test/catlfish-test-local-1.cfg b/test/catlfish-test-local-1.cfg index 5e9a593..608d4c0 100644 --- a/test/catlfish-test-local-1.cfg +++ b/test/catlfish-test-local-1.cfg @@ -2,6 +2,13 @@ localnodes: - frontend-1 - storage-1 +addresses: + frontend-1: 127.0.0.1:8082 + storage-1: 127.0.0.1:8081 + +publicaddresses: + frontend-1: 127.0.0.1:8080 + paths: configdir: test/config/ knownroots: known_roots diff --git a/test/catlfish-test-local-signing.cfg b/test/catlfish-test-local-signing.cfg index 047c02b..2cc4df2 100644 --- a/test/catlfish-test-local-signing.cfg +++ b/test/catlfish-test-local-signing.cfg @@ -1,6 +1,9 @@ localnodes: - signing-1 +addresses: + signing-1: 127.0.0.1:8088 + paths: configdir: test/config/ https_certfile: catlfish/webroot/certs/webcert.pem diff --git a/test/catlfish-test.cfg b/test/catlfish-test.cfg index 7a4bb18..3131415 100644 --- a/test/catlfish-test.cfg +++ b/test/catlfish-test.cfg @@ -1,17 +1,17 @@ -baseurl: https://127.0.0.1:8080/ +baseurl: https://localhost:8080/ frontendnodes: - name: frontend-1 - publicaddress: 127.0.0.1:8080 - address: 127.0.0.1:8082 + publicaddress: localhost:8080 + address: localhost:8082 storagenodes: - name: storage-1 - address: 127.0.0.1:8081 + address: localhost:8081 signingnodes: - name: signing-1 - address: 127.0.0.1:8088 + address: localhost:8088 mergenodes: - name: merge-1 diff --git a/tools/certkeys.py b/tools/certkeys.py index 3c459e9..52d61be 100644 --- a/tools/certkeys.py +++ b/tools/certkeys.py @@ -8,6 +8,10 @@ publickeys = { "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", + "https://localhost:8080/": + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" + "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", + "https://flimsy.ct.nordu.net/": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", diff --git a/tools/compileconfig.py b/tools/compileconfig.py index 52f10e8..88d6b51 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -77,17 +77,24 @@ def get_node_config(nodename, config): sys.exit(1) return (nodetype, nodeconfig) -def gen_https_servers(nodetype, nodeconfig): +def gen_https_servers(nodetype, nodeconfig, bind_address, bind_publicaddress): + if bind_address: + (host, port) = parse_address(bind_address) + else: + (_, port) = parse_address(nodeconfig["address"]) + host = "0.0.0.0" if nodetype == "frontendnodes": - (publichost, publicport) = parse_address(nodeconfig["publicaddress"]) - (host, port) = parse_address(nodeconfig["address"]) + if bind_publicaddress: + (publichost, publicport) = parse_address(bind_publicaddress) + else: + (_, publicport) = parse_address(nodeconfig["publicaddress"]) + publichost = "0.0.0.0" + return [(Symbol("external_https_api"), publichost, publicport, Symbol("v1")), (Symbol("frontend_https_api"), host, port, Symbol("frontend"))] elif nodetype == "storagenodes": - (host, port) = parse_address(nodeconfig["address"]) return [(Symbol("storage_https_api"), host, port, Symbol("storage"))] elif nodetype == "signingnodes": - (host, port) = parse_address(nodeconfig["address"]) return [(Symbol("signing_https_api"), host, port, Symbol("signing"))] def allowed_clients_frontend(mergenodenames): @@ -136,13 +143,15 @@ def allowed_servers_frontend(signingnodenames, storagenodenames): def gen_config(nodename, config, localconfig): print "generating config for", nodename paths = localconfig["paths"] + bind_address = localconfig.get("addresses", {}).get(nodename) + bind_publicaddress = localconfig.get("publicaddresses", {}).get(nodename) options = localconfig.get("options", []) configfile = open(paths["configdir"] + nodename + ".config", "w") print >>configfile, "%% catlfish configuration file (-*- erlang -*-)" (nodetype, nodeconfig) = get_node_config(nodename, config) - https_servers = gen_https_servers(nodetype, nodeconfig) + https_servers = gen_https_servers(nodetype, nodeconfig, bind_address, bind_publicaddress) catlfishconfig = [] plopconfig = [] -- cgit v1.1 From 22cefc84254cae1f57195da819eba69dbacb5a6e Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 30 Mar 2015 15:41:13 +0200 Subject: Allow non-TLS http Closes CATLFISH-31 --- src/catlfish_sup.erl | 34 ++++++++++++++++++++-------------- src/catlfish_web.erl | 6 +++--- tools/compileconfig.py | 24 ++++++++++++++++++------ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/catlfish_sup.erl b/src/catlfish_sup.erl index 6f918cd..882a017 100644 --- a/src/catlfish_sup.erl +++ b/src/catlfish_sup.erl @@ -9,6 +9,21 @@ start_link(_Args) -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). +gen_http_config(Config, SSLOptions, SSLFlag) -> + {ChildName, IpAddress, Port, Module} = Config, + {ok, IPv4Address} = + inet:parse_ipv4strict_address(IpAddress), + WebConfig = [{ip, IPv4Address}, + {port, Port}, + {ssl, SSLFlag}, + {acceptor_pool_size, application:get_env(catlfish, http_server_pool_size, 16)}, + {ssl_opts, SSLOptions} + ], + {ChildName, + {catlfish_web, start, [WebConfig, Module, ChildName]}, + permanent, 5000, + worker, dynamic}. + init([]) -> SSLOptions = [{certfile, application:get_env(catlfish, https_certfile, none)}, @@ -16,20 +31,11 @@ init([]) -> {cacertfile, application:get_env(catlfish, https_cacertfile, none)}], Servers = lists:map(fun (Config) -> - {ChildName, IpAddress, Port, Module} = Config, - {ok, IPv4Address} = - inet:parse_ipv4strict_address(IpAddress), - WebConfig = [{ip, IPv4Address}, - {port, Port}, - {ssl, true}, - {acceptor_pool_size, application:get_env(catlfish, http_server_pool_size, 16)}, - {ssl_opts, SSLOptions} - ], - {ChildName, - {catlfish_web, start, [WebConfig, Module]}, - permanent, 5000, - worker, dynamic} - end, application:get_env(catlfish, https_servers, [])), + gen_http_config(Config, SSLOptions, true) + end, application:get_env(catlfish, https_servers, [])) ++ + lists:map(fun (Config) -> + gen_http_config(Config, SSLOptions, false) + end, application:get_env(catlfish, http_servers, [])), lager:debug("Starting servers ~p", [Servers]), {ok, {{one_for_one, 3, 10}, diff --git a/src/catlfish_web.erl b/src/catlfish_web.erl index 5ee5743..f9fe6d6 100644 --- a/src/catlfish_web.erl +++ b/src/catlfish_web.erl @@ -2,14 +2,14 @@ %%% See LICENSE for licensing information. -module(catlfish_web). --export([start/2, loop/2]). +-export([start/3, loop/2]). -start(Options, Module) -> +start(Options, Module, Name) -> lager:debug("Starting catlfish web server: ~p", [Module]), Loop = fun (Req) -> ?MODULE:loop(Req, Module) end, - mochiweb_http:start([{name, Module}, {loop, Loop} | Options]). + mochiweb_http:start([{name, Name}, {loop, Loop} | Options]). add_auth(Path, {Code, Headers, Data}) -> diff --git a/tools/compileconfig.py b/tools/compileconfig.py index 88d6b51..4996994 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -77,7 +77,7 @@ def get_node_config(nodename, config): sys.exit(1) return (nodetype, nodeconfig) -def gen_https_servers(nodetype, nodeconfig, bind_address, bind_publicaddress): +def gen_http_servers(nodetype, nodeconfig, bind_address, bind_publicaddress, bind_publichttpaddress): if bind_address: (host, port) = parse_address(bind_address) else: @@ -90,12 +90,22 @@ def gen_https_servers(nodetype, nodeconfig, bind_address, bind_publicaddress): (_, publicport) = parse_address(nodeconfig["publicaddress"]) publichost = "0.0.0.0" - return [(Symbol("external_https_api"), publichost, publicport, Symbol("v1")), - (Symbol("frontend_https_api"), host, port, Symbol("frontend"))] + http_servers = [] + https_servers = [] + if bind_publichttpaddress: + (publichttphost, publichttpport) = parse_address(bind_publichttpaddress) + http_servers.append((Symbol("external_http_api"), publichttphost, publichttpport, Symbol("v1"))) + https_servers.append((Symbol("external_https_api"), publichost, publicport, Symbol("v1"))) + https_servers.append((Symbol("frontend_https_api"), host, port, Symbol("frontend"))) + return (http_servers, + https_servers) + elif nodetype == "storagenodes": - return [(Symbol("storage_https_api"), host, port, Symbol("storage"))] + return ([], + [(Symbol("storage_https_api"), host, port, Symbol("storage"))]) elif nodetype == "signingnodes": - return [(Symbol("signing_https_api"), host, port, Symbol("signing"))] + return ([], + [(Symbol("signing_https_api"), host, port, Symbol("signing"))]) def allowed_clients_frontend(mergenodenames): return [ @@ -145,13 +155,14 @@ def gen_config(nodename, config, localconfig): paths = localconfig["paths"] bind_address = localconfig.get("addresses", {}).get(nodename) bind_publicaddress = localconfig.get("publicaddresses", {}).get(nodename) + bind_publichttpaddress = localconfig.get("publichttpaddresses", {}).get(nodename) options = localconfig.get("options", []) configfile = open(paths["configdir"] + nodename + ".config", "w") print >>configfile, "%% catlfish configuration file (-*- erlang -*-)" (nodetype, nodeconfig) = get_node_config(nodename, config) - https_servers = gen_https_servers(nodetype, nodeconfig, bind_address, bind_publicaddress) + (http_servers, https_servers) = gen_http_servers(nodetype, nodeconfig, bind_address, bind_publicaddress, bind_publichttpaddress=bind_publichttpaddress) catlfishconfig = [] plopconfig = [] @@ -163,6 +174,7 @@ def gen_config(nodename, config, localconfig): catlfishconfig += [ (Symbol("https_servers"), https_servers), + (Symbol("http_servers"), http_servers), (Symbol("https_certfile"), paths["https_certfile"]), (Symbol("https_keyfile"), paths["https_keyfile"]), (Symbol("https_cacertfile"), paths["https_cacertfile"]), -- cgit v1.1 From 6b62ebbf1de5b9e55b04e9cfafd0620f1374c2d4 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Tue, 31 Mar 2015 14:27:23 +0200 Subject: Cleanup tests and use urllib2.build_opener Remove unused files Generate test config files directly in release directory Move test database files to "tests" directory Generate log key when preparing tests Report error when STH not found in v1.erl Make merge, fetchallcerts, submitcert, verifysct, and testcase1 take log key as argument --- .gitignore | 1 + Makefile | 76 +++++++++++++++++------------------- catlfish.config | 27 ------------- httpd_props.conf | 22 ----------- src/v1.erl | 16 +++++++- storage_node.config | 19 --------- storage_node_httpd.conf | 21 ---------- test/catlfish-test-local-1.cfg | 10 ++--- test/catlfish-test-local-merge.cfg | 8 ++-- test/catlfish-test-local-signing.cfg | 10 ++--- tools/certkeys.py | 8 ---- tools/certtools.py | 50 +++++++++++++++--------- tools/compileconfig.py | 2 +- tools/create-key.sh | 4 ++ tools/fetchallcerts.py | 5 ++- tools/merge.py | 16 ++++++-- tools/submitcert.py | 5 ++- tools/testcase1.py | 12 ++++-- tools/verifysct.py | 5 ++- 19 files changed, 134 insertions(+), 183 deletions(-) delete mode 100644 catlfish.config delete mode 100644 httpd_props.conf delete mode 100644 storage_node.config delete mode 100644 storage_node_httpd.conf create mode 100755 tools/create-key.sh diff --git a/.gitignore b/.gitignore index ca93487..be00b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.beam rel test/test.mk +*.pyc diff --git a/Makefile b/Makefile index 58a1160..261def1 100644 --- a/Makefile +++ b/Makefile @@ -1,48 +1,44 @@ +RELDIR=rel + build all: ./make.erl clean: -rm ebin/*.beam release: all - rm -rf rel - mkdir rel + rm -rf $(RELDIR) + mkdir $(RELDIR) ./makerelease.erl - (cd rel; ln -s ../../plop/test .) - cp httpd_props.conf rel - cp catlfish.config rel - cp storage_node.config rel - cp storage_node_httpd.conf rel - mkdir rel/catlfish - cp -r webroot rel/catlfish - test -d rel/catlfish/webroot/log || mkdir rel/catlfish/webroot/log + mkdir $(RELDIR)/catlfish + cp -r webroot $(RELDIR)/catlfish -include test/test.mk tests-prepare: - rm -r rel/mergedb || true - mkdir rel/mergedb - mkdir rel/mergedb/chains - touch rel/mergedb/logorder - rm -r rel/known_roots || true - mkdir rel/known_roots - cp tools/testcerts/roots/* rel/known_roots - cp -r test/config/privatekeys rel - cp -r test/config/publickeys rel - rm -r rel/tests || true + rm -r $(RELDIR)/tests || true + mkdir $(RELDIR)/tests + mkdir $(RELDIR)/tests/keys + (cd $(RELDIR)/tests/keys ; ../../../tools/create-key.sh logkey) + mkdir $(RELDIR)/tests/mergedb + mkdir $(RELDIR)/tests/mergedb/chains + touch $(RELDIR)/tests/mergedb/logorder + mkdir $(RELDIR)/tests/known_roots + cp tools/testcerts/roots/* $(RELDIR)/tests/known_roots + cp -r test/config/privatekeys $(RELDIR)/tests + cp -r test/config/publickeys $(RELDIR)/tests @for machine in $(MACHINES); do \ - tools/compileconfig.py --config=test/catlfish-test.cfg --localconfig test/catlfish-test-local-$$machine.cfg ; \ - mkdir -p rel/tests/machine/machine-$$machine/db ; \ - touch rel/tests/machine/machine-$$machine/db/index ; \ - touch rel/tests/machine/machine-$$machine/db/newentries ; \ + (cd $(RELDIR); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-$$machine.cfg) ; \ + mkdir -p $(RELDIR)/tests/machine/machine-$$machine/db ; \ + touch $(RELDIR)/tests/machine/machine-$$machine/db/index ; \ + touch $(RELDIR)/tests/machine/machine-$$machine/db/newentries ; \ done - tools/compileconfig.py --config=test/catlfish-test.cfg --localconfig test/catlfish-test-local-signing.cfg + (cd $(RELDIR); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-signing.cfg) @for node in $(NODES); do \ mkdir -p test/nodes/$$node/log ; \ - cp test/config/$$node.config rel ; \ done tests-start: @for node in $(NODES); do \ - (cd rel ; bin/run_erl -daemon ../test/nodes/$$node/ ../test/nodes/$$node/log/ "exec bin/erl -config $$node") \ + (cd $(RELDIR) ; bin/run_erl -daemon ../test/nodes/$$node/ ../test/nodes/$$node/log/ "exec bin/erl -config $$node") \ done @for i in 1 2 3 4 5 6 7 8 9 10; do \ echo "waiting for system to start" ; \ @@ -50,7 +46,7 @@ tests-start: allstarted=1 ; \ notstarted= ; \ for testurl in $(TESTURLS); do \ - if curl -s -k https://$$testurl > /dev/null ; then : ; else allstarted=0 ; notstarted="$$testurl $$notstarted" ; fi ; \ + if curl -s -k -4 https://$$testurl > /dev/null ; then : ; else allstarted=0 ; notstarted="$$testurl $$notstarted" ; fi ; \ : ; \ done ; \ if [ $$allstarted -eq 1 ]; then break ; \ @@ -58,20 +54,20 @@ tests-start: done tests-run: - @(cd rel && python ../tools/testcase1.py ) || (echo "Tests failed" ; false) - @(cd rel && python ../tools/fetchallcerts.py $(BASEURL)) || (echo "Verification failed" ; false) - @(cd rel && rm -f submittedcerts) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert1.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert2.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert3.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert4.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert5.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre1.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre2.txt --check-sct --sct-file=submittedcerts $(BASEURL)) || (echo "Submission failed" ; false) - @(cd rel && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg) || (echo "Merge failed" ; false) + @(cd $(RELDIR) && python ../tools/testcase1.py https://localhost:8080/ tests/keys/logkey.pem) || (echo "Tests failed" ; false) + @(cd $(RELDIR) && python ../tools/fetchallcerts.py $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Verification failed" ; false) + @(cd $(RELDIR) && rm -f submittedcerts) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert1.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert2.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert3.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert4.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert5.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre1.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre2.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(RELDIR) && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg) || (echo "Merge failed" ; false) tests-run2: - @(cd rel ; python ../tools/verifysct.py --sct-file=submittedcerts --parallel 1 $(BASEURL)) || echo "Verification of SCT:s failed" + @(cd $(RELDIR) ; python ../tools/verifysct.py --sct-file=submittedcerts --parallel 1 $(BASEURL) --publickey=tests/keys/logkey.pem) || echo "Verification of SCT:s failed" tests-stop: @for node in $(NODES); do \ diff --git a/catlfish.config b/catlfish.config deleted file mode 100644 index 91868e5..0000000 --- a/catlfish.config +++ /dev/null @@ -1,27 +0,0 @@ -%% catlfish configuration file (-*- erlang -*-) -%% Start like this: -%% $ erl -boot start_sasl -config catlfish -run inets -[{sasl, - [{sasl_error_logger, false}, - {errlog_type, error}, - {error_logger_mf_dir, "log"}, - {error_logger_mf_maxbytes, 10485760}, % 10 MB - {error_logger_mf_maxfiles, 10}]}, - {catlfish, - [{known_roots_path, "known_roots"}, - {https_servers, - [{external_https_api, "127.0.0.1", 8080, v1} - ]}, - {https_certfile, "catlfish/webroot/certs/webcert.pem"}, - {https_keyfile, "catlfish/webroot/keys/webkey.pem"}, - {https_cacertfile, "catlfish/webroot/certs/webcert.pem"} - ]}, - {plop, - [{entry_root_path, "db/certentries/"}, - {index_path, "db/index"}, - {entryhash_root_path, "db/entryhash/"}, - {treesize_path, "db/treesize"}, - {indexforhash_root_path, "db/certindex/"}, - %{storage_nodes, ["https://127.0.0.1:8081/ct/storage/"]}, - {storage_nodes_quorum, 1} - ]}]. diff --git a/httpd_props.conf b/httpd_props.conf deleted file mode 100644 index 9ea7b30..0000000 --- a/httpd_props.conf +++ /dev/null @@ -1,22 +0,0 @@ -%%% Copyright (c) 2014, NORDUnet A/S. -%%% See LICENSE for licensing information. -[ - {port, 8080}, - {bind_address, {127, 0, 0, 1}}, - {server_name, "flimsy"}, - {server_root, "catlfish/webroot"}, - {document_root, "catlfish/webroot/docroot"}, - {modules, [mod_alias, mod_auth, mod_esi, mod_get, mod_head, - mod_log, mod_disk_log]}, - {erl_script_alias, {"/ct", [v1, frontend]}}, - {erl_script_nocache, true}, - {error_log, "log/error"}, - {security_log, "log/security"}, - {transfer_log, "log/transfer"}, - {socket_type, - {essl, % See ssl(3erl) for SSL options. - [{versions, ['tlsv1.2', 'tlsv1.1', 'tlsv1']}, - {certfile, "catlfish/webroot/certs/webcert.pem"}, - {keyfile, "catlfish/webroot/keys/webkey.pem"}, - {cacertfile, "catlfish/webroot/certs/webcert.pem"}]}} -]. diff --git a/src/v1.erl b/src/v1.erl index e672182..e2cadb3 100644 --- a/src/v1.erl +++ b/src/v1.erl @@ -15,8 +15,13 @@ request(post, "ct/v1/add-pre-chain", Input) -> add_chain(Input, precert); request(get, "ct/v1/get-sth", _Query) -> - R = plop:sth(), - success(R); + case plop:sth() of + noentry -> + lager:error("No valid STH found"), + internalerror("No valid STH found"); + R -> + success(R) + end; request(get, "ct/v1/get-sth-consistency", Query) -> case lists:sort(Query) of @@ -109,6 +114,13 @@ html(Text, Input) -> success(Data) -> {200, [{"Content-Type", "text/json"}], mochijson2:encode(Data)}. +internalerror(Text) -> + {500, [{"Content-Type", "text/html"}], + io_lib:format( + "

~n" ++ + "~s~n" ++ + "~n", [Text])}. + -spec add_chain(any(), normal|precert) -> any(). add_chain(Input, Type) -> case (catch mochijson2:decode(Input)) of diff --git a/storage_node.config b/storage_node.config deleted file mode 100644 index 47a1326..0000000 --- a/storage_node.config +++ /dev/null @@ -1,19 +0,0 @@ -%% catlfish configuration file (-*- erlang -*-) -%% Start like this: -%% $ erl -boot start_sasl -config catlfish -run inets -[{sasl, - [{sasl_error_logger, false}, - {errlog_type, error}, - {error_logger_mf_dir, "log"}, - {error_logger_mf_maxbytes, 10485760}, % 10 MB - {error_logger_mf_maxfiles, 10}]}, - {inets, - [{services, - [{httpd, [{proplist_file, "storage_node_httpd.conf"}]}]}]}, - {plop, - [{entry_root_path, "db/certentries/"}, - {index_path, "db/index"}, - {newentries_path, "db/newentries"}, - {entryhash_root_path, "db/entryhash/"}, - {treesize_path, "db/treesize"}, - {indexforhash_root_path, "db/certindex/"}]}]. diff --git a/storage_node_httpd.conf b/storage_node_httpd.conf deleted file mode 100644 index 2f271f8..0000000 --- a/storage_node_httpd.conf +++ /dev/null @@ -1,21 +0,0 @@ -%%% Copyright (c) 2014, NORDUnet A/S. -%%% See LICENSE for licensing information. -[ - {port, 8081}, - {bind_address, {127, 0, 0, 1}}, - {server_name, "flimsy"}, - {server_root, "catlfish/webroot"}, - {document_root, "catlfish/webroot/docroot"}, - {modules, [mod_alias, mod_auth, mod_esi, mod_get, mod_head, - mod_log, mod_disk_log]}, - {erl_script_alias, {"/ct", [storage]}}, - {erl_script_nocache, true}, - {error_log, "log/error_storage"}, - {security_log, "log/security_storage"}, - {transfer_log, "log/transfer_storage"}, - {socket_type, - {essl, % See ssl(3erl) for SSL options. - [{certfile, "catlfish/webroot/certs/webcert.pem"}, - {keyfile, "catlfish/webroot/keys/webkey.pem"}, - {cacertfile, "catlfish/webroot/certs/webcert.pem"}]}} -]. diff --git a/test/catlfish-test-local-1.cfg b/test/catlfish-test-local-1.cfg index 608d4c0..7caacdc 100644 --- a/test/catlfish-test-local-1.cfg +++ b/test/catlfish-test-local-1.cfg @@ -10,15 +10,15 @@ publicaddresses: frontend-1: 127.0.0.1:8080 paths: - configdir: test/config/ - knownroots: known_roots + configdir: . + knownroots: tests/known_roots https_certfile: catlfish/webroot/certs/webcert.pem https_keyfile: catlfish/webroot/keys/webkey.pem https_cacertfile: catlfish/webroot/certs/webcert.pem db: tests/machine/machine-1/db/ - publickeys: publickeys - logpublickey: test/eckey-public.pem - privatekeys: privatekeys + publickeys: tests/publickeys + logpublickey: tests/keys/logkey.pem + privatekeys: tests/privatekeys #options: # - sctcaching diff --git a/test/catlfish-test-local-merge.cfg b/test/catlfish-test-local-merge.cfg index b7f5009..4a77708 100644 --- a/test/catlfish-test-local-merge.cfg +++ b/test/catlfish-test-local-merge.cfg @@ -1,8 +1,8 @@ nodename: merge-1 paths: - mergedb: ../rel/mergedb + mergedb: tests/mergedb https_cacertfile: catlfish/webroot/certs/webcert.pem - publickeys: publickeys - logpublickey: test/eckey-public.pem - privatekeys: privatekeys + publickeys: tests/publickeys + logpublickey: tests/keys/logkey.pem + privatekeys: tests/privatekeys diff --git a/test/catlfish-test-local-signing.cfg b/test/catlfish-test-local-signing.cfg index 2cc4df2..b08bf2f 100644 --- a/test/catlfish-test-local-signing.cfg +++ b/test/catlfish-test-local-signing.cfg @@ -5,11 +5,11 @@ addresses: signing-1: 127.0.0.1:8088 paths: - configdir: test/config/ + configdir: . https_certfile: catlfish/webroot/certs/webcert.pem https_keyfile: catlfish/webroot/keys/webkey.pem https_cacertfile: catlfish/webroot/certs/webcert.pem - publickeys: publickeys - logpublickey: test/eckey-public.pem - logprivatekey: test/eckey.pem - privatekeys: privatekeys + publickeys: tests/publickeys + logpublickey: tests/keys/logkey.pem + logprivatekey: tests/keys/logkey-private.pem + privatekeys: tests/privatekeys diff --git a/tools/certkeys.py b/tools/certkeys.py index 52d61be..43646ef 100644 --- a/tools/certkeys.py +++ b/tools/certkeys.py @@ -4,14 +4,6 @@ publickeys = { "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTD" "M0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==", - "https://127.0.0.1:8080/": - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" - "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", - - "https://localhost:8080/": - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" - "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", - "https://flimsy.ct.nordu.net/": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", diff --git a/tools/certtools.py b/tools/certtools.py index 2c97dfb..da5021a 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -88,8 +88,15 @@ def get_root_cert(issuer): return root_cert +def urlopen(url, data=None): + try: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=None)) + except TypeError: + opener = urllib2.build_opener(urllib2.HTTPSHandler()) + return opener.open(url, data) + def get_sth(baseurl): - result = urllib2.urlopen(baseurl + "ct/v1/get-sth").read() + result = urlopen(baseurl + "ct/v1/get-sth").read() return json.loads(result) def get_proof_by_hash(baseurl, hash, tree_size): @@ -97,7 +104,7 @@ def get_proof_by_hash(baseurl, hash, tree_size): params = urllib.urlencode({"hash":base64.b64encode(hash), "tree_size":tree_size}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params).read() + urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -108,7 +115,7 @@ def get_consistency_proof(baseurl, tree_size1, tree_size2): params = urllib.urlencode({"first":tree_size1, "second":tree_size2}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-sth-consistency?" + params).read() + urlopen(baseurl + "ct/v1/get-sth-consistency?" + params).read() return json.loads(result)["consistency"] except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -131,7 +138,7 @@ def unpack_tls_array(packed_data, length_len): def add_chain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission)).read() + result = urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission)).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR", e.code,":", e.read() @@ -148,7 +155,7 @@ def add_chain(baseurl, submission): def add_prechain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-pre-chain", + result = urlopen(baseurl + "ct/v1/add-pre-chain", json.dumps(submission)).read() return json.loads(result) except urllib2.HTTPError, e: @@ -167,7 +174,7 @@ def add_prechain(baseurl, submission): def get_entries(baseurl, start, end): try: params = urllib.urlencode({"start":start, "end":end}) - result = urllib2.urlopen(baseurl + "ct/v1/get-entries?" + params).read() + result = urlopen(baseurl + "ct/v1/get-entries?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -198,8 +205,9 @@ def encode_signature(hash_alg, signature_alg, unpacked_signature): signature += tls_array(unpacked_signature, 2) return signature -def check_signature(baseurl, signature, data): - publickey = base64.decodestring(publickeys[baseurl]) +def check_signature(baseurl, signature, data, publickey=None): + if publickey == None: + publickey = base64.decodestring(publickeys[baseurl]) (hash_alg, signature_alg, unpacked_signature) = decode_signature(signature) assert hash_alg == 4, \ "hash_alg is %d, expected 4" % (hash_alg,) # sha256 @@ -230,20 +238,25 @@ def check_auth_header(authheader, expected_key, publickeydir, data, path): return True def http_request(url, data=None, key=None, verifynode=None, publickeydir="."): - req = urllib2.Request(url, data) + try: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=None)) + except TypeError: + opener = urllib2.build_opener(urllib2.HTTPSHandler()) + (keyname, keyfile) = key privatekey = get_eckey_from_file(keyfile) sk = ecdsa.SigningKey.from_der(privatekey) parsed_url = urlparse.urlparse(url) if data == None: - data = parsed_url.query + data_to_sign = parsed_url.query method = "GET" else: + data_to_sign = data method = "POST" - signature = sk.sign("%s\0%s\0%s" % (method, parsed_url.path, data), hashfunc=hashlib.sha256, + signature = sk.sign("%s\0%s\0%s" % (method, parsed_url.path, data_to_sign), hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der) - req.add_header('X-Catlfish-Auth', base64.b64encode(signature) + ";key=" + keyname) - result = urllib2.urlopen(req) + opener.addheaders = [('X-Catlfish-Auth', base64.b64encode(signature) + ";key=" + keyname)] + result = opener.open(url, data) authheader = result.info().get('X-Catlfish-Auth') data = result.read() check_auth_header(authheader, verifynode, publickeydir, data, parsed_url.path) @@ -263,7 +276,7 @@ def create_signature(baseurl, data, key=None): unpacked_signature = get_signature(baseurl, data, key) return encode_signature(4, 3, unpacked_signature) -def check_sth_signature(baseurl, sth): +def check_sth_signature(baseurl, sth, publickey=None): signature = base64.decodestring(sth["tree_head_signature"]) version = struct.pack(">b", 0) @@ -273,7 +286,7 @@ def check_sth_signature(baseurl, sth): hash = base64.decodestring(sth["sha256_root_hash"]) tree_head = version + signature_type + timestamp + tree_size + hash - check_signature(baseurl, signature, tree_head) + check_signature(baseurl, signature, tree_head, publickey=publickey) def create_sth_signature(tree_size, timestamp, root_hash, baseurl, key=None): version = struct.pack(">b", 0) @@ -284,8 +297,9 @@ def create_sth_signature(tree_size, timestamp, root_hash, baseurl, key=None): return create_signature(baseurl, tree_head, key=key) -def check_sct_signature(baseurl, signed_entry, sct, precert=False): - publickey = base64.decodestring(publickeys[baseurl]) +def check_sct_signature(baseurl, signed_entry, sct, precert=False, publickey=None): + if publickey == None: + publickey = base64.decodestring(publickeys[baseurl]) calculated_logid = hashlib.sha256(publickey).digest() received_logid = base64.decodestring(sct["id"]) assert calculated_logid == received_logid, \ @@ -306,7 +320,7 @@ def check_sct_signature(baseurl, signed_entry, sct, precert=False): entry_type + signed_entry + \ tls_array(base64.decodestring(sct["extensions"]), 2) - check_signature(baseurl, signature, signed_struct) + check_signature(baseurl, signature, signed_struct, publickey=publickey) def pack_mtl(timestamp, leafcert): entry_type = struct.pack(">H", 0) diff --git a/tools/compileconfig.py b/tools/compileconfig.py index 4996994..c239bd0 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -158,7 +158,7 @@ def gen_config(nodename, config, localconfig): bind_publichttpaddress = localconfig.get("publichttpaddresses", {}).get(nodename) options = localconfig.get("options", []) - configfile = open(paths["configdir"] + nodename + ".config", "w") + configfile = open(paths["configdir"] + "/" + nodename + ".config", "w") print >>configfile, "%% catlfish configuration file (-*- erlang -*-)" (nodetype, nodeconfig) = get_node_config(nodename, config) diff --git a/tools/create-key.sh b/tools/create-key.sh new file mode 100755 index 0000000..9d29c86 --- /dev/null +++ b/tools/create-key.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +openssl ecparam -name prime256v1 -genkey -noout -out $1-private.pem +openssl ec -in $1-private.pem -pubout -out $1.pem diff --git a/tools/fetchallcerts.py b/tools/fetchallcerts.py index e0ea92f..395fe69 100755 --- a/tools/fetchallcerts.py +++ b/tools/fetchallcerts.py @@ -22,6 +22,7 @@ parser = argparse.ArgumentParser(description='') parser.add_argument('baseurl', help="Base URL for CT server") parser.add_argument('--store', default=None, metavar="dir", help='Store certificates in directory dir') parser.add_argument('--write-sth', action='store_true', help='Write STH') +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') args = parser.parse_args() def get_entries_wrapper(baseurl, start, end): @@ -39,8 +40,10 @@ def print_layer(layer): for entry in layer: print base64.b16encode(entry) +logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + sth = get_sth(args.baseurl) -check_sth_signature(args.baseurl, sth) +check_sth_signature(args.baseurl, sth, publickey=logpublickey) tree_size = sth["tree_size"] root_hash = base64.decodestring(sth["sha256_root_hash"]) diff --git a/tools/merge.py b/tools/merge.py index e6fae24..f9c93d9 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -16,7 +16,9 @@ import hashlib import urlparse import os import yaml -from certtools import build_merkle_tree, create_sth_signature, check_sth_signature, get_eckey_from_file, timing_point, http_request +from certtools import build_merkle_tree, create_sth_signature, \ + check_sth_signature, get_eckey_from_file, timing_point, http_request, \ + get_public_key_from_file parser = argparse.ArgumentParser(description="") parser.add_argument('--config', help="System configuration", required=True) @@ -41,6 +43,8 @@ logorderfile = mergedb + "/logorder" own_key = (localconfig["nodename"], "%s/%s-private.pem" % (paths["privatekeys"], localconfig["nodename"])) +logpublickey = get_public_key_from_file(paths["logpublickey"]) + hashed_dir = True def parselogrow(row): @@ -238,19 +242,23 @@ tree_size = len(logorder) root_hash = tree[-1][0] timestamp = int(time.time() * 1000) +tree_head_signature = None for signingnode in signingnodes: try: tree_head_signature = create_sth_signature(tree_size, timestamp, root_hash, "https://%s/" % signingnode["address"], key=own_key) break - except urllib2.URLError: - pass + except urllib2.URLError, e: + print e +if tree_head_signature == None: + print >>sys.stderr, "Could not contact any signing nodes" + sys.exit(1) sth = {"tree_size": tree_size, "timestamp": timestamp, "sha256_root_hash": base64.b64encode(root_hash), "tree_head_signature": base64.b64encode(tree_head_signature)} -check_sth_signature(ctbaseurl, sth) +check_sth_signature(ctbaseurl, sth, publickey=logpublickey) timing_point(timing, "build sth") diff --git a/tools/submitcert.py b/tools/submitcert.py index 2e8cc33..ba4b337 100755 --- a/tools/submitcert.py +++ b/tools/submitcert.py @@ -30,6 +30,7 @@ parser.add_argument('--sct-file', default=None, metavar="file", help='Store SCT: parser.add_argument('--parallel', type=int, default=16, metavar="n", help="Number of parallel submits") parser.add_argument('--check-sct', action='store_true', help="Check SCT signature") parser.add_argument('--pre-warm', action='store_true', help="Wait 3 seconds after first submit") +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') args = parser.parse_args() from multiprocessing import Pool @@ -37,6 +38,8 @@ from multiprocessing import Pool baseurl = args.baseurl certfilepath = args.store +logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + lookup_in_log = False if certfilepath[-1] == "/": @@ -84,7 +87,7 @@ def submitcert((certfile, cert)): try: if args.check_sct: - check_sct_signature(baseurl, signed_entry, result, precert=precert) + check_sct_signature(baseurl, signed_entry, result, precert=precert, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", certfile, e diff --git a/tools/testcase1.py b/tools/testcase1.py index 4502b56..1d46230 100755 --- a/tools/testcase1.py +++ b/tools/testcase1.py @@ -14,7 +14,9 @@ import hashlib import itertools from certtools import * -baseurls = ["https://127.0.0.1:8080/"] +baseurls = [sys.argv[1]] +logpublickeyfile = sys.argv[2] + certfiles = ["../tools/testcerts/cert1.txt", "../tools/testcerts/cert2.txt", "../tools/testcerts/cert3.txt", "../tools/testcerts/cert4.txt", "../tools/testcerts/cert5.txt"] @@ -28,6 +30,8 @@ cc5 = get_certs_from_file(certfiles[4]) failures = 0 indentation = "" +logpublickey = get_public_key_from_file(logpublickeyfile) + def testgroup(name): global indentation print name + ":" @@ -55,7 +59,7 @@ def print_and_check_tree_size(expected, baseurl): global failures sth = get_sth(baseurl) try: - check_sth_signature(baseurl, sth) + check_sth_signature(baseurl, sth, publickey=logpublickey) except AssertionError, e: print_error("%s", e) except ecdsa.keys.BadSignatureError, e: @@ -71,13 +75,13 @@ def do_add_chain(chain, baseurl): print_error("%s", e) try: signed_entry = pack_cert(chain[0]) - check_sct_signature(baseurl, signed_entry, result) + check_sct_signature(baseurl, signed_entry, result, publickey=logpublickey) + print_success("signature check succeeded") except AssertionError, e: print_error("%s", e) except ecdsa.keys.BadSignatureError, e: print e print_error("bad SCT signature") - print_success("signature check succeeded") return result def get_and_validate_proof(timestamp, chain, leaf_index, nentries, baseurl): diff --git a/tools/verifysct.py b/tools/verifysct.py index 27ab4c9..4b8e38a 100755 --- a/tools/verifysct.py +++ b/tools/verifysct.py @@ -22,12 +22,15 @@ parser = argparse.ArgumentParser(description='') parser.add_argument('baseurl', help="Base URL for CT server") parser.add_argument('--sct-file', default=None, metavar="dir", help='SCT:s to verify') parser.add_argument('--parallel', type=int, default=16, metavar="n", help="Number of parallel verifications") +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') args = parser.parse_args() from multiprocessing import Pool baseurl = args.baseurl +logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + sth = get_sth(baseurl) def verifysct(sctentry): @@ -43,7 +46,7 @@ def verifysct(sctentry): signed_entry = pack_precert(leafcert, issuer_key_hash) else: signed_entry = pack_cert(leafcert) - check_sct_signature(baseurl, signed_entry, sctentry["sct"], precert=issuer_key_hash) + check_sct_signature(baseurl, signed_entry, sctentry["sct"], precert=issuer_key_hash, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", e -- cgit v1.1 From 064556bd0e531c71dd285de372312d2851abf29f Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Tue, 31 Mar 2015 15:23:12 +0200 Subject: X.509 cert generation for http server --- Makefile | 28 +++++++++++++++++++++++++++- test/catlfish-test-local-1.cfg | 9 ++++++--- test/catlfish-test-local-merge.cfg | 2 +- test/catlfish-test-local-signing.cfg | 6 +++--- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 261def1..d7b5ee3 100644 --- a/Makefile +++ b/Makefile @@ -9,13 +9,14 @@ release: all mkdir $(RELDIR) ./makerelease.erl mkdir $(RELDIR)/catlfish - cp -r webroot $(RELDIR)/catlfish -include test/test.mk tests-prepare: rm -r $(RELDIR)/tests || true mkdir $(RELDIR)/tests + make tests-createca + make tests-createcert mkdir $(RELDIR)/tests/keys (cd $(RELDIR)/tests/keys ; ../../../tools/create-key.sh logkey) mkdir $(RELDIR)/tests/mergedb @@ -90,6 +91,31 @@ tests: @make tests-wait @make tests-stop +tests-createca: + mkdir $(RELDIR)/tests/httpsca + ( cd $(RELDIR)/tests/httpsca ; \ + mkdir -p demoCA/newcerts ; \ + touch demoCA/index.txt ; \ + echo 00 > demoCA/serial ; \ + echo '[ req ]' > caconfig.txt ; \ + echo 'distinguished_name = req_distinguished_name' >> caconfig.txt ; \ + echo 'x509_extensions = v3_ca' >> caconfig.txt ; \ + echo '[ req_distinguished_name ]' >> caconfig.txt ; \ + echo '[ v3_ca ]' >> caconfig.txt ; \ + echo 'basicConstraints=CA:true' >> caconfig.txt ; \ + openssl req -newkey rsa:2048 -keyout key.pem -out req.csr -nodes -subj '/countryName=SE/stateOrProvinceName=Stockholm/organizationName=Test/commonName=ca/O=ca' -config caconfig.txt ; \ + openssl ca -in req.csr -selfsign -keyfile key.pem -out demoCA/cacert.pem -batch \ + ) + +tests-createcert: + mkdir $(RELDIR)/tests/httpscert + openssl req -new -newkey rsa:2048 -keyout $(RELDIR)/tests/httpscert/httpskey-1.pem -out $(RELDIR)/tests/httpsca/httpscert-1.csr -nodes -subj '/countryName=SE/stateOrProvinceName=Stockholm/organizationName=Test/CN=localhost' + ( cd $(RELDIR)/tests/httpsca ; \ + openssl ca -in httpscert-1.csr -keyfile key.pem -out httpscert-1.pem -batch \ + ) + cp $(RELDIR)/tests/httpsca/httpscert-1.pem $(RELDIR)/tests/httpscert/ + + # Unit testing. check: all test/check.erl diff --git a/test/catlfish-test-local-1.cfg b/test/catlfish-test-local-1.cfg index 7caacdc..be1c5b3 100644 --- a/test/catlfish-test-local-1.cfg +++ b/test/catlfish-test-local-1.cfg @@ -9,12 +9,15 @@ addresses: publicaddresses: frontend-1: 127.0.0.1:8080 +#publichttpaddresses: +# frontend-1: 127.0.0.1:8090 + paths: configdir: . knownroots: tests/known_roots - https_certfile: catlfish/webroot/certs/webcert.pem - https_keyfile: catlfish/webroot/keys/webkey.pem - https_cacertfile: catlfish/webroot/certs/webcert.pem + https_certfile: tests/httpscert/httpscert-1.pem + https_keyfile: tests/httpscert/httpskey-1.pem + https_cacertfile: tests/httpsca/demoCA/cacert.pem db: tests/machine/machine-1/db/ publickeys: tests/publickeys logpublickey: tests/keys/logkey.pem diff --git a/test/catlfish-test-local-merge.cfg b/test/catlfish-test-local-merge.cfg index 4a77708..3de20ee 100644 --- a/test/catlfish-test-local-merge.cfg +++ b/test/catlfish-test-local-merge.cfg @@ -2,7 +2,7 @@ nodename: merge-1 paths: mergedb: tests/mergedb - https_cacertfile: catlfish/webroot/certs/webcert.pem + https_cacertfile: tests/httpsca/demoCA/cacert.pem publickeys: tests/publickeys logpublickey: tests/keys/logkey.pem privatekeys: tests/privatekeys diff --git a/test/catlfish-test-local-signing.cfg b/test/catlfish-test-local-signing.cfg index b08bf2f..c8b27ae 100644 --- a/test/catlfish-test-local-signing.cfg +++ b/test/catlfish-test-local-signing.cfg @@ -6,9 +6,9 @@ addresses: paths: configdir: . - https_certfile: catlfish/webroot/certs/webcert.pem - https_keyfile: catlfish/webroot/keys/webkey.pem - https_cacertfile: catlfish/webroot/certs/webcert.pem + https_certfile: tests/httpscert/httpscert-1.pem + https_keyfile: tests/httpscert/httpskey-1.pem + https_cacertfile: tests/httpsca/demoCA/cacert.pem publickeys: tests/publickeys logpublickey: tests/keys/logkey.pem logprivatekey: tests/keys/logkey-private.pem -- cgit v1.1 From e4476f7e71e0ebbfe5594bc90d22a9d74d638211 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 31 Mar 2015 16:39:22 +0200 Subject: Pass an SSL context to urrllib2. --- tools/certtools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/certtools.py b/tools/certtools.py index da5021a..498a2e0 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -90,8 +90,8 @@ def get_root_cert(issuer): def urlopen(url, data=None): try: - opener = urllib2.build_opener(urllib2.HTTPSHandler(context=None)) - except TypeError: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=ssl.SSLContext(ssl.PROTOCOL_TLSv1))) + except AttributeError: opener = urllib2.build_opener(urllib2.HTTPSHandler()) return opener.open(url, data) @@ -239,8 +239,8 @@ def check_auth_header(authheader, expected_key, publickeydir, data, path): def http_request(url, data=None, key=None, verifynode=None, publickeydir="."): try: - opener = urllib2.build_opener(urllib2.HTTPSHandler(context=None)) - except TypeError: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=ssl.SSLContext(ssl.PROTOCOL_TLSv1))) + except AttributeError: opener = urllib2.build_opener(urllib2.HTTPSHandler()) (keyname, keyfile) = key -- cgit v1.1 From 07bbae40520a0fe5e893f73a29d785d2f3b59eed Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Tue, 31 Mar 2015 16:41:08 +0200 Subject: Rename RELDIR -> PREFIX. --- Makefile | 78 ++++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index d7b5ee3..bda7f3a 100644 --- a/Makefile +++ b/Makefile @@ -1,45 +1,45 @@ -RELDIR=rel +PREFIX=rel build all: ./make.erl clean: -rm ebin/*.beam release: all - rm -rf $(RELDIR) - mkdir $(RELDIR) + rm -rf $(PREFIX) + mkdir $(PREFIX) ./makerelease.erl - mkdir $(RELDIR)/catlfish + mkdir $(PREFIX)/catlfish -include test/test.mk tests-prepare: - rm -r $(RELDIR)/tests || true - mkdir $(RELDIR)/tests + rm -r $(PREFIX)/tests || true + mkdir $(PREFIX)/tests make tests-createca make tests-createcert - mkdir $(RELDIR)/tests/keys - (cd $(RELDIR)/tests/keys ; ../../../tools/create-key.sh logkey) - mkdir $(RELDIR)/tests/mergedb - mkdir $(RELDIR)/tests/mergedb/chains - touch $(RELDIR)/tests/mergedb/logorder - mkdir $(RELDIR)/tests/known_roots - cp tools/testcerts/roots/* $(RELDIR)/tests/known_roots - cp -r test/config/privatekeys $(RELDIR)/tests - cp -r test/config/publickeys $(RELDIR)/tests + mkdir $(PREFIX)/tests/keys + (cd $(PREFIX)/tests/keys ; ../../../tools/create-key.sh logkey) + mkdir $(PREFIX)/tests/mergedb + mkdir $(PREFIX)/tests/mergedb/chains + touch $(PREFIX)/tests/mergedb/logorder + mkdir $(PREFIX)/tests/known_roots + cp tools/testcerts/roots/* $(PREFIX)/tests/known_roots + cp -r test/config/privatekeys $(PREFIX)/tests + cp -r test/config/publickeys $(PREFIX)/tests @for machine in $(MACHINES); do \ - (cd $(RELDIR); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-$$machine.cfg) ; \ - mkdir -p $(RELDIR)/tests/machine/machine-$$machine/db ; \ - touch $(RELDIR)/tests/machine/machine-$$machine/db/index ; \ - touch $(RELDIR)/tests/machine/machine-$$machine/db/newentries ; \ + (cd $(PREFIX); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-$$machine.cfg) ; \ + mkdir -p $(PREFIX)/tests/machine/machine-$$machine/db ; \ + touch $(PREFIX)/tests/machine/machine-$$machine/db/index ; \ + touch $(PREFIX)/tests/machine/machine-$$machine/db/newentries ; \ done - (cd $(RELDIR); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-signing.cfg) + (cd $(PREFIX); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-signing.cfg) @for node in $(NODES); do \ mkdir -p test/nodes/$$node/log ; \ done tests-start: @for node in $(NODES); do \ - (cd $(RELDIR) ; bin/run_erl -daemon ../test/nodes/$$node/ ../test/nodes/$$node/log/ "exec bin/erl -config $$node") \ + (cd $(PREFIX) ; bin/run_erl -daemon ../test/nodes/$$node/ ../test/nodes/$$node/log/ "exec bin/erl -config $$node") \ done @for i in 1 2 3 4 5 6 7 8 9 10; do \ echo "waiting for system to start" ; \ @@ -55,20 +55,20 @@ tests-start: done tests-run: - @(cd $(RELDIR) && python ../tools/testcase1.py https://localhost:8080/ tests/keys/logkey.pem) || (echo "Tests failed" ; false) - @(cd $(RELDIR) && python ../tools/fetchallcerts.py $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Verification failed" ; false) - @(cd $(RELDIR) && rm -f submittedcerts) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert1.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert2.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert3.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert4.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert5.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre1.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre2.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) - @(cd $(RELDIR) && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg) || (echo "Merge failed" ; false) + @(cd $(PREFIX) && python ../tools/testcase1.py https://localhost:8080/ tests/keys/logkey.pem) || (echo "Tests failed" ; false) + @(cd $(PREFIX) && python ../tools/fetchallcerts.py $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Verification failed" ; false) + @(cd $(PREFIX) && rm -f submittedcerts) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert1.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert2.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert3.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert4.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/cert5.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre1.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/submitcert.py --parallel=1 --store ../tools/testcerts/pre2.txt --check-sct --sct-file=submittedcerts $(BASEURL) --publickey=tests/keys/logkey.pem) || (echo "Submission failed" ; false) + @(cd $(PREFIX) && python ../tools/merge.py --config ../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-merge.cfg) || (echo "Merge failed" ; false) tests-run2: - @(cd $(RELDIR) ; python ../tools/verifysct.py --sct-file=submittedcerts --parallel 1 $(BASEURL) --publickey=tests/keys/logkey.pem) || echo "Verification of SCT:s failed" + @(cd $(PREFIX) ; python ../tools/verifysct.py --sct-file=submittedcerts --parallel 1 $(BASEURL) --publickey=tests/keys/logkey.pem) || echo "Verification of SCT:s failed" tests-stop: @for node in $(NODES); do \ @@ -92,8 +92,8 @@ tests: @make tests-stop tests-createca: - mkdir $(RELDIR)/tests/httpsca - ( cd $(RELDIR)/tests/httpsca ; \ + mkdir $(PREFIX)/tests/httpsca + ( cd $(PREFIX)/tests/httpsca ; \ mkdir -p demoCA/newcerts ; \ touch demoCA/index.txt ; \ echo 00 > demoCA/serial ; \ @@ -108,12 +108,12 @@ tests-createca: ) tests-createcert: - mkdir $(RELDIR)/tests/httpscert - openssl req -new -newkey rsa:2048 -keyout $(RELDIR)/tests/httpscert/httpskey-1.pem -out $(RELDIR)/tests/httpsca/httpscert-1.csr -nodes -subj '/countryName=SE/stateOrProvinceName=Stockholm/organizationName=Test/CN=localhost' - ( cd $(RELDIR)/tests/httpsca ; \ + mkdir $(PREFIX)/tests/httpscert + openssl req -new -newkey rsa:2048 -keyout $(PREFIX)/tests/httpscert/httpskey-1.pem -out $(PREFIX)/tests/httpsca/httpscert-1.csr -nodes -subj '/countryName=SE/stateOrProvinceName=Stockholm/organizationName=Test/CN=localhost' + ( cd $(PREFIX)/tests/httpsca ; \ openssl ca -in httpscert-1.csr -keyfile key.pem -out httpscert-1.pem -batch \ ) - cp $(RELDIR)/tests/httpsca/httpscert-1.pem $(RELDIR)/tests/httpscert/ + cp $(PREFIX)/tests/httpsca/httpscert-1.pem $(PREFIX)/tests/httpscert/ # Unit testing. -- cgit v1.1 From 1f9535763a3a5c49728c375988a83897fad48142 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Tue, 31 Mar 2015 16:49:53 +0200 Subject: Generate test authentication keys dynamically --- Makefile | 8 ++++++-- test/config/privatekeys/frontend-1-private.pem | 5 ----- test/config/privatekeys/merge-1-private.pem | 5 ----- test/config/privatekeys/signing-1-private.pem | 5 ----- test/config/privatekeys/storage-1-private.pem | 5 ----- test/config/publickeys/frontend-1.pem | 4 ---- test/config/publickeys/merge-1.pem | 4 ---- test/config/publickeys/signing-1.pem | 4 ---- test/config/publickeys/storage-1.pem | 4 ---- 9 files changed, 6 insertions(+), 38 deletions(-) delete mode 100644 test/config/privatekeys/frontend-1-private.pem delete mode 100644 test/config/privatekeys/merge-1-private.pem delete mode 100644 test/config/privatekeys/signing-1-private.pem delete mode 100644 test/config/privatekeys/storage-1-private.pem delete mode 100644 test/config/publickeys/frontend-1.pem delete mode 100644 test/config/publickeys/merge-1.pem delete mode 100644 test/config/publickeys/signing-1.pem delete mode 100644 test/config/publickeys/storage-1.pem diff --git a/Makefile b/Makefile index bda7f3a..51fd786 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,6 @@ tests-prepare: touch $(PREFIX)/tests/mergedb/logorder mkdir $(PREFIX)/tests/known_roots cp tools/testcerts/roots/* $(PREFIX)/tests/known_roots - cp -r test/config/privatekeys $(PREFIX)/tests - cp -r test/config/publickeys $(PREFIX)/tests @for machine in $(MACHINES); do \ (cd $(PREFIX); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-$$machine.cfg) ; \ mkdir -p $(PREFIX)/tests/machine/machine-$$machine/db ; \ @@ -33,9 +31,15 @@ tests-prepare: touch $(PREFIX)/tests/machine/machine-$$machine/db/newentries ; \ done (cd $(PREFIX); ../tools/compileconfig.py --config=../test/catlfish-test.cfg --localconfig ../test/catlfish-test-local-signing.cfg) + mkdir $(PREFIX)/tests/privatekeys + mkdir $(PREFIX)/tests/publickeys @for node in $(NODES); do \ + (cd $(PREFIX)/tests/privatekeys ; ../../../tools/create-key.sh $$node) ; \ + mv $(PREFIX)/tests/privatekeys/$$node.pem $(PREFIX)/tests/publickeys/ ; \ mkdir -p test/nodes/$$node/log ; \ done + (cd $(PREFIX)/tests/privatekeys ; ../../../tools/create-key.sh merge-1) + mv $(PREFIX)/tests/privatekeys/merge-1.pem $(PREFIX)/tests/publickeys/ tests-start: @for node in $(NODES); do \ diff --git a/test/config/privatekeys/frontend-1-private.pem b/test/config/privatekeys/frontend-1-private.pem deleted file mode 100644 index 718efda..0000000 --- a/test/config/privatekeys/frontend-1-private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIPER9WFIxLXvXDHTwPvGnNvBAKOB+/6ahpvuCjtlzOU8oAoGCCqGSM49 -AwEHoUQDQgAEibeLqrVV7QAE6Wytzpxi4sd0JtGNGRfXNZ9r9CNIVudDnNjtFRF5 -gwm/AxUWEuBXjnbVvq4HOLqZ0bP2qc+uRQ== ------END EC PRIVATE KEY----- diff --git a/test/config/privatekeys/merge-1-private.pem b/test/config/privatekeys/merge-1-private.pem deleted file mode 100644 index 55d50b1..0000000 --- a/test/config/privatekeys/merge-1-private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIBQcXtOVX29dno+aYqGddVOpg23FfhJmrMFOpOegyYZxoAoGCCqGSM49 -AwEHoUQDQgAExHAsjFFgKFlrcCveHhVdjE7A/Uh0gXdAeN9+P7SDGgRNe0WWDjCr -0Da3c8X5JulA1cOLlQ0h2B67Yp3WZ9ONHg== ------END EC PRIVATE KEY----- diff --git a/test/config/privatekeys/signing-1-private.pem b/test/config/privatekeys/signing-1-private.pem deleted file mode 100644 index 0c9f1ac..0000000 --- a/test/config/privatekeys/signing-1-private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEICQ+kchWtj3ZwGhzz+QkKl/CM0fsfQCDtI+1Cb3GID+moAoGCCqGSM49 -AwEHoUQDQgAEeVsqn8x1CWv4BK9+o6qQqVt+lQ7+dI6VoiwwNOT2CAvocdYHzzqW -2/dstQZIiYSdUw1SWQMR+7fTTRDZh5bDoQ== ------END EC PRIVATE KEY----- diff --git a/test/config/privatekeys/storage-1-private.pem b/test/config/privatekeys/storage-1-private.pem deleted file mode 100644 index b68d2a9..0000000 --- a/test/config/privatekeys/storage-1-private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIAjVa6lTbhiNUfrfTGELRXqHSHF0nuk13lKF8NSHzU07oAoGCCqGSM49 -AwEHoUQDQgAE1vFWiMT9PItJGvyhMKPF5TnFirHPSh5u5swetajmNLyClWIDGXql -RlXlcPwuKxTISI4rFJATBkKhNjvSZ5L3oA== ------END EC PRIVATE KEY----- diff --git a/test/config/publickeys/frontend-1.pem b/test/config/publickeys/frontend-1.pem deleted file mode 100644 index 938ef29..0000000 --- a/test/config/publickeys/frontend-1.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEibeLqrVV7QAE6Wytzpxi4sd0JtGN -GRfXNZ9r9CNIVudDnNjtFRF5gwm/AxUWEuBXjnbVvq4HOLqZ0bP2qc+uRQ== ------END PUBLIC KEY----- diff --git a/test/config/publickeys/merge-1.pem b/test/config/publickeys/merge-1.pem deleted file mode 100644 index 95a75f7..0000000 --- a/test/config/publickeys/merge-1.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExHAsjFFgKFlrcCveHhVdjE7A/Uh0 -gXdAeN9+P7SDGgRNe0WWDjCr0Da3c8X5JulA1cOLlQ0h2B67Yp3WZ9ONHg== ------END PUBLIC KEY----- diff --git a/test/config/publickeys/signing-1.pem b/test/config/publickeys/signing-1.pem deleted file mode 100644 index cc5f472..0000000 --- a/test/config/publickeys/signing-1.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeVsqn8x1CWv4BK9+o6qQqVt+lQ7+ -dI6VoiwwNOT2CAvocdYHzzqW2/dstQZIiYSdUw1SWQMR+7fTTRDZh5bDoQ== ------END PUBLIC KEY----- diff --git a/test/config/publickeys/storage-1.pem b/test/config/publickeys/storage-1.pem deleted file mode 100644 index 0b862a1..0000000 --- a/test/config/publickeys/storage-1.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1vFWiMT9PItJGvyhMKPF5TnFirHP -Sh5u5swetajmNLyClWIDGXqlRlXlcPwuKxTISI4rFJATBkKhNjvSZ5L3oA== ------END PUBLIC KEY----- -- cgit v1.1 From 13c3789add4f1630c4bc8dfccb229ebc7d4bfa38 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Tue, 31 Mar 2015 17:36:41 +0200 Subject: Only include relevant files in release --- reltool.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reltool.config b/reltool.config index e2928a7..31fd1b0 100644 --- a/reltool.config +++ b/reltool.config @@ -10,7 +10,7 @@ catlfish ]}, {boot_rel, "catlfish"}, - {excl_archive_filters, ["^include$","^priv$","^\\.git$"]}, + {incl_app_filters, ["^ebin/", "^priv/", "^src/"]}, {app, catlfish, [{app_file, all}, {lib_dir, "."}]}, {app, plop, [{app_file, all}, {lib_dir, "../plop"}]}, {app, mochiweb, [{app_file, all}, {lib_dir, "../mochiweb"}]}, -- cgit v1.1