ソースを参照

Added crypto.new_hmac() feature. (#1499)

Docs for crypto module refactored for easier maintenance.
Johny Mattsson 7 年 前
コミット
a112296850
4 ファイル変更147 行追加69 行削除
  1. 36 19
      app/crypto/digests.c
  2. 29 1
      app/crypto/digests.h
  3. 46 15
      app/modules/crypto.c
  4. 36 34
      docs/en/modules/crypto.md

+ 36 - 19
app/crypto/digests.c

@@ -156,31 +156,22 @@ int ICACHE_FLASH_ATTR crypto_fhash (const digest_mech_info_t *mi,
 }
 
 
-int ICACHE_FLASH_ATTR crypto_hmac (const digest_mech_info_t *mi,
-   const char *data, size_t data_len,
-   const char *key, size_t key_len,
-   uint8_t *digest)
+void crypto_hmac_begin (void *ctx, const digest_mech_info_t *mi,
+  const char *key, size_t key_len, uint8_t *k_opad)
 {
-  if (!mi)
-    return EINVAL;
-
-  void *ctx = (void *)os_malloc (mi->ctx_size);
-  if (!ctx)
-    return ENOMEM;
-
   // If key too long, it needs to be hashed before use
+  char tmp[mi->digest_size];
   if (key_len > mi->block_size)
   {
-    mi->create (ctx);
     mi->update (ctx, key, key_len);
-    mi->finalize (digest, ctx);
-    key = digest;
+    mi->finalize (tmp, ctx);
+    key = tmp;
     key_len = mi->digest_size;
+    mi->create (ctx); // refresh
   }
 
   const size_t bs = mi->block_size;
   uint8_t k_ipad[bs];
-  uint8_t k_opad[bs];
 
   os_memset (k_ipad, 0x36, bs);
   os_memset (k_opad, 0x5c, bs);
@@ -191,16 +182,42 @@ int ICACHE_FLASH_ATTR crypto_hmac (const digest_mech_info_t *mi,
     k_opad[i] ^= key[i];
   }
 
-  mi->create (ctx);
   mi->update (ctx, k_ipad, bs);
-  mi->update (ctx, data, data_len);
+}
+
+
+void crypto_hmac_finalize (void *ctx, const digest_mech_info_t *mi,
+  const uint8_t *k_opad, uint8_t *digest)
+{
   mi->finalize (digest, ctx);
 
   mi->create (ctx);
-  mi->update (ctx, k_opad, bs);
+  mi->update (ctx, k_opad, mi->block_size);
   mi->update (ctx, digest, mi->digest_size);
   mi->finalize (digest, ctx);
+}
 
-  os_free (ctx);
+
+int crypto_hmac (const digest_mech_info_t *mi,
+   const char *data, size_t data_len,
+   const char *key, size_t key_len,
+   uint8_t *digest)
+{
+  if (!mi)
+    return EINVAL;
+
+  struct {
+    uint8_t ctx[mi->ctx_size];
+    uint8_t k_opad[mi->block_size];
+  } *tmp = os_malloc (sizeof (*tmp));
+  if (!tmp)
+    return ENOMEM;
+
+  mi->create (tmp->ctx);
+  crypto_hmac_begin (tmp->ctx, mi, key, key_len, tmp->k_opad);
+  mi->update (tmp->ctx, data, data_len);
+  crypto_hmac_finalize (tmp->ctx, mi, tmp->k_opad, digest);
+
+  os_free (tmp);
   return 0;
 }

+ 29 - 1
app/crypto/digests.h

@@ -66,7 +66,35 @@ int crypto_hash (const digest_mech_info_t *mi, const char *data, size_t data_len
 int crypto_fhash (const digest_mech_info_t *mi, read_fn read, int readarg, uint8_t *digest);
 
 /**
- * Generate a HMAC signature.
+ * Commence calculating a HMAC signature.
+ *
+ * Once this fuction returns successfully, the @c ctx may be updated with
+ * data in multiple passes uses @c mi->update(), before being finalized via
+ * @c crypto_hmac_finalize().
+ *
+ * @param ctx     A created context for the given mech @c mi.
+ * @param mi      A mech from @c crypto_digest_mech().
+ * @param key     The key bytes to use.
+ * @param key_len Number of bytes the @c key comprises.
+ * @param k_opad  Buffer of size @c mi->block_size bytes to store the second
+ *                round of key material in.
+ *                Must be passed to @c crypto_hmac_finalize().
+ */
+void crypto_hmac_begin (void *ctx, const digest_mech_info_t *mi, const char *key, size_t key_len, uint8_t *k_opad);
+
+/**
+ * Finalizes calculating a HMAC signature.
+ * @param ctx     The created context used with @c crypto_hmac_begin().
+ * @param mi      The mech used with @c crypto_hmac_begin().
+ * @param k_opad  Second round of keying material, obtained
+ *                from @c crypto_hmac_begin().
+ * @param digest  Output buffer, must be at least @c mi->digest_size in size.
+ */
+void crypto_hmac_finalize (void *ctx, const digest_mech_info_t *mi, const uint8_t *k_opad, uint8_t *digest);
+
+/**
+ * Generate a HMAC signature in one pass.
+ * Implemented in terms of @c crypto_hmac_begin() / @c crypto_hmac_end().
  * @param mi       A mech from @c crypto_digest_mech(). A null pointer @c mi
  *                 is harmless, but will of course result in an error return.
  * @param data     The data to generate a signature for.

+ 46 - 15
app/modules/crypto.c

@@ -9,11 +9,18 @@
 #include "vfs.h"
 #include "../crypto/digests.h"
 #include "../crypto/mech.h"
+#include "lmem.h"
 
 #include "user_interface.h"
 
 #include "rom.h"
 
+typedef struct {
+  const digest_mech_info_t *mech_info;
+  void *ctx;
+  uint8_t *k_opad;
+} digest_user_datum_t;
+
 /**
   * hash = crypto.sha1(input)
   *
@@ -150,11 +157,6 @@ static int crypto_lhash (lua_State *L)
   return 1;
 }
 
-typedef struct digest_user_datum_t  {
-	const digest_mech_info_t *mech_info;
-	void *ctx;
-} digest_user_datum_t;
-
 /* General Usage for extensible hash functions:
  * sha = crypto.new_hash("MD5")
  * sha.update("Data")
@@ -162,19 +164,30 @@ typedef struct digest_user_datum_t  {
  * strdigest = crypto.toHex(sha.finalize())
  */
 
-/* crypto.new_hash("MECHTYPE") */
-static int crypto_new_hash (lua_State *L)
+#define WANT_HASH 0
+#define WANT_HMAC 1
+
+static int crypto_new_hash_hmac (lua_State *L, int what)
 {
   const digest_mech_info_t *mi = crypto_digest_mech (luaL_checkstring (L, 1));
   if (!mi)
     return bad_mech (L);
 
-  void *ctx = os_malloc (mi->ctx_size);
-  if (ctx==NULL)
-    return bad_mem (L);
+  size_t len = 0;
+  const char *key = 0;
+  uint8_t *k_opad = 0;
+  if (what == WANT_HMAC)
+  {
+    key = luaL_checklstring (L, 2, &len);
+    k_opad = luaM_malloc (L, mi->block_size);
+  }
+  void *ctx = luaM_malloc (L, mi->ctx_size);
 
   mi->create (ctx);
 
+  if (what == WANT_HMAC)
+    crypto_hmac_begin (ctx, mi, key, len, k_opad);
+
   // create a userdataum with specific metatable
   digest_user_datum_t *dudat = (digest_user_datum_t *)lua_newuserdata(L, sizeof(digest_user_datum_t));
   luaL_getmetatable(L, "crypto.hash");
@@ -183,10 +196,24 @@ static int crypto_new_hash (lua_State *L)
   // Set pointers to the mechanics and CTX
   dudat->mech_info = mi;
   dudat->ctx       = ctx;
+  dudat->k_opad    = k_opad;
 
   return 1; // Pass userdata object back
 }
 
+/* crypto.new_hash("MECHTYPE") */
+static int crypto_new_hash (lua_State *L)
+{
+  return crypto_new_hash_hmac (L, WANT_HASH);
+}
+
+/* crypto.new_hmac("MECHTYPE", "KEY") */
+static int crypto_new_hmac (lua_State *L)
+{
+  return crypto_new_hash_hmac (L, WANT_HMAC);
+}
+
+
 /* Called as object, params:
    1 - userdata "this"
    2 - new string to add to the hash state  */
@@ -197,7 +224,6 @@ static int crypto_hash_update (lua_State *L)
   size_t sl;
 
   dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash");
-  luaL_argcheck(L, dudat, 1, "crypto.hash expected");
 
   const digest_mech_info_t *mi = dudat->mech_info;
 
@@ -217,12 +243,14 @@ static int crypto_hash_finalize (lua_State *L)
   size_t sl;
 
   dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash");
-  luaL_argcheck(L, dudat, 1, "crypto.hash expected");
 
   const digest_mech_info_t *mi = dudat->mech_info;
 
   uint8_t digest[mi->digest_size]; // Allocate as local
-  mi->finalize (digest, dudat->ctx);
+  if (dudat->k_opad)
+    crypto_hmac_finalize (dudat->ctx, mi, dudat->k_opad, digest);
+  else
+    mi->finalize (digest, dudat->ctx);
 
   lua_pushlstring (L, digest, sizeof (digest));
   return 1;
@@ -235,9 +263,11 @@ static int crypto_hash_gcdelete (lua_State *L)
   digest_user_datum_t *dudat;
 
   dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash");
-  luaL_argcheck(L, dudat, 1, "crypto.hash expected");
 
-  os_free(dudat->ctx);
+  // luaM_free() uses type info to obtain original size, so have to delve
+  // one level deeper and explicitly pass the size due to void*
+  luaM_realloc_ (L, dudat->ctx, dudat->mech_info->ctx_size, 0);
+  luaM_free (L, dudat->k_opad);
 
   return 0;
 }
@@ -386,6 +416,7 @@ static const LUA_REG_TYPE crypto_map[] = {
   { LSTRKEY( "fhash"  ),   LFUNCVAL( crypto_flhash ) },
   { LSTRKEY( "new_hash"   ),   LFUNCVAL( crypto_new_hash ) },
   { LSTRKEY( "hmac"   ),   LFUNCVAL( crypto_lhmac ) },
+  { LSTRKEY( "new_hmac"   ),   LFUNCVAL( crypto_new_hmac ) },
   { LSTRKEY( "encrypt" ),  LFUNCVAL( lcrypto_encrypt ) },
   { LSTRKEY( "decrypt" ),  LFUNCVAL( lcrypto_decrypt ) },
   { LNILKEY, LNILVAL }

+ 36 - 34
docs/en/modules/crypto.md

@@ -5,6 +5,16 @@
 
 The crypto modules provides various functions for working with cryptographic algorithms.
 
+The following encryption/decryption algorithms/modes are supported:
+- `"AES-ECB"` for 128-bit AES in ECB mode (NOT recommended)
+- `"AES-CBC"` for 128-bit AES in CBC mode
+
+The following hash algorithms are supported:
+- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`)
+- MD5
+- SHA1
+- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`)
+
 ## crypto.encrypt()
 
 Encrypts Lua strings.
@@ -13,9 +23,7 @@ Encrypts Lua strings.
 `crypto.encrypt(algo, key, plain [, iv])`
 
 #### Parameters
-  - `algo` the name of the encryption algorithm to use, one of
-    - `"AES-ECB"` for 128-bit AES in ECB mode
-    - `"AES-CBC"` for 128-bit AES in CBC mode
+  - `algo` the name of a supported encryption algorithm to use
   - `key` the encryption key as a string; for AES encryption this *MUST* be 16 bytes long
   - `plain` the string to encrypt; it will be automatically zero-padded to a 16-byte boundary if necessary
   - `iv` the initilization vector, if using AES-CBC; defaults to all-zero if not given
@@ -40,9 +48,7 @@ Decrypts previously encrypted data.
 `crypto.decrypt(algo, key, cipher [, iv])`
 
 #### Parameters
-  - `algo` the name of the encryption algorithm to use, one of
-    - `"AES-ECB"` for 128-bit AES in ECB mode
-    - `"AES-CBC"` for 128-bit AES in CBC mode
+  - `algo` the name of a supported encryption algorithm to use
   - `key` the encryption key as a string; for AES encryption this *MUST* be 16 bytes long
   - `cipher` the cipher text to decrypt (as obtained from `crypto.encrypt()`)
   - `iv` the initilization vector, if using AES-CBC; defaults to all-zero if not given
@@ -75,13 +81,6 @@ Compute a cryptographic hash of a a file.
 - `algo` the hash algorithm to use, case insensitive string
 - `filename` the path to the file to hash
 
-Supported hash algorithms are:
-
-- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`)
-- MD5
-- SHA1
-- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`)
-
 #### Returns
 A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`crypto.toHex()`](#cryptotohex ).
 
@@ -101,13 +100,6 @@ Compute a cryptographic hash of a Lua string.
 `algo` the hash algorithm to use, case insensitive string
 `str` string to hash contents of
 
-Supported hash algorithms are:
-
-- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`)
-- MD5
-- SHA1
-- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`)
-
 #### Returns
 A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`crypto.toHex()`](#cryptotohex	).
 
@@ -126,13 +118,6 @@ Create a digest/hash object that can have any number of strings added to it. Obj
 #### Parameters
 `algo` the hash algorithm to use, case insensitive string
 
-Supported hash algorithms are:
-
-- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`)
-- MD5
-- SHA1
-- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`)
-
 #### Returns
 Userdata object with `update` and `finalize` functions available.
 
@@ -157,13 +142,6 @@ Compute a [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication
 - `str` data to calculate the hash for
 - `key` key to use for signing, may be a binary string
 
-Supported hash algorithms are:
-
-- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`)
-- MD5
-- SHA1
-- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`)
-
 #### Returns
 A binary string containing the HMAC signature. Use [`crypto.toHex()`](#cryptotohex	) to obtain the textual version.
 
@@ -172,6 +150,30 @@ A binary string containing the HMAC signature. Use [`crypto.toHex()`](#cryptotoh
 print(crypto.toHex(crypto.hmac("sha1","abc","mysecret")))
 ```
 
+## crypto.new_hmac()
+
+Create a hmac object that can have any number of strings added to it. Object has `update` and `finalize` functions.
+
+#### Syntax
+`hmacobj = crypto.new_hmac(algo, key)`
+
+#### Parameters
+- `algo` the hash algorithm to use, case insensitive string
+- `key` the key to use (may be a binary string)
+
+#### Returns
+Userdata object with `update` and `finalize` functions available.
+
+#### Example
+```lua
+hmacobj = crypto.new_hmac("SHA1", "s3kr3t")
+hmacobj:update("FirstString"))
+hmacobj:update("SecondString"))
+digest = hmacobj:finalize()
+print(crypto.toHex(digest))
+```
+
+
 ## crypto.mask()
 
 Applies an XOR mask to a Lua string. Note that this is not a proper cryptographic mechanism, but some protocols may use it nevertheless.