summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/plop.erl134
-rw-r--r--src/plop.hrl27
-rw-r--r--src/test/plop_test.erl57
-rw-r--r--src/test/rsakey.pem30
4 files changed, 227 insertions, 21 deletions
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}) ->
+ [<<EntryType:16>>, Entry];
+serialise(#plop_data{version = Version,
+ signature_type = Sigtype,
+ timestamp = Timestamp,
+ entry = Entry}) ->
+ [<<Version:8, Sigtype:8, Timestamp:64>>, 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 = <<?PLOPVERSION:8,
+ LogID/binary,
+ Timestamp:64,
+ Signature/binary>>,
+ %%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-----