From 2f733d1018c9602ecf8bac5e2516e69285120563 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Sat, 26 Apr 2014 19:01:41 +0200 Subject: Add STH support, with failing tests due to gen_server testing woes. Move things out of spt() for reuse by sth(). --- src/plop.erl | 132 ++++++++++++++++++++++++++++++++----------------- src/plop.hrl | 30 ++++++----- src/test/plop_test.erl | 68 ++++++++++++++++--------- 3 files changed, 149 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/plop.erl b/src/plop.erl index 4515d25..390299d 100644 --- a/src/plop.erl +++ b/src/plop.erl @@ -12,6 +12,8 @@ %% API. -export([start_link/0, start_link/2, stop/0]). -export([add/1, sth/0]). +%% API for tests. +-export([sth/1]). %% gen_server callbacks. -export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2, code_change/3]). @@ -57,66 +59,73 @@ terminate(_Reason, _State) -> ok. %%%%%%%%%%%%%%%%%%%% -add(Data) when is_record(Data, plop_data) -> +add(Data) when is_record(Data, spt) -> gen_server:call(?MODULE, {add, Data}). sth() -> - gen_server:call(?MODULE, sth). -%%%%%%%%%%%%%%%%%%%% + gen_server:call(?MODULE, {sth, []}). +sth(Data) -> + gen_server:call(?MODULE, {sth, Data}). +%%%%%%%%%%%%%%%%%%%% handle_call(stop, _From, State) -> {stop, normal, stopped, State}; -handle_call({add, Data = #plop_data{entry = Entry}}, - _From, + +handle_call({add, Data = #spt{entry = Entry}}, _From, Plop = #plop{privkey = Privkey, logid = LogID, hashtree = Tree}) -> %% fixme: add Entry to db, - ht:append(Tree, serialise(Entry)), + NewTree = ht:append(Tree, serialise(Entry)), + io:format("Tree: ~p~nNewTree: ~p~n", [Tree, NewTree]), SPT = spt(LogID, Privkey, Data), - {reply, SPT, Plop}; -handle_call(sth, _From, Plop = #plop{hashtree = Tree}) -> - {reply, sth(Tree), Plop}. + {reply, SPT, Plop#plop{hashtree = NewTree}}; + +handle_call({sth, Data}, _From, + Plop = #plop{privkey = PrivKey, + hashtree = Tree}) -> + {reply, sth(PrivKey, Tree, Data), Plop}. %%%%%%%%%%%%%%%%%%%% %% @doc Signed Plop Timestamp according to RFC6962 3.2 and RFC5246 4.7. -spt(LogID, PrivKey, Data = #plop_data{timestamp = Timestamp_in}) -> - 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(Data)), - - %% 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}]), +-spec spt(binary(), binary(), spt()) -> binary(). +spt(LogID, PrivKey, Data = #spt{timestamp = Timestamp_in}) -> + Timestamp = timestamp(Timestamp_in), + BinToSign = + list_to_binary(serialise(Data#spt{ + signature_type = certificate_timestamp, + timestamp = Timestamp})), + Signature = signhash(BinToSign, PrivKey), SPT = <>, - %%io:format("SPT: ~p~nBinToSign: ~p~nSignature = ~p~n", [SPT, BinToSign, Signature]), + %%io:format("SPT: ~p~nBinToSign: ~p~nSignature = ~p~n", + %% [SPT, BinToSign, Signature]), SPT. -%% @doc Signed Tree Head - %% digitally-signed struct { - %% Version version; - %% SignatureType signature_type = tree_hash; - %% uint64 timestamp; - %% uint64 tree_size; - %% opaque sha256_root_hash[32]; - %% } TreeHeadSignature; -sth(Tree) -> - "FIXME: signed tree head for " ++ Tree. +%% @doc Signed Tree Head as described in RFC6962 section 3.2. +sth(PrivKey, Tree, []) -> + sth(PrivKey, Tree, #sth{timestamp = now}); +sth(PrivKey, Tree, #sth{version = Version, timestamp = Timestamp_in}) -> + Timestamp = timestamp(Timestamp_in), + Treesize = ht:size(Tree), + Roothash = ht:tree_hash(Tree), + BinToSign = + list_to_binary(serialise(#sth{version = Version, + signature_type = tree_hash, + timestamp = Timestamp, + tree_size = Treesize, + root_hash = Roothash})), + Signature = signhash(BinToSign, PrivKey), + STH = <>, + %% io:format("STH: ~p~nBinToSign: ~p~nSignature: ~p~nTimestamp: ~p~n", + %% [STH, BinToSign, Signature, Timestamp]), + STH. read_keyfile(Filename, Passphrase) -> {ok, PemBin} = file:read_file(Filename), @@ -127,20 +136,51 @@ read_keyfile(Filename, Passphrase) -> public_key(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) -> #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}. --spec serialise(plop_data() | plop_entry()) -> iolist(). -serialise(#plop_data{version = Version, - signature_type = SigtypeAtom, - timestamp = Timestamp, - entry = Entry}) -> +-spec signhash(iolist() | binary(), binary()) -> binary(). +signhash(Data, PrivKey) -> + %% Was going to just crypto: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) + public_key:encrypt_private(crypto:hash(sha256, Data), + PrivKey, + [{rsa_pad, rsa_pkcs1_padding}]). + +-spec timestamp(now | integer()) -> integer(). +timestamp(Timestamp) -> + case Timestamp of + now -> + {NowMegaSec, NowSec, NowMicroSec} = now(), + trunc(NowMegaSec * 1.0e9 + + NowSec * 1.0e3 + + NowMicroSec / 1.0e3); + _ -> Timestamp + end. + +-spec serialise(spt() | sth() | plop_entry()) -> iolist(). +serialise(#spt{version = Version, + signature_type = SigtypeAtom, + timestamp = Timestamp, + entry = Entry}) -> Sigtype = signature_type(SigtypeAtom), [<>, serialise(Entry)]; serialise(#plop_entry{type = TypeAtom, data = Data}) -> Type = entry_type(TypeAtom), - [<>, Data]. + [<>, Data]; +serialise(#sth{version = Version, + signature_type = SigtypeAtom, + timestamp = Timestamp, + tree_size = Treesize, + root_hash = Roothash}) -> + Sigtype = signature_type(SigtypeAtom), + [<>]. +-spec signature_type(signature_type()) -> integer(). signature_type(certificate_timestamp) -> 0; signature_type(tree_hash) -> 1; signature_type(test) -> 2. + +-spec entry_type(entry_type()) -> integer(). entry_type(x509) -> 0; entry_type(precert) -> 1; entry_type(test) -> 2. @@ -150,7 +190,7 @@ entry_type(test) -> 2. serialise_test_() -> [?_assertEqual( <<1:8, 0:8, 0:64, 0:16, "foo">>, - list_to_binary(serialise(#plop_data{ + list_to_binary(serialise(#spt{ signature_type = certificate_timestamp, timestamp = 0, entry = #plop_entry{type = x509, diff --git a/src/plop.hrl b/src/plop.hrl index bfd900b..7275f5a 100644 --- a/src/plop.hrl +++ b/src/plop.hrl @@ -1,21 +1,29 @@ -%% A plop_entry has a type and some data. -%% A plop_data record has the meta data necessary for constructing a -%% signed timestamp. +-type signature_type() :: certificate_timestamp | tree_hash | test. +-type entry_type() :: x509 | precert | test. --record(plop_data, { +%% @doc The parts of an SPT which is to be signed. +-record(spt, { version = 1 :: integer(), - signature_type = certificate_timestamp :: certificate_timestamp | - tree_hash | - test, + signature_type :: signature_type(), timestamp = now :: 'now' | integer(), entry :: plop_entry() }). +-type spt() :: #spt{}. + -record(plop_entry, { - type = x509 :: x509 | precert | test, + type :: entry_type(), data = <<>> :: binary() }). - --type plop_data() :: #plop_data{}. -type plop_entry() :: #plop_entry{}. --export_type([plop_data/0, plop_entry/0]). +%% @doc The parts of an STH which is to be signed. +-record(sth, { + version = 1 :: integer(), + signature_type :: signature_type(), + timestamp = now :: 'now' | integer(), + tree_size :: integer(), + root_hash :: binary() % sha256 + }). +-type sth() :: #sth{}. + +-export_type([plop_entry/0, entry_type/0]). diff --git a/src/test/plop_test.erl b/src/test/plop_test.erl index b453301..e682b28 100644 --- a/src/test/plop_test.erl +++ b/src/test/plop_test.erl @@ -3,40 +3,60 @@ -include_lib("eunit/include/eunit.hrl"). adding_test_() -> - {"Verifies startup and adding log entries.", + {"Verifies startup, adding log entries and STH's.", {setup, % TODO: use 'foreach' fun start/0, fun stop/1, fun (Arg) -> - [test_add(Arg)] + [test_add(Arg), test_sth(Arg), + test_add(Arg), + test_sth(Arg)] % <-- should fail! end}}. start() -> + io:format("Starting.~n"), plop:start_link("test/rsakey.pem", "sikrit"). stop(_Pid) -> - [?_assertEqual({ok, plop}, plop:stop())]. + [?_assertEqual({qok, plop}, plop:stop())]. 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 = test, - data = <<"some data">>}, - PlopData = #plop_data{signature_type = test, - timestamp = 4711, - entry = Entry}, - [?_assertEqual(TestVector, plop:add(PlopData))]. + <<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,69,73,8,105,107, + 47,97,130,137,92,201,148,11,68,203,103,216,217,249,38,109,208,23,55,107,21, + 110,128,207,151,46,4,178,228,74,5,247,64,180,85,122,236,127,97,226,50,124, + 212,251,227,65,248,18,36,124,252,103,24,35,99,180,207,126,63,116,149,21,86, + 255,197,248,212,93,100,123,161,159,94,29,112,23,246,98,3,124,89,135,234,71, + 246,21,93,152,214,209,58,25,52,132,219,22,0,38,237,226,118,1,168,86,218,18, + 112,227,11,25,199,15,151,246,253,7,91,72,88,169,164,79,143,160,157,241,168, + 15,230,1,216,93,67,24,230,106,203,61,115,100,172,238,165,236,198,222,33,126, + 12,163,226,165,161,232,106,39,94,93,247,2,164,163,72,34,236,228,168,53,19, + 128,111,78,34,54,166,95,78,11,131,241,191,254,82,225,72,68,111,229,169,24,75, + 90,254,167,119,10,136,211,20,178,251,244,124,87,223,61,102,244,143,98,213,59, + 217,84,80,64,22,209,1,63,64,185,63,13,115,43,36,143,93,19,206,234,100,181, + 203,214,189,144,145,21,247,165,125,192,43,94,247,209,81,50,100>>, + Entry = #plop_entry{type = test, data = <<"some data">>}, + SPT = #spt{signature_type = test, timestamp = 4711, entry = Entry}, + [?_assertEqual(TestVector, plop:add(SPT))]. + +test_sth(_Pid) -> + TestVector = + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,103,93,90,159,157,211,129,96,54,161,145,226, + 218,28,127,43,87,221,243,153,101,255,249,156,114,234,50,84,163,183,64,215, + 227,112,208,11,52,174,79,151,153,101,56,175,228,12,222,146,138,32,222,199, + 187,222,38,67,121,85,204,137,41,61,226,88,128,19,77,73,1,208,145,83,142,28, + 130,99,147,213,29,103,113,232,20,209,250,76,198,16,4,249,65,26,232,149,1,21, + 42,95,229,251,167,126,220,212,74,195,215,225,22,145,98,51,11,28,83,30,213,15, + 42,162,135,251,71,195,145,165,194,247,181,213,47,126,95,232,111,231,49,46, + 150,177,118,142,28,230,228,167,54,209,42,177,108,62,39,236,85,153,176,117, + 111,61,220,120,49,159,89,185,220,212,53,71,69,144,188,200,16,29,234,255,242, + 126,223,87,146,219,248,164,180,20,229,161,227,24,32,209,180,122,61,195,149, + 49,3,195,162,218,56,86,185,200,17,188,82,23,64,67,187,160,167,13,175,233,211, + 226,72,198,182,6,6,65,123,76,10,81,66,55,159,120,162,56,102,233,35,102,153, + 214,238,116,108,181,106,159,157,241,101,120,35,107,152,36,193,241,211,210,72, + 184,166,220,155,198,238,246,47,181,89,80,29,52,222,181,25,81,76,123,249,225, + 5,192,233,133,131>>, + STH = plop:sth(#sth{timestamp = 4711}), + %%io:format(element(2, file:open("foo", write)), "~p", [STH]), + [?_assertEqual(TestVector, STH)]. -- cgit v1.1