diff --git a/src/enoise.erl b/src/enoise.erl index 8932277..9c15f39 100644 --- a/src/enoise.erl +++ b/src/enoise.erl @@ -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 diff --git a/src/enoise_crypto.erl b/src/enoise_crypto.erl index 77a19e6..507f86f 100644 --- a/src/enoise_crypto.erl +++ b/src/enoise_crypto.erl @@ -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) -> <> 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; diff --git a/src/enoise_hs_state.erl b/src/enoise_hs_state.erl index 1e68fb3..ad6a278 100644 --- a/src/enoise_hs_state.erl +++ b/src/enoise_hs_state.erl @@ -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), - <> = 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), + <> = 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, <> = 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), diff --git a/src/enoise_keypair.erl b/src/enoise_keypair.erl new file mode 100644 index 0000000..26340b4 --- /dev/null +++ b/src/enoise_keypair.erl @@ -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). diff --git a/test/enoise_crypto_tests.erl b/test/enoise_crypto_tests.erl index d04ade4..d52e2eb 100644 --- a/test/enoise_crypto_tests.erl +++ b/test/enoise_crypto_tests.erl @@ -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. diff --git a/test/enoise_hs_state_tests.erl b/test/enoise_hs_state_tests.erl index 3fe006d..1445770 100644 --- a/test/enoise_hs_state_tests.erl +++ b/test/enoise_hs_state_tests.erl @@ -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), diff --git a/test/enoise_sym_state_tests.erl b/test/enoise_sym_state_tests.erl index 4bf2e5d..268a5b9 100644 --- a/test/enoise_sym_state_tests.erl +++ b/test/enoise_sym_state_tests.erl @@ -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"), diff --git a/test/enoise_tests.erl b/test/enoise_tests.erl index 225c158..055980d 100644 --- a/test/enoise_tests.erl +++ b/test/enoise_tests.erl @@ -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]),