From 501bd1fb9ada2cd70456f685d6977d9907b5e9bb Mon Sep 17 00:00:00 2001 From: philip Date: Sun, 6 Mar 2016 20:25:05 -0500 Subject: [PATCH] Adds support for verifying the certificate offered by an SSL server. Squashed commit of the following: commit 4439b8c45192f6dee1222df78bbb59f74509e0ee Author: philip Date: Sun Mar 6 20:23:21 2016 -0500 Fix the ignore commit a07ee5acdf91286607c3e2dce128c9b8bfd7bd80 Author: philip Date: Sun Mar 6 20:20:41 2016 -0500 Remove uneeded stuff commit b3604ace92fc13b30161d385c354b0f1c5fe4046 Author: philip Date: Sun Mar 6 20:15:26 2016 -0500 Remove client cert auth commit 6e48c633569630736a986cd07a59a12de954391e Author: philip Date: Sun Mar 6 20:11:42 2016 -0500 More cleanup commit d40eade405ef071d0d1b60d038566b5b8f2cafa3 Author: philip Date: Sat Mar 5 10:56:56 2016 -0500 Move to almost working version commit 1860a2d90afa94461c53bd41251d4870d6527f9d Author: philip Date: Fri Mar 4 08:04:09 2016 -0500 Changed the naem to server-ca.crt commit e7a315660843273fe62943b7fe8ee6c0541dada2 Author: philip Date: Thu Mar 3 21:16:26 2016 -0500 Update gitignores commit 2b037d185c396209b64381399c40821c15e1840e Author: philip Date: Thu Mar 3 08:56:17 2016 -0500 Getting better commit 763255cffba8e279158cd7f43391a3573efdeca8 Author: philip Date: Wed Mar 2 22:28:21 2016 -0500 Works a bit better commit a38325d1a47dbad255cb3e681da8415e8cf699ea Author: philip Date: Wed Mar 2 09:11:04 2016 -0500 First building version commit 4aef13da33470ed954f2eaf5f7ac0ac3dcdf3774 Merge: 180e147 ebb0c33 Author: philip Date: Tue Mar 1 22:03:06 2016 -0500 Merge remote-tracking branch 'upstream/dev' into ssl-client commit 180e147c1abdcf4046ad9be9b3c1a48f4a875312 Author: philip Date: Sun Feb 28 21:34:21 2016 -0500 Missing files from espressif Try to imporve layout Align the file names with the contents Missing file Review comments More review coments --- .gitignore | 1 + Makefile | 15 ++- app/http/httpclient.c | 11 +- app/modules/.gitignore | 1 + app/modules/net.c | 222 ++++++++++++++++++++++++++++++++++++-- docs/en/modules/net.md | 97 +++++++++++++++++ ld/nodemcu.ld | 6 +- tools/.gitignore | 3 + tools/make_cert.py | 42 ++++++++ tools/make_server_cert.py | 69 ++++++++++++ tools/makefile.sh | 89 ++++++++++++++- 11 files changed, 538 insertions(+), 18 deletions(-) create mode 100644 app/modules/.gitignore create mode 100644 tools/.gitignore create mode 100644 tools/make_cert.py create mode 100644 tools/make_server_cert.py mode change 100755 => 100644 tools/makefile.sh diff --git a/.gitignore b/.gitignore index a7c13e91..74916742 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ sdk/ cache/ user_config.h +server-ca.crt diff --git a/Makefile b/Makefile index ec3a5f24..05b0d11b 100644 --- a/Makefile +++ b/Makefile @@ -132,7 +132,7 @@ CCFLAGS += \ # -Wall ifneq ($(wildcard $(TOP_DIR)/user_config.h),) -INCLUDES := $(INCLUDES) -include "$(TOP_DIR)/user_config.h" +INCLUDES += -include "$(TOP_DIR)/user_config.h" endif CFLAGS = $(CCFLAGS) $(DEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES) @@ -175,7 +175,7 @@ $(BINODIR)/%.bin: $(IMAGEODIR)/%.out # Should be done in top-level makefile only # -all: sdk_extracted .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS) +all: sdk_extracted pre_build .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS) .PHONY: sdk_extracted @@ -222,6 +222,17 @@ endif endif endif +.PHONY: pre_build + +pre_build: +ifneq ($(wildcard $(TOP_DIR)/server-ca.crt),) + python $(TOP_DIR)/tools/make_server_cert.py $(TOP_DIR)/server-ca.crt > $(TOP_DIR)/app/modules/server-ca.crt.h +DEFINES += -DHAVE_SSL_SERVER_CRT=\"server-ca.crt.h\" +else + @-rm -f $(TOP_DIR)/app/modules/server-ca.crt.h +endif + + $(OBJODIR)/%.o: %.c @mkdir -p $(OBJODIR); $(CC) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) -o $@ -c $< diff --git a/app/http/httpclient.c b/app/http/httpclient.c index 3d0f4faa..da3bf78f 100644 --- a/app/http/httpclient.c +++ b/app/http/httpclient.c @@ -437,10 +437,10 @@ static void ICACHE_FLASH_ATTR http_dns_callback( const char * hostname, ip_addr_ { HTTPCLIENT_DEBUG( "DNS found %s " IPSTR "\n", hostname, IP2STR( addr ) ); - struct espconn * conn = (struct espconn *) os_malloc( sizeof(struct espconn) ); + struct espconn * conn = (struct espconn *) os_zalloc( sizeof(struct espconn) ); conn->type = ESPCONN_TCP; conn->state = ESPCONN_NONE; - conn->proto.tcp = (esp_tcp *) os_malloc( sizeof(esp_tcp) ); + conn->proto.tcp = (esp_tcp *) os_zalloc( sizeof(esp_tcp) ); conn->proto.tcp->local_port = espconn_port(); conn->proto.tcp->remote_port = req->port; conn->reverse = req; @@ -473,7 +473,7 @@ void ICACHE_FLASH_ATTR http_raw_request( const char * hostname, int port, bool s { HTTPCLIENT_DEBUG( "DNS request\n" ); - request_args_t * req = (request_args_t *) os_malloc( sizeof(request_args_t) ); + request_args_t * req = (request_args_t *) os_zalloc( sizeof(request_args_t) ); req->hostname = esp_strdup( hostname ); req->port = port; req->secure = secure; @@ -558,6 +558,11 @@ void ICACHE_FLASH_ATTR http_request( const char * url, const char * method, cons colon = NULL; /* Limit the search to characters before the path. */ } + if (path - url >= sizeof(hostname)) { + HTTPCLIENT_DEBUG( "hostname is too long %s\n", url ); + return; + } + if ( colon == NULL ) /* The port is not present. */ { os_memcpy( hostname, url, path - url ); diff --git a/app/modules/.gitignore b/app/modules/.gitignore new file mode 100644 index 00000000..79f61066 --- /dev/null +++ b/app/modules/.gitignore @@ -0,0 +1 @@ +server-ca.crt.h diff --git a/app/modules/net.c b/app/modules/net.c index faab54de..8181b947 100644 --- a/app/modules/net.c +++ b/app/modules/net.c @@ -3,6 +3,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "lmem.h" #include "c_string.h" #include "c_stdlib.h" @@ -13,18 +14,19 @@ #include "espconn.h" #include "lwip/dns.h" -#ifdef CLIENT_SSL_ENABLE -unsigned char *default_certificate; -unsigned int default_certificate_len = 0; -unsigned char *default_private_key; -unsigned int default_private_key_len = 0; -#endif - #define TCP ESPCONN_TCP #define UDP ESPCONN_UDP static ip_addr_t host_ip; // for dns +#ifdef HAVE_SSL_SERVER_CRT +#include HAVE_SSL_SERVER_CRT +#else +__attribute__((section(".servercert.flash"))) unsigned char net_server_cert_area[INTERNAL_FLASH_SECTOR_SIZE]; +#endif + +__attribute__((section(".clientcert.flash"))) unsigned char net_client_cert_area[INTERNAL_FLASH_SECTOR_SIZE]; + #if 0 static int expose_array(lua_State* L, char *array, unsigned short len); #endif @@ -1432,6 +1434,201 @@ static int net_multicastLeave( lua_State* L ) return net_multicastJoinLeave(L,0); } +// Returns NULL on success, error message otherwise +static const char *append_pem_blob(const char *pem, const char *type, uint8_t **buffer_p, uint8_t *buffer_limit, const char *name) { + char unb64[256]; + memset(unb64, 0xff, sizeof(unb64)); + int i; + for (i = 0; i < 64; i++) { + unb64["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; + } + + if (!pem) { + return "No PEM blob"; + } + + // Scan for -----BEGIN CERT + pem = strstr(pem, "-----BEGIN "); + if (!pem) { + return "No PEM header"; + } + + if (strncmp(pem + 11, type, strlen(type))) { + return "Wrong PEM type"; + } + + pem = strchr(pem, '\n'); + if (!pem) { + return "Incorrect PEM format"; + } + // + // Base64 encoded data starts here + // Get all the base64 data into a single buffer.... + // We will use the back end of the buffer.... + // + + uint8_t *buffer = *buffer_p; + + uint8_t *dest = buffer + 32 + 2; // Leave space for name and length + int bitcount = 0; + int accumulator = 0; + for (; *pem && dest < buffer_limit; pem++) { + int val = unb64[*(uint8_t*) pem]; + if (val & 0xC0) { + // not a base64 character + if (isspace(*(uint8_t*) pem)) { + continue; + } + if (*pem == '=') { + // just ignore -- at the end + bitcount = 0; + continue; + } + if (*pem == '-') { + break; + } + return "Invalid character in PEM"; + } else { + bitcount += 6; + accumulator = (accumulator << 6) + val; + if (bitcount >= 8) { + bitcount -= 8; + *dest++ = accumulator >> bitcount; + } + } + } + if (dest >= buffer_limit || strncmp(pem, "-----END ", 9) || strncmp(pem + 9, type, strlen(type)) || bitcount) { + return "Invalid PEM format data"; + } + size_t len = dest - (buffer + 32 + 2); + + memset(buffer, 0, 32); + strcpy(buffer, name); + buffer[32] = len & 0xff; + buffer[33] = (len >> 8) & 0xff; + *buffer_p = dest; + return NULL; +} + +static const char *fill_page_with_pem(lua_State *L, const unsigned char *flash_memory, int flash_offset, const char **types, const char **names) +{ + uint8_t *buffer = luaM_malloc(L, INTERNAL_FLASH_SECTOR_SIZE); + uint8_t *buffer_base = buffer; + uint8_t *buffer_limit = buffer + INTERNAL_FLASH_SECTOR_SIZE; + + int argno; + + for (argno = 1; argno <= lua_gettop(L) && types[argno - 1]; argno++) { + const char *pem = lua_tostring(L, argno); + + const char *error = append_pem_blob(pem, types[argno - 1], &buffer, buffer_limit, names[argno - 1]); + if (error) { + luaM_free(L, buffer_base); + return error; + } + } + + memset(buffer, 0xff, buffer_limit - buffer); + + // Lets see if it matches what is already there.... + if (c_memcmp(buffer_base, flash_memory, INTERNAL_FLASH_SECTOR_SIZE) != 0) { + // Starts being dangerous + if (platform_flash_erase_sector(flash_offset / INTERNAL_FLASH_SECTOR_SIZE) != PLATFORM_OK) { + luaM_free(L, buffer_base); + return "Failed to erase sector"; + } + if (platform_s_flash_write(buffer_base, flash_offset, INTERNAL_FLASH_SECTOR_SIZE) != INTERNAL_FLASH_SECTOR_SIZE) { + luaM_free(L, buffer_base); + return "Failed to write sector"; + } + // ends being dangerous + } + + luaM_free(L, buffer_base); + + return NULL; +} + +// Lua: net.cert.auth(true / false | PEM data [, PEM data] ) +static int net_cert_auth(lua_State *L) +{ + int enable; + + uint32_t flash_offset = platform_flash_mapped2phys((uint32_t) &net_client_cert_area[0]); + if ((flash_offset & 0xfff) || flash_offset > 0xff000 || INTERNAL_FLASH_SECTOR_SIZE != 0x1000) { + // THis should never happen + return luaL_error( L, "bad offset" ); + } + + if (lua_type(L, 1) == LUA_TSTRING) { + const char *types[3] = { "CERTIFICATE", "RSA PRIVATE KEY", NULL }; + const char *names[2] = { "certificate", "private_key" }; + const char *error = fill_page_with_pem(L, &net_client_cert_area[0], flash_offset, types, names); + if (error) { + return luaL_error(L, error); + } + + enable = 1; + } else { + enable = lua_toboolean(L, 1); + } + + bool rc; + + if (enable) { + // See if there is a cert there + if (net_client_cert_area[0] == 0x00 || net_client_cert_area[0] == 0xff) { + return luaL_error( L, "no certificates found" ); + } + rc = espconn_secure_cert_req_enable(1, flash_offset / INTERNAL_FLASH_SECTOR_SIZE); + } else { + rc = espconn_secure_cert_req_disable(1); + } + + lua_pushboolean(L, rc); + return 1; +} + +// Lua: net.cert.verify(true / false | PEM data [, PEM data] ) +static int net_cert_verify(lua_State *L) +{ + int enable; + + uint32_t flash_offset = platform_flash_mapped2phys((uint32_t) &net_server_cert_area[0]); + if ((flash_offset & 0xfff) || flash_offset > 0xff000 || INTERNAL_FLASH_SECTOR_SIZE != 0x1000) { + // THis should never happen + return luaL_error( L, "bad offset" ); + } + + if (lua_type(L, 1) == LUA_TSTRING) { + const char *types[2] = { "CERTIFICATE", NULL }; + const char *names[1] = { "certificate" }; + + const char *error = fill_page_with_pem(L, &net_server_cert_area[0], flash_offset, types, names); + if (error) { + return luaL_error(L, error); + } + + enable = 1; + } else { + enable = lua_toboolean(L, 1); + } + + bool rc; + + if (enable) { + // See if there is a cert there + if (net_server_cert_area[0] == 0x00 || net_server_cert_area[0] == 0xff) { + return luaL_error( L, "no certificates found" ); + } + rc = espconn_secure_ca_enable(1, flash_offset / INTERNAL_FLASH_SECTOR_SIZE); + } else { + rc = espconn_secure_ca_disable(1); + } + + lua_pushboolean(L, rc); + return 1; +} // Lua: s = net.dns.setdnsserver(ip_addr, [index]) static int net_setdnsserver( lua_State* L ) @@ -1539,6 +1736,14 @@ static const LUA_REG_TYPE net_array_map[] = { }; #endif +static const LUA_REG_TYPE net_cert_map[] = { + { LSTRKEY( "verify" ), LFUNCVAL( net_cert_verify ) }, +#ifdef CLIENT_SSL_CERT_AUTH_ENABLE + { LSTRKEY( "auth" ), LFUNCVAL( net_cert_auth ) }, +#endif + { LNILKEY, LNILVAL } +}; + static const LUA_REG_TYPE net_dns_map[] = { { LSTRKEY( "setdnsserver" ), LFUNCVAL( net_setdnsserver ) }, { LSTRKEY( "getdnsserver" ), LFUNCVAL( net_getdnsserver ) }, @@ -1552,6 +1757,9 @@ static const LUA_REG_TYPE net_map[] = { { LSTRKEY( "multicastJoin"), LFUNCVAL( net_multicastJoin ) }, { LSTRKEY( "multicastLeave"), LFUNCVAL( net_multicastLeave ) }, { LSTRKEY( "dns" ), LROVAL( net_dns_map ) }, +#ifdef CLIENT_SSL_ENABLE + { LSTRKEY( "cert" ), LROVAL(net_cert_map) }, +#endif { LSTRKEY( "TCP" ), LNUMVAL( TCP ) }, { LSTRKEY( "UDP" ), LNUMVAL( UDP ) }, { LSTRKEY( "__metatable" ), LROVAL( net_map ) }, diff --git a/docs/en/modules/net.md b/docs/en/modules/net.md index 61d40459..126e6a4a 100644 --- a/docs/en/modules/net.md +++ b/docs/en/modules/net.md @@ -286,3 +286,100 @@ Sets the IP of the DNS server used to resolve hostnames. Default: resolver1.open #### See also [`net.dns:getdnsserver()`](#netdnsgetdnsserver) +# net.cert Module + +This controls certificate verification when SSL is in use. + +## net.cert.verify() + +Controls the vertificate verification process when the Nodemcu makes a secure connection. + +#### Syntax +`net.cert.verify(enable)` + +`net.cert.verify(pemdata)` + +#### 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. + +#### Returns +`true` if it worked. + +Can throw a number of errors if invalid data is supplied. + +#### Example +Make a secure https connection and verify that the certificate chain is valid. +``` +net.cert.verify(true) +http.get("https://example.com/info", nil, function (code, resp) print(code, resp) end) +``` + +Load a certificate into the flash chip and make a request. This is the [startssl](https://startssl.com) root certificate. They provide free +certificates. + +``` +net.cert.verify([[ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j +ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js +LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM +BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy +dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh +cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh +YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg +dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp +bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ +YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT +TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ +9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 +jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW +FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz +ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 +ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L +EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu +L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC +O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V +um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh +NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= +-----END CERTIFICATE----- +]]) + +http.get("https://pskreporter.info/gen404", nil, function (code, resp) print(code, resp) end) +``` + + +#### Notes +The certificate needed for verification is stored in the flash chip. The `net.cert.verify` call with `true` +enables verification against the value stored in the flash. + +The certificate can be loaded into the flash chip in two ways -- one at firmware build time, and the other at initial boot +of the firmware. In order to load the certificate at build time, just place a file containing the CA certificate (in PEM format) +at `server-ca.crt` in the root of the nodemcu-firmware build tree. The build scripts will incorporate this into the resulting +firmware image. + +The alternative approach is easier for development, and that is to supply the PEM data as a string value to `net.cert.verify`. This +will store the certificate into the flash chip and turn on verification for that certificate. Subsequent boots of the nodemcu can then +use `net.cert.verify(true)` and use the stored certificate. + diff --git a/ld/nodemcu.ld b/ld/nodemcu.ld index fa2c5938..0a2298bb 100644 --- a/ld/nodemcu.ld +++ b/ld/nodemcu.ld @@ -71,9 +71,11 @@ SECTIONS _dport0_data_end = ABSOLUTE(.); } >dport0_0_seg :dport0_0_phdr - .irom0.text : ALIGN(4) + .irom0.text : ALIGN(0x1000) { _irom0_text_start = ABSOLUTE(.); + *(.servercert.flash) + *(.clientcert.flash) *(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text) *(.literal.* .text.*) *(.rodata*) @@ -95,7 +97,7 @@ SECTIONS _irom0_text_end = ABSOLUTE(.); _flash_used_end = ABSOLUTE(.); - } >irom0_0_seg :irom0_0_phdr + } >irom0_0_seg :irom0_0_phdr =0xffffffff .data : ALIGN(4) { diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 00000000..c2021fdb --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,3 @@ +TLS.* +private_key.h +cert.h diff --git a/tools/make_cert.py b/tools/make_cert.py new file mode 100644 index 00000000..b417dcee --- /dev/null +++ b/tools/make_cert.py @@ -0,0 +1,42 @@ +import os + + +class Cert(object): + def __init__(self, name, buff): + self.name = name + self.len = len(buff) + self.buff = buff + pass + + def __str__(self): + out_str = ['\0']*32 + for i in range(len(self.name)): + out_str[i] = self.name[i] + out_str = "".join(out_str) + out_str += str(chr(self.len & 0xFF)) + out_str += str(chr((self.len & 0xFF00) >> 8)) + out_str += self.buff + return out_str + pass + + +def main(): + cert_list = [] + file_list = os.listdir(os.getcwd()) + cert_file_list = [] + for _file in file_list: + pos = _file.find(".cer") + if pos != -1: + cert_file_list.append(_file[:pos]) + + for cert_file in cert_file_list: + with open(cert_file+".cer", 'rb') as f: + buff = f.read() + cert_list.append(Cert(cert_file, buff)) + with open('esp_ca_cert.bin', 'wb+') as f: + for _cert in cert_list: + f.write("%s" % _cert) + pass +if __name__ == '__main__': + main() + diff --git a/tools/make_server_cert.py b/tools/make_server_cert.py new file mode 100644 index 00000000..89cec10e --- /dev/null +++ b/tools/make_server_cert.py @@ -0,0 +1,69 @@ +import os +import argparse +import base64 +import re +import sys + + +class Cert(object): + def __init__(self, name, buff): + self.name = name + self.len = len(buff) + self.buff = buff + pass + + def __str__(self): + out_str = ['\0']*32 + for i in range(len(self.name)): + out_str[i] = self.name[i] + out_str = "".join(out_str) + out_str += str(chr(self.len & 0xFF)) + out_str += str(chr((self.len & 0xFF00) >> 8)) + out_str += self.buff + return out_str + + +def main(): + parser = argparse.ArgumentParser(description='Convert PEM file(s) into C source file.') + + parser.add_argument('--section', + default='.servercert.flash', + help='specify the section for the data (default is .servercert.flash)') + + parser.add_argument('--name', + default='net_server_cert_area', + help='specify the variable name for the data (default is net_server_cert_area)') + + parser.add_argument('file', nargs='+', + help='One or more PEM files') + + args = parser.parse_args() + + cert_list = [] + cert_file_list = [] + + for cert_file in args.file: + with open(cert_file, 'r') as f: + buff = f.read() + m = re.search(r"-----BEGIN ([A-Z ]+)-----([^-]+?)-----END \1-----", buff, flags=re.DOTALL) + if not m: + sys.exit("Input file was not in PEM format") + + if "----BEGIN" in buff[m.end(0):]: + sys.exit("Input file contains more than one PEM object") + + cert_list.append(Cert(m.group(1), base64.b64decode(''.join(m.group(2).split())))) + + print '__attribute__((section("%s"))) unsigned char %s[INTERNAL_FLASH_SECTOR_SIZE] = {' % (args.section, args.name) + for _cert in cert_list: + col = 0 + for ch in str(_cert): + print ("0x%02x," % ord(ch)), + if col & 15 == 15: + print + col = col + 1 + print '\n0xff};\n' + +if __name__ == '__main__': + main() + diff --git a/tools/makefile.sh b/tools/makefile.sh old mode 100755 new mode 100644 index 5b35868a..e883a9dd --- a/tools/makefile.sh +++ b/tools/makefile.sh @@ -1,11 +1,92 @@ +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the axTLS project nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# +# Generate the certificates and keys for testing. +# + +PROJECT_NAME="Nodemcu Project" + +# Generate the openssl configuration files. +cat > ca_cert.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = $PROJECT_NAME Dodgy Certificate Authority +EOF + +cat > certs.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = $PROJECT_NAME + CN = Nodemcu Client cert +EOF + +cat > device_cert.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = $PROJECT_NAME Device Certificate +EOF + +# private key generation +openssl genrsa -out TLS.ca_key.pem 2048 +openssl genrsa -out TLS.key_2048.pem 2048 + +# convert private keys into DER format +openssl rsa -in TLS.key_2048.pem -out TLS.key_2048 -outform DER + +# cert requests +openssl req -out TLS.ca_x509.req -sha256 -key TLS.ca_key.pem -new \ + -config ./ca_cert.conf +openssl req -out TLS.x509_2048.req -sha256 -key TLS.key_2048.pem -new \ + -config ./certs.conf + +# generate the actual certs. +openssl x509 -req -in TLS.ca_x509.req -sha256 -out TLS.ca_x509.pem \ + -sha1 -days 5000 -signkey TLS.ca_key.pem +openssl x509 -req -in TLS.x509_2048.req -sha256 -out TLS.x509_2048.pem \ + -sha1 -CAcreateserial -days 5000 \ + -CA TLS.ca_x509.pem -CAkey TLS.ca_key.pem + +# some cleanup +rm TLS*.req +rm *.conf + +openssl x509 -in TLS.ca_x509.pem -outform DER -out TLS.ca_x509.cer +openssl x509 -in TLS.x509_2048.pem -outform DER -out TLS.x509_2048.cer # # Generate the certificates and keys for encrypt. # # set default cert for use in the client -xxd -i client.cer | sed -e \ - "s/client_cer/default_certificate/" > cert.h +xxd -i TLS.x509_2048.cer | sed -e \ + "s/TLS_x509_2048_cer/default_certificate/" > cert.h # set default key for use in the server -xxd -i server.key_1024 | sed -e \ - "s/server_key_1024/default_private_key/" > private_key.h +xxd -i TLS.key_2048 | sed -e \ + "s/TLS_key_2048/default_private_key/" > private_key.h