All crypto and top level in place
Limited support for protocols, virtually no error handling
This commit is contained in:
parent
d0723eb247
commit
4d2af24250
@ -1,20 +1,5 @@
|
||||
-define(MAX_NONCE, 16#FFFFFFFFFFFFFFFF).
|
||||
-define(AD_LEN, 16).
|
||||
|
||||
-record(noise_protocol,
|
||||
{ hs_pattern = noiseNN %:: noise_hs_pattern()
|
||||
, dh = dh25519 %:: noise_dh()
|
||||
, cipher = 'ChaChaPoly' %:: noise_cipher()
|
||||
, hash = blake2b %:: noise_hash()
|
||||
}).
|
||||
|
||||
-record(key_pair, { puk, pik }).
|
||||
|
||||
-record(noise_hs, { ss :: enoise_sym_state: state()
|
||||
, s :: #key_pair{} | undefined
|
||||
, e :: #key_pair{} | undefined
|
||||
, rs :: binary() | undefined
|
||||
, re :: binary() | undefined
|
||||
, role = initiatior :: initiator | responder
|
||||
, dh
|
||||
, msgs = [] }).
|
||||
|
121
src/enoise.erl
Normal file
121
src/enoise.erl
Normal file
@ -0,0 +1,121 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(enoise).
|
||||
|
||||
%% API exports - Mainly mimicing gen_tcp
|
||||
%% -export([ accept/1
|
||||
%% , accept/2
|
||||
%% , close/1
|
||||
%% , connect/3
|
||||
%% , connect/4
|
||||
%% , controlling_process/2
|
||||
%% , listen/2
|
||||
%% , recv/2
|
||||
%% , recv/3
|
||||
%% , send/2
|
||||
%% , shutdown/2 ]).
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include("enoise.hrl").
|
||||
|
||||
-record(enoise, { tcp_sock, rx, tx }).
|
||||
|
||||
%% -type noise_hs_pattern() :: noiseNN | noiseKN.
|
||||
%% -type noise_dh() :: dh448 | dh25519.
|
||||
%% -type noise_cipher() :: 'AESGCM' | 'ChaChaPoly'.
|
||||
%% -type noise_hash() :: sha256 | sha512 | blake2s | blake2b.
|
||||
|
||||
%% -type noise_protocol() :: #noise_protocol{}.
|
||||
|
||||
%%====================================================================
|
||||
%% API functions
|
||||
%%====================================================================
|
||||
connect(Address, Port, Options) ->
|
||||
connect(Address, Port, Options, infinity).
|
||||
|
||||
|
||||
connect(Address, Port, Options, Timeout) ->
|
||||
case initiate_handshake(initiator, Options) of
|
||||
{ok, HS} ->
|
||||
TcpOpts = enoise_opts:tcp_opts(Options),
|
||||
case gen_tcp:connect(Address, Port, TcpOpts, Timeout) of
|
||||
{ok, TcpSock} ->
|
||||
do_handshake(TcpSock, HS, Options);
|
||||
Err = {error, _Reason} ->
|
||||
Err
|
||||
end;
|
||||
Err = {error, _Reason} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
send(E = #enoise{ tcp_sock = TcpSock, rx = RX0 }, Msg0) ->
|
||||
{ok, RX1, Msg1} = enoise_cipher_state:encrypt_with_ad(RX0, <<>>, Msg0),
|
||||
gen_tcp:send(TcpSock, <<(byte_size(Msg1)):16, Msg1/binary>>),
|
||||
E#enoise{ rx = RX1 }.
|
||||
|
||||
recv(E = #enoise{ tcp_sock = TcpSock, tx = TX0 }) ->
|
||||
receive {tcp, TcpSock, <<Size:16, Data/binary>>} ->
|
||||
Size = byte_size(Data),
|
||||
{ok, TX1, Msg1} = enoise_cipher_state:decrypt_with_ad(TX0, <<>>, Data),
|
||||
{E#enoise{ tx = TX1 }, Msg1}
|
||||
after 1000 -> error(timeout) end.
|
||||
|
||||
close(#enoise{ tcp_sock = TcpSock }) ->
|
||||
gen_tcp:close(TcpSock).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
initiate_handshake(Role, Options) ->
|
||||
Prologue = proplists:get_value(prologue, Options, <<>>),
|
||||
NoiseProtocol = proplists:get_value(noise, Options),
|
||||
|
||||
S = proplists:get_value(s, Options, undefined),
|
||||
E = proplists:get_value(e, Options, undefined),
|
||||
RS = proplists:get_value(rs, Options, undefined),
|
||||
RE = proplists:get_value(re, Options, undefined),
|
||||
|
||||
HSState = enoise_hs_state:init(NoiseProtocol, Role, Prologue, {S, E, RS, RE}),
|
||||
{ok, HSState}.
|
||||
|
||||
|
||||
do_handshake(TcpSock, HState, Options) ->
|
||||
PreComm = proplists:get_value(pre_comm, Options, <<>>), %% TODO: Not standard!
|
||||
|
||||
gen_tcp:send(TcpSock, PreComm),
|
||||
|
||||
do_handshake(TcpSock, HState).
|
||||
|
||||
|
||||
do_handshake(TcpSock, HState) ->
|
||||
case enoise_hs_state:next_message(HState) of
|
||||
in ->
|
||||
receive {tcp, TcpSock, Data} ->
|
||||
case enoise_hs_state:read_message(HState, Data) of
|
||||
{ok, HState1, _Msg} ->
|
||||
do_handshake(TcpSock, HState1);
|
||||
{done, _HState1, _Msg, {C1, C2}} ->
|
||||
{ok, #enoise{ tcp_sock = TcpSock, rx = C1, tx = C2 }}
|
||||
end
|
||||
after 1000 ->
|
||||
error(timeout)
|
||||
end;
|
||||
out ->
|
||||
case enoise_hs_state:write_message(HState, <<>>) of
|
||||
{ok, HState1, Msg} ->
|
||||
io:format("Sending: ~p\n", [add_len(Msg)]),
|
||||
gen_tcp:send(TcpSock, add_len(Msg)),
|
||||
do_handshake(TcpSock, HState1);
|
||||
{done, _HState1, Msg, {C1, C2}} ->
|
||||
io:format("Sending: ~p\n", [add_len(Msg)]),
|
||||
gen_tcp:send(TcpSock, add_len(Msg)),
|
||||
{ok, #enoise{ tcp_sock = TcpSock, rx = C1, tx = C2 }}
|
||||
end
|
||||
end.
|
||||
|
||||
add_len(Msg) ->
|
||||
Len = byte_size(Msg),
|
||||
<<Len:16, Msg/binary>>.
|
151
src/enoise_hs_state.erl
Normal file
151
src/enoise_hs_state.erl
Normal file
@ -0,0 +1,151 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(enoise_hs_state).
|
||||
|
||||
-export([init/4, next_message/1, read_message/2, write_message/2]).
|
||||
|
||||
-include("enoise.hrl").
|
||||
|
||||
-type noise_role() :: initiator | responder.
|
||||
-type noise_dh() :: dh25519 | dh448.
|
||||
-type noise_token() :: s | e | ee | ss | es | se.
|
||||
-type noise_msg() :: {in | out, [noise_token()]}.
|
||||
|
||||
-record(noise_hs, { ss :: enoise_sym_state:state()
|
||||
, s :: #key_pair{} | undefined
|
||||
, e :: #key_pair{} | undefined
|
||||
, rs :: binary() | undefined
|
||||
, re :: binary() | undefined
|
||||
, role = initiatior :: noise_role()
|
||||
, dh = dh25519 :: noise_dh()
|
||||
, msgs = [] :: [noise_msg()] }).
|
||||
|
||||
init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
|
||||
SS0 = enoise_sym_state:init(Protocol),
|
||||
SS1 = enoise_sym_state:mix_hash(SS0, Prologue),
|
||||
HS = #noise_hs{ ss = SS1
|
||||
, s = S, e = E, rs = RS, re = RE
|
||||
, role = Role
|
||||
, dh = enoise_protocol:dh(Protocol)
|
||||
, msgs = msgs(Role, enoise_protocol:pattern(Protocol)) },
|
||||
PreMsgs = pre_msgs(Role, enoise_protocol:pattern(Protocol)),
|
||||
lists:foldl(fun({out, [s]}, HS0) -> mix_hash(HS0, S#key_pair.puk);
|
||||
({out, [e]}, HS0) -> mix_hash(HS0, E#key_pair.puk);
|
||||
({in, [s]}, HS0) -> mix_hash(HS0, RS);
|
||||
({in, [e]}, HS0) -> mix_hash(HS0, RE)
|
||||
end, HS, PreMsgs).
|
||||
|
||||
next_message(#noise_hs{ msgs = [{Dir, _} | _] }) -> Dir;
|
||||
next_message(_) -> done.
|
||||
|
||||
write_message(HS = #noise_hs{ msgs = [{out, Msg} | Msgs] }, PayLoad) ->
|
||||
{HS1, MsgBuf1} = write_message(HS#noise_hs{ msgs = Msgs }, Msg, <<>>),
|
||||
{ok, HS2, MsgBuf2} = encrypt_and_hash(HS1, PayLoad),
|
||||
MsgBuf = <<MsgBuf1/binary, MsgBuf2/binary>>,
|
||||
case Msgs of
|
||||
[] -> {done, HS2, MsgBuf, enoise_sym_state:split(HS2#noise_hs.ss)};
|
||||
_ -> {ok, HS2, MsgBuf}
|
||||
end.
|
||||
|
||||
read_message(HS = #noise_hs{ msgs = [{in, Msg} | Msgs] }, <<Size:16, Message/binary>>) ->
|
||||
Size = byte_size(Message),
|
||||
{HS1, RestBuf1} = read_message(HS#noise_hs{ msgs = Msgs }, Msg, Message),
|
||||
{ok, HS2, PlainBuf} = decrypt_and_hash(HS1, RestBuf1),
|
||||
case Msgs of
|
||||
[] -> {done, HS2, PlainBuf, enoise_sym_state:split(HS2#noise_hs.ss)};
|
||||
_ -> {ok, HS2, PlainBuf}
|
||||
end.
|
||||
|
||||
write_message(HS, [], MsgBuf) ->
|
||||
{HS, MsgBuf};
|
||||
write_message(HS, [Token | Tokens], MsgBuf0) ->
|
||||
{HS1, MsgBuf1} = write_token(HS, Token),
|
||||
write_message(HS1, Tokens, <<MsgBuf0/binary, MsgBuf1/binary>>).
|
||||
|
||||
read_message(HS, [], Data) ->
|
||||
{HS, Data};
|
||||
read_message(HS, [Token | Tokens], Data0) ->
|
||||
{HS1, Data1} = read_token(HS, Token, Data0),
|
||||
read_message(HS1, Tokens, Data1).
|
||||
|
||||
write_token(HS = #noise_hs{ e = undefined }, e) ->
|
||||
E = #key_pair{ puk = PubE } = new_key_pair(HS),
|
||||
{mix_hash(HS#noise_hs{ e = E }, PubE), PubE};
|
||||
write_token(HS = #noise_hs{ s = S }, s) ->
|
||||
{ok, HS1, Msg} = encrypt_and_hash(HS, S#key_pair.puk),
|
||||
{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) ->
|
||||
DHLen = case has_key(HS) of
|
||||
true -> dhlen(HS) + 16;
|
||||
false -> dhlen(HS)
|
||||
end,
|
||||
<<Temp:DHLen/binary, Data1/binary>> = Data0,
|
||||
{ok, HS1, RS} = decrypt_and_hash(HS, Temp),
|
||||
{HS1#noise_hs{ rs = RS }, Data1};
|
||||
read_token(HS, Token, Data) ->
|
||||
{K1, K2} = dh_token(HS, Token),
|
||||
{mix_key(HS, dh(HS, K1, K2)), Data}.
|
||||
|
||||
dh_token(#noise_hs{ e = E, re = RE } , ee) -> {E, RE};
|
||||
dh_token(#noise_hs{ e = E, rs = RS, role = initiator }, es) -> {E, RS};
|
||||
dh_token(#noise_hs{ s = S, re = RE, role = responder }, es) -> {S, RE};
|
||||
dh_token(#noise_hs{ s = S, re = RE, role = initiator }, se) -> {S, RE};
|
||||
dh_token(#noise_hs{ e = E, rs = RS, role = responder }, se) -> {E, RS};
|
||||
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).
|
||||
|
||||
dh(#noise_hs{ dh = DH }, KeyPair, PubKey) ->
|
||||
enoise_crypto:dh(DH, KeyPair, PubKey).
|
||||
|
||||
dhlen(#noise_hs{ dh = DH }) ->
|
||||
enoise_crypto:dhlen(DH).
|
||||
|
||||
has_key(#noise_hs{ ss = SS }) ->
|
||||
CS = enoise_sym_state:cipher_state(SS),
|
||||
enoise_cipher_state:has_key(CS).
|
||||
|
||||
mix_key(HS = #noise_hs{ ss = SS0 }, Data) ->
|
||||
HS#noise_hs{ ss = enoise_sym_state:mix_key(SS0, Data) }.
|
||||
|
||||
mix_hash(HS = #noise_hs{ ss = SS0 }, Data) ->
|
||||
HS#noise_hs{ ss = enoise_sym_state:mix_hash(SS0, Data) }.
|
||||
|
||||
encrypt_and_hash(HS = #noise_hs{ ss = SS0 }, PlainText) ->
|
||||
{ok, SS1, CipherText} = enoise_sym_state:encrypt_and_hash(SS0, PlainText),
|
||||
{ok, HS#noise_hs{ ss = SS1 }, CipherText}.
|
||||
|
||||
decrypt_and_hash(HS = #noise_hs{ ss = SS0 }, CipherText) ->
|
||||
{ok, SS1, PlainText} = enoise_sym_state:decrypt_and_hash(SS0, CipherText),
|
||||
{ok, HS#noise_hs{ ss = SS1 }, PlainText}.
|
||||
|
||||
msgs(Role, Protocol) ->
|
||||
{_Pre, Msgs} = protocol(Protocol),
|
||||
role_adapt(Role, Msgs).
|
||||
|
||||
pre_msgs(Role, Protocol) ->
|
||||
{PreMsgs, _Msgs} = protocol(Protocol),
|
||||
role_adapt(Role, PreMsgs).
|
||||
|
||||
role_adapt(initiator, Msgs) ->
|
||||
Msgs;
|
||||
role_adapt(responder, Msgs) ->
|
||||
Flip = fun(in) -> out; (out) -> in end,
|
||||
lists:map(Flip, Msgs).
|
||||
|
||||
protocol(nn) ->
|
||||
{[], [{out, [e]}, {in, [e, ee]}]};
|
||||
protocol(xk) ->
|
||||
{[{in, [s]}], [{out, [e, es]}, {in, [e, ee]}, {out, [s, se]}]}.
|
10
src/enoise_opts.erl
Normal file
10
src/enoise_opts.erl
Normal file
@ -0,0 +1,10 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(enoise_opts).
|
||||
|
||||
-export([tcp_opts/1]).
|
||||
|
||||
tcp_opts(_Options) ->
|
||||
[{active, true}, binary, {reuseaddr, true}].
|
@ -4,9 +4,47 @@
|
||||
|
||||
-module(enoise_protocol).
|
||||
|
||||
-include("enoise.hrl").
|
||||
-export([ cipher/1
|
||||
, dh/1
|
||||
, from_name/1
|
||||
, hash/1
|
||||
, pattern/1
|
||||
, to_name/1]).
|
||||
|
||||
-export([to_name/1]).
|
||||
-type noise_pattern() :: nn | xk.
|
||||
|
||||
-record(noise_protocol,
|
||||
{ hs_pattern = noiseNN :: noise_pattern()
|
||||
, dh = dh25519 :: enoise_hs_state:noise_dh()
|
||||
, cipher = 'ChaChaPoly' :: enoise_cipher_state:noise_cipher()
|
||||
, hash = blake2b :: enoise_sym_state:noise_hash()
|
||||
}).
|
||||
|
||||
-opaque protocol() :: #noise_protocol{}.
|
||||
|
||||
-export_type([noise_pattern/0, protocol/0]).
|
||||
|
||||
-spec cipher(Protocol :: protocol()) -> enoise_cipher_state:noise_cipher().
|
||||
cipher(#noise_protocol{ cipher = Cipher }) ->
|
||||
Cipher.
|
||||
|
||||
-spec dh(Protocol :: protocol()) -> enoise_hs_state:noise_dh().
|
||||
dh(#noise_protocol{ dh = Dh }) ->
|
||||
Dh.
|
||||
|
||||
-spec hash(Protocol :: protocol()) -> enoise_sym_state:noise_hash().
|
||||
hash(#noise_protocol{ hash = Hash }) ->
|
||||
Hash.
|
||||
|
||||
-spec pattern(Protocol :: protocol()) -> noise_pattern().
|
||||
pattern(#noise_protocol{ hs_pattern = Pattern }) ->
|
||||
Pattern.
|
||||
|
||||
to_name(_Protocol) ->
|
||||
<<"Noise_XK_25519_ChaChaPoly_BLAKE2b">>.
|
||||
|
||||
from_name("Noise_XK_25519_ChaChaPoly_Blake2b") ->
|
||||
#noise_protocol{ hs_pattern = xk, dh = dh25519, cipher = 'ChaChaPoly', hash = blake2b };
|
||||
from_name(Name) ->
|
||||
error({protocol_not_implemented_yet, Name}).
|
||||
|
||||
|
@ -29,8 +29,10 @@
|
||||
-opaque state() :: #noise_ss{}.
|
||||
-export_type([noise_hash/0, state/0]).
|
||||
|
||||
-spec init(Protocol :: #noise_protocol{}) -> state().
|
||||
init(Protocol = #noise_protocol{ hash = Hash, cipher = Cipher }) ->
|
||||
-spec init(Protocol :: enoise_protocol:protocol()) -> state().
|
||||
init(Protocol) ->
|
||||
Hash = enoise_protocol:hash(Protocol),
|
||||
Cipher = enoise_protocol:cipher(Protocol),
|
||||
Name = enoise_protocol:to_name(Protocol),
|
||||
HashLen = enoise_crypto:hashlen(Hash),
|
||||
H1 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user