From 80ae7bd6d3e139078ee58d9861f159b555d28b38 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 25 Apr 2014 12:21:38 +0200 Subject: Produce SPT's, add tests. NOTE: Test vectors not verified. --- src/plop.erl | 134 +++++++++++++++++++++++++++++++++++++++++-------- src/plop.hrl | 27 ++++++++++ src/test/plop_test.erl | 57 +++++++++++++++++++++ src/test/rsakey.pem | 30 +++++++++++ 4 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 src/plop.hrl create mode 100644 src/test/plop_test.erl create mode 100644 src/test/rsakey.pem (limited to 'src') diff --git a/src/plop.erl b/src/plop.erl index 4af3f25..8af6418 100644 --- a/src/plop.erl +++ b/src/plop.erl @@ -7,43 +7,73 @@ %%% to prove that your entry is indeed present in the log. -module('plop'). --export([start/0, loop/2]). +-include("plop.hrl"). +-include_lib("public_key/include/public_key.hrl"). +-include_lib("eunit/include/eunit.hrl"). --record(plop, {pubkey :: crypto:rsa_public(), - privkey :: crypt:rsa_private()}). +-export([start/2, loop/1, dummy_add/1]). -start(PlopKey) -> - Tree = ht:create(), - register(plop, spawn(plop, loop, [PlopKey, Tree])). +-record(plop, {pubkey :: public_key:rsa_public_key(), + privkey :: public_key:rsa_private_key(), + logid :: binary(), + hashtree :: ht:head()}). + +-spec start(string(), string()) -> pid(). +start(Keyfile, Passphrase) -> + {Private_key, Public_key} = read_keyfile(Keyfile, Passphrase), + LogID = crypto:hash(sha256, public_key:der_encode('RSAPublicKey', Public_key)), + Plop = #plop{pubkey = Public_key, + privkey = Private_key, + logid = LogID, + hashtree = ht:create()}, + Pid = spawn_link(plop, loop, [Plop]), + register(plop, Pid), + Pid. log(Format, Data) -> io:format(Format, Data). -loop(PlopKey, Tree) -> +loop(Plop) -> receive {From, quit} -> - From ! {quit, ok}; + From ! {ok, quit}; {From, Data} -> - handle_req(From, Tree, Data), - loop(Tree); + handle_req(From, Plop, Data), + loop(Plop); Unknown -> log("DEBUG: Received malformed command: ~p~n", [Unknown]), - loop(Tree) + loop(Plop) end. -handle_req(From, Tree, Arg) -> +-spec serialise(plop_entry() | plop_data()) -> iolist(). +serialise(#plop_entry{type = EntryType, entry = Entry}) -> + [<>, Entry]; +serialise(#plop_data{version = Version, + signature_type = Sigtype, + timestamp = Timestamp, + entry = Entry}) -> + [<>, serialise(Entry)]. + +handle_req(From, + #plop{privkey = Privkey, + logid = LogID, + hashtree = Tree}, + Arg) -> case Arg of - {add, Data} -> - From ! spt(ht:append(Tree, Data)); - %% {diff, Tree2} -> - %% From ! ht:diff(Tree, Tree2); - {sth} -> % Signed tree head. - sth(Tree); + {add, PlopData = #plop_data{entry = Entry}} when is_record(Entry, plop_entry) -> + %% fixme: add Entry to db, + H = ht:append(Tree, serialise(Entry)), + SPT = spt(LogID, Privkey, PlopData), + %%io:format("adding ~p to ~p -> H: ~p, SPT: ~p~n", + [Entry, Tree, H, SPT]), + From ! {ok, SPT}; + sth -> % Signed tree head. + From ! {ok, sth(Tree)}; Unknown -> From ! {error, Unknown} end. -%% @doc Signed Plop Timestamp. +%% RFC6962 %% Signed Timestamp %% struct { %% Version sct_version; @@ -62,8 +92,45 @@ handle_req(From, Tree, Arg) -> %% CtExtensions extensions; %% }; %% } SignedCertificateTimestamp; -spt(LogID, Data) -> - "FIXME: a signed timestamp for " ++ Data. +%% RRC 5246 + %% A digitally-signed element is encoded as a struct DigitallySigned: + %% struct { + %% SignatureAndHashAlgorithm algorithm; + %% opaque signature<0..2^16-1>; + %% } DigitallySigned; + +-define(PLOPVERSION, 1). + +%% @doc Signed Plop Timestamp. +spt(LogID, PrivKey, #plop_data{version = Version, % >= 1 + signature_type = Sigtype, % >= 0 + timestamp = Timestamp_in, + entry = Entry = #plop_entry{}}) when is_binary(LogID) -> + Timestamp = case Timestamp_in of + now -> + {NowMegaSec, NowSec, NowMicroSec} = now(), + trunc(NowMegaSec * 1.0e9 + NowSec * 1.0e3 + NowMicroSec / 1.0e3); + _ -> Timestamp_in + end, + BinToSign = list_to_binary( + serialise(#plop_data{version = Version, + signature_type = Sigtype, + timestamp = Timestamp, + entry = Entry})), + + %% Was going to just sign/3 the hash but looking at + %% digitally_signed() in lib/ssl/src/ssl_handshake.erl it seems + %% like we should rather use (undocumented) encrypt_private/3. + %public_key:sign(hash(sha256, BinToSign), sha256, PrivKey) + Signature = public_key:encrypt_private(crypto:hash(sha256, BinToSign), + PrivKey, + [{rsa_pad, rsa_pkcs1_padding}]), + SPT = <>, + %%io:format("SPT: ~p~nBinToSign: ~p~nSignature = ~p~n", [SPT, BinToSign, Signature]), + SPT. %% @doc Signed Tree Head %% digitally-signed struct { @@ -75,3 +142,28 @@ spt(LogID, Data) -> %% } TreeHeadSignature; sth(Tree) -> "FIXME: signed tree head for " ++ Tree. + +read_keyfile(Filename, Passphrase) -> + {ok, PemBin} = file:read_file(Filename), + [Entry] = public_key:pem_decode(PemBin), + Privatekey = public_key:pem_entry_decode(Entry, Passphrase), + {Privatekey, public_key(Privatekey)}. + +public_key(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) -> + #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}. + +%%%%%%%%%%%%%%%%%%%% +%% Playing around +dummy_add(String) -> + String. + +%%%%%%%%%%%%%%%%%%%% +%% Tests. +serialise_test_() -> + Entry = #plop_entry{type = ?PLOP_ENTRY_TYPE_X509, entry = "foo"}, + Entry_serialised = <<0:16, "foo">>, + [?_assertEqual(Entry_serialised, list_to_binary(serialise(Entry))), + ?_assertEqual(<<1:8, 0:8, 0:64, Entry_serialised/binary>>, + list_to_binary(serialise(#plop_data{signature_type = 0, + timestamp = 0, + entry = Entry})))]. diff --git a/src/plop.hrl b/src/plop.hrl new file mode 100644 index 0000000..a4f2bb6 --- /dev/null +++ b/src/plop.hrl @@ -0,0 +1,27 @@ +% TODO: move to plop.hrl? +%% -record(spt, { +%% version :: integer(), % 8_bit_int +%% logid :: binary(), % 32_bit_binary() sha256 hash +%% signed_data :: signed_data() +%% }). +-define(PLOP_ENTRY_TYPE_X509, 0). +-define(PLOP_ENTRY_TYPE_PRECERT, 1). +-define(PLOP_ENTRY_TYPE_TEST, 2). +-record(plop_entry, { + type :: integer(), % uint16 + entry :: binary() + }). +-type(plop_entry() :: #plop_entry{}). + +-define(PLOP_SIGTYPE_CERTIFICATE_TIMESTAMP, 0). +-define(PLOP_SIGTYPE_TREE_HASH, 1). +-define(PLOP_SIGTYPE_TEST, 2). +-record(plop_data, { + version = 1 :: integer(), % uint8 + signature_type :: integer(), % uint8 + timestamp = now :: 'now' | binary(), % atom or uint64 + entry :: plop_entry() + }). +-type plop_data() :: #plop_data{}. + +-export_type([plop_entry/0, plop_data/0]). diff --git a/src/test/plop_test.erl b/src/test/plop_test.erl new file mode 100644 index 0000000..0cbe5cc --- /dev/null +++ b/src/test/plop_test.erl @@ -0,0 +1,57 @@ +-module(plop_test). +-include("plop.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +adding_test_() -> + {"Verifies startup and adding log entries.", + {setup, % TODO: use 'foreach' + fun start/0, + fun stop/1, + fun (Arg) -> + [test_simple_add(Arg), + test_add(Arg)] + end}}. + +start() -> + plop:start("test/rsakey.pem", "sikrit"). + +stop(Pid) -> + Pid ! {self(), quit}, + [?_assert(receive + {ok, quit} -> true + after 500 -> false + end)]. + +test_simple_add(Pid) -> + ?_assertEqual("foo", Pid ! plop:dummy_add("foo")). + +test_add(Pid) -> + TestVector = + <<1,247,141,118,3,148,171,128,29,143,106,97,200,179,204,166,242,98,70,185, + 231,78,193,39,12,245,82,254,230,136,69,69,0,0,0,0,0,0,0,18,103,71,39,45, + 246,185,69,39,104,49,16,150,96,168,113,65,36,147,66,168,111,142,244,248, + 11,108,194,21,223,116,136,222,26,163,97,48,219,95,0,43,82,83,197,76,120, + 166,210,62,103,219,185,216,102,111,217,48,18,100,24,180,17,226,107,180, + 101,221,143,50,132,243,45,181,74,217,231,119,222,8,37,8,94,155,113,29, + 115,237,147,115,133,21,63,145,44,115,22,72,237,76,0,14,38,140,193,213, + 178,112,35,63,183,26,36,160,52,72,17,202,235,114,22,51,25,7,181,32,75, + 122,184,47,236,22,184,155,189,253,39,71,149,74,169,234,41,62,89,217,49, + 144,134,206,92,112,126,96,199,131,214,17,74,236,193,188,56,150,91,12, + 157,93,192,124,192,79,89,164,194,135,159,136,202,198,115,236,76,90,233, + 225,109,97,2,194,182,222,4,242,183,44,83,132,240,67,192,128,86,115,236, + 84,193,120,213,10,25,198,189,197,147,117,151,103,12,6,1,80,37,237,125, + 233,158,237,1,93,202,223,88,245,234,34,113,157,92,39,186,103,89,66,14, + 78,168,208,141,78,183,57,28,196,252,251,249,153,203>>, + Entry = #plop_entry{type = ?PLOP_ENTRY_TYPE_TEST, + entry = <<"some data">>}, + PlopData = #plop_data{signature_type = ?PLOP_SIGTYPE_TEST, + timestamp = 4711, + entry = Entry}, + Pid ! {self(), {add, PlopData}}, + Res = receive M -> M end, + %% {ok, Dev} = file:open("d", write), + %% io:format(Dev, "~p~n", [Res]), + %% file:close(Dev), + [?_assertEqual({ok, TestVector}, Res)]. + +% Helpers. diff --git a/src/test/rsakey.pem b/src/test/rsakey.pem new file mode 100644 index 0000000..8e3fc97 --- /dev/null +++ b/src/test/rsakey.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,1DF607A561756223 + +REwpVd1/3Z/5BZ78/pzbWhSIY1ncFPGooHDEZtHzwnthAEzHvKFqDmCzvN9/HozD +zmb2NbZag4/jbSBFGVqQzGIsnz11pLiyLSAjfoSC1sREJ9XCInF7F1xBUluL5rJB +62pOVVfmoQqOk3hyIiwLzEneagFDMoZXPwkoCmda7sT1uLoo94zAzkhcoeP9rohm +5ssI4woebqwgLDlX/LvLQjbmvAPdtqb2FApH3FTvlqJbPBWPVRQSYMhM32FJPmbC +gPEpZJa1wfMu4bULHw0/GYtlb5MvoNDNe+NxnDpG88Gkh+qFQvARDGLwnuHttrQD +YTWqCPaAd3qaZSNgau2QYEXiOyTKYhPJhgPNTKN3BTx8w7Msk/OoXCKAfuNjsi3a +iX0AmloIMmZUMkk2rsfiGfy8C3buVvdJ7trcUrSm3+QfQVOvPdCSFj96r0iBG9C0 +klQvilVMnd/M1Uk2DCG2rP4HV4DO3fSVx6Vbyl2MqPZgPZkvdrBZThhXsgIFkNlX +WUtpbcscBmQFJlRNUbGT/syVWYSIhpvZBwWj9stY+Tn2jmQ+TUO9Xr7NG3PpJ8J+ +jFU1b30DF6OC9BQ4yPDqXzRshyRpafs7SXgGUOAiLESIs+/HrL5hKAmv+Wb0pb2x +PQ+p0Rsk9aRocODb1pkeRFKJT+sKR1LfaIgwPDhU7HHJp8OQxPdT2pYjpvpxFcb4 ++2MKnfideWIxux7yfFrLUbPh5DqWRIspNsxNagelj/EY6uTaBZdnMumkTgVXyD2Q +tS9LQFYJ7HrxWdZLHrybd7TJlch0dTr9Q+ToSkZq7WAn2qlcs13tVD+ns64u4TFR +KZ5ayA6K7CJzIBVEI4ZymAKvaHTyGcCi7lEcMj0vfps/gjr6+uJ4KnGBrqvAZXoz +oXsIpcJvSPf6IR1mB5NyCVs1aAZ1WGSuA1uTMjLyjmugWFg4dvVGL5nMRcOC2AEM +ayhy9Lref6YxkDIGktkIsZRcUcySK5w4c8xTNoYo/a8sh7zkTjaHFWcw+Ur5AmBx +A7VdQIGyBRtXoJZ9wNyNWqB88Pspo7u/EarAR+fowr8Wtlad7Dm1FAScObCCGiPQ +rbkJ0tzGdNZqJoe37lJ9EznsLUfgGQI/3HyTp9uwPOLfyTeR8adjop6QbMXazBNI +Yqvo58H/QwGxQIg/BdgQW64h4ruVcXFd5YFfzJO9cTH+g2CsFAkeYjU4IcZE6pN6 +BILWKIYhxbYLvjIyigyEhRQbQPKEtViONc02l5cwvN163+wo/1LcdwrXEAAbglPk +8Ka1bkTapk3kH3qJZjiarHoLugQXcSi3clcFkgC8U7+orf3iRQcNn96hzIz/uX9k +BvLFq5lsQKjfhFhUdS07cQM4CLbBVPE9FSiGw37QBeP4HLPf0YIbVy3Ggv12psO7 +/sr+gXqqKFvriCoLhThyW6izm3LTVoGjGN8O+RD/yyBnIfl/8WT+ioyEbSyvGBEr +GgSsAagkoMRj8mFXMWgN042k1Uu28cSQ5xVQAHnNXePljTDTPXbUffSW2zLAc1yJ +p6ga/eX3bFUCZqIwKbsJw/Q1eh3AeG4krtlKHbylH9ShLGNMNbYviCXs4LUD4zu7 +-----END RSA PRIVATE KEY----- -- cgit v1.1