Skip to content

Commit

Permalink
crypto: add support for ChaCha20/Policy1305 AEAD cipher
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreas Schultz committed Sep 3, 2014
1 parent d1dcc88 commit fb9d36c
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 9 deletions.
158 changes: 156 additions & 2 deletions lib/crypto/c_src/crypto.c
Expand Up @@ -93,13 +93,28 @@
# define HAVE_GCM
#endif

#if defined(NID_chacha20) && !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305)
# define HAVE_CHACHA20_POLY1305
#endif

#if defined(HAVE_EC)
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/ecdsa.h>
#endif

#if defined(HAVE_CHACHA20_POLY1305)
#include <openssl/chacha.h>
#include <openssl/poly1305.h>

#if !defined(CHACHA20_NONCE_LEN)
# define CHACHA20_NONCE_LEN 8
#endif
#if !defined(POLY1305_TAG_LEN)
# define POLY1305_TAG_LEN 16
#endif

#endif

#ifdef VALGRIND
# include <valgrind/memcheck.h>
Expand Down Expand Up @@ -268,6 +283,9 @@ static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
static ERL_NIF_TERM aes_gcm_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM aes_gcm_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);

static ERL_NIF_TERM chacha20_poly1305_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM chacha20_poly1305_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);

/* helpers */
static void init_algorithms_types(ErlNifEnv*);
static void init_digest_types(ErlNifEnv* env);
Expand Down Expand Up @@ -400,7 +418,11 @@ static ErlNifFunc nif_funcs[] = {
{"rand_seed_nif", 1, rand_seed_nif},

{"aes_gcm_encrypt", 4, aes_gcm_encrypt},
{"aes_gcm_decrypt", 5, aes_gcm_decrypt}
{"aes_gcm_decrypt", 5, aes_gcm_decrypt},

{"chacha20_poly1305_encrypt", 4, chacha20_poly1305_encrypt},
{"chacha20_poly1305_decrypt", 5, chacha20_poly1305_decrypt}


};

Expand Down Expand Up @@ -717,7 +739,7 @@ static ERL_NIF_TERM algo_hash[8]; /* increase when extending the list */
static int algo_pubkey_cnt;
static ERL_NIF_TERM algo_pubkey[3]; /* increase when extending the list */
static int algo_cipher_cnt;
static ERL_NIF_TERM algo_cipher[3]; /* increase when extending the list */
static ERL_NIF_TERM algo_cipher[4]; /* increase when extending the list */

static void init_algorithms_types(ErlNifEnv* env)
{
Expand Down Expand Up @@ -758,6 +780,9 @@ static void init_algorithms_types(ErlNifEnv* env)
#if defined(HAVE_GCM)
algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_gcm");
#endif
#if defined(HAVE_CHACHA20_POLY1305)
algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20_poly1305");
#endif

ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
Expand Down Expand Up @@ -1856,6 +1881,135 @@ static ERL_NIF_TERM aes_gcm_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM
#endif
}

#if defined(HAVE_CHACHA20_POLY1305)
static void
poly1305_update_with_length(poly1305_state *poly1305,
const unsigned char *data, size_t data_len)
{
size_t j = data_len;
unsigned char length_bytes[8];
unsigned i;

for (i = 0; i < sizeof(length_bytes); i++) {
length_bytes[i] = j;
j >>= 8;
}

CRYPTO_poly1305_update(poly1305, data, data_len);
CRYPTO_poly1305_update(poly1305, length_bytes, sizeof(length_bytes));
}
#endif

static ERL_NIF_TERM chacha20_poly1305_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Key,Iv,AAD,In) */
#if defined(HAVE_CHACHA20_POLY1305)
ErlNifBinary key, iv, aad, in;
unsigned char *outp;
ERL_NIF_TERM out, out_tag;
ErlNifUInt64 in_len_64;
unsigned char poly1305_key[32];
poly1305_state poly1305;

CHECK_OSE_CRYPTO();

if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 32
|| !enif_inspect_binary(env, argv[1], &iv) || iv.size != CHACHA20_NONCE_LEN
|| !enif_inspect_iolist_as_binary(env, argv[2], &aad)
|| !enif_inspect_iolist_as_binary(env, argv[3], &in)) {
return enif_make_badarg(env);
}

/* Take from OpenSSL patch set/LibreSSL:
*
* The underlying ChaCha implementation may not overflow the block
* counter into the second counter word. Therefore we disallow
* individual operations that work on more than 2TB at a time.
* in_len_64 is needed because, on 32-bit platforms, size_t is only
* 32-bits and this produces a warning because it's always false.
* Casting to uint64_t inside the conditional is not sufficient to stop
* the warning. */
in_len_64 = in.size;
if (in_len_64 >= (1ULL << 32) * 64 - 64)
return enif_make_badarg(env);

memset(poly1305_key, 0, sizeof(poly1305_key));
CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key.data, iv.data, 0);

outp = enif_make_new_binary(env, in.size, &out);

CRYPTO_poly1305_init(&poly1305, poly1305_key);
poly1305_update_with_length(&poly1305, aad.data, aad.size);
CRYPTO_chacha_20(outp, in.data, in.size, key.data, iv.data, 1);
poly1305_update_with_length(&poly1305, outp, in.size);

CRYPTO_poly1305_finish(&poly1305, enif_make_new_binary(env, POLY1305_TAG_LEN, &out_tag));

CONSUME_REDS(env, in);

return enif_make_tuple2(env, out, out_tag);

#else
return atom_notsup;
#endif
}

static ERL_NIF_TERM chacha20_poly1305_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Key,Iv,AAD,In,Tag) */
#if defined(HAVE_CHACHA20_POLY1305)
ErlNifBinary key, iv, aad, in, tag;
unsigned char *outp;
ERL_NIF_TERM out;
ErlNifUInt64 in_len_64;
unsigned char poly1305_key[32];
unsigned char mac[POLY1305_TAG_LEN];
poly1305_state poly1305;

CHECK_OSE_CRYPTO();

if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 32
|| !enif_inspect_binary(env, argv[1], &iv) || iv.size != CHACHA20_NONCE_LEN
|| !enif_inspect_iolist_as_binary(env, argv[2], &aad)
|| !enif_inspect_iolist_as_binary(env, argv[3], &in)
|| !enif_inspect_iolist_as_binary(env, argv[4], &tag) || tag.size != POLY1305_TAG_LEN) {
return enif_make_badarg(env);
}

/* Take from OpenSSL patch set/LibreSSL:
*
* The underlying ChaCha implementation may not overflow the block
* counter into the second counter word. Therefore we disallow
* individual operations that work on more than 2TB at a time.
* in_len_64 is needed because, on 32-bit platforms, size_t is only
* 32-bits and this produces a warning because it's always false.
* Casting to uint64_t inside the conditional is not sufficient to stop
* the warning. */
in_len_64 = in.size;
if (in_len_64 >= (1ULL << 32) * 64 - 64)
return enif_make_badarg(env);

memset(poly1305_key, 0, sizeof(poly1305_key));
CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key.data, iv.data, 0);

CRYPTO_poly1305_init(&poly1305, poly1305_key);
poly1305_update_with_length(&poly1305, aad.data, aad.size);
poly1305_update_with_length(&poly1305, in.data, in.size);
CRYPTO_poly1305_finish(&poly1305, mac);

if (memcmp(mac, tag.data, POLY1305_TAG_LEN) != 0)
return atom_error;

outp = enif_make_new_binary(env, in.size, &out);

CRYPTO_chacha_20(outp, in.data, in.size, key.data, iv.data, 1);

CONSUME_REDS(env, in);

return out;
#else
return atom_notsup;
#endif
}

static ERL_NIF_TERM rand_bytes_1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Bytes) */
unsigned bytes;
Expand Down
12 changes: 8 additions & 4 deletions lib/crypto/doc/src/crypto.xml
Expand Up @@ -136,7 +136,9 @@

<p><code>block_cipher() = aes_cbc128 | aes_cfb8 | aes_cfb128 | aes_ige256 | blowfish_cbc |
blowfish_cfb64 | des_cbc | des_cfb | des3_cbc | des3_cbf
| des_ede3 | rc2_cbc | aes_gcm </code></p>
| des_ede3 | rc2_cbc </code></p>

<p><code>aead_cipher() = aes_gcm | chacha20_poly1305 </code></p>

<p><code>stream_key() = aes_key() | rc4_key() </code></p>

Expand All @@ -158,7 +160,7 @@
Note that both md4 and md5 are recommended only for compatibility with existing applications.
</p>
<p><code> cipher_algorithms() = des_cbc | des_cfb | des3_cbc | des3_cbf | des_ede3 |
blowfish_cbc | blowfish_cfb64 | aes_cbc128 | aes_cfb8 | aes_cfb128| aes_cbc256 | aes_ige256 | aes_gcm | rc2_cbc | aes_ctr| rc4 </code> </p>
blowfish_cbc | blowfish_cfb64 | aes_cbc128 | aes_cfb8 | aes_cfb128| aes_cbc256 | aes_ige256 | aes_gcm | chacha20_poly1305 | rc2_cbc | aes_ctr| rc4 </code> </p>
<p><code> public_key_algorithms() = rsa |dss | ecdsa | dh | ecdh | ec_gf2m</code>
Note that ec_gf2m is not strictly a public key algorithm, but a restriction on what curves are supported
with ecdsa and ecdh.
Expand All @@ -169,10 +171,11 @@
<funcs>
<func>
<name>block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name>
<name>block_encrypt(aes_gcm, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name>
<name>block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<type>
<v>Type = block_cipher() </v>
<v>AeadType = aead_cipher() </v>
<v>Key = block_key() </v>
<v>PlainText = iodata() </v>
<v>AAD = IVec = CipherText = CipherTag = binary()</v>
Expand All @@ -190,10 +193,11 @@

<func>
<name>block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name>
<name>block_decrypt(aes_gcm, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name>
<name>block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<type>
<v>Type = block_cipher() </v>
<v>AeadType = aead_cipher() </v>
<v>Key = block_key() </v>
<v>PlainText = iodata() </v>
<v>AAD = IVec = CipherText = CipherTag = binary()</v>
Expand Down
20 changes: 18 additions & 2 deletions lib/crypto/src/crypto.erl
Expand Up @@ -283,7 +283,7 @@ hmac_final_n(_Context, _HashLen) -> ? nif_stub.
-spec block_encrypt(des_cbc | des_cfb | des3_cbc | des3_cbf | des_ede3 | blowfish_cbc |
blowfish_cfb64 | aes_cbc128 | aes_cfb8 | aes_cfb128 | aes_cbc256 | rc2_cbc,
Key::iodata(), Ivec::binary(), Data::iodata()) -> binary();
(aes_gcm, Key::iodata(), Ivec::binary(), {AAD::binary(), Data::iodata()}) -> {binary(), binary()}.
(aes_gcm | chacha20_poly1305, Key::iodata(), Ivec::binary(), {AAD::binary(), Data::iodata()}) -> {binary(), binary()}.

block_encrypt(des_cbc, Key, Ivec, Data) ->
des_cbc_encrypt(Key, Ivec, Data);
Expand Down Expand Up @@ -316,14 +316,19 @@ block_encrypt(aes_gcm, Key, Ivec, {AAD, Data}) ->
notsup -> erlang:error(notsup);
Return -> Return
end;
block_encrypt(chacha20_poly1305, Key, Ivec, {AAD, Data}) ->
case chacha20_poly1305_encrypt(Key, Ivec, AAD, Data) of
notsup -> erlang:error(notsup);
Return -> Return
end;
block_encrypt(rc2_cbc, Key, Ivec, Data) ->
rc2_cbc_encrypt(Key, Ivec, Data).

-spec block_decrypt(des_cbc | des_cfb | des3_cbc | des3_cbf | des_ede3 | blowfish_cbc |
blowfish_cfb64 | blowfish_ofb64 | aes_cbc128 | aes_cbc256 | aes_ige256 |
aes_cfb8 | aes_cfb128 | rc2_cbc,
Key::iodata(), Ivec::binary(), Data::iodata()) -> binary();
(aes_gcm, Key::iodata(), Ivec::binary(),
(aes_gcm | chacha20_poly1305, Key::iodata(), Ivec::binary(),
{AAD::binary(), Data::iodata(), Tag::binary()}) -> binary() | error.
block_decrypt(des_cbc, Key, Ivec, Data) ->
des_cbc_decrypt(Key, Ivec, Data);
Expand Down Expand Up @@ -356,6 +361,11 @@ block_decrypt(aes_gcm, Key, Ivec, {AAD, Data, Tag}) ->
notsup -> erlang:error(notsup);
Return -> Return
end;
block_decrypt(chacha20_poly1305, Key, Ivec, {AAD, Data, Tag}) ->
case chacha20_poly1305_decrypt(Key, Ivec, AAD, Data, Tag) of
notsup -> erlang:error(notsup);
Return -> Return
end;
block_decrypt(rc2_cbc, Key, Ivec, Data) ->
rc2_cbc_decrypt(Key, Ivec, Data).
-spec block_encrypt(des_ecb | blowfish_ecb, Key::iodata(), Data::iodata()) -> binary().
Expand Down Expand Up @@ -1207,6 +1217,12 @@ aes_cfb_128_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
aes_gcm_encrypt(_Key, _Ivec, _AAD, _In) -> ?nif_stub.
aes_gcm_decrypt(_Key, _Ivec, _AAD, _In, _Tag) -> ?nif_stub.

%%
%% Chacha20/Ppoly1305
%%
chacha20_poly1305_encrypt(_Key, _Ivec, _AAD, _In) -> ?nif_stub.
chacha20_poly1305_decrypt(_Key, _Ivec, _AAD, _In, _Tag) -> ?nif_stub.

%%
%% DES - in cipher block chaining mode (CBC)
%%
Expand Down
19 changes: 18 additions & 1 deletion lib/crypto/test/crypto_SUITE.erl
Expand Up @@ -63,6 +63,7 @@ all() ->
{group, rc4},
{group, aes_ctr},
{group, aes_gcm},
{group, chacha20_poly1305},
mod_pow,
exor,
rand_uniform
Expand Down Expand Up @@ -102,7 +103,8 @@ groups() ->
{blowfish_ofb64,[], [block]},
{rc4, [], [stream]},
{aes_ctr, [], [stream]},
{aes_gcm, [], [aead]}
{aes_gcm, [], [aead]},
{chacha20_poly1305, [], [aead]}
].

%%-------------------------------------------------------------------
Expand Down Expand Up @@ -777,6 +779,9 @@ group_config(aes_ctr, Config) ->
group_config(aes_gcm, Config) ->
AEAD = aes_gcm(),
[{aead, AEAD} | Config];
group_config(chacha20_poly1305, Config) ->
AEAD = chacha20_poly1305(),
[{aead, AEAD} | Config];
group_config(_, Config) ->
Config.

Expand Down Expand Up @@ -1657,6 +1662,18 @@ aes_gcm() ->
hexstr2bin("a44a8266ee1c8eb0c8b5d4cf5ae9f19a")} %% CipherTag
].

%% http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04
chacha20_poly1305() ->
[
{chacha20_poly1305, hexstr2bin("4290bcb154173531f314af57f3be3b500" %% Key
"6da371ece272afa1b5dbdd1100a1007"),
hexstr2bin("86d09974840bded2a5ca"), %% PlainText
hexstr2bin("cd7cf67be39c794a"), %% Nonce
hexstr2bin("87e229d4500845a079c0"), %% AAD
hexstr2bin("e3e446f7ede9a19b62a4"), %% CipherText
hexstr2bin("677dabf4e3d24b876bb284753896e1d6")} %% CipherTag
].

rsa_plain() ->
<<"7896345786348756234 Hejsan Svejsan, erlang crypto debugger"
"09812312908312378623487263487623412039812 huagasd">>.
Expand Down

0 comments on commit fb9d36c

Please sign in to comment.