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 +++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 21 deletions(-) (limited to 'src/plop.erl') 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})))]. -- cgit v1.1