Refactor keypair into separate module enoise_keypair

This commit is contained in:
Hans Svensson 2018-03-08 11:22:00 +01:00
parent dd5200d64f
commit a11887393f
8 changed files with 149 additions and 82 deletions

View File

@ -25,7 +25,7 @@
-record(enoise, { pid }).
-type noise_key() :: binary().
-type noise_keypair() :: term().
-type noise_keypair() :: enoise_keypair:keypair().
-type noise_options() :: [noise_option()].
-type noise_option() :: {noise, noise_protocol_option()} %% Required

View File

@ -14,30 +14,23 @@
, hashlen/1
, hkdf/3
, hmac/3
, new_key_pair/1
, pad/3
, pub_key/1
, rekey/2
]).
-record(key_pair, { puk, pik }).
-opaque key_pair() :: #key_pair{}.
-export_type([key_pair/0]).
-define(MAC_LEN, 16).
-spec new_key_pair(Algo :: enoise_hs_state:noise_dh()) -> key_pair().
new_key_pair(dh25519) ->
KeyPair = enacl:crypto_sign_ed25519_keypair(),
#key_pair{ puk = enacl:crypto_sign_ed25519_public_to_curve25519(maps:get(public, KeyPair))
, pik = enacl:crypto_sign_ed25519_secret_to_curve25519(maps:get(secret, KeyPair)) }.
-type keypair() :: enoise_keypair:keypair().
%% @doc Perform a Diffie-Hellman calculation with the secret key from `Key1'
%% and the public key from `Key2' with algorithm `Algo'.
-spec dh(Algo :: enoise_hs_state:noise_dh(),
PrivKey :: key_pair(), PubKey :: binary()) -> binary().
dh(dh25519, KeyPair, PubKey) ->
enacl:curve25519_scalarmult(KeyPair#key_pair.pik, PubKey).
Key1:: keypair(), Key2 :: keypair()) -> binary().
dh(dh25519, Key1, Key2) ->
enacl:curve25519_scalarmult( enoise_keypair:seckey(Key1)
, enoise_keypair:pubkey(Key2));
dh(Type, _Key1, _Key2) ->
error({unsupported_diffie_hellman, Type}).
-spec hmac(Hash :: enoise_sym_state:noise_hash(),
Key :: binary(), Data :: binary()) -> binary().
@ -107,9 +100,6 @@ pad(Data, MinSize, PadByte) ->
<<Data/binary, PadData/binary>>
end.
-spec pub_key(KeyPair :: key_pair()) -> binary().
pub_key(#key_pair{ puk = PubKey }) -> PubKey.
-spec hashlen(Hash :: enoise_sym_state:noise_hash()) -> non_neg_integer().
hashlen(sha256) -> 32;
hashlen(sha512) -> 64;

View File

@ -11,12 +11,13 @@
-type noise_role() :: initiator | responder.
-type noise_dh() :: dh25519 | dh448.
-type noise_token() :: s | e | ee | ss | es | se.
-type keypair() :: enoise_keypair:keypair().
-record(noise_hs, { ss :: enoise_sym_state:state()
, s :: enoise_crypto:key_pair() | undefined
, e :: enoise_crypto:key_pair() | undefined
, rs :: binary() | undefined
, re :: binary() | undefined
, s :: keypair() | undefined
, e :: keypair() | undefined
, rs :: keypair() | undefined
, re :: keypair() | undefined
, role = initiator :: noise_role()
, dh = dh25519 :: noise_dh()
, msgs = [] :: [enoise_protocol:noise_msg()] }).
@ -24,7 +25,8 @@
-export_type([noise_dh/0, noise_role/0, noise_token/0]).
-spec init(Protocol :: string() | enoise_protocol:protocol(),
Role :: noise_role(), Prologue :: binary(), Keys :: term()) -> #noise_hs{}.
Role :: noise_role(), Prologue :: binary(),
Keys :: term()) -> #noise_hs{}.
init(ProtocolName, Role, Prologue, Keys) when is_list(ProtocolName) ->
init(enoise_protocol:from_name(ProtocolName), Role, Prologue, Keys);
init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
@ -36,10 +38,10 @@ init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
, dh = enoise_protocol:dh(Protocol)
, msgs = enoise_protocol:msgs(Role, Protocol) },
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
lists:foldl(fun({out, [s]}, HS0) -> mix_hash(HS0, enoise_crypto:pub_key(S));
({out, [e]}, HS0) -> mix_hash(HS0, enoise_crypto:pub_key(E));
({in, [s]}, HS0) -> mix_hash(HS0, RS);
({in, [e]}, HS0) -> mix_hash(HS0, RE)
lists:foldl(fun({out, [s]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(S));
({out, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(E));
({in, [s]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RS));
({in, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RE))
end, HS, PreMsgs).
finalize(#noise_hs{ msgs = [], ss = SS, role = Role }) ->
@ -79,30 +81,32 @@ read_message(HS, [Token | Tokens], Data0) ->
write_token(HS = #noise_hs{ e = undefined }, e) ->
E = new_key_pair(HS),
PubE = enoise_crypto:pub_key(E),
PubE = enoise_keypair:pubkey(E),
{mix_hash(HS#noise_hs{ e = E }, PubE), PubE};
%% Should only apply during test - TODO: secure this
write_token(HS = #noise_hs{ e = E }, e) ->
PubE = enoise_crypto:pub_key(E),
PubE = enoise_keypair:pubkey(E),
{mix_hash(HS, PubE), PubE};
write_token(HS = #noise_hs{ s = S }, s) ->
{ok, HS1, Msg} = encrypt_and_hash(HS, enoise_crypto:pub_key(S)),
{ok, HS1, Msg} = encrypt_and_hash(HS, enoise_keypair:pubkey(S)),
{HS1, Msg};
write_token(HS, Token) ->
{K1, K2} = dh_token(HS, Token),
{mix_key(HS, dh(HS, K1, K2)), <<>>}.
read_token(HS = #noise_hs{ re = undefined }, e, Data0) ->
DHLen = dhlen(HS),
<<RE:DHLen/binary, Data1/binary>> = Data0,
{mix_hash(HS#noise_hs{ re = RE }, RE), Data1};
read_token(HS = #noise_hs{ rs = undefined }, s, Data0) ->
read_token(HS = #noise_hs{ re = undefined, dh = DH }, e, Data0) ->
DHLen = enoise_crypto:dhlen(DH),
<<REPub:DHLen/binary, Data1/binary>> = Data0,
RE = enoise_keypair:new(DH, REPub),
{mix_hash(HS#noise_hs{ re = RE }, REPub), Data1};
read_token(HS = #noise_hs{ rs = undefined, dh = DH }, s, Data0) ->
DHLen = case has_key(HS) of
true -> dhlen(HS) + 16;
false -> dhlen(HS)
true -> enoise_crypto:dhlen(DH) + 16;
false -> enoise_crypto:dhlen(DH)
end,
<<Temp:DHLen/binary, Data1/binary>> = Data0,
{ok, HS1, RS} = decrypt_and_hash(HS, Temp),
{ok, HS1, RSPub} = decrypt_and_hash(HS, Temp),
RS = enoise_keypair:new(DH, RSPub),
{HS1#noise_hs{ rs = RS }, Data1};
read_token(HS, Token, Data) ->
{K1, K2} = dh_token(HS, Token),
@ -117,13 +121,10 @@ dh_token(#noise_hs{ s = S, rs = RS } , ss) -> {S, RS}.
%% Local wrappers
new_key_pair(#noise_hs{ dh = DH }) ->
enoise_crypto:new_key_pair(DH).
enoise_keypair:new(DH).
dh(#noise_hs{ dh = DH }, KeyPair, PubKey) ->
enoise_crypto:dh(DH, KeyPair, PubKey).
dhlen(#noise_hs{ dh = DH }) ->
enoise_crypto:dhlen(DH).
dh(#noise_hs{ dh = DH }, Key1, Key2) ->
enoise_crypto:dh(DH, Key1, Key2).
has_key(#noise_hs{ ss = SS }) ->
CS = enoise_sym_state:cipher_state(SS),

77
src/enoise_keypair.erl Normal file
View File

@ -0,0 +1,77 @@
%%% ------------------------------------------------------------------
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module is an abstract data type for a key pair.
%%%
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_keypair).
-export([ key_type/1
, new/1
, new/2
, new/3
, pubkey/1
, seckey/1
]).
-type key_type() :: dh25519 | dh448.
-record(kp, { type :: key_type()
, sec :: binary() | undefined
, pub :: binary() }).
-opaque keypair() :: #kp{}.
%% Abstract keypair holding a secret key/public key pair and its type.
-export_type([keypair/0]).
%% @doc Generate a new keypair of type `Type'.
-spec new(Type :: key_type()) -> keypair().
new(Type) ->
{Sec, Pub} = new_key_pair(Type),
#kp{ type = Type, sec = Sec, pub = Pub }.
%% @doc Create a new keypair of type `Type'. If `Public' is `undefined'
%% it will be computed from the `Secret' (using the curve/algorithm
%% indicated by `Type').
-spec new(Type :: key_type(), Secret :: binary(), Public :: binary() | undefined) -> keypair().
new(Type, Secret, undefined) ->
new(Type, Secret, pubkey_from_secret(Type, Secret));
new(Type, Secret, Public) ->
#kp{ type = Type, sec = Secret, pub = Public }.
%% @doc Define a "public only" keypair - holding just a public key and
%% `undefined' for secret key.
-spec new(Type :: key_type(), Public :: binary()) -> keypair().
new(Type, Public) ->
#kp{ type = Type, sec = undefined, pub = Public }.
%% @doc Accessor function - return the key type of the key pair.
-spec key_type(KeyPair :: keypair()) -> key_type().
key_type(#kp{ type = T }) ->
T.
%% @doc Accessor function - return the public key of the key pair.
-spec pubkey(KeyPair :: keypair()) -> binary().
pubkey(#kp{ pub = P }) ->
P.
%% @doc Accessor function - return the secret key of the key pair.
-spec seckey(KeyPair :: keypair()) -> binary().
seckey(#kp{ sec = undefined }) ->
error(keypair_is_public_only);
seckey(#kp{ sec = S }) ->
S.
%% -- Local functions --------------------------------------------------------
new_key_pair(dh25519) ->
KeyPair = enacl:crypto_sign_ed25519_keypair(),
{enacl:crypto_sign_ed25519_secret_to_curve25519(maps:get(secret, KeyPair)),
enacl:crypto_sign_ed25519_public_to_curve25519(maps:get(public, KeyPair))};
new_key_pair(Type) ->
error({unsupported_key_type, Type}).
pubkey_from_secret(dh25519, Secret) ->
enacl:curve25519_scalarmult_base(Secret).

View File

@ -5,21 +5,22 @@
-module(enoise_crypto_tests).
-include_lib("eunit/include/eunit.hrl").
-record(key_pair, { puk, pik }).
curve25519_test() ->
KeyPair1 = enoise_crypto:new_key_pair(dh25519),
KeyPair2 = enoise_crypto:new_key_pair(dh25519),
KeyPair1 = enoise_keypair:new(dh25519),
KeyPair2 = enoise_keypair:new(dh25519),
SharedA = enoise_crypto:dh(dh25519, KeyPair1, KeyPair2#key_pair.puk),
SharedB = enoise_crypto:dh(dh25519, KeyPair2, KeyPair1#key_pair.puk),
SharedA = enoise_crypto:dh(dh25519, KeyPair1, KeyPair2),
SharedB = enoise_crypto:dh(dh25519, KeyPair2, KeyPair1),
?assertMatch(SharedA, SharedB),
#{ a_pub := APub, a_priv := APriv,
b_pub := BPub, b_priv := BPriv, shared := Shared } = test_utils:curve25519_data(),
?assertMatch(Shared, enoise_crypto:dh(dh25519, #key_pair{ puk = APub, pik = APriv }, BPub)),
?assertMatch(Shared, enoise_crypto:dh(dh25519, #key_pair{ puk = BPub, pik = BPriv }, APub)),
KeyPair3 = enoise_keypair:new(dh25519, APriv, APub),
KeyPair4 = enoise_keypair:new(dh25519, BPriv, BPub),
?assertMatch(Shared, enoise_crypto:dh(dh25519, KeyPair3, KeyPair4)),
?assertMatch(Shared, enoise_crypto:dh(dh25519, KeyPair4, KeyPair3)),
ok.

View File

@ -5,7 +5,6 @@
-module(enoise_hs_state_tests).
-include_lib("eunit/include/eunit.hrl").
-record(key_pair, { puk, pik }).
noise_hs_test_() ->
%% Test vectors from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt
@ -40,11 +39,11 @@ noise_hs_test(V = #{ name := Name }) ->
ok.
noise_test(_Name, Protocol, Init, Resp, Messages, HSHash) ->
PubK = fun(undefined) -> undefined; (S) -> enacl:curve25519_scalarmult_base(S) end,
DH = enoise_protocol:dh(Protocol),
SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end,
PubK = fun(undefined) -> undefined; (Pub) -> enoise_keypair:new(DH, Pub) end,
HSInit = fun(P, R, #{ e := E, s := S, rs := RS, prologue := PL }) ->
enoise_hs_state:init(P, R, PL, {#key_pair{ pik = S, puk = PubK(S) },
#key_pair{ pik = E, puk = PubK(E) },
RS, undefined})
enoise_hs_state:init(P, R, PL, {SecK(S), SecK(E), PubK(RS), undefined})
end,
InitHS = HSInit(Protocol, initiator, Init),

View File

@ -5,7 +5,6 @@
-module(enoise_sym_state_tests).
-include_lib("eunit/include/eunit.hrl").
-include("enoise.hrl").
noise_XK_25519_ChaChaPoly_Blake2b_test() ->
Protocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),

View File

@ -18,8 +18,8 @@ noise_dh25519_test_() ->
setup_dh25519() ->
%% Generate a static key-pair for Client and Server
SrvKeyPair = enoise_crypto:new_key_pair(dh25519),
CliKeyPair = enoise_crypto:new_key_pair(dh25519),
SrvKeyPair = enoise_keypair:new(dh25519),
CliKeyPair = enoise_keypair:new(dh25519),
#{ hs_pattern := Ps, hash := Hs, cipher := Cs } = enoise_protocol:supported(),
Configurations = [ enoise_protocol:to_name(P, dh25519, C, H)
@ -31,11 +31,11 @@ noise_test(Conf, SKP, CKP) ->
Protocol = enoise_protocol:from_name(Conf),
Port = 4556,
EchoSrv = echo_srv_start(Port, Protocol, SKP, enoise_crypto:pub_key(CKP)),
EchoSrv = echo_srv_start(Port, Protocol, SKP, CKP),
{ok, TcpSock} = gen_tcp:connect("localhost", Port, [{active, false}, binary, {reuseaddr, true}], 100),
Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, enoise_crypto:pub_key(SKP)} || need_rs(initiator, Conf) ],
Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, SKP} || need_rs(initiator, Conf) ],
{ok, EConn} = enoise:connect(TcpSock, Opts),
ok = enoise:send(EConn, <<"Hello World!">>),
@ -86,27 +86,27 @@ need_rs(Role, Protocol) ->
lists:member({in, [s]}, PreMsgs).
%% Talks to local echo-server (noise-c)
%% client_test() ->
%% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
%% ClientPrivKey = <<64,168,119,119,151,194,94,141,86,245,144,220,78,53,243,231,168,216,66,199,49,148,202,117,98,40,61,109,170,37,133,122>>,
%% ClientPubKey = <<115,39,86,77,44,85,192,176,202,11,4,6,194,144,127,123, 34,67,62,180,190,232,251,5,216,168,192,190,134,65,13,64>>,
%% ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
client_test() ->
TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
ClientPrivKey = <<64,168,119,119,151,194,94,141,86,245,144,220,78,53,243,231,168,216,66,199,49,148,202,117,98,40,61,109,170,37,133,122>>,
ClientPubKey = <<115,39,86,77,44,85,192,176,202,11,4,6,194,144,127,123, 34,67,62,180,190,232,251,5,216,168,192,190,134,65,13,64>>,
ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
%% {ok, TcpSock} = gen_tcp:connect("localhost", 7890, [{active, false}, binary, {reuseaddr, true}], 1000),
%% gen_tcp:send(TcpSock, <<0,8,0,0,3>>), %% "Noise_XK_25519_ChaChaPoly_Blake2b"
{ok, TcpSock} = gen_tcp:connect("localhost", 7890, [{active, false}, binary, {reuseaddr, true}], 1000),
gen_tcp:send(TcpSock, <<0,8,0,0,3>>), %% "Noise_XK_25519_ChaChaPoly_Blake2b"
%% Opts = [ {noise, TestProtocol}
%% , {s, #key_pair{ pik = ClientPrivKey, puk = ClientPubKey }}
%% , {rs, ServerPubKey}
%% , {prologue, <<0,8,0,0,3>>}],
Opts = [ {noise, TestProtocol}
, {s, enoise_keypair:new(dh25519, ClientPrivKey, ClientPubKey)}
, {rs, enoise_keypair:new(dh25519, ServerPubKey)}
, {prologue, <<0,8,0,0,3>>}],
%% {ok, EConn} = enoise:connect(TcpSock, Opts),
%% ok = enoise:send(EConn, <<"ok\n">>),
%% %% receive
%% %% {noise, EConn, <<"ok\n">>} -> ok
%% %% after 1000 -> error(timeout) end,
%% {ok, <<"ok\n">>} = enoise:recv(EConn, 3, 1000),
%% enoise:close(EConn).
{ok, EConn} = enoise:connect(TcpSock, Opts),
ok = enoise:send(EConn, <<"ok\n">>),
%% receive
%% {noise, EConn, <<"ok\n">>} -> ok
%% after 1000 -> error(timeout) end,
{ok, <<"ok\n">>} = enoise:recv(EConn, 3, 1000),
enoise:close(EConn).
%% Expects a call-in from a local echo-client (noise-c)
@ -118,7 +118,7 @@ need_rs(Role, Protocol) ->
%% ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
%% Opts = [ {noise, TestProtocol}
%% , {s, #key_pair{ pik = ServerPrivKey, puk = ServerPubKey }}
%% , {s, enoise_keypair:new(dh25519, ServerPrivKey, ServerPubKey)}
%% , {prologue, <<0,8,0,0,3>>}],
%% {ok, LSock} = gen_tcp:listen(7891, [{reuseaddr, true}, binary]),