Refactored + added some test vector support

This commit is contained in:
Hans Svensson
2018-03-06 10:06:03 +01:00
parent bc8ebc7ec6
commit b1d63ad3b9
11 changed files with 20033 additions and 54 deletions
+60 -4
View File
@@ -6,23 +6,50 @@
-include("enoise.hrl").
-export([decrypt/5, encrypt/5, rekey/2, hash/2, pad/3, hashlen/1, dhlen/1, new_key_pair/1, hmac/3, hkdf/3, dh/3]).
-export([ decrypt/5
, dh/3
, dhlen/1
, encrypt/5
, hash/2
, 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)) }.
-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).
-spec hmac(Hash :: enoise_sym_state:noise_hash(),
Key :: binary(), Data :: binary()) -> binary().
hmac(Hash, Key, Data) ->
BLen = blocklen(blake2b),
BLen = blocklen(Hash),
Block1 = hmac_format_key(Hash, Key, 16#36, BLen),
Hash1 = hash(Hash, <<Block1/binary, Data/binary>>),
Block2 = hmac_format_key(Hash, Key, 16#5C, BLen),
hash(Hash, <<Block2/binary, Hash1/binary>>).
-spec hkdf(Hash :: enoise_sym_state:noise_hash(),
Key :: binary(), Data :: binary()) -> [binary()].
hkdf(Hash, Key, Data) ->
TempKey = hmac(Hash, Key, Data),
Output1 = hmac(Hash, TempKey, <<1:8>>),
@@ -30,24 +57,47 @@ hkdf(Hash, Key, Data) ->
Output3 = hmac(Hash, TempKey, <<Output2/binary, 3:8>>),
[Output1, Output2, Output3].
-spec rekey(Cipher :: enoise_cipher_state:noise_cipher(),
Key :: binary()) -> binary().
rekey(Cipher, K) ->
encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>).
-spec encrypt(Cipher :: enoise_cipher_state:noise_cipher(),
Key :: binary(), Nonce :: non_neg_integer(),
Ad :: binary(), PlainText :: binary()) ->
binary() | {error, term()}.
encrypt('ChaChaPoly', K, N, Ad, PlainText) ->
enacl:aead_chacha20poly1305_encrypt(K, N, Ad, PlainText).
enacl:aead_chacha20poly1305_encrypt(K, N, Ad, PlainText);
encrypt('AESGCM', K, N, Ad, PlainText) ->
Nonce = <<0:32, N:64>>,
{CipherText, CipherTag} = crypto:block_encrypt(aes_gcm, K, Nonce, {Ad, PlainText}),
<<CipherText/binary, CipherTag/binary>>.
-spec decrypt(Cipher ::enoise_cipher_state:noise_cipher(),
Key :: binary(), Nonce :: non_neg_integer(),
AD :: binary(), CipherText :: binary()) ->
binary() | {error, term()}.
decrypt('ChaChaPoly', K, N, Ad, CipherText) ->
enacl:aead_chacha20poly1305_decrypt(K, N, Ad, CipherText).
enacl:aead_chacha20poly1305_decrypt(K, N, Ad, CipherText);
decrypt('AESGCM', K, N, Ad, CipherText0) ->
CTLen = byte_size(CipherText0) - ?MAC_LEN,
<<CipherText:CTLen/binary, MAC:?MAC_LEN/binary>> = CipherText0,
Nonce = <<0:32, N:64>>,
crypto:block_decrypt(aes_gcm, K, Nonce, {Ad, CipherText, MAC}).
-spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary().
hash(blake2b, Data) ->
{ok, Hash} = enacl:generichash(64, Data), Hash;
hash(sha256, Data) ->
crypto:hash(sha256, Data);
hash(sha512, Data) ->
crypto:hash(sha512, Data);
hash(Hash, _Data) ->
error({hash_not_implemented_yet, Hash}).
-spec pad(Data :: binary(), MinSize :: non_neg_integer(),
PadByte :: integer()) -> binary().
pad(Data, MinSize, PadByte) ->
case byte_size(Data) of
N when N >= MinSize ->
@@ -57,16 +107,22 @@ 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;
hashlen(blake2s) -> 32;
hashlen(blake2b) -> 64.
-spec blocklen(Hash :: enoise_sym_state:noise_hash()) -> non_neg_integer().
blocklen(sha256) -> 64;
blocklen(sha512) -> 128;
blocklen(blake2s) -> 64;
blocklen(blake2b) -> 128.
-spec dhlen(DH :: enoise_hs_state:noise_dh()) -> non_neg_integer().
dhlen(dh25519) -> 32;
dhlen(dh448) -> 56.
+25 -31
View File
@@ -11,17 +11,22 @@
-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
, s :: enoise_crypto:key_pair() | undefined
, e :: enoise_crypto:key_pair() | undefined
, rs :: binary() | undefined
, re :: binary() | undefined
, role = initiatior :: noise_role()
, dh = dh25519 :: noise_dh()
, msgs = [] :: [noise_msg()] }).
, msgs = [] :: [enoise_protocol:noise_msg()] }).
-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{}.
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}) ->
SS0 = enoise_sym_state:init(Protocol),
SS1 = enoise_sym_state:mix_hash(SS0, Prologue),
@@ -29,19 +34,20 @@ init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
, 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)
, 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)
end, HS, PreMsgs).
finalize(#noise_hs{ msgs = [], ss = SS, role = Role }) ->
{C1, C2} = enoise_sym_state:split(SS),
HSHash = enoise_sym_state:h(SS),
case Role of
initiator -> {ok, #{ tx => C1, rx => C2 }};
responder -> {ok, #{ rx => C1, tx => C2 }}
initiator -> {ok, #{ tx => C1, rx => C2, hs_hash => HSHash }};
responder -> {ok, #{ rx => C1, tx => C2, hs_hash => HSHash }}
end;
finalize(_) ->
error({bad_state, finalize}).
@@ -73,10 +79,15 @@ read_message(HS, [Token | Tokens], Data0) ->
read_message(HS1, Tokens, Data1).
write_token(HS = #noise_hs{ e = undefined }, e) ->
E = #key_pair{ puk = PubE } = new_key_pair(HS),
E = new_key_pair(HS),
PubE = enoise_crypto:pub_key(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),
{mix_hash(HS, PubE), PubE};
write_token(HS = #noise_hs{ s = S }, s) ->
{ok, HS1, Msg} = encrypt_and_hash(HS, S#key_pair.puk),
{ok, HS1, Msg} = encrypt_and_hash(HS, enoise_crypto:pub_key(S)),
{HS1, Msg};
write_token(HS, Token) ->
{K1, K2} = dh_token(HS, Token),
@@ -134,21 +145,4 @@ decrypt_and_hash(HS = #noise_hs{ ss = 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, Msg}) -> {out, Msg}; ({out, Msg}) -> {in, Msg} 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]}]}.
+100 -8
View File
@@ -8,10 +8,13 @@
, dh/1
, from_name/1
, hash/1
, msgs/2
, pattern/1
, pre_msgs/2
, to_name/1]).
-compile(export_all).
-type noise_pattern() :: nn | xk.
-type noise_msg() :: {in | out, [enoise_hs_state:noise_token()]}.
-record(noise_protocol,
{ hs_pattern = noiseNN :: noise_pattern()
@@ -22,7 +25,7 @@
-opaque protocol() :: #noise_protocol{}.
-export_type([noise_pattern/0, protocol/0]).
-export_type([noise_msg/0, noise_pattern/0, protocol/0]).
-spec cipher(Protocol :: protocol()) -> enoise_cipher_state:noise_cipher().
cipher(#noise_protocol{ cipher = Cipher }) ->
@@ -40,11 +43,100 @@ hash(#noise_protocol{ hash = Hash }) ->
pattern(#noise_protocol{ hs_pattern = Pattern }) ->
Pattern.
to_name(_Protocol) ->
<<"Noise_XK_25519_ChaChaPoly_BLAKE2b">>.
-spec to_name(Protocol :: protocol()) -> binary().
to_name(Protocol = #noise_protocol{ hs_pattern = Pattern, dh = Dh
, cipher = Cipher, hash = Hash }) ->
case supported_pattern(Pattern) andalso supported_dh(Dh) andalso
supported_cipher(Cipher) andalso supported_hash(Hash) of
true -> to_name(Pattern, Dh, Cipher, Hash);
false -> error({protocol_not_recognized, Protocol})
end.
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}).
-spec from_name(Name :: string() | binary()) -> protocol().
from_name(Bin) when is_binary(Bin) -> from_name(binary_to_list(Bin));
from_name(String) ->
case string:lexemes(String, "_") of
["Noise", PatStr, DhStr, CipStr, HashStr] ->
Pattern = from_name_pattern(PatStr),
Dh = from_name_dh(DhStr),
Cipher = from_name_cipher(CipStr),
Hash = from_name_hash(HashStr),
case supported_pattern(Pattern) andalso supported_dh(Dh) andalso
supported_cipher(Cipher) andalso supported_hash(Hash) of
true -> #noise_protocol{ hs_pattern = Pattern, dh = Dh
, cipher = Cipher, hash = Hash };
false -> error({name_not_recognized, String})
end;
_ ->
error({name_not_recognized, String})
end.
-spec msgs(Role :: enoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
{_Pre, Msgs} = protocol(Pattern),
role_adapt(Role, Msgs).
-spec pre_msgs(Role :: enoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
pre_msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
{PreMsgs, _Msgs} = protocol(Pattern),
role_adapt(Role, PreMsgs).
role_adapt(initiator, Msgs) ->
Msgs;
role_adapt(responder, Msgs) ->
Flip = fun({in, Msg}) -> {out, Msg}; ({out, Msg}) -> {in, Msg} end,
lists:map(Flip, Msgs).
protocol(nn) ->
{[], [{out, [e]}, {in, [e, ee]}]};
protocol(kn) ->
{[{out, [s]}], [{out, [e]}, {in, [e, ee, se]}]};
protocol(nk) ->
{[{in, [s]}], [{out, [e, es]}, {in, [e, ee]}]};
protocol(xk) ->
{[{in, [s]}], [{out, [e, es]}, {in, [e, ee]}, {out, [s, se]}]}.
supported_hash(Hash) ->
lists:member(Hash, [blake2b, sha256, sha512]).
supported_cipher(Cipher) ->
lists:member(Cipher, ['ChaChaPoly', 'AESGCM']).
supported_dh(Dh) ->
lists:member(Dh, [dh25519]).
supported_pattern(P) ->
lists:member(P, [nn, kn, nk, xk]).
to_name(Pattern, Dh, Cipher, Hash) ->
list_to_binary(lists:join("_", ["Noise", to_name_pattern(Pattern), to_name_dh(Dh),
to_name_cipher(Cipher), to_name_hash(Hash)])).
to_name_pattern(Atom) ->
[Simple | Rest] = string:lexemes(atom_to_list(Atom), "_"),
string:uppercase(Simple) ++ lists:join("+", Rest).
from_name_pattern(String) ->
[Init | Mod2] = string:lexemes(String, "+"),
{Simple, Mod1} = lists:splitwith(fun(C) -> C >= $A andalso C =< $Z end, Init),
list_to_atom(string:lowercase(Simple) ++
case Mod1 of
"" -> "";
_ -> "_" ++ lists:join([Mod1 | Mod2], "_")
end).
to_name_dh(dh25519) -> "25519";
to_name_dh(dh448) -> "448".
from_name_dh(Dh) -> list_to_atom("dh" ++ Dh).
to_name_cipher(Cipher) -> atom_to_list(Cipher).
from_name_cipher(Cipher) -> list_to_atom(Cipher).
to_name_hash(sha256) -> "SHA256";
to_name_hash(sha512) -> "SHA512";
to_name_hash(blake2s) -> "BLAKE2s";
to_name_hash(blake2b) -> "BLAKE2b".
from_name_hash(Hash) -> list_to_atom(string:lowercase(Hash)).