From 160cea3f655913b370650f93b0c8f6c1bd163e32 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 14 May 2019 17:01:17 +0200 Subject: crypto: MAC nif unifying HMAC, CMAC and POLY1305 into one nif using the EVP_DigestSign interface. This enables acceleration if available in lower layers, that is, in cryptolib and lower. However, for older cryptolibs the old HMAC and CMAC low-level interfaces are used, but moved from hmac.c and cmac.c into mac.c. --- lib/crypto/c_src/Makefile.in | 1 + lib/crypto/c_src/algorithms.c | 27 +-- lib/crypto/c_src/atoms.c | 9 + lib/crypto/c_src/atoms.h | 4 + lib/crypto/c_src/crypto.c | 3 + lib/crypto/c_src/mac.c | 389 ++++++++++++++++++++++++++++++++++++++ lib/crypto/c_src/mac.h | 33 ++++ lib/crypto/c_src/openssl_config.h | 7 + lib/crypto/src/crypto.erl | 16 +- 9 files changed, 464 insertions(+), 25 deletions(-) create mode 100644 lib/crypto/c_src/mac.c create mode 100644 lib/crypto/c_src/mac.h diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in index b6a65d7488..35ded001d6 100644 --- a/lib/crypto/c_src/Makefile.in +++ b/lib/crypto/c_src/Makefile.in @@ -92,6 +92,7 @@ CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \ $(OBJDIR)/hash$(TYPEMARKER).o \ $(OBJDIR)/hmac$(TYPEMARKER).o \ $(OBJDIR)/info$(TYPEMARKER).o \ + $(OBJDIR)/mac$(TYPEMARKER).o \ $(OBJDIR)/math$(TYPEMARKER).o \ $(OBJDIR)/pkey$(TYPEMARKER).o \ $(OBJDIR)/poly1305$(TYPEMARKER).o \ diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c index 75cddeb1e9..53b8b7eaa9 100644 --- a/lib/crypto/c_src/algorithms.c +++ b/lib/crypto/c_src/algorithms.c @@ -20,13 +20,12 @@ #include "algorithms.h" #include "cipher.h" +#include "mac.h" static unsigned int algo_hash_cnt, algo_hash_fips_cnt; static ERL_NIF_TERM algo_hash[14]; /* increase when extending the list */ static unsigned int algo_pubkey_cnt, algo_pubkey_fips_cnt; static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */ -static unsigned int algo_mac_cnt, algo_mac_fips_cnt; -static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */ static unsigned int algo_curve_cnt, algo_curve_fips_cnt; static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */ static unsigned int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt; @@ -101,19 +100,6 @@ void init_algorithms_types(ErlNifEnv* env) #endif algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp"); - - // Validated algorithms first - algo_mac_cnt = 0; - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac"); -#ifdef HAVE_CMAC - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac"); -#endif -#ifdef HAVE_POLY1305 - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305"); -#endif - // Non-validated algorithms follow - algo_mac_fips_cnt = algo_mac_cnt; - // Validated algorithms first algo_curve_cnt = 0; #if defined(HAVE_EC) @@ -250,7 +236,6 @@ void init_algorithms_types(ErlNifEnv* env) // Check that the max number of algos is updated ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM)); ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM)); ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM)); ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM)); } @@ -284,18 +269,12 @@ ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv return cipher_types_as_list(env); /* Exclude old api ciphers */ } + ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - unsigned int cnt = -#ifdef FIPS_SUPPORT - FIPS_mode() ? algo_mac_fips_cnt : -#endif - algo_mac_cnt; - - return enif_make_list_from_array(env, algo_mac, cnt); + return mac_types_as_list(env); } - ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { unsigned int cnt = diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c index 059c14690f..bbeb329fa2 100644 --- a/lib/crypto/c_src/atoms.c +++ b/lib/crypto/c_src/atoms.c @@ -30,6 +30,10 @@ ERL_NIF_TERM atom_rsa_no_padding; ERL_NIF_TERM atom_signature_md; ERL_NIF_TERM atom_undefined; +ERL_NIF_TERM atom_hmac; +ERL_NIF_TERM atom_cmac; +ERL_NIF_TERM atom_poly1305; + ERL_NIF_TERM atom_ok; ERL_NIF_TERM atom_none; ERL_NIF_TERM atom_notsup; @@ -155,6 +159,11 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding"); atom_signature_md = enif_make_atom(env,"signature_md"); atom_undefined = enif_make_atom(env,"undefined"); + + atom_hmac = enif_make_atom(env,"hmac"); + atom_cmac = enif_make_atom(env,"cmac"); + atom_poly1305 = enif_make_atom(env,"poly1305"); + atom_ok = enif_make_atom(env,"ok"); atom_none = enif_make_atom(env,"none"); atom_notsup = enif_make_atom(env,"notsup"); diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h index f5913de96f..0e2f1a0022 100644 --- a/lib/crypto/c_src/atoms.h +++ b/lib/crypto/c_src/atoms.h @@ -34,6 +34,10 @@ extern ERL_NIF_TERM atom_rsa_no_padding; extern ERL_NIF_TERM atom_signature_md; extern ERL_NIF_TERM atom_undefined; +extern ERL_NIF_TERM atom_hmac; +extern ERL_NIF_TERM atom_cmac; +extern ERL_NIF_TERM atom_poly1305; + extern ERL_NIF_TERM atom_ok; extern ERL_NIF_TERM atom_none; extern ERL_NIF_TERM atom_notsup; diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index d533cba140..7cdc95a841 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -32,6 +32,7 @@ #include "bn.h" #include "cipher.h" #include "cmac.h" +#include "mac.h" #include "dh.h" #include "digest.h" #include "dss.h" @@ -81,6 +82,7 @@ static ErlNifFunc nif_funcs[] = { {"hmac_final_nif", 1, hmac_final_nif, 0}, {"hmac_final_nif", 2, hmac_final_nif, 0}, {"cmac_nif", 3, cmac_nif, 0}, + {"mac_nif", 4, mac_nif, 0}, {"cipher_info_nif", 1, cipher_info_nif, 0}, {"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0}, {"ng_crypto_init_nif", 4, ng_crypto_init_nif, 0}, @@ -248,6 +250,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) #endif /* OPENSSL_THREADS */ init_digest_types(env); + init_mac_types(env); init_cipher_types(env); init_algorithms_types(env); diff --git a/lib/crypto/c_src/mac.c b/lib/crypto/c_src/mac.c new file mode 100644 index 0000000000..91dd42314e --- /dev/null +++ b/lib/crypto/c_src/mac.c @@ -0,0 +1,389 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2019. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +#include "common.h" +#include "cipher.h" +#include "digest.h" +#include "mac.h" + +#ifdef HAS_EVP_PKEY_CTX +struct mac_type_t { + union { + const char* str; /* before init, NULL for end-of-table */ + ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */ + }type; + union { + const int type; + }alg; +}; + + +static struct mac_type_t mac_types[] = +{ + {{"poly1305"}, +#ifdef HAVE_POLY1305 + {EVP_PKEY_POLY1305} +#else + {EVP_PKEY_NONE} +#endif + }, + + {{"hmac"}, +#ifdef HAVE_PKEY_HMAC + {EVP_PKEY_HMAC} +#else + {EVP_PKEY_NONE} +#endif + }, + + {{"cmac"}, +#ifdef HAVE_CMAC + {EVP_PKEY_CMAC} +#else + {EVP_PKEY_NONE} +#endif + }, + /*==== End of list ==== */ + {{NULL},{0}} +}; + +#endif /* HAS_EVP_PKEY_CTX */ + + +void init_mac_types(ErlNifEnv* env) +{ +#ifdef HAS_EVP_PKEY_CTX + struct mac_type_t* p = mac_types; + + for (p = mac_types; p->type.str; p++) { + p->type.atom = enif_make_atom(env, p->type.str); + } + p->type.atom = atom_false; /* end marker */ +#endif +} + + +ERL_NIF_TERM mac_types_as_list(ErlNifEnv* env) +{ +#ifdef HAS_EVP_PKEY_CTX + struct mac_type_t* p; + ERL_NIF_TERM prev, hd; + + hd = enif_make_list(env, 0); + prev = atom_undefined; + + for (p = mac_types; (p->type.atom & (p->type.atom != atom_false)); p++) { + if (prev == p->type.atom) + continue; + + if (p->alg.type != EVP_PKEY_NONE) + { + hd = enif_make_list_cell(env, p->type.atom, hd); + } + } + + return hd; +#else + return enif_make_list1(env, atom_hmac); +#endif +} + + +#ifdef HAS_EVP_PKEY_CTX +struct mac_type_t* get_mac_type(ERL_NIF_TERM type); + +struct mac_type_t* get_mac_type(ERL_NIF_TERM type) +{ + struct mac_type_t* p = NULL; + for (p = mac_types; p->type.atom != atom_false; p++) { + if (type == p->type.atom) { + return p; + } + } + return NULL; +} +#endif + + + + +ERL_NIF_TERM mac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (MacType, SubType, Key, Text) */ + + ErlNifBinary key_bin, text; + int ret_bin_alloc = 0; + size_t size; + ERL_NIF_TERM return_term; + const EVP_MD *md = NULL; + ErlNifBinary ret_bin; +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY *pkey = NULL; + EVP_MD_CTX *mctx = NULL; + EVP_PKEY_CTX *pctx = NULL; +#endif + + if (!enif_inspect_iolist_as_binary(env, argv[2], &key_bin)) + { + return_term = EXCP_BADARG(env, "Bad key"); + goto err; + } + + if (!enif_inspect_iolist_as_binary(env, argv[3], &text)) + { + return_term = EXCP_BADARG(env, "Bad text"); + goto err; + } + + if (argv[0] == atom_hmac) + { + struct digest_type_t *digp; + + if ((digp = get_digest_type(argv[1])) == NULL) + { + return_term = EXCP_BADARG(env, "Bad digest algorithm for HMAC"); + goto err; + } + if (digp->md.p == NULL) + { + return_term = EXCP_NOTSUP(env, "Unsupported digest algorithm"); + goto err; + } + + md = digp->md.p; + +#ifndef HAS_EVP_PKEY_CTX + /* Old cryptolib: use low level functions */ + { + unsigned int size_int; + + /* Find the needed space */ + if (HMAC(md, + key_bin.data, (int)key_bin.size, + text.data, text.size, + NULL, &size_int) == NULL) + { + return_term = EXCP_ERROR(env, "Get HMAC size failed"); + goto err; + } + + size = (size_t)size_int; /* Otherwise "size" is unused in 0.9.8.... */ + if (!enif_alloc_binary(size, &ret_bin)) + { + return_term = EXCP_ERROR(env, "Alloc binary"); + goto err; + } + ret_bin_alloc = 1; + + /* And do the real HMAC calc */ + if (HMAC(md, + key_bin.data, (int)key_bin.size, + text.data, text.size, + ret_bin.data, &size_int) == NULL) + { + return_term = EXCP_ERROR(env, "HMAC sign failed"); + goto err; + } + } +#else +/* HAS_EVP_PKEY_CTX and HMAC is the type. Produce a PKEY for later use */ + +# ifdef HAVE_PKEY_new_raw_private_key + /* Prefered for new applications according to EVP_PKEY_new_mac_key(3) */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size); +# else + /* Available in older versions */ + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size); +# endif + } + + else if (argv[0] == atom_cmac) + { +#ifdef HAVE_CMAC + const struct cipher_type_t *cipherp; + if (!(cipherp = get_cipher_type(argv[1], key_bin.size))) + { /* Something went wrong. Find out what by retrying in another way. */ + if (!get_cipher_type_no_key(argv[1])) + return_term = EXCP_BADARG(env, "Unknown cipher"); + else + /* Cipher exists, so it must be the key size that is wrong */ + return_term = EXCP_BADARG(env, "Bad key size"); + goto err; + } + + if (FORBIDDEN_IN_FIPS(cipherp)) + { + return_term = EXCP_NOTSUP(env, "Cipher algorithm not supported in FIPS"); + goto err; + } + + if (cipherp->cipher.p == NULL) + { + return_term = EXCP_NOTSUP(env, "Unsupported cipher algorithm"); + goto err; + } + +# ifdef HAVE_EVP_PKEY_new_CMAC_key + pkey = EVP_PKEY_new_CMAC_key(/*engine*/ NULL, key_bin.data, key_bin.size, cipherp->cipher.p); +# else + /* Compatibility with < 1.1.1 that doesn't have EVP_PKEY_new_CMAC_key + It is a complicated flow so just do some goto to get out of it. + */ + { + CMAC_CTX *ctx = NULL; + + if ((ctx = CMAC_CTX_new()) == NULL) + goto local_err; + + if (!CMAC_Init(ctx, key_bin.data, key_bin.size, cipherp->cipher.p, NULL)) + goto local_err; + + if (!CMAC_Update(ctx, text.data, text.size)) + goto local_err; + + if ((size = (size_t)EVP_CIPHER_block_size(cipherp->cipher.p)) < 0) + goto local_err; + + if (!enif_alloc_binary(size, &ret_bin)) + goto local_err; + ret_bin_alloc = 1; + + if (!CMAC_Final(ctx, ret_bin.data, &ret_bin.size)) + goto local_err; + + CONSUME_REDS(env, text); + + return_term = enif_make_binary(env, &ret_bin); + ret_bin_alloc = 0; + goto done; + + local_err: + if (ctx) + CMAC_CTX_free(ctx); + + return_term=EXCP_ERROR(env,"Compat cmac"); + goto err; + } +# endif +#else + return_term = EXCP_NOTSUP(env, "Unsupported mac type"); + goto err; +#endif /* HAVE_CMAC */ + } + + else if (argv[0] == atom_poly1305) + { +#ifdef HAVE_POLY1305 + if (key_bin.size != 32) + { + return_term = EXCP_BADARG(env, "Bad key size, != 32 bytes"); + goto err; + } + /* poly1305 implies that EVP_PKEY_new_raw_private_key exists */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_POLY1305, /*engine*/ NULL, key_bin.data, key_bin.size); +#else + return_term = EXCP_NOTSUP(env, "Unsupported mac type"); + goto err; +#endif /* HAVE_POLY1305 */ +#endif /* HAS_EVP_PKEY_CTX */ + + } + else + { + return_term = EXCP_BADARG(env, "Bad mac type"); + goto err; + } + +#ifdef HAS_EVP_PKEY_CTX + if (!pkey) + { + return_term = EXCP_ERROR(env, "EVP_PKEY_key creation"); + goto err; + } + + + if ((mctx = EVP_MD_CTX_new()) == NULL) + { + return_term = EXCP_ERROR(env, "EVP_MD_CTX_new"); + goto err; + } + + if (EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, pkey) != 1) + { + return_term = EXCP_ERROR(env, "EVP_DigestSign"); + goto err; + } + +# ifdef HAVE_DigestSign_as_single_op + if (EVP_DigestSign(mctx, NULL, &size, text.data, text.size) != 1) +# else + if (EVP_DigestSignUpdate(mctx, text.data, text.size) != 1) + { + return_term = EXCP_ERROR(env, "EVP_DigestSignUpdate"); + goto err; + } + + if (EVP_DigestSignFinal(mctx, NULL, &size) != 1) +# endif + { + return_term = EXCP_ERROR(env, "Can't get sign size"); + goto err; + } + + if (!enif_alloc_binary(size, &ret_bin)) + { + return_term = EXCP_ERROR(env, "Alloc binary"); + goto err; + } + ret_bin_alloc = 1; + +# ifdef HAVE_DigestSign_as_single_op + if (EVP_DigestSign(mctx, ret_bin.data, &size, text.data, text.size) != 1) +# else + if (EVP_DigestSignFinal(mctx, ret_bin.data, &size) != 1) +# endif + { + return_term = EXCP_ERROR(env, "Signing"); + goto err; + } + +#endif /* ifdef HAS_EVP_PKEY_CTX */ + + CONSUME_REDS(env, text); + + return_term = enif_make_binary(env, &ret_bin); + ret_bin_alloc = 0; + goto done; + +err: + if (ret_bin_alloc) + enif_release_binary(&ret_bin); + +done: +#ifdef HAS_EVP_PKEY_CTX + if (pkey) + EVP_PKEY_free(pkey); +#endif + return return_term; +} + + + + + diff --git a/lib/crypto/c_src/mac.h b/lib/crypto/c_src/mac.h new file mode 100644 index 0000000000..00b94a2232 --- /dev/null +++ b/lib/crypto/c_src/mac.h @@ -0,0 +1,33 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +#ifndef E_MAC_H__ +#define E_MAC_H__ 1 + +#include "common.h" + + +void init_mac_types(ErlNifEnv* env); + +ERL_NIF_TERM mac_types_as_list(ErlNifEnv* env); + +ERL_NIF_TERM mac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_MAC_H__ */ diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h index 339eb5b8f4..aee08ea65f 100644 --- a/lib/crypto/c_src/openssl_config.h +++ b/lib/crypto/c_src/openssl_config.h @@ -110,6 +110,12 @@ # define HAS_EVP_PKEY_CTX # define HAVE_EVP_CIPHER_CTX_COPY # endif + +# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1) +# define HAVE_PKEY_new_raw_private_key +# define HAVE_EVP_PKEY_new_CMAC_key +# define HAVE_DigestSign_as_single_op +# endif #endif @@ -278,6 +284,7 @@ #endif #if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) +# define HAVE_PKEY_HMAC # ifdef RSA_PKCS1_PSS_PADDING # define HAVE_RSA_PKCS1_PSS_PADDING # endif diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 169c0f2e91..fd4b9df5e0 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -62,7 +62,8 @@ crypto_one_time_aead/6, crypto_one_time_aead/7, crypto_dyn_iv_init/3, crypto_dyn_iv_update/3, - supports/1 + supports/1, + mac/3, mac/4, mac/5 ]). @@ -602,6 +603,19 @@ hash_final(Context) -> %%% %%%================================================================ +mac(Type, SubType, Key, Data, MacLength) -> + erlang:binary_part(mac(Type, SubType, Key, Data), 0, MacLength). + +mac(poly1305, _, Key, Data) -> mac_nif(poly1305, undefined, Key, Data); +mac(Type, SubType, Key, Data) -> mac_nif(Type, SubType, Key, Data). + +mac(poly1305, Key, Data) -> mac(poly1305, undefined, Key, Data). + + + +mac_nif(_Type, _SubType, _Key, _Data) -> ?nif_stub. + + %%%---- HMAC -type hmac_hash_algorithm() :: sha1() | sha2() | sha3() | compatibility_only_hash(). -- cgit v1.2.3