diff options
64 files changed, 3659 insertions, 734 deletions
@@ -1 +1,4 @@ *.beam +rel +test/test.mk +*.pyc @@ -1,82 +1,124 @@ +PREFIX=rel + build all: - erl -pa ../lager/ebin -make + ./make.erl 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 + rm -rf $(PREFIX) + mkdir $(PREFIX) ./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 - 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 - -tests-prepare: - rm -r rel/known_roots || true - mkdir rel/known_roots - cp tools/testcerts/roots/* rel/known_roots + mkdir $(PREFIX)/catlfish - 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 +-include test/test.mk -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/ +tests-prepare: + rm -r $(PREFIX)/tests || true + mkdir $(PREFIX)/tests + make tests-createca + make tests-createcert + 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 + @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 ; \ + touch $(PREFIX)/tests/machine/machine-$$machine/db/index ; \ + 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 \ - (cd rel ; 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" ; \ 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 -4 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 $(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 $(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 \ - ./tools/halt.py ./rel/bin/to_erl test/nodes/$$node/ ; \ + ./tools/halt.py to_erl test/nodes/$$node/ ; \ done tests-wait: sleep 5 -tests: tests-prepare tests-start tests-run tests-wait tests-stop +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-stop ; false) + @make tests-wait + @make tests-stop + @make tests-wait + @make tests-start + @make tests-run2 || (make tests-stop ; false) + @make tests-wait + @make tests-stop + +tests-createca: + mkdir $(PREFIX)/tests/httpsca + ( cd $(PREFIX)/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 $(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 $(PREFIX)/tests/httpsca/httpscert-1.pem $(PREFIX)/tests/httpscert/ + # Unit testing. check: all @@ -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) 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/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. 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 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/build.sh b/packaging/docker/build.sh index 2b47222..2b47222 100644..100755 --- a/packaging/docker/build.sh +++ b/packaging/docker/build.sh diff --git a/packaging/docker/catlfish-dev/Dockerfile b/packaging/docker/catlfish-dev/Dockerfile index 0326aea..cbfc285 100644 --- a/packaging/docker/catlfish-dev/Dockerfile +++ b/packaging/docker/catlfish-dev/Dockerfile @@ -1,19 +1,20 @@ 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 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 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 \ 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"}]}, diff --git a/src/catlfish.erl b/src/catlfish.erl index 3956eec..ed75495 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -2,9 +2,10 @@ %%% 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]). +-export([entryhash_from_entry/1]). -include_lib("eunit/include/eunit.hrl"). -define(PROTOCOL_VERSION, 0). @@ -21,99 +22,202 @@ -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( - [<<Timestamp:64>>, - 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( + [<<Timestamp:64>>, + 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. +-spec serialise_signature_type(certificate_timestamp|tree_hash) -> binary(). serialise_signature_type(certificate_timestamp) -> <<0:8>>; serialise_signature_type(tree_hash) -> <<1:8>>. -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). - --spec add_chain(binary(), [binary()]) -> nonempty_string(). -add_chain(LeafCert, CertChain) -> +calc_sct(TimestampedEntry) -> + plop:serialise( + plop:spt(list_to_binary([<<?PROTOCOL_VERSION:8>>, + 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; + _ -> + calc_sct(TimestampedEntry) + end. + +-spec add_chain(binary(), [binary()], normal|precert) -> nonempty_string(). +add_chain(LeafCert, CertChain, Type) -> EntryHash = crypto:hash(sha256, [LeafCert | CertChain]), - TimestampedEntry = + 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}, - ok = plop:add( - serialise_logentry(Timestamp, LeafCert, CertChain), - ht:leaf_hash(serialise(MTL)), - EntryHash), - TSE; - {_Index, _MTLHash, Entry} -> - <<Timestamp:64, _LogEntry/binary>> = 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} + 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, DBEntry} -> + {MTLText, _ExtraData} = unpack_entry(DBEntry), + MTL = deserialise_mtl(MTLText), + MTLText = serialise(MTL), % verify FIXME: remove + {MTL#mtl.entry, MTLHash} end, - SCT_sig = - plop:spt(list_to_binary([<<?PROTOCOL_VERSION:8>>, - 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) -> - list_to_binary( - [<<Timestamp:64>>, - 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 deserialise_mtl(binary()) -> mtl(). +deserialise_mtl(Data) -> + <<LeafVersionBin:1/binary, + LeafTypeBin:1/binary, + TimestampedEntryBin/binary>> = 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) -> + <<Timestamp:64, EntryTypeBin:2/binary, RestData/binary>> = 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) -> + <<IssuerKeyHash:32/binary, RestData/binary>> = 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) -> @@ -123,10 +227,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}, @@ -141,21 +244,45 @@ 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) -> - <<Timestamp:64, LogEntry/binary>> = 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/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}) -> @@ -9,38 +9,19 @@ %% 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(), - 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 @@ -130,8 +111,40 @@ html(Text, Input) -> "~p~n" ++ "</body></html>~n", [Text, Input])}. -niy() -> - html("NIY - Not Implemented Yet|", []). - success(Data) -> {200, [{"Content-Type", "text/json"}], mochijson2:encode(Data)}. + +internalerror(Text) -> + {500, [{"Content-Type", "text/html"}], + io_lib:format( + "<html><body><p>~n" ++ + "~s~n" ++ + "</body></html>~n", [Text])}. + +-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..1b0db5e 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 | @@ -14,19 +14,57 @@ 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()}. 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. -%%%%%%%%%%%%%%%%%%%% +-spec cert_string(binary()) -> string(). +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). + +-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)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% 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 @@ -37,12 +75,10 @@ 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 -> +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,17 +94,17 @@ 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. +-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]) -> @@ -82,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} = @@ -90,45 +127,59 @@ encoded_tbs_cert(DerCert) -> PKIXCert, EncodedTBSCert. -%% Code from pubkey_cert:extract_verify_data/2. --spec verifydata_from_cert(#'Certificate'{}, binary()) -> {ok, tuple()} | error. -verifydata_from_cert(Cert, DerCert) -> - PlainText = encoded_tbs_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}} - 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 verifydata_from_cert(Cert, DerCert) of - error -> false; - {ok, Tuple} -> verify_sig2(IssuerSPKI, Tuple) +parsable_cert_p(Der) -> + case decode_cert(Der) of + error -> + false; + _ -> + true + end. + +%% @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_sig2(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> - %% Dig out issuer key from issuer cert. +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, 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' -> @@ -137,96 +188,166 @@ 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, - - 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). -%% @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)). - -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'{} -> - 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. --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. +%% +%% 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; + _ -> E + end + end, + TBSCert#'TBSCertificate'.extensions), + lager:debug("setting issuer and auth key id", []), + TBSCert#'TBSCertificate'{issuer = ParentIssuer, + extensions = NewExtensions} + end. -%% 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. - --spec detox_precert([#'Certificate'{}]) -> [#'Certificate'{}]. -detox_precert(CertChain) -> - CertChain. % NYI +-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) +-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} -> + 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 = + filter(fun(E) -> not poisoned_leaf_p(E) end, + 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}) -> + true; +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), @@ -238,6 +359,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} -> @@ -259,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. @@ -292,36 +414,64 @@ 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, _}, valid_chain_p(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}, - valid_chain_p(KnownRoots, - lists:nth(2, Chains), 10)), + normalise_chain(KnownRoots, + 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, + 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, + 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)))), + + %% 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_() -> @@ -333,21 +483,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/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 new file mode 100644 index 0000000..be1c5b3 --- /dev/null +++ b/test/catlfish-test-local-1.cfg @@ -0,0 +1,27 @@ +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 + +#publichttpaddresses: +# frontend-1: 127.0.0.1:8090 + +paths: + configdir: . + knownroots: tests/known_roots + 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 + privatekeys: tests/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..3de20ee --- /dev/null +++ b/test/catlfish-test-local-merge.cfg @@ -0,0 +1,8 @@ +nodename: merge-1 + +paths: + mergedb: tests/mergedb + 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 new file mode 100644 index 0000000..c8b27ae --- /dev/null +++ b/test/catlfish-test-local-signing.cfg @@ -0,0 +1,15 @@ +localnodes: + - signing-1 + +addresses: + signing-1: 127.0.0.1:8088 + +paths: + configdir: . + 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 + privatekeys: tests/privatekeys diff --git a/test/catlfish-test.cfg b/test/catlfish-test.cfg new file mode 100644 index 0000000..3131415 --- /dev/null +++ b/test/catlfish-test.cfg @@ -0,0 +1,19 @@ +baseurl: https://localhost:8080/ + +frontendnodes: + - name: frontend-1 + publicaddress: localhost:8080 + address: localhost:8082 + +storagenodes: + - name: storage-1 + address: localhost:8081 + +signingnodes: + - name: signing-1 + address: localhost: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 e7e8af2..0000000 --- a/test/config/frontend-1.config +++ /dev/null @@ -1,60 +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, - [{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/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----- 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/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/5.96974cd6b663a7184526b1d648ad815cf51e801a.pem b/test/testdata/chains/006.96974cd6b663a7184526b1d648ad815cf51e801a.pem index 4b0bcf5..4b0bcf5 100644 --- a/test/testdata/chains/5.96974cd6b663a7184526b1d648ad815cf51e801a.pem +++ b/test/testdata/chains/006.96974cd6b663a7184526b1d648ad815cf51e801a.pem 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/tools/certkeys.py b/tools/certkeys.py index 3c459e9..43646ef 100644 --- a/tools/certkeys.py +++ b/tools/certkeys.py @@ -4,10 +4,6 @@ publickeys = { "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTD" "M0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==", - "https://127.0.0.1:8080/": - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" - "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", - "https://flimsy.ct.nordu.net/": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", diff --git a/tools/certtools.py b/tools/certtools.py index 222497f..498a2e0 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 @@ -60,11 +61,20 @@ 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 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"] @@ -78,8 +88,15 @@ def get_root_cert(issuer): return root_cert +def urlopen(url, data=None): + try: + 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) + 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): @@ -87,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() @@ -98,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() @@ -121,7 +138,24 @@ def unpack_tls_array(packed_data, length_len): def add_chain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-chain", + 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() + 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 add_prechain(baseurl, submission): + try: + result = urlopen(baseurl + "ct/v1/add-pre-chain", json.dumps(submission)).read() return json.loads(result) except urllib2.HTTPError, e: @@ -140,7 +174,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 = urlopen(baseurl + "ct/v1/get-entries?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -171,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 @@ -183,22 +218,49 @@ 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): - req = urllib2.Request(url, data) +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="."): + try: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=ssl.SSLContext(ssl.PROTOCOL_TLSv1))) + except AttributeError: + 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).read() - return result + 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) + return data def get_signature(baseurl, data, key=None): try: @@ -214,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) @@ -224,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) @@ -235,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, leafcert, sct): - 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, \ @@ -249,12 +312,15 @@ 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) + check_signature(baseurl, signature, signed_struct, publickey=publickey) def pack_mtl(timestamp, leafcert): entry_type = struct.pack(">H", 0) @@ -267,6 +333,25 @@ 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 = "" + + timestamped_entry = struct.pack(">Q", timestamp) + entry_type + \ + 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] @@ -353,6 +438,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) @@ -586,5 +679,16 @@ 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 + +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/compileconfig.py b/tools/compileconfig.py new file mode 100755 index 0000000..c239bd0 --- /dev/null +++ b/tools/compileconfig.py @@ -0,0 +1,307 @@ +#!/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_http_servers(nodetype, nodeconfig, bind_address, bind_publicaddress, bind_publichttpaddress): + if bind_address: + (host, port) = parse_address(bind_address) + else: + (_, port) = parse_address(nodeconfig["address"]) + host = "0.0.0.0" + if nodetype == "frontendnodes": + if bind_publicaddress: + (publichost, publicport) = parse_address(bind_publicaddress) + else: + (_, publicport) = parse_address(nodeconfig["publicaddress"]) + publichost = "0.0.0.0" + + 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"))]) + elif nodetype == "signingnodes": + 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"] + 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) + (http_servers, https_servers) = gen_http_servers(nodetype, nodeconfig, bind_address, bind_publicaddress, bind_publichttpaddress=bind_publichttpaddress) + + 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("http_servers"), http_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"), + (Symbol("entryhash_from_entry"), + (Symbol("catlfish"), Symbol("entryhash_from_entry"))), + ] + + 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"]] + + 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([node["name"] for node in signingnodes], 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_nodes"), signingnodeaddresses)) + 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/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/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..395fe69 100755 --- a/tools/fetchallcerts.py +++ b/tools/fetchallcerts.py @@ -22,18 +22,9 @@ 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 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): @@ -49,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 c137f4b..f9c93d9 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -14,27 +14,38 @@ import time import ecdsa import hashlib import urlparse -from certtools import build_merkle_tree, create_sth_signature, check_sth_signature, get_eckey_from_file, timing_point, http_request +import os +import yaml +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("--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) +signingnodes = config["signingnodes"] + +chainsdir = mergedb + "/chains" +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): return base64.b16decode(row) @@ -44,12 +55,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 @@ -59,9 +84,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"]] @@ -71,10 +96,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"]]) @@ -87,9 +112,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"] @@ -99,10 +124,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() @@ -115,10 +140,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() @@ -131,10 +157,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() @@ -147,9 +173,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"] @@ -175,10 +201,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") @@ -191,16 +217,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) @@ -216,30 +242,42 @@ 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, args.signing, key=own_key) +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, 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") -print timing["deltatimes"] +if args.timing: + print timing["deltatimes"] 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) @@ -248,19 +286,20 @@ 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) timing_point(timing, "send sth") - print timing["deltatimes"] + if args.timing: + print timing["deltatimes"] 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 <ilya@glas.net> +# 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/submitcert.py b/tools/submitcert.py index 9f0be67..ba4b337 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 @@ -25,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 @@ -32,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] == "/": @@ -44,10 +52,28 @@ 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: + 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 select.select([], [], [], 1.0) @@ -61,7 +87,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, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", certfile, e @@ -75,7 +101,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) @@ -113,7 +139,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 @@ -136,9 +162,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() @@ -157,8 +186,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) @@ -175,8 +204,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/testcase1.py b/tools/testcase1.py index 73613fb..1d46230 100755 --- a/tools/testcase1.py +++ b/tools/testcase1.py @@ -14,10 +14,12 @@ import hashlib import itertools from certtools import * -baseurl = "https://127.0.0.1:8080/" -certfiles = ["testcerts/cert1.txt", "testcerts/cert2.txt", - "testcerts/cert3.txt", "testcerts/cert4.txt", - "testcerts/cert5.txt"] +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"] cc1 = get_certs_from_file(certfiles[0]) cc2 = get_certs_from_file(certfiles[1]) @@ -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 + ":" @@ -51,34 +55,36 @@ 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: - check_sth_signature(baseurl, sth) + check_sth_signature(baseurl, sth, publickey=logpublickey) except AssertionError, e: print_error("%s", e) 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)}) 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, 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): +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) @@ -86,31 +92,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) @@ -118,7 +124,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"] @@ -136,106 +142,114 @@ 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(["../tools/merge.py", "--config", "../test/catlfish-test.cfg", + "--localconfig", "../test/catlfish-test-local-merge.cfg"]) 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: 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----- 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() diff --git a/tools/verifysct.py b/tools/verifysct.py index 699a0ad..4b8e38a 100755 --- a/tools/verifysct.py +++ b/tools/verifysct.py @@ -22,20 +22,31 @@ 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): 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, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", e @@ -47,7 +58,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 +90,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 |