From 7cbbfa7c7e0fba134838c5c9c58d2d3174232882 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Thu, 19 Feb 2015 16:17:01 +0100 Subject: Make unit tests work again. Makefile target 'check' runs them. --- src/catlfish.erl | 31 ++++++++++++++++++++++--------- src/catlfish.hrl | 4 ---- src/catlfish_app.erl | 10 +--------- src/x509.erl | 15 +++++++++------ 4 files changed, 32 insertions(+), 28 deletions(-) delete mode 100644 src/catlfish.hrl (limited to 'src') diff --git a/src/catlfish.erl b/src/catlfish.erl index 83ca3db..765a8a6 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -4,8 +4,8 @@ -module(catlfish). -export([add_chain/2, entries/2, entry_and_proof/2]). -export([known_roots/0, update_known_roots/0]). +-export([init_cache_table/0]). -include_lib("eunit/include/eunit.hrl"). --include("catlfish.hrl"). -define(PROTOCOL_VERSION, 0). @@ -133,6 +133,14 @@ entry_and_proof(Index, TreeSize) -> {error_message, list_to_binary(Msg)}]} end. +-define(CACHE_TABLE, catlfish_cache). +init_cache_table() -> + case ets:info(?CACHE_TABLE) of + undefined -> ok; + _ -> ets:delete(?CACHE_TABLE) + end, + ets:new(?CACHE_TABLE, [set, public, named_table]). + %% Private functions. unpack_entry(Entry) -> <> = Entry, @@ -183,28 +191,30 @@ known_roots(Directory, CacheUsage) -> use_cache -> case ets:lookup(?CACHE_TABLE, ?ROOTS_CACHE_KEY) of [] -> - read_files_and_udpate_table(Directory); + read_files_and_update_table(Directory); [{roots, DerList}] -> DerList end; update_tab -> - read_files_and_udpate_table(Directory) + read_files_and_update_table(Directory) end. -read_files_and_udpate_table(Directory) -> +read_files_and_update_table(Directory) -> L = x509:read_pemfiles_from_dir(Directory), true = ets:insert(?CACHE_TABLE, {?ROOTS_CACHE_KEY, L}), L. %%%%%%%%%%%%%%%%%%%% %% Testing internal functions. --define(PEMFILES_DIR_OK, "../test/testdata/known-roots"). --define(PEMFILES_DIR_NONEXISTENT, "../test/testdata/nonexistent-dir"). +-define(PEMFILES_DIR_OK, "test/testdata/known_roots"). +-define(PEMFILES_DIR_NONEXISTENT, "test/testdata/nonexistent-dir"). read_pemfiles_test_() -> {setup, - fun() -> {known_roots(?PEMFILES_DIR_OK, use_cache), - known_roots(?PEMFILES_DIR_OK, use_cache)} + fun() -> + init_cache_table(), + {known_roots(?PEMFILES_DIR_OK, update_tab), + known_roots(?PEMFILES_DIR_OK, use_cache)} end, fun(_) -> ets:delete(?CACHE_TABLE, ?ROOTS_CACHE_KEY) end, fun({L, LCached}) -> @@ -214,6 +224,9 @@ read_pemfiles_test_() -> read_pemfiles_fail_test_() -> {setup, - fun() -> known_roots(?PEMFILES_DIR_NONEXISTENT, use_cache) end, + fun() -> + init_cache_table(), + known_roots(?PEMFILES_DIR_NONEXISTENT, update_tab) + end, fun(_) -> ets:delete(?CACHE_TABLE, ?ROOTS_CACHE_KEY) end, fun(Empty) -> [?_assertMatch([], Empty)] end}. diff --git a/src/catlfish.hrl b/src/catlfish.hrl deleted file mode 100644 index 46e882b..0000000 --- a/src/catlfish.hrl +++ /dev/null @@ -1,4 +0,0 @@ -%%% Copyright (c) 2014, NORDUnet A/S. -%%% See LICENSE for licensing information. - --define(CACHE_TABLE, catlfish_cache). diff --git a/src/catlfish_app.erl b/src/catlfish_app.erl index e24a1bb..56f6cc2 100644 --- a/src/catlfish_app.erl +++ b/src/catlfish_app.erl @@ -8,20 +8,12 @@ %% Application callbacks -export([start/2, stop/1]). --include("catlfish.hrl"). - %% =================================================================== %% Application callbacks %% =================================================================== start(normal, Args) -> - case ets:info(?CACHE_TABLE) of - undefined -> - ok; - _ -> - ets:delete(?CACHE_TABLE) - end, - ets:new(?CACHE_TABLE, [set, public, named_table]), + catlfish:init_cache_table(), catlfish_sup:start_link(Args). stop(_State) -> diff --git a/src/x509.erl b/src/x509.erl index b0363cd..32ade83 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -31,7 +31,6 @@ normalise_chain(AcceptableRootCerts, CertChain) -> %% an acceptable root cert. Order of certificates in second argument %% is: leaf cert in head, chain in tail. Order of first argument is %% irrelevant. - -spec valid_chain_p([binary()], [binary()], integer()) -> {false, reason()} | {true, list()}. valid_chain_p(_, _, MaxChainLength) when MaxChainLength =< 0 -> @@ -279,8 +278,8 @@ sign_test_() -> valid_cert_test_() -> {setup, - fun() -> {read_pemfiles_from_dir("../test/testdata/known_roots"), - read_certs("../test/testdata/chains")} end, + fun() -> {read_pemfiles_from_dir("test/testdata/known_roots"), + read_certs("test/testdata/chains")} end, fun(_) -> ok end, fun({KnownRoots, Chains}) -> [ @@ -298,7 +297,13 @@ valid_cert_test_() -> %% leaf signed by known CA ?_assertMatch({true, _}, valid_chain_p(KnownRoots, - lists:nth(3, Chains), 10)) + lists:nth(3, Chains), 10)), + %% 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(lists:nth(4, Chains), + lists:nth(4, Chains), 10)) ] end}. chain_test_() -> @@ -320,8 +325,6 @@ chain_test(C0, C1) -> ?_assertMatch({false, chain_too_long}, valid_chain_p([C1], [C0, C1], 1)), %% Root not in trust store. ?_assertMatch({false, root_unknown}, valid_chain_p([], [C0, C1], 10)), - %% Invalid signer. - ?_assertMatch({false, chain_broken}, valid_chain_p([C0], [C1, C0], 10)), %% Selfsigned. Actually OK. ?_assertMatch({true, []}, valid_chain_p([C0], [C0], 10)), ?_assertMatch({true, []}, valid_chain_p([C0], [C0], 1)), -- cgit v1.1 From 4d2993890fed46c5611735e84d4f737e8c342718 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 20 Feb 2015 12:20:09 +0100 Subject: Stop validating that cert.issuer matches issuer.subject. Even canoncalized versions of this data mismatch in otherwise proper chains. Since we're not here to validate chains for any other reasons than attribution and spam control, let's stop validate cert.issuer==candidate.subject. We still verify the cryptographic chain with signatures of tbsCertificates of course. Resolves CATLFISH-19. --- src/x509.erl | 73 ++++++++++++++++++++++-------------------------------------- 1 file changed, 27 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/x509.erl b/src/x509.erl index 32ade83..c815ca4 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -9,7 +9,6 @@ -type reason() :: {chain_too_long | root_unknown | - chain_broken | signature_mismatch | encoding_invalid}. @@ -58,7 +57,10 @@ valid_chain_p(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> Err -> Err end. -%% @doc Return first cert in list signing Cert, or notfound. +%% @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; @@ -68,15 +70,15 @@ signer(Cert, [H|T]) -> {false, _} -> signer(Cert, T) end. -%% encoded_tbs_cert: verbatim from pubkey_cert.erl -encoded_tbs_cert(Cert) -> +%% Code from pubkey_cert:encoded_tbs_cert/1. +encoded_tbs_cert(DerCert) -> {ok, PKIXCert} = - 'OTP-PUB-KEY':decode_TBSCert_exclusive(Cert), - {'Certificate', - {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert, + 'OTP-PUB-KEY':decode_TBSCert_exclusive(DerCert), + {'Certificate', {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = + PKIXCert, EncodedTBSCert. -%% extract_verify_data: close to pubkey_cert:extract_verify_data/2 +%% Code from pubkey_cert:extract_verify_data/2. verifydata_from_cert(Cert, DerCert) -> PlainText = encoded_tbs_cert(DerCert), {_, Sig} = Cert#'Certificate'.signature, @@ -85,18 +87,20 @@ verifydata_from_cert(Cert, DerCert) -> {DigestType,_} = public_key:pkix_sign_types(SigAlg), {PlainText, DigestType, Sig}. -verify(Cert, DerCert, - #'Certificate'{ +%% @doc Verify that Cert/DerCert is signed by Issuer. +verify(Cert, DerCert, % Certificate to verify. + #'Certificate'{ % Issuer. tbsCertificate = #'TBSCertificate'{ subjectPublicKeyInfo = IssuerSPKI}}) -> - {DigestOrPlainText, DigestType, Signature} = - verifydata_from_cert(Cert, DerCert), + + %% Dig out digest, digest type and signature from Cert/DerCert. + {DigestOrPlainText, DigestType, Signature} = verifydata_from_cert(Cert, + DerCert), + %% Dig out issuer key from issuer cert. #'SubjectPublicKeyInfo'{ algorithm = #'AlgorithmIdentifier'{algorithm = Alg, parameters = Params}, subjectPublicKey = {0, Key0}} = IssuerSPKI, KeyType = pubkey_cert_records:supportedPublicKeyAlgorithms(Alg), - - %% public_key:pem_entry_decode() IssuerKey = case KeyType of 'RSAPublicKey' -> @@ -107,43 +111,20 @@ verify(Cert, DerCert, 'ECPoint' -> public_key:der_decode(KeyType, Key0) end, + + %% Verify the signature. public_key:verify(DigestOrPlainText, DigestType, Signature, IssuerKey). +%% @doc Is Cert signed by Issuer? Only verify that the signature +%% matches and don't check things like Cert.issuer == Issuer.subject. -spec signed_by_p(binary(), binary()) -> true | {false, reason()}. signed_by_p(DerCert, IssuerDerCert) when is_binary(DerCert), is_binary(IssuerDerCert) -> - Cert = public_key:pkix_decode_cert(DerCert, plain), - TBSCert = Cert#'Certificate'.tbsCertificate, - IssuerCert = public_key:pkix_decode_cert(IssuerDerCert, plain), - IssuerTBSCert = IssuerCert#'Certificate'.tbsCertificate, - case pubkey_cert:is_issuer(TBSCert#'TBSCertificate'.issuer, - IssuerTBSCert#'TBSCertificate'.subject) of - false -> - {false, chain_broken}; - true -> % Verify signature. - case verify(Cert, DerCert, IssuerCert) of - false -> {false, signature_mismatch}; - true -> true - end - end; -signed_by_p(#'OTPCertificate'{} = Cert, - #'OTPCertificate'{} = IssuerCert) -> - %% FIXME: Validate presence and contents (against constraints) of - %% names (subject, subjectAltName, emailAddress) too? - case (catch public_key:pkix_is_issuer(Cert, IssuerCert)) of - {'EXIT', Reason} -> - lager:info("invalid certificate: ~p: ~p", - [cert_string(Cert), Reason]), - {false, encoding_invalid}; - true -> - %% Cert.issuer does match IssuerCert.subject. Now verify - %% the signature. - case public_key:pkix_verify(Cert, public_key(IssuerCert)) of - true -> true; - false -> {false, signature_mismatch} - end; - false -> - {false, chain_broken} + case verify(public_key:pkix_decode_cert(DerCert, plain), + DerCert, + public_key:pkix_decode_cert(IssuerDerCert, plain)) of + false -> {false, signature_mismatch}; + true -> true end. -spec public_key(binary() | #'OTPCertificate'{}) -> public_key:public_key(). -- cgit v1.1 From 5fbbb3e0bf7ea28dc8c5061ccb73fa5827872537 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 30 Jan 2015 00:32:15 +0100 Subject: Make mochiweb pool size configurable --- src/catlfish_sup.erl | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/catlfish_sup.erl b/src/catlfish_sup.erl index 0b6c306..6f918cd 100644 --- a/src/catlfish_sup.erl +++ b/src/catlfish_sup.erl @@ -22,6 +22,7 @@ init([]) -> WebConfig = [{ip, IPv4Address}, {port, Port}, {ssl, true}, + {acceptor_pool_size, application:get_env(catlfish, http_server_pool_size, 16)}, {ssl_opts, SSLOptions} ], {ChildName, -- cgit v1.1 From 4f4a1cb883f53538ee25ba618aeae5d00202166f Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Mon, 2 Feb 2015 16:47:01 +0100 Subject: Log time spent serving a request --- src/catlfish_web.erl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/catlfish_web.erl b/src/catlfish_web.erl index f3231e4..9869b21 100644 --- a/src/catlfish_web.erl +++ b/src/catlfish_web.erl @@ -14,11 +14,13 @@ start(Options, Module) -> loop(Req, Module) -> "/" ++ Path = Req:get(path), try + Starttime = os:timestamp(), case Req:get(method) of 'GET' -> Query = Req:parse_qs(), lager:debug("GET ~p ~p", [Path, Query]), Result = Module:request(get, Path, Query), + lager:debug("GET finished: ~p us", [timer:now_diff(os:timestamp(), Starttime)]), case Result of none -> Req:respond({404, [{"Content-Type", "text/plain"}], "Page not found"}); @@ -29,6 +31,7 @@ loop(Req, Module) -> Body = Req:recv_body(), lager:debug("POST ~p ~p", [Path, Body]), Result = Module:request(post, Path, Body), + lager:debug("POST finished: ~p us", [timer:now_diff(os:timestamp(), Starttime)]), case Result of none -> Req:respond({404, [{"Content-Type", "text/plain"}], "Page not found"}); -- cgit v1.1 From 4312e98aad7aaf1d2bc43b3e0ef0ace5a5788c5f Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 25 Feb 2015 16:01:14 +0100 Subject: Add debug logging. Trying to figure out why public_key:verify isn't found in docker images. --- src/x509.erl | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/x509.erl b/src/x509.erl index c815ca4..96a8ea2 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -112,6 +112,11 @@ verify(Cert, DerCert, % Certificate to verify. public_key:der_decode(KeyType, Key0) 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). -- cgit v1.1 From bdfde9547c151588917fd932ecf74377d3c378c3 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 25 Feb 2015 16:21:35 +0100 Subject: Even more debug logging. --- src/x509.erl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/x509.erl b/src/x509.erl index 96a8ea2..a0aaed4 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -101,6 +101,9 @@ verify(Cert, DerCert, % Certificate to verify. algorithm = #'AlgorithmIdentifier'{algorithm = Alg, parameters = Params}, subjectPublicKey = {0, Key0}} = IssuerSPKI, KeyType = pubkey_cert_records:supportedPublicKeyAlgorithms(Alg), + lager:debug("Alg: ~p", [Alg]), + lager:debug("KeyType: ~p", [KeyType]), + lager:debug("Key0: ~p", [Key0]), IssuerKey = case KeyType of 'RSAPublicKey' -> -- cgit v1.1 From 85615c8e621aa16026faf07f01bf0ba0776c191f Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 27 Feb 2015 01:51:12 +0100 Subject: Verify that known roots are indeed signing themselves. This filters out certificates with signing algorithms that we can't handle. Also, make unit tests better. --- src/catlfish.erl | 16 ++++++++++++---- src/x509.erl | 58 ++++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/catlfish.erl b/src/catlfish.erl index 765a8a6..3956eec 100644 --- a/src/catlfish.erl +++ b/src/catlfish.erl @@ -200,9 +200,17 @@ known_roots(Directory, CacheUsage) -> end. read_files_and_update_table(Directory) -> - L = x509:read_pemfiles_from_dir(Directory), - true = ets:insert(?CACHE_TABLE, {?ROOTS_CACHE_KEY, L}), - L. + Certs = x509:read_pemfiles_from_dir(Directory), + Proper = x509:self_signed(Certs), + case length(Certs) - length(Proper) of + 0 -> ok; + N -> lager:warning( + "Ignoring ~p root certificates not signing themselves properly", + [N]) + end, + true = ets:insert(?CACHE_TABLE, {?ROOTS_CACHE_KEY, Proper}), + lager:info("Known roots imported: ~p", [length(Proper)]), + Proper. %%%%%%%%%%%%%%%%%%%% %% Testing internal functions. @@ -218,7 +226,7 @@ read_pemfiles_test_() -> end, fun(_) -> ets:delete(?CACHE_TABLE, ?ROOTS_CACHE_KEY) end, fun({L, LCached}) -> - [?_assertMatch(7, length(L)), + [?_assertMatch(4, length(L)), ?_assertEqual(L, LCached)] end}. diff --git a/src/x509.erl b/src/x509.erl index a0aaed4..9030e04 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -2,7 +2,8 @@ %%% See LICENSE for licensing information. -module(x509). --export([normalise_chain/2, cert_string/1, read_pemfiles_from_dir/1]). +-export([normalise_chain/2, cert_string/1, read_pemfiles_from_dir/1, + self_signed/1]). -include_lib("public_key/include/public_key.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -27,9 +28,15 @@ normalise_chain(AcceptableRootCerts, CertChain) -> %%%%%%%%%%%%%%%%%%%% %% @doc Verify that the leaf cert or precert has a valid chain back to -%% an acceptable root cert. Order of certificates in second argument -%% is: leaf cert in head, chain in tail. Order of first argument is -%% irrelevant. +%% an acceptable root cert. The order of certificates in the second +%% argument is: leaf cert in head, chain in tail. Order of first +%% argument is irrelevant. +%% +%% Return {false, Reason} or {true, ListWithRoot}. Note that +%% ListWithRoot is the empty list when the root of the chain is found +%% 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 -> @@ -161,6 +168,10 @@ parsable_cert_p(Der) -> false end. +-spec self_signed([binary()]) -> [binary()]. +self_signed(L) -> + lists:filter(fun(Cert) -> signed_by_p(Cert, Cert) end, L). + %%%%%%%%%%%%%%%%%%%% %% Precertificates according to draft-ietf-trans-rfc6962-bis-04. @@ -210,6 +221,7 @@ ders_from_pemfiles(Dir, Filenames) -> [ders_from_pemfile(filename:join(Dir, X)) || X <- Filenames]). ders_from_pemfile(Filename) -> + lager:debug("reading PEM from ~s", [Filename]), PemBins = pems_from_file(Filename), Pems = case (catch public_key:pem_decode(PemBins)) of {'EXIT', Reason} -> @@ -272,27 +284,37 @@ valid_cert_test_() -> fun(_) -> ok end, fun({KnownRoots, Chains}) -> [ - %% self-signed, not a valid OTPCertificate: + %% 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 doesn't make much sense -- is my environment borked? - ?_assertMatch({true, _}, - valid_chain_p(lists:nth(1, Chains), - lists:nth(1, Chains), 10)), - %% self-signed + %% 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)), + %% Self-signed so fail. ?_assertMatch({false, root_unknown}, valid_chain_p(KnownRoots, lists:nth(2, Chains), 10)), - %% leaf signed by known CA - ?_assertMatch({true, _}, - valid_chain_p(KnownRoots, - lists:nth(3, Chains), 10)), - %% bug CATLFISH-19 --> [info] rejecting "3ee62cb678014c14d22ebf96f44cc899adea72f1": chain_broken + %% Leaf signed by known CA, pass. + ?_assertMatch({true, _}, valid_chain_p(KnownRoots, + lists:nth(3, Chains), 10)), + %% Proper 3-depth chain with root in KnownRoots, pass. + %% Bug CATLFISH-19 --> [info] rejecting "3ee62cb678014c14d22ebf96f44cc899adea72f1": chain_broken %% leaf sha1: 3ee62cb678014c14d22ebf96f44cc899adea72f1 %% leaf Subject: C=KR, O=Government of Korea, OU=Group of Server, OU=\xEA\xB5\x90\xEC\x9C\xA1\xEA\xB3\xBC\xED\x95\x99\xEA\xB8\xB0\xEC\x88\xA0\xEB\xB6\x80, CN=www.berea.ac.kr, CN=haksa.bits.ac.kr - ?_assertMatch({true, _}, - valid_chain_p(lists:nth(4, Chains), - lists:nth(4, Chains), 10)) + ?_assertMatch({true, _}, valid_chain_p(KnownRoots, + lists: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)))), + %% 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)))) ] end}. chain_test_() -> -- cgit v1.1 From b41acdada125a41c40e94177b8ebdc2bb7d130b6 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 27 Feb 2015 01:55:15 +0100 Subject: Fix a bug where verification of EC signatures made us crash. Also, have valid_chain_p return boolean, add some debug logging and detect invalid signature types instead of crashing. --- src/x509.erl | 73 +++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/x509.erl b/src/x509.erl index 9030e04..5a0e871 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -61,7 +61,7 @@ valid_chain_p(AcceptableRootCerts, [TopCert], MaxChainLength) -> valid_chain_p(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> case signed_by_p(BottomCert, hd(Rest)) of true -> valid_chain_p(AcceptableRootCerts, Rest, MaxChainLength - 1); - Err -> Err + false -> {false, signature_mismatch} end. %% @doc Return first cert in list signing Cert, or notfound. NOTE: @@ -72,9 +72,14 @@ valid_chain_p(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> signer(_Cert, []) -> notfound; signer(Cert, [H|T]) -> + lager:debug("Is ~p signed by ~p?", [cert_string(Cert), cert_string(H)]), case signed_by_p(Cert, H) of - true -> H; - {false, _} -> signer(Cert, T) + true -> + lager:debug("~p is signed by ~p", + [cert_string(Cert), cert_string(H)]), + H; + false -> + signer(Cert, T) end. %% Code from pubkey_cert:encoded_tbs_cert/1. @@ -86,40 +91,55 @@ encoded_tbs_cert(DerCert) -> 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, - {DigestType,_} = public_key:pkix_sign_types(SigAlg), - {PlainText, DigestType, Sig}. + 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]), + error + end. %% @doc Verify that Cert/DerCert is signed by Issuer. -verify(Cert, DerCert, % Certificate to verify. - #'Certificate'{ % Issuer. - tbsCertificate = #'TBSCertificate'{ - subjectPublicKeyInfo = IssuerSPKI}}) -> - +-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. - {DigestOrPlainText, DigestType, Signature} = verifydata_from_cert(Cert, - DerCert), + case verifydata_from_cert(Cert, DerCert) of + error -> false; + {ok, Tuple} -> verify_sig2(IssuerSPKI, Tuple) + end. + +verify_sig2(IssuerSPKI, {DigestOrPlainText, DigestType, Signature}) -> %% Dig out issuer key from issuer cert. #'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' -> public_key:der_decode(KeyType, Key0); - 'DSAPublicKey' -> - {params, DssParams} = public_key:der_decode('DSAParams', Params), - {public_key:der_decode(KeyType, Key0), DssParams}; 'ECPoint' -> - public_key:der_decode(KeyType, Key0) + Point = #'ECPoint'{point = Key0}, + ECParams = public_key:der_decode('EcpkParameters', Params), + {Point, ECParams}; + _ -> % FIXME: 'DSAPublicKey' + lager:error("NIY: Issuer key type ~p", [KeyType]), + false end, lager:debug("DigestOrPlainText: ~p", [DigestOrPlainText]), @@ -132,25 +152,12 @@ verify(Cert, DerCert, % Certificate to verify. %% @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()) -> true | {false, reason()}. +-spec signed_by_p(binary(), binary()) -> boolean(). signed_by_p(DerCert, IssuerDerCert) when is_binary(DerCert), is_binary(IssuerDerCert) -> - case verify(public_key:pkix_decode_cert(DerCert, plain), - DerCert, - public_key:pkix_decode_cert(IssuerDerCert, plain)) of - false -> {false, signature_mismatch}; - true -> true - end. - --spec public_key(binary() | #'OTPCertificate'{}) -> public_key:public_key(). -public_key(CertDer) when is_binary(CertDer) -> - public_key(public_key:pkix_decode_cert(CertDer, otp)); -public_key(#'OTPCertificate'{ - tbsCertificate = - #'OTPTBSCertificate'{subjectPublicKeyInfo = - #'OTPSubjectPublicKeyInfo'{ - subjectPublicKey = Key}}}) -> - Key. + 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)). -- cgit v1.1 From 103e0ee850404a5c8dc69bbbf79b2508a9c55d7a Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 26 Feb 2015 16:54:26 +0100 Subject: Added authentication between frontend and storage nodes --- src/catlfish_web.erl | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/catlfish_web.erl b/src/catlfish_web.erl index 9869b21..5ee5743 100644 --- a/src/catlfish_web.erl +++ b/src/catlfish_web.erl @@ -11,15 +11,31 @@ start(Options, Module) -> end, mochiweb_http:start([{name, Module}, {loop, Loop} | Options]). + +add_auth(Path, {Code, Headers, Data}) -> + AuthHeader = http_auth:create_auth("REPLY", Path, Data), + lager:debug("sent auth header: ~p", [AuthHeader]), + {Code, [{"X-Catlfish-Auth", AuthHeader} | Headers], Data}. + loop(Req, Module) -> "/" ++ Path = Req:get(path), try Starttime = os:timestamp(), + AuthHeader = Req:get_header_value("X-Catlfish-Auth"), case Req:get(method) of 'GET' -> Query = Req:parse_qs(), - lager:debug("GET ~p ~p", [Path, Query]), - Result = Module:request(get, Path, Query), + {_, RawQuery, _} = mochiweb_util:urlsplit_path(Req:get(raw_path)), + Result = case http_auth:verify_auth(AuthHeader, "GET", "/" ++ Path, RawQuery) of + failure -> + {403, [{"Content-Type", "text/plain"}], "Invalid credentials"}; + success -> + lager:debug("GET ~p ~p", [Path, Query]), + add_auth("/" ++ Path, Module:request(get, Path, Query)); + noauth -> + lager:debug("GET ~p ~p", [Path, Query]), + Module:request(get, Path, Query) + end, lager:debug("GET finished: ~p us", [timer:now_diff(os:timestamp(), Starttime)]), case Result of none -> @@ -29,8 +45,16 @@ loop(Req, Module) -> end; 'POST' -> Body = Req:recv_body(), - lager:debug("POST ~p ~p", [Path, Body]), - Result = Module:request(post, Path, Body), + Result = case http_auth:verify_auth(AuthHeader, "POST", "/" ++ Path, Body) of + failure -> + {403, [{"Content-Type", "text/plain"}], "Invalid credentials"}; + success -> + lager:debug("POST ~p ~p", [Path, Body]), + add_auth("/" ++ Path, Module:request(post, Path, Body)); + noauth -> + lager:debug("POST ~p ~p", [Path, Body]), + Module:request(post, Path, Body) + end, lager:debug("POST finished: ~p us", [timer:now_diff(os:timestamp(), Starttime)]), case Result of none -> -- cgit v1.1 From e0f11a58033d52c70bc76b4b5611cb88485d4653 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Wed, 4 Mar 2015 15:42:59 +0100 Subject: Save STH instead of calculating a new one each time. --- src/v1.erl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/v1.erl b/src/v1.erl index d9796fa..006990d 100644 --- a/src/v1.erl +++ b/src/v1.erl @@ -39,16 +39,8 @@ request(post, "ct/v1/add-pre-chain", _Input) -> niy(); request(get, "ct/v1/get-sth", _Query) -> - { Treesize, - Timestamp, - Roothash, - Signature} = plop:sth(), - R = [{tree_size, Treesize}, - {timestamp, Timestamp}, - {sha256_root_hash, base64:encode(Roothash)}, - {tree_head_signature, base64:encode( - plop:serialise(Signature))}], - success({R}); + R = plop:sth(), + success(R); request(get, "ct/v1/get-sth-consistency", Query) -> case lists:sort(Query) of -- cgit v1.1