summaryrefslogtreecommitdiff
path: root/src/sign.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/sign.erl')
-rw-r--r--src/sign.erl142
1 files changed, 142 insertions, 0 deletions
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}.