Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: Support chacha20_poly1305 #1291

Merged
merged 1 commit into from Dec 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
152 changes: 60 additions & 92 deletions lib/crypto/c_src/crypto.c
Expand Up @@ -120,7 +120,7 @@
# endif
#endif

#if defined(NID_chacha20) && !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305)
#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
# define HAVE_CHACHA20_POLY1305
#endif

Expand All @@ -138,27 +138,6 @@
#include <openssl/ecdsa.h>
#endif

/*
* FIXME: The support for ChaCha and Poly1305 is based on pre-releases
* of OpenSSL 1.1.0. It is seriously broken when used with the released
* OpenSSL 1.1.0 or later.
*/
#undef HAVE_CHACHA20_POLY1305

#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 @@ -2093,71 +2072,61 @@ static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_N
}
#endif /* HAVE_GCM_EVP_DECRYPT_BUG */

#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)
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher = NULL;
ErlNifBinary key, iv, aad, in;
unsigned char *outp;
unsigned char *outp, *tagp;
ERL_NIF_TERM out, out_tag;
ErlNifUInt64 in_len_64;
unsigned char poly1305_key[32];
poly1305_state poly1305;
int len;

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_binary(env, argv[1], &iv) || iv.size == 0 || iv.size > 16
|| !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);
cipher = EVP_chacha20_poly1305();

ctx = EVP_CIPHER_CTX_new();

if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1)
goto out_err;

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

if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, iv.size, NULL) != 1)
goto out_err;
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1)
goto out_err;
if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1)
goto out_err;

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);
if (EVP_EncryptUpdate(ctx, outp, &len, in.data, in.size) != 1)
goto out_err;
if (EVP_EncryptFinal_ex(ctx, outp+len, &len) != 1)
goto out_err;

tagp = enif_make_new_binary(env, 16, &out_tag);

CRYPTO_poly1305_finish(&poly1305, enif_make_new_binary(env, POLY1305_TAG_LEN, &out_tag));
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tagp) != 1)
goto out_err;

EVP_CIPHER_CTX_free(ctx);

CONSUME_REDS(env, in);

return enif_make_tuple2(env, out, out_tag);

out_err:
EVP_CIPHER_CTX_free(ctx);
return atom_error;
#else
return enif_raise_exception(env, atom_notsup);
#endif
Expand All @@ -2166,53 +2135,52 @@ static ERL_NIF_TERM chacha20_poly1305_encrypt(ErlNifEnv* env, int argc, const ER
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)
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher = NULL;
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;
int len;

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_binary(env, argv[1], &iv) || iv.size == 0 || iv.size > 16
|| !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) {
|| !enif_inspect_iolist_as_binary(env, argv[4], &tag) || tag.size != 16) {
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);
cipher = EVP_chacha20_poly1305();

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);
ctx = EVP_CIPHER_CTX_new();

if (memcmp(mac, tag.data, POLY1305_TAG_LEN) != 0)
return atom_error;
if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1)
goto out_err;
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, iv.size, NULL) != 1)
goto out_err;
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1)
goto out_err;
if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1)
goto out_err;

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

CRYPTO_chacha_20(outp, in.data, in.size, key.data, iv.data, 1);
if (EVP_DecryptUpdate(ctx, outp, &len, in.data, in.size) != 1)
goto out_err;
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag.size, tag.data) != 1)
goto out_err;
if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1)
goto out_err;

EVP_CIPHER_CTX_free(ctx);

CONSUME_REDS(env, in);

return out;

out_err:
EVP_CIPHER_CTX_free(ctx);
return atom_error;
#else
return enif_raise_exception(env, atom_notsup);
#endif
Expand Down
49 changes: 41 additions & 8 deletions lib/crypto/test/crypto_SUITE.erl
Expand Up @@ -2249,16 +2249,49 @@ aes_gcm() ->
1} %% TagLength
].

%% http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04
%% https://tools.ietf.org/html/rfc7539#appendix-A.5
chacha20_poly1305() ->
[
{chacha20_poly1305, hexstr2bin("4290bcb154173531f314af57f3be3b500" %% Key
"6da371ece272afa1b5dbdd1100a1007"),
hexstr2bin("86d09974840bded2a5ca"), %% PlainText
hexstr2bin("cd7cf67be39c794a"), %% Nonce
hexstr2bin("87e229d4500845a079c0"), %% AAD
hexstr2bin("e3e446f7ede9a19b62a4"), %% CipherText
hexstr2bin("677dabf4e3d24b876bb284753896e1d6")} %% CipherTag
{chacha20_poly1305,
hexstr2bin("1c9240a5eb55d38af333888604f6b5f0" %% Key
"473917c1402b80099dca5cbc207075c0"),
hexstr2bin("496e7465726e65742d44726166747320" %% PlainText
"61726520647261667420646f63756d65"
"6e74732076616c696420666f72206120"
"6d6178696d756d206f6620736978206d"
"6f6e74687320616e64206d6179206265"
"20757064617465642c207265706c6163"
"65642c206f72206f62736f6c65746564"
"206279206f7468657220646f63756d65"
"6e747320617420616e792074696d652e"
"20497420697320696e617070726f7072"
"6961746520746f2075736520496e7465"
"726e65742d4472616674732061732072"
"65666572656e6365206d617465726961"
"6c206f7220746f206369746520746865"
"6d206f74686572207468616e20617320"
"2fe2809c776f726b20696e2070726f67"
"726573732e2fe2809d"),
hexstr2bin("000000000102030405060708"), %% Nonce
hexstr2bin("f33388860000000000004e91"), %% AAD
hexstr2bin("64a0861575861af460f062c79be643bd" %% CipherText
"5e805cfd345cf389f108670ac76c8cb2"
"4c6cfc18755d43eea09ee94e382d26b0"
"bdb7b73c321b0100d4f03b7f355894cf"
"332f830e710b97ce98c8a84abd0b9481"
"14ad176e008d33bd60f982b1ff37c855"
"9797a06ef4f0ef61c186324e2b350638"
"3606907b6a7c02b0f9f6157b53c867e4"
"b9166c767b804d46a59b5216cde7a4e9"
"9040c5a40433225ee282a1b0a06c523e"
"af4534d7f83fa1155b0047718cbc546a"
"0d072b04b3564eea1b422273f548271a"
"0bb2316053fa76991955ebd63159434e"
"cebb4e466dae5a1073a6727627097a10"
"49e617d91d361094fa68f0ff77987130"
"305beaba2eda04df997b714d6c6f2c29"
"a6ad5cb4022b02709b"),
hexstr2bin("eead9d67890cbb22392336fea1851f38")} %% CipherTag
].

rsa_plain() ->
Expand Down