From eabc9b4bdca8409601400276018eb9eec6a162d0 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Sat, 15 Nov 2014 00:22:05 +0100 Subject: Move signing code to sign module --- src/plop.erl | 134 ++++++++------------------------------------------- src/plop_sup.erl | 7 ++- src/sign.erl | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 114 deletions(-) create mode 100644 src/sign.erl (limited to 'src') diff --git a/src/plop.erl b/src/plop.erl index b1ad658..24c4b8d 100644 --- a/src/plop.erl +++ b/src/plop.erl @@ -24,12 +24,11 @@ -behaviour(gen_server). %% API. --export([start_link/2, stop/0]). +-export([start_link/0, stop/0]). -export([get_logid/0, serialise/1]). -export([add/3, sth/0, get/1, get/2, spt/1, consistency/2, inclusion/2, inclusion_and_entry/2]). -export([generate_timestamp/0]). %% API for tests. --export([read_keyfile_rsa/2, read_keyfiles_ec/2]). -export([testing_get_pubkey/0]). %% gen_server callbacks. -export([init/1, handle_call/3, terminate/2, @@ -41,13 +40,7 @@ -include_lib("public_key/include/public_key.hrl"). -include_lib("eunit/include/eunit.hrl"). --define(TESTPRIVKEYFILE, "test/eckey.pem"). --define(TESTPUBKEYFILE, "test/eckey-public.pem"). - --record(state, {pubkey :: public_key:rsa_public_key(), - privkey :: public_key:rsa_private_key(), - logid :: binary(), - http_requests, +-record(state, {http_requests, own_requests }). @@ -68,8 +61,8 @@ }). -type sth_signed() :: #sth_signed{}. -start_link(Keyfile, Passphrase) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [Keyfile, Passphrase], []). +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). stop() -> call(?MODULE, stop). @@ -91,18 +84,9 @@ remove_own_request(Plop, RequestId) -> Plop#state.own_requests)}. %%%%%%%%%%%%%%%%%%%% -init([PrivKeyfile, PubKeyfile]) -> - %% Read RSA keypair. - %% {Private_key, Public_key} = read_keyfile_rsa(Keyfile, Passphrase), - %% LogID = crypto:hash(sha256, - %% public_key:der_encode('RSAPublicKey', Public_key)), - %% Read EC keypair. - {Private_key, Public_key, LogID} = read_keyfiles_ec(PrivKeyfile, PubKeyfile), +init([]) -> _Tree = ht:reset_tree([db:size() - 1]), - {ok, #state{pubkey = Public_key, - privkey = Private_key, - logid = LogID, - http_requests = dict:new(), + {ok, #state{http_requests = dict:new(), own_requests = dict:new()}}. handle_cast(_Request, State) -> @@ -163,7 +147,7 @@ add(LogEntry, TreeLeafHash, EntryHash) -> {add, {LogEntry, TreeLeafHash, EntryHash}}). sth() -> - call(?MODULE, {sth, []}). + sth([]). -spec get(non_neg_integer(), non_neg_integer()) -> [{non_neg_integer(), binary(), binary()}]. @@ -174,7 +158,10 @@ get(Hash) -> call(?MODULE, {get, {hash, Hash}}). spt(Data) -> - call(?MODULE, {spt, Data}). + #signature{algorithm = #sig_and_hash_alg{ + hash_alg = sha256, + signature_alg = ecdsa}, + signature = sign:sign(Data)}. consistency(TreeSizeFirst, TreeSizeSecond) -> call(?MODULE, {consistency, {TreeSizeFirst, TreeSizeSecond}}). @@ -188,9 +175,9 @@ inclusion(Hash, TreeSize) -> inclusion_and_entry(Index, TreeSize) -> call(?MODULE, {inclusion_and_entry, {Index, TreeSize}}). get_logid() -> - call(?MODULE, {get, logid}). + sign:get_logid(). testing_get_pubkey() -> - call(?MODULE, {test, pubkey}). + sign:get_pubkey(). storage_nodes() -> application:get_env(plop, storage_nodes, []). @@ -266,10 +253,6 @@ handle_call({get, {index, Start, End}}, _From, Plop) -> handle_call({get, {hash, EntryHash}}, _From, Plop) -> {reply, db:get_by_entry_hash(EntryHash), Plop}; -handle_call({get, logid}, _From, - Plop = #state{logid = LogID}) -> - {reply, LogID, Plop}; - handle_call({add, {LogEntry, TreeLeafHash, EntryHash}}, From, Plop) -> lager:debug("add leafhash ~p", [TreeLeafHash]), case storage_nodes() of @@ -281,14 +264,6 @@ handle_call({add, {LogEntry, TreeLeafHash, EntryHash}}, From, Plop) -> From, Plop)} end; -handle_call({sth, Data}, _From, - Plop = #state{privkey = PrivKey}) -> - {reply, sth(PrivKey, Data), Plop}; - -handle_call({spt, Data}, _From, - Plop = #state{privkey = PrivKey}) -> - {reply, spt(PrivKey, Data), Plop}; - handle_call({consistency, {First, Second}}, _From, Plop) -> {reply, ht:consistency(First - 1, Second - 1), Plop}; @@ -308,26 +283,16 @@ handle_call({inclusion_and_entry, {Index, TreeSize}}, _From, Plop) -> {I, _MTLHash, Entry} -> {ok, Entry, ht:path(I, TreeSize - 1)} end, - {reply, R, Plop}; - -handle_call({test, pubkey}, _From, - Plop = #state{pubkey = PK}) -> - {reply, PK, Plop}. + {reply, R, Plop}. %% @doc Signed Plop Timestamp, conformant to an SCT in RFC6962 3.2 and %% RFC5246 4.7. --spec spt(public_key:ec_private_key(), binary()) -> signature(). -spt(PrivKey, SerialisedData) -> - #signature{algorithm = #sig_and_hash_alg{ - hash_alg = sha256, - signature_alg = ecdsa}, - signature = signhash(SerialisedData, PrivKey)}. %% @doc Signed Tree Head as specified in RFC6962 section 3.2. --spec sth(#'ECPrivateKey'{}, sth_signed() | list()) -> sth(). -sth(PrivKey, []) -> - sth(PrivKey, #sth_signed{timestamp = now}); -sth(PrivKey, #sth_signed{version = Version, timestamp = Timestamp_in}) -> +-spec sth(sth_signed() | list()) -> sth(). +sth([]) -> + sth(#sth_signed{timestamp = now}); +sth(#sth_signed{version = Version, timestamp = Timestamp_in}) -> Timestamp = timestamp(Timestamp_in), Treesize = ht:size(), Roothash = ht:root(), @@ -341,71 +306,14 @@ sth(PrivKey, #sth_signed{version = Version, timestamp = Timestamp_in}) -> algorithm = #sig_and_hash_alg{ hash_alg = sha256, signature_alg = ecdsa}, - signature = signhash(BinToSign, PrivKey)}, + signature = sign:sign(BinToSign)}, STH = {Treesize, Timestamp, Roothash, Signature}, %%io:format("STH: ~p~nBinToSign: ~p~nSignature: ~p~nTimestamp: ~p~n", %% [STH, BinToSign, Signature, Timestamp]), STH. -%% TODO: Merge the keyfile reading functions. -%% @doc Read one password protected PEM file with an RSA keypair. -read_keyfile_rsa(Filename, Passphrase) -> - {ok, PemBin} = file:read_file(Filename), - [KeyPem] = public_key:pem_decode(PemBin), % Use first entry. - Privatekey = decode_key(KeyPem, Passphrase), - {Privatekey, public_key(Privatekey)}. - -%% @doc Read two PEM files, one with a private EC key and one with the -%% corresponding public EC key. -read_keyfiles_ec(PrivkeyFile, Pubkeyfile) -> - {ok, PemBinPriv} = file:read_file(PrivkeyFile), - [OTPPubParamsPem, PrivkeyPem] = public_key:pem_decode(PemBinPriv), - Privatekey = decode_key(PrivkeyPem), - - {_, ParamsBin, ParamsEnc} = OTPPubParamsPem, - PubParamsPem = {'EcpkParameters', ParamsBin, ParamsEnc}, - Params = public_key:pem_entry_decode(PubParamsPem), - - {ok, PemBinPub} = file:read_file(Pubkeyfile), - [SPKIPem] = public_key:pem_decode(PemBinPub), - %% SPKI is missing #'AlgorithmIdentifier' so pem_entry_decode won't do. - %% Publickey = public_key:pem_entry_decode(SPKIPem), - #'SubjectPublicKeyInfo'{algorithm = AlgoDer} = SPKIPem, - SPKI = public_key:der_decode('SubjectPublicKeyInfo', AlgoDer), - #'SubjectPublicKeyInfo'{subjectPublicKey = {_, Octets}} = SPKI, - Point = #'ECPoint'{point = Octets}, - Publickey = {Point, Params}, - - KeyID = crypto:hash(sha256, AlgoDer), - - {Privatekey, Publickey, KeyID}. - -decode_key(Entry) -> - public_key:pem_entry_decode(Entry). -decode_key(Entry, Passphrase) -> - public_key:pem_entry_decode(Entry, Passphrase). - -public_key(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) -> - #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}. - - -%% FIXME: Merge RSA and DC. -signhash(D, K) -> - signhash_ec(D, K). - --spec signhash_ec(iolist() | binary(), public_key:ec_private_key()) -> binary(). -signhash_ec(Data, PrivKey) -> - public_key:sign(Data, sha256, PrivKey). - -%% -spec signhash_rsa(iolist() | binary(), public_key:rsa_private_key()) -> binary(). -%% signhash_rsa(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}]). + + %%%%%%%%%%%%%%%%%%%% %% Serialisation of data. diff --git a/src/plop_sup.erl b/src/plop_sup.erl index 663b3bc..eb65925 100644 --- a/src/plop_sup.erl +++ b/src/plop_sup.erl @@ -38,8 +38,13 @@ init(Args) -> permanent, 10000, worker, [ht]}, + {the_signing, + {sign, start_link, Args}, % All arguments go to sign. + permanent, + 10000, + worker, [sign]}, {the_plop, - {plop, start_link, Args}, % All arguments go to plop. + {plop, start_link, []}, permanent, 10000, % Shut down within 10s. worker, [plop]}]}}. diff --git a/src/sign.erl b/src/sign.erl new file mode 100644 index 0000000..1239023 --- /dev/null +++ b/src/sign.erl @@ -0,0 +1,142 @@ +%%% Copyright (c) 2014, NORDUnet A/S. +%%% See LICENSE for licensing information. +%%% +%%% @doc Signing service + +-module(sign). +-behaviour(gen_server). + +%% API. +-export([start_link/2, stop/0]). +-export([sign/1, get_pubkey/0, get_logid/0]). +%% API for tests. +-export([read_keyfile_rsa/2, read_keyfiles_ec/2]). +%% gen_server callbacks. +-export([init/1, handle_call/3, terminate/2, + handle_cast/2, handle_info/2, code_change/3]). + +-import(stacktrace, [call/2]). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-record(state, {pubkey :: public_key:rsa_public_key(), + privkey :: public_key:rsa_private_key(), + logid :: binary() + }). + +start_link(Keyfile, Passphrase) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [Keyfile, Passphrase], []). + +stop() -> + call(?MODULE, stop). + + +init([PrivKeyfile, PubKeyfile]) -> + %% Read RSA keypair. + %% {Private_key, Public_key} = read_keyfile_rsa(Keyfile, Passphrase), + %% LogID = crypto:hash(sha256, + %% public_key:der_encode('RSAPublicKey', Public_key)), + %% Read EC keypair. + {Private_key, Public_key, LogID} = read_keyfiles_ec(PrivKeyfile, PubKeyfile), + _Tree = ht:reset_tree([db:size() - 1]), + {ok, #state{pubkey = Public_key, + privkey = Private_key, + logid = LogID}}. + +%% TODO: Merge the keyfile reading functions. +%% @doc Read one password protected PEM file with an RSA keypair. +read_keyfile_rsa(Filename, Passphrase) -> + {ok, PemBin} = file:read_file(Filename), + [KeyPem] = public_key:pem_decode(PemBin), % Use first entry. + Privatekey = decode_key(KeyPem, Passphrase), + {Privatekey, public_key(Privatekey)}. + +%% @doc Read two PEM files, one with a private EC key and one with the +%% corresponding public EC key. +read_keyfiles_ec(PrivkeyFile, Pubkeyfile) -> + {ok, PemBinPriv} = file:read_file(PrivkeyFile), + [OTPPubParamsPem, PrivkeyPem] = public_key:pem_decode(PemBinPriv), + Privatekey = decode_key(PrivkeyPem), + + {_, ParamsBin, ParamsEnc} = OTPPubParamsPem, + PubParamsPem = {'EcpkParameters', ParamsBin, ParamsEnc}, + Params = public_key:pem_entry_decode(PubParamsPem), + + {ok, PemBinPub} = file:read_file(Pubkeyfile), + [SPKIPem] = public_key:pem_decode(PemBinPub), + %% SPKI is missing #'AlgorithmIdentifier' so pem_entry_decode won't do. + %% Publickey = public_key:pem_entry_decode(SPKIPem), + #'SubjectPublicKeyInfo'{algorithm = AlgoDer} = SPKIPem, + SPKI = public_key:der_decode('SubjectPublicKeyInfo', AlgoDer), + #'SubjectPublicKeyInfo'{subjectPublicKey = {_, Octets}} = SPKI, + Point = #'ECPoint'{point = Octets}, + Publickey = {Point, Params}, + + KeyID = crypto:hash(sha256, AlgoDer), + + {Privatekey, Publickey, KeyID}. + +%% -spec signhash_rsa(iolist() | binary(), public_key:rsa_private_key()) -> binary(). +%% signhash_rsa(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 signhash_ec(iolist() | binary(), public_key:ec_private_key()) -> binary(). +signhash_ec(Data, PrivKey) -> + public_key:sign(Data, sha256, PrivKey). + +decode_key(Entry) -> + public_key:pem_entry_decode(Entry). +decode_key(Entry, Passphrase) -> + public_key:pem_entry_decode(Entry, Passphrase). + +public_key(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) -> + #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}. + + +%%%%%%%%%%%%%%%%%%%% +%% Public API. + +sign(Data) -> + call(?MODULE, {sign, Data}). + +get_pubkey() -> + call(?MODULE, {get, pubkey}). + +get_logid() -> + call(?MODULE, {get, logid}). + +%%%%%%%%%%%%%%%%%%%% +%% gen_server callbacks. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +terminate(_Reason, _State) -> + io:format("~p terminating~n", [?MODULE]), + ok. + +handle_call(stop, _From, State) -> + {stop, normal, stopped, State}; + +handle_call({get, logid}, _From, State) -> + {reply, State#state.logid, State}; + +handle_call({get, pubkey}, _From, State) -> + {reply, State#state.pubkey, State}; + +handle_call({sign, Data}, _From, State) -> + %% FIXME: Merge RSA and DC. + {reply, signhash_ec(Data, State#state.privkey), State}. -- cgit v1.1