Improved crypto module for ESP32, now with HMAC (#2815)

* Leaner, meaner crypto module; now with HMAC

Based on my testing, mbedtls pulls in all its algorithm regardless of
whether the NodeMCU crypto module was using them or not. As such, the
space savings from omitting algorithms were only in the tens of bytes.

By switching to using the mbedtls generic message digest interface, the
crypto module itself could be shrunk in size and complexity. Despite
adding support for HMAC on all algorithms (plus including RIPEMD160),
this version is 330 bytes smaller.

* Updated crypto module docs.

* Removed superfluous brackets in crypto docs.

Copy-paste considered harmful... >.>
This commit is contained in:
Johny Mattsson 2019-07-05 07:25:00 +10:00 committed by Arnim Läuger
parent 1f1eeaf358
commit e11087bfdf
3 changed files with 108 additions and 179 deletions

View File

@ -80,32 +80,6 @@ config LUA_MODULE_CRYPTO
help help
Includes the crypto module. Includes the crypto module.
menu "Crypto module hashing algorithms"
depends on LUA_MODULE_CRYPTO
config CRYPTO_HASH_SHA1
bool "SHA1"
default "y"
help
Includes the SHA1 hashing algorithm
config CRYPTO_HASH_SHA256
bool "SHA256 and SHA224"
default "y"
help
Includes the SHA256 and SHA224 hashing algorithms
config CRYPTO_HASH_SHA512
bool "SHA512 and SHA384"
default "y"
help
Includes the SHA256 and SHA384 hashing algorithms
config CRYPTO_HASH_MD5
bool "MD5"
default "n"
help
Includes the MD5 hashing algorithm
endmenu
config LUA_MODULE_DAC config LUA_MODULE_DAC
bool "DAC module" bool "DAC module"
default "n" default "n"

View File

@ -2,145 +2,51 @@
#include <string.h> #include <string.h>
#include "lauxlib.h" #include "lauxlib.h"
#include "lmem.h" #include "lmem.h"
#include "mbedtls/md5.h" #include "mbedtls/md.h"
#include "mbedtls/sha1.h"
#include "mbedtls/sha256.h"
#include "mbedtls/sha512.h"
#include "module.h" #include "module.h"
#include "platform.h" #include "platform.h"
#define HASH_METATABLE "crypto.hasher" #define HASH_METATABLE "crypto.hasher"
// The following function typedefs aim to generalize mbedtls functions // algo_info_t describes a hashing algorithm and output size
// so that we can use the same code independent of what hashing algorithm
typedef void (*hash_init_t)(void* ctx);
typedef int (*hash_starts_ret_t)(void* ctx);
typedef int (*hash_update_ret_t)(void* ctx, const unsigned char* input, size_t ilen);
typedef int (*hash_finish_ret_t)(void* ctx, unsigned char* output);
typedef void (*hash_free_t)(void* ctx);
// algo_info_t describes a hashing algorithm and the mbedtls functions
// for initializing, hashing data, finalizing and freeing resources.
typedef struct { typedef struct {
const char* name; const char* name;
const size_t size; const size_t size;
const size_t context_size; const mbedtls_md_type_t type;
const hash_init_t init;
const hash_starts_ret_t starts;
const hash_update_ret_t update;
const hash_finish_ret_t finish;
const hash_free_t free;
} algo_info_t; } algo_info_t;
// hash_context_t contains information about an ongoing hash operation // hash_context_t contains information about an ongoing hash operation
typedef struct { typedef struct {
void* mbedtls_context; mbedtls_md_context_t mbedtls_context;
const algo_info_t* ainfo; const algo_info_t* ainfo;
bool hmac_mode;
} hash_context_t; } hash_context_t;
// if SHA256+SHA224 are enabled, the following two functions
// allow to call mbedtls appropriately depending on the algorithm
#ifdef CONFIG_CRYPTO_HASH_SHA256
static int sha256_starts_ret(mbedtls_sha256_context* ctx) {
return mbedtls_sha256_starts_ret(ctx, false); // false=SHA256
}
static int sha224_starts_ret(mbedtls_sha256_context* ctx) {
return mbedtls_sha256_starts_ret(ctx, true); // true=SHA224
}
#endif
// if SHA512+SHA384 are enabled, the following two functions
// allow to call mbedtls appropriately depending on the algorithm
#ifdef CONFIG_CRYPTO_HASH_SHA512
static int sha512_starts_ret(mbedtls_sha512_context* ctx) {
return mbedtls_sha512_starts_ret(ctx, false); // false=SHA512
}
static int sha384_starts_ret(mbedtls_sha512_context* ctx) {
return mbedtls_sha512_starts_ret(ctx, true); // true=SHA384
}
#endif
// the constant algorithms array below contains a table of functions and other // the constant algorithms array below contains a table of functions and other
// information about each enabled hashing algorithm // information about each enabled hashing algorithm
static const algo_info_t algorithms[] = { static const algo_info_t algorithms[] = {
#ifdef CONFIG_CRYPTO_HASH_SHA1 { "MD5", 16, MBEDTLS_MD_MD5 },
{ { "RIPEMD160", 20, MBEDTLS_MD_RIPEMD160 },
"SHA1", { "SHA1", 20, MBEDTLS_MD_SHA1 },
20, { "SHA224", 32, MBEDTLS_MD_SHA224 },
sizeof(mbedtls_sha1_context), { "SHA256", 32, MBEDTLS_MD_SHA256 },
(hash_init_t)mbedtls_sha1_init, { "SHA384", 64, MBEDTLS_MD_SHA384 },
(hash_starts_ret_t)mbedtls_sha1_starts_ret, { "SHA512", 64, MBEDTLS_MD_SHA512 },
(hash_update_ret_t)mbedtls_sha1_update_ret,
(hash_finish_ret_t)mbedtls_sha1_finish_ret,
(hash_free_t)mbedtls_sha1_free,
},
#endif
#ifdef CONFIG_CRYPTO_HASH_SHA256
{
"SHA256",
32,
sizeof(mbedtls_sha256_context),
(hash_init_t)mbedtls_sha256_init,
(hash_starts_ret_t)sha256_starts_ret,
(hash_update_ret_t)mbedtls_sha256_update_ret,
(hash_finish_ret_t)mbedtls_sha256_finish_ret,
(hash_free_t)mbedtls_sha256_free,
},
{
"SHA224",
32,
sizeof(mbedtls_sha256_context),
(hash_init_t)mbedtls_sha256_init,
(hash_starts_ret_t)sha224_starts_ret,
(hash_update_ret_t)mbedtls_sha256_update_ret,
(hash_finish_ret_t)mbedtls_sha256_finish_ret,
(hash_free_t)mbedtls_sha256_free,
},
#endif
#ifdef CONFIG_CRYPTO_HASH_SHA512
{
"SHA512",
64,
sizeof(mbedtls_sha512_context),
(hash_init_t)mbedtls_sha512_init,
(hash_starts_ret_t)sha512_starts_ret,
(hash_update_ret_t)mbedtls_sha512_update_ret,
(hash_finish_ret_t)mbedtls_sha512_finish_ret,
(hash_free_t)mbedtls_sha512_free,
},
{
"SHA384",
64,
sizeof(mbedtls_sha512_context),
(hash_init_t)mbedtls_sha512_init,
(hash_starts_ret_t)sha384_starts_ret,
(hash_update_ret_t)mbedtls_sha512_update_ret,
(hash_finish_ret_t)mbedtls_sha512_finish_ret,
(hash_free_t)mbedtls_sha512_free,
},
#endif
#ifdef CONFIG_CRYPTO_HASH_MD5
{
"MD5",
16,
sizeof(mbedtls_md5_context),
(hash_init_t)mbedtls_md5_init,
(hash_starts_ret_t)mbedtls_md5_starts_ret,
(hash_update_ret_t)mbedtls_md5_update_ret,
(hash_finish_ret_t)mbedtls_md5_finish_ret,
(hash_free_t)mbedtls_md5_free,
},
#endif
}; };
//NUM_ALGORITHMS contains the actual number of enabled algorithms //NUM_ALGORITHMS contains the actual number of enabled algorithms
const int NUM_ALGORITHMS = sizeof(algorithms) / sizeof(algo_info_t); const int NUM_ALGORITHMS = sizeof(algorithms) / sizeof(algo_info_t);
// crypto_new_hash (LUA: hasher = crypto.new_hash(algo)) allocates // crypto_new_hash (LUA: hasher = crypto.new_hash(algo)) allocates
// a hashing context for the requested algorithm // a hashing context for the requested algorithm
static int crypto_new_hash(lua_State* L) { static int crypto_new_hash_or_hmac(lua_State* L, bool is_hmac) {
const char* algo = luaL_checkstring(L, 1); const algo_info_t *ainfo = NULL;
const algo_info_t* ainfo = NULL; const char *algo = luaL_checkstring(L, 1);
const unsigned char *key = NULL;
size_t key_len = 0;
if (is_hmac)
key = (const unsigned char *)luaL_checklstring(L, 2, &key_len);
for (int i = 0; i < NUM_ALGORITHMS; i++) { for (int i = 0; i < NUM_ALGORITHMS; i++) {
if (strcasecmp(algo, algorithms[i].name) == 0) { if (strcasecmp(algo, algorithms[i].name) == 0) {
@ -150,7 +56,7 @@ static int crypto_new_hash(lua_State* L) {
} }
if (ainfo == NULL) { if (ainfo == NULL) {
luaL_error(L, "Unsupported algorithm: %s", algo); // returns return luaL_error(L, "Unsupported algorithm: %s", algo);
} }
// Instantiate a hasher object as a Lua userdata object // Instantiate a hasher object as a Lua userdata object
@ -161,19 +67,35 @@ static int crypto_new_hash(lua_State* L) {
luaL_getmetatable(L, HASH_METATABLE); luaL_getmetatable(L, HASH_METATABLE);
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
phctx->ainfo = ainfo; // save a pointer to the algorithm function table and information phctx->ainfo = ainfo;
phctx->mbedtls_context = luaM_malloc(L, ainfo->context_size); // make some space for the mbedtls context phctx->hmac_mode = is_hmac;
if (phctx->mbedtls_context == NULL) {
luaL_error(L, "Out of memory allocating context"); mbedtls_md_init(&phctx->mbedtls_context);
} int err =
mbedtls_md_setup(
&phctx->mbedtls_context,
mbedtls_md_info_from_type(phctx->ainfo->type),
is_hmac);
if (phctx->hmac_mode)
err |= mbedtls_md_hmac_starts(&phctx->mbedtls_context, key, key_len);
else
err |= mbedtls_md_starts(&phctx->mbedtls_context);
if (err != 0)
return luaL_error(L, "Error starting context");
ainfo->init(phctx->mbedtls_context); // initialize the hashing function
if (ainfo->starts(phctx->mbedtls_context) != 0) {
luaL_error(L, "Error starting context");
}
return 1; // one object returned, the hasher userdata object. return 1; // one object returned, the hasher userdata object.
} }
static int crypto_new_hash(lua_State* L) {
return crypto_new_hash_or_hmac(L, false);
}
static int crypto_new_hmac(lua_State* L)
{
return crypto_new_hash_or_hmac(L, true);
}
// crypto_hash_update (LUA: hasher:update(data)) submits data // crypto_hash_update (LUA: hasher:update(data)) submits data
// to be hashed. // to be hashed.
static int crypto_hash_update(lua_State* L) { static int crypto_hash_update(lua_State* L) {
@ -184,10 +106,15 @@ static int crypto_hash_update(lua_State* L) {
// retrieve the input string: // retrieve the input string:
const unsigned char* input = (const unsigned char*)luaL_checklstring(L, 2, &size); const unsigned char* input = (const unsigned char*)luaL_checklstring(L, 2, &size);
int err = 0;
// call the update hashing function: // call the update hashing function:
if (phctx->ainfo->update(phctx->mbedtls_context, input, size) != 0) { if (phctx->hmac_mode)
err = mbedtls_md_hmac_update(&phctx->mbedtls_context, input, size);
else
err = mbedtls_md_update(&phctx->mbedtls_context, input, size);
if (err != 0)
luaL_error(L, "Error updating hash"); luaL_error(L, "Error updating hash");
}
return 0; // no return value return 0; // no return value
} }
@ -201,10 +128,14 @@ static int crypto_hash_finalize(lua_State* L) {
// reserve some space to retrieve the output hash, according to the current algorithm // reserve some space to retrieve the output hash, according to the current algorithm
unsigned char output[phctx->ainfo->size]; unsigned char output[phctx->ainfo->size];
int err = 0;
// call the hash finish function to retrieve the result // call the hash finish function to retrieve the result
if (phctx->ainfo->finish(phctx->mbedtls_context, output) != 0) { if (phctx->hmac_mode)
err = mbedtls_md_hmac_finish(&phctx->mbedtls_context, output);
else
err = mbedtls_md_finish(&phctx->mbedtls_context, output);
if (err != 0)
luaL_error(L, "Error finalizing hash"); luaL_error(L, "Error finalizing hash");
}
// pack the output into a lua string // pack the output into a lua string
lua_pushlstring(L, (const char*)output, phctx->ainfo->size); lua_pushlstring(L, (const char*)output, phctx->ainfo->size);
@ -217,16 +148,7 @@ static int crypto_hash_finalize(lua_State* L) {
static int crypto_hash_gc(lua_State* L) { static int crypto_hash_gc(lua_State* L) {
// retrieve the hashing context: // retrieve the hashing context:
hash_context_t* phctx = (hash_context_t*)luaL_checkudata(L, 1, HASH_METATABLE); hash_context_t* phctx = (hash_context_t*)luaL_checkudata(L, 1, HASH_METATABLE);
mbedtls_md_free(&phctx->mbedtls_context);
// if the mbedtls context is NULL, it means allocation failed in new_hash(), so nothing to do.
if (phctx->mbedtls_context == NULL)
return 0;
// free mbedtls-related resources for this hash operation:
phctx->ainfo->free(phctx->mbedtls_context);
// free the memory allocated to store the mbedtls context:
luaM_freemem(L, phctx->mbedtls_context, phctx->ainfo->context_size);
return 0; return 0;
} }
@ -241,6 +163,7 @@ static const LUA_REG_TYPE crypto_hasher_map[] = {
// This table defines the functions of the crypto module: // This table defines the functions of the crypto module:
static const LUA_REG_TYPE crypto_map[] = { static const LUA_REG_TYPE crypto_map[] = {
{LSTRKEY("new_hash"), LFUNCVAL(crypto_new_hash)}, {LSTRKEY("new_hash"), LFUNCVAL(crypto_new_hash)},
{LSTRKEY("new_hmac"), LFUNCVAL(crypto_new_hmac)},
{LNILKEY, LNILVAL}}; {LNILKEY, LNILVAL}};
// luaopen_crypto is the crypto module initialization function // luaopen_crypto is the crypto module initialization function

View File

@ -1,30 +1,32 @@
# crypto Module # crypto Module
| Since | Origin / Contributor | Maintainer | Source | | Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ | | :----- | :-------------------- | :---------- | :------ |
| 2019-01-13 | [Javier Peletier](https://github.com/jpeletier) | [Javier Peletier](https://github.com/jpeletier) | [crypto.c](../../../components/modules/crypto.c)| | 2019-01-13 | [Javier Peletier](https://github.com/jpeletier), [Johny Mattsson](https://github.com/jmattsson) | [Javier Peletier](https://github.com/jpeletier) | [crypto.c](../../../components/modules/crypto.c)|
The crypto module provides various functions for working with cryptographic algorithms. The crypto module provides various functions for working with cryptographic algorithms.
This is work in progress, for now only a number of hashing functions are available: The following algorithms are supported, both in digest mode and in HMAC mode:
* SHA1
* SHA256
* SHA224
* SHA512
* SHA384
* MD5 * MD5
* SHA1
All except MD5 are enabled by default. To disable algorithms you don't need, find the "Crypto module hashing algorithms" under the NodeMCU modules section in menuconfig. * RIPEMD160
* SHA224
* SHA256
* SHA384
* SHA512
## crypto.new_hash() ## crypto.new_hash()
Create a digest/hash object that can have any number of strings added to it. Object has `update` and `finalize` functions. Create a digest/hash object that can have any number of strings added to it.
The returned object has `update(str)` and `finalize()` functions, for
streaming data through the hash function, and for finalizing and returning
the resulting digest.
#### Syntax #### Syntax
`hashobj = crypto.new_hash(algo)` `hashobj = crypto.new_hash(algo)`
#### Parameters #### Parameters
`algo` the hash algorithm to use, case insensitive string - `algo` the hash algorithm to use, case insensitive string
#### Returns #### Returns
Hasher object with `update` and `finalize` functions available. Hasher object with `update` and `finalize` functions available.
@ -32,8 +34,38 @@ Hasher object with `update` and `finalize` functions available.
#### Example #### Example
```lua ```lua
hashobj = crypto.new_hash("SHA1") hashobj = crypto.new_hash("SHA1")
hashobj:update("FirstString")) hashobj:update("FirstString")
hashobj:update("SecondString")) hashobj:update("SecondString")
digest = hashobj:finalize() digest = hashobj:finalize()
print(encoder.toHex(digest)) print(encoder.toHex(digest))
``` ```
## crypto.new_hmac()
Create an object for calculating a HMAC (Hashed Message Authentication Code,
aka "signature"). A HMAC can be used to simultaneously verify both integrity
and authenticity of data.
The returned object has `update(str)` and `finalize()` functions, for
streaming data through the hash function, and for finalizing and returning
the resulting signature.
#### Syntax
`hashobj = crypto.new_hmac(algo, key)`
#### Parameters
- `algo` the hash algorithm to use, case insensitive string
- `key` the signing key (may be a binary string)
#### Returns
Hasher object with `update` and `finalize` functions available.
#### Example
```lua
hmac = crypto.new_hmac("SHA1", "top s3cr3t key")
hmac:update("Some data")
hmac:update("... more data")
signature = hmac:finalize()
print(encoder.toHex(signature))
```