Adds support for verifying the certificate offered by an SSL server.
Squashed commit of the following:
commit 4439b8c45192f6dee1222df78bbb59f74509e0ee
Author: philip <philip@gladstonefamily.net>
Date: Sun Mar 6 20:23:21 2016 -0500
Fix the ignore
commit a07ee5acdf91286607c3e2dce128c9b8bfd7bd80
Author: philip <philip@gladstonefamily.net>
Date: Sun Mar 6 20:20:41 2016 -0500
Remove uneeded stuff
commit b3604ace92fc13b30161d385c354b0f1c5fe4046
Author: philip <philip@gladstonefamily.net>
Date: Sun Mar 6 20:15:26 2016 -0500
Remove client cert auth
commit 6e48c633569630736a986cd07a59a12de954391e
Author: philip <philip@gladstonefamily.net>
Date: Sun Mar 6 20:11:42 2016 -0500
More cleanup
commit d40eade405ef071d0d1b60d038566b5b8f2cafa3
Author: philip <philip@gladstonefamily.net>
Date: Sat Mar 5 10:56:56 2016 -0500
Move to almost working version
commit 1860a2d90afa94461c53bd41251d4870d6527f9d
Author: philip <philip@gladstonefamily.net>
Date: Fri Mar 4 08:04:09 2016 -0500
Changed the naem to server-ca.crt
commit e7a315660843273fe62943b7fe8ee6c0541dada2
Author: philip <philip@gladstonefamily.net>
Date: Thu Mar 3 21:16:26 2016 -0500
Update gitignores
commit 2b037d185c396209b64381399c40821c15e1840e
Author: philip <philip@gladstonefamily.net>
Date: Thu Mar 3 08:56:17 2016 -0500
Getting better
commit 763255cffba8e279158cd7f43391a3573efdeca8
Author: philip <philip@gladstonefamily.net>
Date: Wed Mar 2 22:28:21 2016 -0500
Works a bit better
commit a38325d1a47dbad255cb3e681da8415e8cf699ea
Author: philip <philip@gladstonefamily.net>
Date: Wed Mar 2 09:11:04 2016 -0500
First building version
commit 4aef13da33470ed954f2eaf5f7ac0ac3dcdf3774
Merge: 180e147 ebb0c33
Author: philip <philip@gladstonefamily.net>
Date: Tue Mar 1 22:03:06 2016 -0500
Merge remote-tracking branch 'upstream/dev' into ssl-client
commit 180e147c1abdcf4046ad9be9b3c1a48f4a875312
Author: philip <philip@gladstonefamily.net>
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
This commit is contained in:
parent
f5acf99620
commit
501bd1fb9a
|
@ -1,3 +1,4 @@
|
||||||
sdk/
|
sdk/
|
||||||
cache/
|
cache/
|
||||||
user_config.h
|
user_config.h
|
||||||
|
server-ca.crt
|
||||||
|
|
15
Makefile
15
Makefile
|
@ -132,7 +132,7 @@ CCFLAGS += \
|
||||||
# -Wall
|
# -Wall
|
||||||
|
|
||||||
ifneq ($(wildcard $(TOP_DIR)/user_config.h),)
|
ifneq ($(wildcard $(TOP_DIR)/user_config.h),)
|
||||||
INCLUDES := $(INCLUDES) -include "$(TOP_DIR)/user_config.h"
|
INCLUDES += -include "$(TOP_DIR)/user_config.h"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CFLAGS = $(CCFLAGS) $(DEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES)
|
CFLAGS = $(CCFLAGS) $(DEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES)
|
||||||
|
@ -175,7 +175,7 @@ $(BINODIR)/%.bin: $(IMAGEODIR)/%.out
|
||||||
# Should be done in top-level makefile only
|
# 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
|
.PHONY: sdk_extracted
|
||||||
|
|
||||||
|
@ -222,6 +222,17 @@ endif
|
||||||
endif
|
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
|
$(OBJODIR)/%.o: %.c
|
||||||
@mkdir -p $(OBJODIR);
|
@mkdir -p $(OBJODIR);
|
||||||
$(CC) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) -o $@ -c $<
|
$(CC) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) -o $@ -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 ) );
|
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->type = ESPCONN_TCP;
|
||||||
conn->state = ESPCONN_NONE;
|
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->local_port = espconn_port();
|
||||||
conn->proto.tcp->remote_port = req->port;
|
conn->proto.tcp->remote_port = req->port;
|
||||||
conn->reverse = req;
|
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" );
|
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->hostname = esp_strdup( hostname );
|
||||||
req->port = port;
|
req->port = port;
|
||||||
req->secure = secure;
|
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. */
|
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. */
|
if ( colon == NULL ) /* The port is not present. */
|
||||||
{
|
{
|
||||||
os_memcpy( hostname, url, path - url );
|
os_memcpy( hostname, url, path - url );
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
server-ca.crt.h
|
|
@ -3,6 +3,7 @@
|
||||||
#include "module.h"
|
#include "module.h"
|
||||||
#include "lauxlib.h"
|
#include "lauxlib.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
|
||||||
#include "c_string.h"
|
#include "c_string.h"
|
||||||
#include "c_stdlib.h"
|
#include "c_stdlib.h"
|
||||||
|
@ -13,18 +14,19 @@
|
||||||
#include "espconn.h"
|
#include "espconn.h"
|
||||||
#include "lwip/dns.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 TCP ESPCONN_TCP
|
||||||
#define UDP ESPCONN_UDP
|
#define UDP ESPCONN_UDP
|
||||||
|
|
||||||
static ip_addr_t host_ip; // for dns
|
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
|
#if 0
|
||||||
static int expose_array(lua_State* L, char *array, unsigned short len);
|
static int expose_array(lua_State* L, char *array, unsigned short len);
|
||||||
#endif
|
#endif
|
||||||
|
@ -1432,6 +1434,201 @@ static int net_multicastLeave( lua_State* L )
|
||||||
return net_multicastJoinLeave(L,0);
|
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])
|
// Lua: s = net.dns.setdnsserver(ip_addr, [index])
|
||||||
static int net_setdnsserver( lua_State* L )
|
static int net_setdnsserver( lua_State* L )
|
||||||
|
@ -1539,6 +1736,14 @@ static const LUA_REG_TYPE net_array_map[] = {
|
||||||
};
|
};
|
||||||
#endif
|
#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[] = {
|
static const LUA_REG_TYPE net_dns_map[] = {
|
||||||
{ LSTRKEY( "setdnsserver" ), LFUNCVAL( net_setdnsserver ) },
|
{ LSTRKEY( "setdnsserver" ), LFUNCVAL( net_setdnsserver ) },
|
||||||
{ LSTRKEY( "getdnsserver" ), LFUNCVAL( net_getdnsserver ) },
|
{ LSTRKEY( "getdnsserver" ), LFUNCVAL( net_getdnsserver ) },
|
||||||
|
@ -1552,6 +1757,9 @@ static const LUA_REG_TYPE net_map[] = {
|
||||||
{ LSTRKEY( "multicastJoin"), LFUNCVAL( net_multicastJoin ) },
|
{ LSTRKEY( "multicastJoin"), LFUNCVAL( net_multicastJoin ) },
|
||||||
{ LSTRKEY( "multicastLeave"), LFUNCVAL( net_multicastLeave ) },
|
{ LSTRKEY( "multicastLeave"), LFUNCVAL( net_multicastLeave ) },
|
||||||
{ LSTRKEY( "dns" ), LROVAL( net_dns_map ) },
|
{ LSTRKEY( "dns" ), LROVAL( net_dns_map ) },
|
||||||
|
#ifdef CLIENT_SSL_ENABLE
|
||||||
|
{ LSTRKEY( "cert" ), LROVAL(net_cert_map) },
|
||||||
|
#endif
|
||||||
{ LSTRKEY( "TCP" ), LNUMVAL( TCP ) },
|
{ LSTRKEY( "TCP" ), LNUMVAL( TCP ) },
|
||||||
{ LSTRKEY( "UDP" ), LNUMVAL( UDP ) },
|
{ LSTRKEY( "UDP" ), LNUMVAL( UDP ) },
|
||||||
{ LSTRKEY( "__metatable" ), LROVAL( net_map ) },
|
{ LSTRKEY( "__metatable" ), LROVAL( net_map ) },
|
||||||
|
|
|
@ -286,3 +286,100 @@ Sets the IP of the DNS server used to resolve hostnames. Default: resolver1.open
|
||||||
#### See also
|
#### See also
|
||||||
[`net.dns:getdnsserver()`](#netdnsgetdnsserver)
|
[`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.
|
||||||
|
|
||||||
|
|
|
@ -71,9 +71,11 @@ SECTIONS
|
||||||
_dport0_data_end = ABSOLUTE(.);
|
_dport0_data_end = ABSOLUTE(.);
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
} >dport0_0_seg :dport0_0_phdr
|
||||||
|
|
||||||
.irom0.text : ALIGN(4)
|
.irom0.text : ALIGN(0x1000)
|
||||||
{
|
{
|
||||||
_irom0_text_start = ABSOLUTE(.);
|
_irom0_text_start = ABSOLUTE(.);
|
||||||
|
*(.servercert.flash)
|
||||||
|
*(.clientcert.flash)
|
||||||
*(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text)
|
*(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text)
|
||||||
*(.literal.* .text.*)
|
*(.literal.* .text.*)
|
||||||
*(.rodata*)
|
*(.rodata*)
|
||||||
|
@ -95,7 +97,7 @@ SECTIONS
|
||||||
|
|
||||||
_irom0_text_end = ABSOLUTE(.);
|
_irom0_text_end = ABSOLUTE(.);
|
||||||
_flash_used_end = ABSOLUTE(.);
|
_flash_used_end = ABSOLUTE(.);
|
||||||
} >irom0_0_seg :irom0_0_phdr
|
} >irom0_0_seg :irom0_0_phdr =0xffffffff
|
||||||
|
|
||||||
.data : ALIGN(4)
|
.data : ALIGN(4)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
TLS.*
|
||||||
|
private_key.h
|
||||||
|
cert.h
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
# Generate the certificates and keys for encrypt.
|
||||||
#
|
#
|
||||||
|
|
||||||
# set default cert for use in the client
|
# set default cert for use in the client
|
||||||
xxd -i client.cer | sed -e \
|
xxd -i TLS.x509_2048.cer | sed -e \
|
||||||
"s/client_cer/default_certificate/" > cert.h
|
"s/TLS_x509_2048_cer/default_certificate/" > cert.h
|
||||||
# set default key for use in the server
|
# set default key for use in the server
|
||||||
xxd -i server.key_1024 | sed -e \
|
xxd -i TLS.key_2048 | sed -e \
|
||||||
"s/server_key_1024/default_private_key/" > private_key.h
|
"s/TLS_key_2048/default_private_key/" > private_key.h
|
||||||
|
|
Loading…
Reference in New Issue