summaryrefslogtreecommitdiff
path: root/src/plop.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/plop.erl')
-rw-r--r--src/plop.erl227
1 files changed, 163 insertions, 64 deletions
diff --git a/src/plop.erl b/src/plop.erl
index 390299d..bee9cb4 100644
--- a/src/plop.erl
+++ b/src/plop.erl
@@ -1,4 +1,6 @@
-%%% @doc Server holding log entries in a database and hashes in a Merkle tree.
+%%% @doc Server holding log entries in a database and hashes in a
+%%% Merkle tree. A backend for things like Certificate Transparency
+%%% (RFC 6962).
%%%
%%% When you submit data for insertion in the log, the data and a hash
%%% of it is stored in a way that [mumble FIXME and FIXME]. In return
@@ -23,12 +25,13 @@
-include_lib("eunit/include/eunit.hrl").
-define(PLOPVERSION, 1).
--record(plop, {pubkey :: public_key:rsa_public_key(),
- privkey :: public_key:rsa_private_key(),
- logid :: binary(),
- hashtree :: ht:head()}).
+-record(state, {pubkey :: public_key:rsa_public_key(),
+ privkey :: public_key:rsa_private_key(),
+ logid :: binary(),
+ hashtree :: ht:head()}).
start_link() ->
+ db:start_link(),
start_link("test/rsakey.pem", "sikrit").
start_link(Keyfile, Passphrase) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Keyfile, Passphrase], []).
@@ -41,10 +44,10 @@ init([Keyfile, Passphrase]) ->
{Private_key, Public_key} = read_keyfile(Keyfile, Passphrase),
LogID = crypto:hash(sha256,
public_key:der_encode('RSAPublicKey', Public_key)),
- {ok, #plop{pubkey = Public_key,
- privkey = Private_key,
- logid = LogID,
- hashtree = ht:create()}}.
+ {ok, #state{pubkey = Public_key,
+ privkey = Private_key,
+ logid = LogID,
+ hashtree = ht:create()}}.
handle_cast(_Request, State) ->
{noreply, State}.
@@ -59,7 +62,7 @@ terminate(_Reason, _State) ->
ok.
%%%%%%%%%%%%%%%%%%%%
-add(Data) when is_record(Data, spt) ->
+add(Data) when is_record(Data, timestamped_entry) ->
gen_server:call(?MODULE, {add, Data}).
sth() ->
@@ -71,39 +74,73 @@ sth(Data) ->
handle_call(stop, _From, State) ->
{stop, normal, stopped, State};
-handle_call({add, Data = #spt{entry = Entry}}, _From,
- Plop = #plop{privkey = Privkey,
- logid = LogID,
- hashtree = Tree}) ->
- %% fixme: add Entry to db,
- NewTree = ht:append(Tree, serialise(Entry)),
- io:format("Tree: ~p~nNewTree: ~p~n", [Tree, NewTree]),
- SPT = spt(LogID, Privkey, Data),
- {reply, SPT, Plop#plop{hashtree = NewTree}};
+%% FIXME: What's the right interface for add()? Need to be able to set
+%% version and signature type, at least. That's missing from
+%% #timestamped_entry, so add it somehow.
+handle_call({add, #timestamped_entry{} = TimestampedEntry},
+ _From, State = #state{privkey = Privkey,
+ logid = LogID,
+ hashtree = Tree}) ->
+ {NewTree, SPT} = do_add(TimestampedEntry, Privkey, LogID, Tree),
+ io:format("Index: ~p~nSPT: ~p~n", [ht:size(NewTree), SPT]),
+ {reply, SPT, State#state{hashtree = NewTree}};
handle_call({sth, Data}, _From,
- Plop = #plop{privkey = PrivKey,
- hashtree = Tree}) ->
+ Plop = #state{privkey = PrivKey,
+ hashtree = Tree}) ->
{reply, sth(PrivKey, Tree, Data), Plop}.
%%%%%%%%%%%%%%%%%%%%
-%% @doc Signed Plop Timestamp according to RFC6962 3.2 and RFC5246 4.7.
--spec spt(binary(), binary(), spt()) -> binary().
-spt(LogID, PrivKey, Data = #spt{timestamp = Timestamp_in}) ->
+do_add(TimestampedEntry, Privkey, LogID, Tree) ->
+ H = crypto:hash(sha256, serialise(TimestampedEntry)),
+ Record = db:find(H),
+ TmpFixm = case Record of
+ #plop{index = I, spt = S} ->
+ io:format("Found entry w/ index=~p, SPT: ~p~n", [I, S]),
+ %% DB consistency check:
+ %%Record = #plop{hash = H, spt = serialise(Data)}, % FIXME: Remove.
+ {Tree, % State not changed.
+ S}; % Cached SPT.
+ _ ->
+ NewSPT = spt(LogID, Privkey, TimestampedEntry),
+ DbData = #plop{index = ht:size(Tree) + 1,
+ hash = H,
+ spt = NewSPT},
+ db:add(DbData),
+ LeafData = #mtl{version = 1,
+ leaf_type = timestamped_entry,
+ entry = TimestampedEntry},
+ {ht:append(Tree, serialise(LeafData)), % New tree.
+ NewSPT} % New SPT.
+ end,
+ TmpFixm.
+
+%% @doc Signed Plop Timestamp, conformant to an SCT in RFC6962 3.2 and
+%% RFC5246 4.7.
+-spec spt(binary(), public_key:rsa_private_key(), timestamped_entry()) -> binary().
+spt(LogID, PrivKey, #timestamped_entry{
+ timestamp = Timestamp_in,
+ entry_type = EntryType,
+ entry = Entry
+ }) ->
Timestamp = timestamp(Timestamp_in),
BinToSign =
- list_to_binary(serialise(Data#spt{
- signature_type = certificate_timestamp,
- timestamp = Timestamp})),
+ list_to_binary(serialise(#spt_signed{
+ version = 1,
+ signature_type = certificate_timestamp,
+ timestamp = Timestamp,
+ entry_type = EntryType,
+ signed_entry = Entry})),
Signature = signhash(BinToSign, PrivKey),
- SPT = <<?PLOPVERSION:8,
- LogID/binary,
- Timestamp:64,
- Signature/binary>>,
- %%io:format("SPT: ~p~nBinToSign: ~p~nSignature = ~p~n",
- %% [SPT, BinToSign, Signature]),
- SPT.
+ SPT = serialise(#spt_on_wire{
+ version = ?PLOPVERSION,
+ logid = LogID,
+ timestamp = Timestamp,
+ signature = Signature}),
+ io:format("SPT: ~p~nBinToSign: ~p~nSignature = ~p~n",
+ [SPT, BinToSign, Signature]),
+ list_to_binary(SPT).
%% @doc Signed Tree Head as described in RFC6962 section 3.2.
sth(PrivKey, Tree, []) ->
@@ -123,8 +160,8 @@ sth(PrivKey, Tree, #sth{version = Version, timestamp = Timestamp_in}) ->
Timestamp:64,
Roothash/binary,
Signature/binary>>,
- %% io:format("STH: ~p~nBinToSign: ~p~nSignature: ~p~nTimestamp: ~p~n",
- %% [STH, BinToSign, Signature, Timestamp]),
+ io:format("STH: ~p~nBinToSign: ~p~nSignature: ~p~nTimestamp: ~p~n",
+ [STH, BinToSign, Signature, Timestamp]),
STH.
read_keyfile(Filename, Passphrase) ->
@@ -136,7 +173,7 @@ read_keyfile(Filename, Passphrase) ->
public_key(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) ->
#'RSAPublicKey'{modulus = Mod, publicExponent = Exp}.
--spec signhash(iolist() | binary(), binary()) -> binary().
+-spec signhash(iolist() | binary(), public_key:rsa_private_key()) -> 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
@@ -146,6 +183,26 @@ signhash(Data, PrivKey) ->
PrivKey,
[{rsa_pad, rsa_pkcs1_padding}]).
+%%%%%%%%%%%%%%%%%%%%
+%% Serialisation of data.
+
+%% FIXME: Make the type conversion functions return binaries instead
+%% of integers. Moving knowledge about width of these to one place.
+
+-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.
+
+-spec leaf_type(leaf_type()) -> integer().
+leaf_type(timestamped_entry) -> 0;
+leaf_type(test) -> 1.
+
-spec timestamp(now | integer()) -> integer().
timestamp(Timestamp) ->
case Timestamp of
@@ -157,41 +214,83 @@ timestamp(Timestamp) ->
_ -> Timestamp
end.
--spec serialise(spt() | sth() | plop_entry()) -> iolist().
-serialise(#spt{version = Version,
- signature_type = SigtypeAtom,
- timestamp = Timestamp,
- entry = Entry}) ->
+-spec serialise(timestamped_entry() | spt_on_wire() | spt_signed() | mtl() | sth()) -> iolist().
+serialise(#timestamped_entry{
+ timestamp = Timestamp,
+ entry_type = TypeAtom,
+ entry = Entry
+ }) ->
+ EntryType = entry_type(TypeAtom),
+ [<<Timestamp:64,
+ EntryType:16,
+ Entry/binary>>];
+serialise(#spt_on_wire{
+ version = Version,
+ logid = LogID,
+ timestamp = Timestamp,
+ signature = Signature
+ }) ->
+ [<<Version:8,
+ LogID/binary,
+ Timestamp:64,
+ Signature/binary>>];
+serialise(#spt_signed{
+ version = Version,
+ signature_type = SigtypeAtom,
+ timestamp = Timestamp,
+ entry_type = EntrytypeAtom,
+ signed_entry = Entry
+ }) ->
Sigtype = signature_type(SigtypeAtom),
- [<<Version:8, Sigtype:8, Timestamp:64>>, serialise(Entry)];
-serialise(#plop_entry{type = TypeAtom, data = Data}) ->
- Type = entry_type(TypeAtom),
- [<<Type:16>>, Data];
-serialise(#sth{version = Version,
- signature_type = SigtypeAtom,
- timestamp = Timestamp,
- tree_size = Treesize,
- root_hash = Roothash}) ->
+ Entrytype = entry_type(EntrytypeAtom),
+ [<<Version:8,
+ Sigtype:8,
+ Timestamp:64,
+ Entrytype:16,
+ Entry/binary>>];
+serialise(#mtl{ % Merkle Tree Leaf.
+ version = Version,
+ leaf_type = TypeAtom,
+ entry = TimestampedEntry
+ }) ->
+ LeafType = leaf_type(TypeAtom),
+ [<<Version:8,
+ LeafType:8>>,
+ serialise(TimestampedEntry)];
+serialise(#sth{ % Signed Tree Head.
+ version = Version,
+ signature_type = SigtypeAtom,
+ timestamp = Timestamp,
+ tree_size = Treesize,
+ root_hash = Roothash
+ }) ->
Sigtype = signature_type(SigtypeAtom),
- [<<Version:8, Sigtype:8, Timestamp:64, Treesize:64, Roothash/binary>>].
+ [<<Version:8,
+ Sigtype:8,
+ Timestamp:64,
+ Treesize:64,
+ Roothash/binary>>].
--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.
%%%%%%%%%%%%%%%%%%%%
%% Tests.
serialise_test_() ->
[?_assertEqual(
<<1:8, 0:8, 0:64, 0:16, "foo">>,
- list_to_binary(serialise(#spt{
+ list_to_binary(serialise(#spt_signed{
+ version = 1,
signature_type = certificate_timestamp,
timestamp = 0,
- entry = #plop_entry{type = x509,
- data = <<"foo">>}})))].
+ entry_type = x509,
+ signed_entry = <<"foo">>})))].
+add_test_() ->
+ {ok, S} = init(["test/rsakey.pem", "sikrit"]),
+ [?_assertEqual(
+ <<"fixme">>,
+ do_add(#timestamped_entry{
+ timestamp = 4711,
+ entry_type = test,
+ entry = <<"some data">>},
+ S#state.privkey,
+ S#state.logid,
+ S#state.hashtree))].