From 5a57f664f0c4b00acc304481e1037183c2341485 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Fri, 14 Feb 2020 14:49:53 +0000 Subject: [PATCH] espconn_secure: introduce TLS cert/key callbacks The new feature part of https://github.com/nodemcu/nodemcu-firmware/issues/3032 Subsequent work will remove the old mechanism. --- app/include/sys/espconn_mbedtls.h | 3 ++ app/mbedtls/app/espconn_mbedtls.c | 88 ++++++++++++++++++++++++++++++- app/mbedtls/app/espconn_secure.c | 4 +- app/modules/tls.c | 22 ++++++++ docs/modules/tls.md | 22 +++++++- 5 files changed, 135 insertions(+), 4 deletions(-) diff --git a/app/include/sys/espconn_mbedtls.h b/app/include/sys/espconn_mbedtls.h index c586492d0d..2d8e35bc5d 100644 --- a/app/include/sys/espconn_mbedtls.h +++ b/app/include/sys/espconn_mbedtls.h @@ -91,6 +91,9 @@ struct ssl_options { uint16 buffer_size; ssl_sector cert_ca_sector; ssl_sector cert_req_sector; + + int cert_verify_callback; + int cert_auth_callback; }; #define SSL_KEEP_INTVL 1 diff --git a/app/mbedtls/app/espconn_mbedtls.c b/app/mbedtls/app/espconn_mbedtls.c index 638fb40259..f1cbc49f98 100644 --- a/app/mbedtls/app/espconn_mbedtls.c +++ b/app/mbedtls/app/espconn_mbedtls.c @@ -34,6 +34,8 @@ #include "mem.h" +#include "lauxlib.h" + #ifdef MEMLEAK_DEBUG static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__; #endif @@ -476,6 +478,77 @@ espconn_mbedtls_parse(mbedtls_msg *msg, mbedtls_auth_type auth_type, const uint8 return (ret >= 0); } +/* + * Three-way return: + * 0 for no commitment, -1 to fail the connection, 1 on success + */ +static int +nodemcu_tls_cert_get(mbedtls_msg *msg, mbedtls_auth_type auth_type) +{ + int cbref; + int cbarg; + int loop = 0; + + switch(auth_type) { + case ESPCONN_CERT_AUTH: + loop = 1; + cbarg = 1; + cbref = ssl_client_options.cert_verify_callback; + break; + case ESPCONN_PK: + loop = 0; + cbarg = 0; + cbref = ssl_client_options.cert_auth_callback; + break; + case ESPCONN_CERT_OWN: + loop = 1; + cbarg = 1; + cbref = ssl_client_options.cert_auth_callback; + break; + default: + return 0; + } + + if (cbref == LUA_NOREF) + return 0; + + lua_State *L = lua_getstate(); + + do { + lua_rawgeti(L, LUA_REGISTRYINDEX, cbref); + lua_pushinteger(L, cbarg); + if (lua_pcall(L, 1, 1, 0) != 0) { + /* call failure; fail the connection attempt */ + return -1; + } + if (lua_isnil(L, -1)) { + /* nil return; stop iteration */ + break; + } + size_t resl; + const char *res = lua_tolstring(L, -1, &resl); + if (res == NULL) { + /* conversion failure; fail the connection attempt */ + lua_pop(L, 1); + return -1; + } + if (!espconn_mbedtls_parse(msg, auth_type, res, resl+1)) { + /* parsing failure; fail the connction attempt */ + lua_pop(L, 1); + return -1; + } + + /* + * Otherwise, parsing successful; if this is a loopy kind of + * callback, then increment the argument and loop. + */ + lua_pop(L, 1); + cbarg++; + } while (loop); + + return 1; +} + static bool mbedtls_msg_info_load(mbedtls_msg *msg, mbedtls_auth_type auth_type) { const char* const begin = "-----BEGIN"; @@ -487,6 +560,14 @@ static bool mbedtls_msg_info_load(mbedtls_msg *msg, mbedtls_auth_type auth_type) size_t load_len = 0; file_param file_param; + /* Override with Lua callbacks, if registered */ + switch(nodemcu_tls_cert_get(msg, auth_type)) { + case -1: + return false; + case 1: + return true; + } + bzero(&file_param, sizeof(file_param)); again: @@ -551,7 +632,9 @@ static bool mbedtls_msg_config(mbedtls_msg *msg) lwIP_REQUIRE_NOERROR(ret, exit); /*Load the certificate and private RSA key*/ - if (ssl_client_options.cert_req_sector.flag) { + if (ssl_client_options.cert_req_sector.flag + || (ssl_client_options.cert_auth_callback != LUA_NOREF)) { + load_flag = mbedtls_msg_info_load(msg, ESPCONN_CERT_OWN); lwIP_REQUIRE_ACTION(load_flag, exit, ret = ESPCONN_MEM); load_flag = mbedtls_msg_info_load(msg, ESPCONN_PK); @@ -559,7 +642,8 @@ static bool mbedtls_msg_config(mbedtls_msg *msg) } /*Load the trusted CA*/ - if(ssl_client_options.cert_ca_sector.flag) { + if(ssl_client_options.cert_ca_sector.flag + || (ssl_client_options.cert_verify_callback != LUA_NOREF)) { load_flag = mbedtls_msg_info_load(msg, ESPCONN_CERT_AUTH); lwIP_REQUIRE_ACTION(load_flag, exit, ret = ESPCONN_MEM); } diff --git a/app/mbedtls/app/espconn_secure.c b/app/mbedtls/app/espconn_secure.c index 7b6ab94a65..ce0f635023 100644 --- a/app/mbedtls/app/espconn_secure.c +++ b/app/mbedtls/app/espconn_secure.c @@ -31,6 +31,8 @@ #include "ets_sys.h" #include "os_type.h" +#include "lauxlib.h" + #ifdef MEMLEAK_DEBUG static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__; #endif @@ -39,7 +41,7 @@ static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__; #include "sys/espconn_mbedtls.h" -struct ssl_options ssl_client_options = {SSL_BUFFER_SIZE, 0, false, 0, false}; +struct ssl_options ssl_client_options = {SSL_BUFFER_SIZE, 0, false, 0, false, LUA_NOREF, LUA_NOREF}; /****************************************************************************** * FunctionName : espconn_encry_connect diff --git a/app/modules/tls.c b/app/modules/tls.c index 1db3f4c0fb..d9cb1254ad 100644 --- a/app/modules/tls.c +++ b/app/modules/tls.c @@ -529,6 +529,17 @@ static const char *fill_page_with_pem(lua_State *L, const unsigned char *flash_m // Lua: tls.cert.auth(true / false) static int tls_cert_auth(lua_State *L) { + if (ssl_client_options.cert_auth_callback != LUA_NOREF) { + lua_unref(L, ssl_client_options.cert_auth_callback); + ssl_client_options.cert_auth_callback = LUA_NOREF; + } + if ((lua_type(L, 1) == LUA_TFUNCTION) + || (lua_type(L, 1) == LUA_TLIGHTFUNCTION)) { + ssl_client_options.cert_auth_callback = lua_ref(L, 1); + lua_pushboolean(L, true); + return 1; + } + int enable; uint32_t flash_offset = platform_flash_mapped2phys((uint32_t) &tls_client_cert_area[0]); @@ -570,6 +581,17 @@ static int tls_cert_auth(lua_State *L) // Lua: tls.cert.verify(true / false) static int tls_cert_verify(lua_State *L) { + if (ssl_client_options.cert_auth_callback != LUA_NOREF) { + lua_unref(L, ssl_client_options.cert_verify_callback); + ssl_client_options.cert_verify_callback = LUA_NOREF; + } + if ((lua_type(L, 1) == LUA_TFUNCTION) + || (lua_type(L, 1) == LUA_TLIGHTFUNCTION)) { + ssl_client_options.cert_verify_callback = lua_ref(L, 1); + lua_pushboolean(L, true); + return 1; + } + int enable; uint32_t flash_offset = platform_flash_mapped2phys((uint32_t) &tls_server_cert_area[0]); diff --git a/docs/modules/tls.md b/docs/modules/tls.md index a6333c28c3..52e78335db 100644 --- a/docs/modules/tls.md +++ b/docs/modules/tls.md @@ -263,10 +263,19 @@ Controls the certificate verification process when the NodeMCU makes a secure co `tls.cert.verify(pemdata[, pemdata])` +`tls.cert.verify(callback)` + #### Parameters - `enable` A boolean which indicates whether verification should be enabled or not. The default at boot is `false`. - `pemdata` A string containing the CA certificate to use for verification. There can be several of these. +- `callback` A Lua function which returns TLS keys and certificates for use + with connections. The callback should expect one, integer argument; for + value k, the callback should return the k-th CA certificate (in either DER or + PEM form) it wishes to use to validate the remote endpoint, or `nil` if no + such CA certificate exists. If no certificates are returned, the device will + not validate the remote endpoint. + #### Returns `true` if it worked. @@ -325,6 +334,9 @@ The alternative approach is easier for development, and that is to supply the PE will store the certificate into the flash chip and turn on verification for that certificate. Subsequent boots of the ESP can then use `tls.cert.verify(true)` and use the stored certificate. +The `callback`-based version will override the in-flash information until the callback +is unregistered *or* one of the other call forms is made. + ## tls.cert.auth() Controls the client key and certificate used when the ESP creates a TLS connection (for example, @@ -335,10 +347,17 @@ through `tls.createConnection` or `https` or `MQTT` connections with `secure = t `tls.cert.auth(pemdata[, pemdata])` +`tls.cert.auth(callback)` + #### Parameters - `enable` A boolean, specifying whether subsequent TLS connections will present a client certificate. The default at boot is `false`. - `pemdata` Two strings, the first containing the PEM-encoded client's certificate and the second containing the PEM-encoded client's private key. +- `callback` A Lua function which returns TLS keys and certificates for use with connections. + The callback should expect one, integer argument; if that is 0, the callback should return + the device's private key. Otherwise, for argument k, the callback should return the k-th + certificate (in either DER or PEM form) in the devices' certificate chain. + #### Returns `true` if it worked. @@ -379,7 +398,8 @@ It can be supplied by passing the PEM data as a string value to `tls.cert.auth`. will store the certificate into the flash chip and turn on proofing with that certificate. Subsequent boots of the ESP can then use `tls.cert.auth(true)` and use the stored certificate. - +The `callback`-based version will override the in-flash information until the callback +is unregistered *or* one of the other call forms is made. # tls.setDebug function