From d78c774b5522910cbab54d6240a6809665bd6a82 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 1 Mar 2018 11:44:23 +0100 Subject: [PATCH] The innermost layer cipher_state --- include/enoise.hrl | 25 +++++++++++++ src/enoise_cipher_state.erl | 72 +++++++++++++++++++++++++++++++++++++ src/enoise_crypto.erl | 54 ++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 include/enoise.hrl create mode 100644 src/enoise_cipher_state.erl create mode 100644 src/enoise_crypto.erl diff --git a/include/enoise.hrl b/include/enoise.hrl new file mode 100644 index 0000000..d96b971 --- /dev/null +++ b/include/enoise.hrl @@ -0,0 +1,25 @@ +-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_ss, { cs :: enoise_cipher_state:state() + , ck = <<>> :: binary() + , h = <<>> :: binary() + , hash = blake2b }). + +-record(noise_hs, { ss :: #noise_ss{} | undefined + , s :: #key_pair{} | undefined + , e :: #key_pair{} | undefined + , rs :: binary() | undefined + , re :: binary() | undefined + , role = initiatior :: initiator | responder + , dh + , msgs = [] }). diff --git a/src/enoise_cipher_state.erl b/src/enoise_cipher_state.erl new file mode 100644 index 0000000..9ebac6b --- /dev/null +++ b/src/enoise_cipher_state.erl @@ -0,0 +1,72 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%%------------------------------------------------------------------- + +-module(enoise_cipher_state). + +-export([ cipher/1 + , decrypt_with_ad/3 + , encrypt_with_ad/3 + , has_key/1 + , init/2 + , rekey/1 + , set_key/2 + , set_nonce/2 + ]). + +-include("enoise.hrl"). + +-type noise_cipher() :: 'ChaChaPoly' | 'AESGCM'. +-type nonce() :: non_neg_integer(). +-type key() :: empty | binary(). + +-record(noise_cs, { k = empty :: key() + , n = 0 :: nonce() + , cipher = 'ChaChaPoly' :: noise_cipher() }). + +-opaque state() :: #noise_cs{}. + +-export_type([noise_cipher/0, state/0]). + +-spec init(Key :: key(), Cipher :: noise_cipher()) -> state(). +init(Key, Cipher) -> + #noise_cs{ k = Key, n = 0, cipher = Cipher }. + +-spec set_key(CState :: state(), NewKey :: key()) -> state(). +set_key(CState, NewKey) -> + CState#noise_cs{ k = NewKey, n = 0 }. + +-spec has_key(CState :: state()) -> boolean(). +has_key(#noise_cs{ k = Key }) -> + Key =/= empty. + +-spec set_nonce(CState :: state(), NewNonce :: nonce()) -> state(). +set_nonce(CState = #noise_cs{}, Nonce) -> + CState#noise_cs{ n = Nonce }. + +-spec encrypt_with_ad(CState :: state(), AD :: binary(), PlainText :: binary()) -> + {ok, state(), binary()}. +encrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, PlainText) -> + {ok, CState, PlainText}; +encrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, PlainText) -> + {ok, CState#noise_cs{ n = N+1 }, enoise_crypto:encrypt(Cipher, K, N, AD, PlainText)}. + +-spec decrypt_with_ad(CState :: state(), AD :: binary(), CipherText :: binary()) -> + {ok, state(), binary()} | {error, term()}. +decrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, CipherText) -> + {ok, CState, CipherText}; +decrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, CipherText) -> + case enoise_crypto:decrypt(Cipher, K, N, AD, CipherText) of + PlainText when is_binary(PlainText) -> + {ok, CState#noise_cs{ n = N+1 }, PlainText}; + Err = {error, _} -> + Err + end. + +-spec rekey(CState :: state()) -> state(). +rekey(CState = #noise_cs{ k = K, cipher = Cipher }) -> + CState#noise_cs{ k = enoise_crypto:rekey(Cipher, K) }. + +-spec cipher(CState :: state()) -> noise_cipher(). +cipher(#noise_cs{ cipher = Cipher }) -> + Cipher. diff --git a/src/enoise_crypto.erl b/src/enoise_crypto.erl new file mode 100644 index 0000000..ac37e35 --- /dev/null +++ b/src/enoise_crypto.erl @@ -0,0 +1,54 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%%------------------------------------------------------------------- + +-module(enoise_crypto). + +-include("enoise.hrl"). + +-export([decrypt/5, encrypt/5, rekey/2, hash/2, pad/3, hashlen/1, dhlen/1, new_key_pair/1, hkdf/3, dh/3]). + +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)) }. + +dh(dh25519, KeyPair, PubKey) -> + enacl:curve25519_scalarmult(KeyPair#key_pair.pik, PubKey). + +hkdf(_, _, _) -> []. + +rekey(Cipher, K) -> + encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>). + +encrypt('ChaChaPoly', K, N, Ad, PlainText) -> + enacl:aead_chacha20poly1305_encrypt(K, N, Ad, PlainText). + +-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). + +hash(blake2b, Data) -> + enacl:generichash(64, Data); +hash(blake2s, Data) -> + enacl:generichash(32, Data). + +pad(Data, MinSize, PadByte) -> + case byte_size(Data) of + N when N >= MinSize -> + Data; + N -> + PadData = << <> || _ <- lists:seq(1, MinSize - N) >>, + <> + end. + +hashlen(sha256) -> 32; +hashlen(sha512) -> 64; +hashlen(blake2s) -> 32; +hashlen(blake2b) -> 64. + +dhlen(dh25519) -> 32; +dhlen(dh448) -> 56.