%%% Copyright (c) 2014, NORDUnet A/S. %%% See LICENSE for licensing information. %%% %%% @doc Signing service -module(sign). -behaviour(gen_server). %% API. -export([start_link/0, stop/0]). -export([sign/1, get_pubkey/0, get_logid/0]). -export([read_keyfile_ec/1]). %% 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() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). stop() -> call(?MODULE, stop). init([]) -> %% 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. PrivKeyfile = application:get_env(plop, log_private_key, none), PubKeyfile = application:get_env(plop, log_public_key, none), {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)}. read_keyfile_ec(KeyFile) -> lager:debug("reading file ~p", [KeyFile]), {ok, PemBin} = file:read_file(KeyFile), [KeyPem] = public_key:pem_decode(PemBin), decode_key(KeyPem). pem_entry_decode({'SubjectPublicKeyInfo', Der, _}) -> SPKI = public_key:der_decode('SubjectPublicKeyInfo', Der), #'SubjectPublicKeyInfo'{subjectPublicKey = {_, Octets}, algorithm = Algorithm} = SPKI, #'AlgorithmIdentifier'{parameters = ECParams} = Algorithm, Params = public_key:der_decode('EcpkParameters', ECParams), Point = #'ECPoint'{point = Octets}, {Point, Params}; pem_entry_decode(Entry) -> public_key:pem_entry_decode(Entry). %% @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) -> 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}.