From 2f0aff476c585524b4eb2d8edb13c5e7357c111d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= <essen@ninenines.eu>
Date: Tue, 5 Mar 2019 13:34:48 +0100
Subject: Add crypto:cipher_info/1 and crypto:hash_info/1

Also adds some more aliases that contain the key length
in their name.
---
 lib/crypto/c_src/atoms.c         | 24 +++++++++++
 lib/crypto/c_src/atoms.h         | 12 ++++++
 lib/crypto/c_src/cipher.c        | 93 ++++++++++++++++++++++++++++++++++++++++
 lib/crypto/c_src/cipher.h        |  4 ++
 lib/crypto/c_src/crypto.c        |  2 +
 lib/crypto/c_src/hash.c          | 26 +++++++++++
 lib/crypto/c_src/hash.h          |  1 +
 lib/crypto/src/crypto.erl        | 15 +++++++
 lib/crypto/test/crypto_SUITE.erl | 21 ++++++++-
 9 files changed, 197 insertions(+), 1 deletion(-)

(limited to 'lib')

diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
index 2e417da7f4..798c26c9bb 100644
--- a/lib/crypto/c_src/atoms.c
+++ b/lib/crypto/c_src/atoms.c
@@ -41,6 +41,18 @@ ERL_NIF_TERM atom_not_enabled;
 ERL_NIF_TERM atom_not_supported;
 #endif
 
+ERL_NIF_TERM atom_type;
+ERL_NIF_TERM atom_size;
+ERL_NIF_TERM atom_block_size;
+ERL_NIF_TERM atom_key_length;
+ERL_NIF_TERM atom_iv_length;
+ERL_NIF_TERM atom_mode;
+ERL_NIF_TERM atom_ecb_mode;
+ERL_NIF_TERM atom_cbc_mode;
+ERL_NIF_TERM atom_cfb_mode;
+ERL_NIF_TERM atom_ofb_mode;
+ERL_NIF_TERM atom_stream_cipher;
+
 #if defined(HAVE_EC)
 ERL_NIF_TERM atom_prime_field;
 ERL_NIF_TERM atom_characteristic_two_field;
@@ -140,6 +152,18 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM
     atom_notsup = enif_make_atom(env,"notsup");
     atom_digest = enif_make_atom(env,"digest");
 
+    atom_type = enif_make_atom(env,"type");
+    atom_size = enif_make_atom(env,"size");
+    atom_block_size = enif_make_atom(env,"block_size");
+    atom_key_length = enif_make_atom(env,"key_length");
+    atom_iv_length = enif_make_atom(env,"iv_length");
+    atom_mode = enif_make_atom(env,"mode");
+    atom_ecb_mode = enif_make_atom(env,"ecb_mode");
+    atom_cbc_mode = enif_make_atom(env,"cbc_mode");
+    atom_cfb_mode = enif_make_atom(env,"cfb_mode");
+    atom_ofb_mode = enif_make_atom(env,"ofb_mode");
+    atom_stream_cipher = enif_make_atom(env,"stream_cipher");
+
 #if defined(HAVE_EC)
     atom_prime_field = enif_make_atom(env,"prime_field");
     atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field");
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
index f15523d865..f8e9211459 100644
--- a/lib/crypto/c_src/atoms.h
+++ b/lib/crypto/c_src/atoms.h
@@ -45,6 +45,18 @@ extern ERL_NIF_TERM atom_not_enabled;
 extern ERL_NIF_TERM atom_not_supported;
 #endif
 
+extern ERL_NIF_TERM atom_type;
+extern ERL_NIF_TERM atom_size;
+extern ERL_NIF_TERM atom_block_size;
+extern ERL_NIF_TERM atom_key_length;
+extern ERL_NIF_TERM atom_iv_length;
+extern ERL_NIF_TERM atom_mode;
+extern ERL_NIF_TERM atom_ecb_mode;
+extern ERL_NIF_TERM atom_cbc_mode;
+extern ERL_NIF_TERM atom_cfb_mode;
+extern ERL_NIF_TERM atom_ofb_mode;
+extern ERL_NIF_TERM atom_stream_cipher;
+
 #if defined(HAVE_EC)
 extern ERL_NIF_TERM atom_prime_field;
 extern ERL_NIF_TERM atom_characteristic_two_field;
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index f8e44b228a..c055a62654 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -67,14 +67,26 @@ static struct cipher_type_t cipher_types[] =
     {{"aes_cfb8"}, {&EVP_aes_192_cfb8}, 24, NO_FIPS_CIPHER | AES_CFBx},
     {{"aes_cfb8"}, {&EVP_aes_256_cfb8}, 32, NO_FIPS_CIPHER | AES_CFBx},
 
+    {{"aes_128_cfb8"}, {&EVP_aes_128_cfb8}, 16, NO_FIPS_CIPHER | AES_CFBx},
+    {{"aes_192_cfb8"}, {&EVP_aes_192_cfb8}, 24, NO_FIPS_CIPHER | AES_CFBx},
+    {{"aes_256_cfb8"}, {&EVP_aes_256_cfb8}, 32, NO_FIPS_CIPHER | AES_CFBx},
+
     {{"aes_cfb128"}, {&EVP_aes_128_cfb128}, 16, NO_FIPS_CIPHER | AES_CFBx},
     {{"aes_cfb128"}, {&EVP_aes_192_cfb128}, 24, NO_FIPS_CIPHER | AES_CFBx},
     {{"aes_cfb128"}, {&EVP_aes_256_cfb128}, 32, NO_FIPS_CIPHER | AES_CFBx},
 
+    {{"aes_128_cfb128"}, {&EVP_aes_128_cfb128}, 16, NO_FIPS_CIPHER | AES_CFBx},
+    {{"aes_192_cfb128"}, {&EVP_aes_192_cfb128}, 24, NO_FIPS_CIPHER | AES_CFBx},
+    {{"aes_256_cfb128"}, {&EVP_aes_256_cfb128}, 32, NO_FIPS_CIPHER | AES_CFBx},
+
     {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
     {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
     {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
 
+    {{"aes_128_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
+    {{"aes_192_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
+    {{"aes_256_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
+
 #if defined(HAVE_EVP_AES_CTR)
     {{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0},
     {{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0},
@@ -207,6 +219,87 @@ int cmp_cipher_types(const void *keyp, const void *elemp) {
 }
 
 
+ERL_NIF_TERM cipher_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+    const struct cipher_type_t *cipherp;
+    const EVP_CIPHER     *cipher;
+    ERL_NIF_TERM         ret, ret_mode;
+    unsigned             type;
+    unsigned long        mode;
+
+    if ((cipherp = get_cipher_type_no_key(argv[0])) == NULL)
+        return enif_make_badarg(env);
+
+    if (FORBIDDEN_IN_FIPS(cipherp))
+        return enif_raise_exception(env, atom_notsup);
+    if ((cipher = cipherp->cipher.p) == NULL)
+        return enif_raise_exception(env, atom_notsup);
+
+    ret = enif_make_new_map(env);
+
+    type = EVP_CIPHER_type(cipher);
+    enif_make_map_put(env, ret, atom_type,
+        type == NID_undef ? atom_undefined : enif_make_int(env, type),
+        &ret);
+
+    enif_make_map_put(env, ret, atom_key_length,
+        enif_make_int(env, EVP_CIPHER_key_length(cipher)), &ret);
+    enif_make_map_put(env, ret, atom_iv_length,
+        enif_make_int(env, EVP_CIPHER_iv_length(cipher)), &ret);
+    enif_make_map_put(env, ret, atom_block_size,
+        enif_make_int(env, EVP_CIPHER_block_size(cipher)), &ret);
+
+    mode = EVP_CIPHER_mode(cipher);
+    switch (mode) {
+        case EVP_CIPH_ECB_MODE:
+            ret_mode = atom_ecb_mode;
+            break;
+
+        case EVP_CIPH_CBC_MODE:
+            ret_mode = atom_cbc_mode;
+            break;
+
+        case EVP_CIPH_CFB_MODE:
+            ret_mode = atom_cfb_mode;
+            break;
+
+        case EVP_CIPH_OFB_MODE:
+            ret_mode = atom_ofb_mode;
+            break;
+
+        case EVP_CIPH_STREAM_CIPHER:
+            ret_mode = atom_stream_cipher;
+            break;
+
+        default:
+            ret_mode = atom_undefined;
+            break;
+    }
+
+    enif_make_map_put(env, ret, atom_mode, ret_mode, &ret);
+
+    return ret;
+}
+
+const struct cipher_type_t* get_cipher_type_no_key(ERL_NIF_TERM type)
+{
+    struct cipher_type_t key;
+
+    key.type.atom = type;
+
+    return bsearch(&key, cipher_types, num_cipher_types, sizeof(cipher_types[0]), cmp_cipher_types_no_key);
+}
+
+int cmp_cipher_types_no_key(const void *keyp, const void *elemp) {
+    const struct cipher_type_t *key  = keyp;
+    const struct cipher_type_t *elem = elemp;
+
+    if (key->type.atom < elem->type.atom) return -1;
+    else if (key->type.atom > elem->type.atom) return 1;
+    else /* key->type.atom == elem->type.atom */ return 0;
+}
+
+
 ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env)
 {
     struct cipher_type_t* p;
diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h
index 6b43afea99..b0d9d324e1 100644
--- a/lib/crypto/c_src/cipher.h
+++ b/lib/crypto/c_src/cipher.h
@@ -61,12 +61,16 @@ struct evp_cipher_ctx {
     EVP_CIPHER_CTX* ctx;
 };
 
+ERL_NIF_TERM cipher_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
 int init_cipher_ctx(ErlNifEnv *env);
 
 void init_cipher_types(ErlNifEnv* env);
+const struct cipher_type_t* get_cipher_type_no_key(ERL_NIF_TERM type);
 const struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len);
 
 int cmp_cipher_types(const void *keyp, const void *elemp);
+int cmp_cipher_types_no_key(const void *keyp, const void *elemp);
 
 ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env);
 
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index 06439c34b2..261590d9a5 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -67,6 +67,7 @@ static ErlNifFunc nif_funcs[] = {
     {"info_fips", 0, info_fips, 0},
     {"enable_fips_mode", 1, enable_fips_mode, 0},
     {"algorithms", 0, algorithms, 0},
+    {"hash_info", 1, hash_info_nif, 0},
     {"hash_nif", 2, hash_nif, 0},
     {"hash_init_nif", 1, hash_init_nif, 0},
     {"hash_update_nif", 2, hash_update_nif, 0},
@@ -78,6 +79,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},
+    {"cipher_info_nif", 1, cipher_info_nif, 0},
     {"block_crypt_nif", 5, block_crypt_nif, 0},
     {"block_crypt_nif", 4, block_crypt_nif, 0},
     {"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0},
diff --git a/lib/crypto/c_src/hash.c b/lib/crypto/c_src/hash.c
index 457e9d071a..0a9f64acef 100644
--- a/lib/crypto/c_src/hash.c
+++ b/lib/crypto/c_src/hash.c
@@ -61,6 +61,32 @@ int init_hash_ctx(ErlNifEnv* env) {
 #endif
 }
 
+ERL_NIF_TERM hash_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+    struct digest_type_t *digp = NULL;
+    const EVP_MD         *md;
+    ERL_NIF_TERM         ret;
+
+    ASSERT(argc == 1);
+
+    if ((digp = get_digest_type(argv[0])) == NULL)
+        return enif_make_badarg(env);
+
+    if ((md = digp->md.p) == NULL)
+        return atom_notsup;
+
+    ret = enif_make_new_map(env);
+
+    enif_make_map_put(env, ret, atom_type,
+        enif_make_int(env, EVP_MD_type(md)), &ret);
+    enif_make_map_put(env, ret, atom_size,
+        enif_make_int(env, EVP_MD_size(md)), &ret);
+    enif_make_map_put(env, ret, atom_block_size,
+        enif_make_int(env, EVP_MD_block_size(md)), &ret);
+
+    return ret;
+}
+
 ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 {/* (Type, Data) */
     struct digest_type_t *digp = NULL;
diff --git a/lib/crypto/c_src/hash.h b/lib/crypto/c_src/hash.h
index 8bae07f39a..92a25cedb7 100644
--- a/lib/crypto/c_src/hash.h
+++ b/lib/crypto/c_src/hash.h
@@ -25,6 +25,7 @@
 
 int init_hash_ctx(ErlNifEnv *env);
 
+ERL_NIF_TERM hash_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index fe8390c5b8..97a4a7a3f0 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -24,6 +24,7 @@
 
 -export([start/0, stop/0, info_lib/0, info_fips/0, supports/0, enable_fips_mode/1,
          version/0, bytes_to_integer/1]).
+-export([cipher_info/1, hash_info/1]).
 -export([hash/2, hash_init/1, hash_update/2, hash_final/1]).
 -export([sign/4, sign/5, verify/5, verify/6]).
 -export([generate_key/2, generate_key/3, compute_key/4]).
@@ -403,6 +404,11 @@ enable_fips_mode(_) -> ?nif_stub.
 
 -define(HASH_HASH_ALGORITHM, sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash() ).
 
+-spec hash_info(Type) -> map() when Type :: ?HASH_HASH_ALGORITHM.
+
+hash_info(Type) ->
+    notsup_to_error(hash_info_nif(Type)).
+
 -spec hash(Type, Data) -> Digest when Type :: ?HASH_HASH_ALGORITHM,
                                       Data :: iodata(),
                                       Digest :: binary().
@@ -531,6 +537,12 @@ poly1305(Key, Data) ->
 %%%
 %%%================================================================
 
+-spec cipher_info(Type) -> map() when Type :: block_cipher_with_iv()
+                                           | aead_cipher()
+                                           | block_cipher_without_iv().
+cipher_info(Type) ->
+    cipher_info_nif(Type).
+
 %%%---- Block ciphers
 
 %%%----------------------------------------------------------------
@@ -1726,6 +1738,7 @@ hash_update(State0, Data, _, MaxBytes) ->
     State = notsup_to_error(hash_update_nif(State0, Increment)),
     hash_update(State, Rest, erlang:byte_size(Rest), MaxBytes).
 
+hash_info_nif(_Hash) -> ?nif_stub.
 hash_nif(_Hash, _Data) -> ?nif_stub.
 hash_init_nif(_Hash) -> ?nif_stub.
 hash_update_nif(_State, _Data) -> ?nif_stub.
@@ -1770,6 +1783,8 @@ poly1305_nif(_Key, _Data) -> ?nif_stub.
 
 %% CIPHERS --------------------------------------------------------------------
 
+cipher_info_nif(_Type) -> ?nif_stub.
+
 block_crypt_nif(_Type, _Key, _Ivec, _Text, _IsEncrypt) -> ?nif_stub.
 block_crypt_nif(_Type, _Key, _Text, _IsEncrypt) -> ?nif_stub.
 
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index ab6d88deb2..7257f4fb9f 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -40,7 +40,9 @@ all() ->
      rand_uniform,
      rand_threads,
      rand_plugin,
-     rand_plugin_s
+     rand_plugin_s,
+     cipher_info,
+     hash_info
     ].
 
 groups() ->
@@ -665,6 +667,23 @@ rand_plugin_s() ->
 rand_plugin_s(Config) when is_list(Config) ->
     rand_plugin_aux(explicit_state).
 
+%%--------------------------------------------------------------------
+cipher_info() ->
+    [{doc, "crypto cipher_info testing"}].
+cipher_info(Config) when is_list(Config) ->
+    #{type := _,key_length := _,iv_length := _,
+        block_size := _,mode := _} = crypto:cipher_info(aes_128_cbc),
+    {'EXIT',_} = (catch crypto:cipher_info(not_a_cipher)),
+    ok.
+
+%%--------------------------------------------------------------------
+hash_info() ->
+    [{doc, "crypto hash_info testing"}].
+hash_info(Config) when is_list(Config) ->
+    #{type := _,size := _,block_size := _} = crypto:hash_info(sha256),
+    {'EXIT',_} = (catch crypto:hash_info(not_a_hash)),
+    ok.
+
 %%--------------------------------------------------------------------
 %% Internal functions ------------------------------------------------
 %%--------------------------------------------------------------------
-- 
cgit v1.2.3