Complete the HTTP GET example

This commit is contained in:
Stefan Schake 2016-11-13 03:45:29 +01:00 committed by Milosch Meriac
parent 3e1a0c2091
commit ca6dfd5099

View file

@ -1,13 +1,13 @@
/* http_get_mbedtls - HTTPS version of the http_get example, using mbed TLS. /* http_get_bearssl - HTTPS version of the http_get example, using BearSSL.
* *
* Retrieves a JSON response from the howsmyssl.com API via HTTPS over TLS v1.2. * Retrieves a JSON response from the howsmyssl.com API via HTTPS over TLS v1.2.
* *
* Validates the server's certificate using the root CA loaded (in PEM format) in cert.c. * Validates the server's certificate using a hardcoded public key.
* *
* Adapted from the ssl_client1 example in mbedtls. * Adapted from the client_basic sample in BearSSL.
* *
* Original Copyright (C) 2006-2015, ARM Limited, All Rights Reserved, Apache 2.0 License. * Original Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>, MIT License.
* Additions Copyright (C) 2015 Angus Gratton, Apache 2.0 License. * Additions Copyright (C) 2016 Stefan Schake, MIT License.
*/ */
#include "espressif/esp_common.h" #include "espressif/esp_common.h"
#include "esp/uart.h" #include "esp/uart.h"
@ -35,83 +35,47 @@
#define GET_REQUEST "GET "WEB_URL" HTTP/1.1\nHost: "WEB_SERVER"\n\n" #define GET_REQUEST "GET "WEB_URL" HTTP/1.1\nHost: "WEB_SERVER"\n\n"
static const unsigned char TA0_DN[] = { /*
0x30, 0x1C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, * howsmyssl.com public key
0x02, 0x43, 0x41, 0x31, 0x0D, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x04, 0x03, * BearSSL doesn't allow disabling validation and we don't have
0x13, 0x04, 0x52, 0x6F, 0x6F, 0x74 * a correct date/time on the ESP so it would always fail.
* Instead, we compare the server key against this static public key
* to validate the server.
*/
static const unsigned char SERVER_PKEY_RSA_N[] = {
0xA9, 0x11, 0x5C, 0xAF, 0x4F, 0xDD, 0xEB, 0x88, 0xCD, 0xF6, 0x47, 0x11,
0xB9, 0x7F, 0xFA, 0x08, 0xD7, 0x90, 0x01, 0x14, 0x36, 0x84, 0xBF, 0xE2,
0xD8, 0xC4, 0x26, 0xBF, 0xD3, 0x6B, 0xBA, 0x2E, 0x06, 0x9C, 0x23, 0x95,
0x98, 0x8E, 0xC0, 0xC5, 0x91, 0xF0, 0xC6, 0xC3, 0x39, 0x98, 0x04, 0xC6,
0x78, 0x14, 0x13, 0x08, 0xA7, 0x12, 0x3D, 0x86, 0x1A, 0x14, 0x71, 0x35,
0x7E, 0xFB, 0x8B, 0xF8, 0xA1, 0x52, 0x21, 0x5B, 0xDE, 0x51, 0x5A, 0x3A,
0x35, 0x6F, 0x8F, 0xDB, 0xF9, 0x6A, 0xA5, 0xF2, 0x74, 0x6E, 0xCA, 0xFC,
0xA4, 0x70, 0xCC, 0x70, 0x4D, 0x5E, 0x4E, 0x69, 0xA2, 0xFE, 0x42, 0x23,
0x8A, 0xC0, 0x01, 0x3B, 0xEF, 0x46, 0xB4, 0xC2, 0x6F, 0x48, 0x26, 0xDD,
0xF5, 0x05, 0x9B, 0x69, 0x88, 0xB0, 0x7D, 0x38, 0xA8, 0x49, 0x07, 0x11,
0xE5, 0xFE, 0x27, 0x0F, 0x4F, 0x28, 0x83, 0x18, 0xAB, 0x47, 0x1F, 0x72,
0x40, 0xEC, 0x67, 0x16, 0xBE, 0x45, 0x76, 0x0B, 0x83, 0x91, 0xE5, 0x42,
0xC2, 0xA2, 0x5B, 0xF2, 0xEB, 0xB1, 0x8C, 0x37, 0x0F, 0x4B, 0x5E, 0x0F,
0x05, 0x7D, 0x54, 0x48, 0x9F, 0x55, 0xAE, 0x41, 0xC9, 0x07, 0x6F, 0xD7,
0xB3, 0xF3, 0xE2, 0x61, 0x18, 0x03, 0x48, 0xA1, 0x3C, 0x8D, 0xBD, 0x5C,
0xCD, 0x3B, 0x55, 0xCD, 0x97, 0xE3, 0xD1, 0x80, 0x8A, 0x30, 0x88, 0x3A,
0xAC, 0xEE, 0x64, 0x08, 0xBD, 0x74, 0x82, 0x24, 0x82, 0x11, 0xD4, 0x93,
0xFA, 0xEF, 0x52, 0x5F, 0x2C, 0x51, 0x0C, 0x88, 0x09, 0xB9, 0x77, 0xA0,
0xFA, 0x6F, 0xAB, 0xEB, 0xE1, 0x08, 0x5C, 0xA5, 0x02, 0x53, 0xA1, 0x18,
0xFC, 0xB8, 0x06, 0x67, 0x7E, 0x80, 0xDF, 0xF1, 0xCD, 0xAE, 0x23, 0x58,
0xCD, 0xCE, 0x7C, 0x69, 0x2E, 0x4D, 0xD6, 0xB0, 0xD2, 0x6D, 0x11, 0x85,
0x5E, 0x36, 0x41, 0x27
}; };
static const unsigned char TA0_RSA_N[] = { static const unsigned char SERVER_PKEY_RSA_E[] = {
0xB6, 0xD9, 0x34, 0xD4, 0x50, 0xFD, 0xB3, 0xAF, 0x7A, 0x73, 0xF1, 0xCE, 0x01, 0x00, 0x01
0x38, 0xBF, 0x5D, 0x6F, 0x45, 0xE1, 0xFD, 0x4E, 0xB1, 0x98, 0xC6, 0x60,
0x83, 0x26, 0xD2, 0x17, 0xD1, 0xC5, 0xB7, 0x9A, 0xA3, 0xC1, 0xDE, 0x63,
0x39, 0x97, 0x9C, 0xF0, 0x5E, 0x5C, 0xC8, 0x1C, 0x17, 0xB9, 0x88, 0x19,
0x6D, 0xF0, 0xB6, 0x2E, 0x30, 0x50, 0xA1, 0x54, 0x6E, 0x93, 0xC0, 0xDB,
0xCF, 0x30, 0xCB, 0x9F, 0x1E, 0x27, 0x79, 0xF1, 0xC3, 0x99, 0x52, 0x35,
0xAA, 0x3D, 0xB6, 0xDF, 0xB0, 0xAD, 0x7C, 0xCB, 0x49, 0xCD, 0xC0, 0xED,
0xE7, 0x66, 0x10, 0x2A, 0xE9, 0xCE, 0x28, 0x1F, 0x21, 0x50, 0xFA, 0x77,
0x4C, 0x2D, 0xDA, 0xEF, 0x3C, 0x58, 0xEB, 0x4E, 0xBF, 0xCE, 0xE9, 0xFB,
0x1A, 0xDA, 0xA3, 0x83, 0xA3, 0xCD, 0xA3, 0xCA, 0x93, 0x80, 0xDC, 0xDA,
0xF3, 0x17, 0xCC, 0x7A, 0xAB, 0x33, 0x80, 0x9C, 0xB2, 0xD4, 0x7F, 0x46,
0x3F, 0xC5, 0x3C, 0xDC, 0x61, 0x94, 0xB7, 0x27, 0x29, 0x6E, 0x2A, 0xBC,
0x5B, 0x09, 0x36, 0xD4, 0xC6, 0x3B, 0x0D, 0xEB, 0xBE, 0xCE, 0xDB, 0x1D,
0x1C, 0xBC, 0x10, 0x6A, 0x71, 0x71, 0xB3, 0xF2, 0xCA, 0x28, 0x9A, 0x77,
0xF2, 0x8A, 0xEC, 0x42, 0xEF, 0xB1, 0x4A, 0x8E, 0xE2, 0xF2, 0x1A, 0x32,
0x2A, 0xCD, 0xC0, 0xA6, 0x46, 0x2C, 0x9A, 0xC2, 0x85, 0x37, 0x91, 0x7F,
0x46, 0xA1, 0x93, 0x81, 0xA1, 0x74, 0x66, 0xDF, 0xBA, 0xB3, 0x39, 0x20,
0x91, 0x93, 0xFA, 0x1D, 0xA1, 0xA8, 0x85, 0xE7, 0xE4, 0xF9, 0x07, 0xF6,
0x10, 0xF6, 0xA8, 0x27, 0x01, 0xB6, 0x7F, 0x12, 0xC3, 0x40, 0xC3, 0xC9,
0xE2, 0xB0, 0xAB, 0x49, 0x18, 0x3A, 0x64, 0xB6, 0x59, 0xB7, 0x95, 0xB5,
0x96, 0x36, 0xDF, 0x22, 0x69, 0xAA, 0x72, 0x6A, 0x54, 0x4E, 0x27, 0x29,
0xA3, 0x0E, 0x97, 0x15
}; };
static const unsigned char TA0_RSA_E[] = { static const br_rsa_public_key server_pkey = {
0x01, 0x00, 0x01 (unsigned char *)SERVER_PKEY_RSA_N, sizeof SERVER_PKEY_RSA_N,
(unsigned char *)SERVER_PKEY_RSA_E, sizeof SERVER_PKEY_RSA_E,
}; };
static const unsigned char TA1_DN[] = {
0x30, 0x1C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
0x02, 0x43, 0x41, 0x31, 0x0D, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x04, 0x03,
0x13, 0x04, 0x52, 0x6F, 0x6F, 0x74
};
static const unsigned char TA1_EC_Q[] = {
0x04, 0x71, 0x74, 0xBA, 0xAB, 0xB9, 0x30, 0x2E, 0x81, 0xD5, 0xE5, 0x57,
0xF9, 0xF3, 0x20, 0x68, 0x0C, 0x9C, 0xF9, 0x64, 0xDB, 0xB4, 0x20, 0x0D,
0x6D, 0xEA, 0x40, 0xD0, 0x4A, 0x6E, 0x42, 0xFD, 0xB6, 0x9A, 0x68, 0x25,
0x44, 0xF6, 0xDF, 0x7B, 0xC4, 0xFC, 0xDE, 0xDD, 0x7B, 0xBB, 0xC5, 0xDB,
0x7C, 0x76, 0x3F, 0x41, 0x66, 0x40, 0x6E, 0xDB, 0xA7, 0x87, 0xC2, 0xE5,
0xD8, 0xC5, 0xF3, 0x7F, 0x8D
};
static const br_x509_trust_anchor TAs[2] = {
{
(unsigned char *)TA0_DN, sizeof TA0_DN,
BR_X509_TA_CA,
{
BR_KEYTYPE_RSA,
{ .rsa = {
(unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N,
(unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E,
} }
}
},
{
(unsigned char *)TA1_DN, sizeof TA1_DN,
BR_X509_TA_CA,
{
BR_KEYTYPE_EC,
{ .ec = {
BR_EC_secp256r1,
(unsigned char *)TA1_EC_Q, sizeof TA1_EC_Q,
} }
}
}
};
#define TAs_NUM 2
/* /*
* Low-level data read callback for the simplified SSL I/O API. * Low-level data read callback for the simplified SSL I/O API.
*/ */
@ -152,20 +116,27 @@ sock_write(void *ctx, const unsigned char *buf, size_t len)
} }
} }
// In mono mode, BearSSL requires ~16k of buffer /*
// TODO: investigate Maximum Fragment Length (RFC 6066) extension * Buffer to store a record + BearSSL state
* We use MONO mode to save 16k of RAM.
* This could be even smaller by using max_fragment_len, but
* the howsmyssl.com server doesn't seem to support it.
*/
static unsigned char bearssl_buffer[BR_SSL_BUFSIZE_MONO]; static unsigned char bearssl_buffer[BR_SSL_BUFSIZE_MONO];
static br_ssl_client_context sc; static br_ssl_client_context sc;
static br_x509_minimal_context xc; static br_x509_minimal_context xc;
static br_x509_knownkey_context kkc;
static br_sslio_context ioc; static br_sslio_context ioc;
void http_get_task(void *pvParameters) void http_get_task(void *pvParameters)
{ {
int successes = 0, failures = 0; int successes = 0, failures = 0;
while (1) { while (1) {
/* Wait until we can resolve the DNS for the server, as an indication /*
our network is probably working... * Wait until we can resolve the DNS for the server, as an indication
*/ * our network is probably working...
*/
const struct addrinfo hints = { const struct addrinfo hints = {
.ai_family = AF_INET, .ai_family = AF_INET,
.ai_socktype = SOCK_STREAM, .ai_socktype = SOCK_STREAM,
@ -188,15 +159,45 @@ void http_get_task(void *pvParameters)
} }
printf("Initializing BearSSL... "); printf("Initializing BearSSL... ");
br_ssl_client_init_full(&sc, &xc, TAs, TAs_NUM); br_ssl_client_init_full(&sc, &xc, NULL, 0);
/*
* Overwrite the default X509 engine with a much simpler known key one
* This sidesteps validation failure in the default engine, as we have no date/time
* Instead, the servers key is compared to the hardcoded public key above
*/
br_x509_knownkey_init_rsa(&kkc, &server_pkey);
br_ssl_engine_set_x509(&sc.eng, &kkc.vtable);
/*
* Set the I/O buffer to the provided array. We allocated a
* buffer large enough for full-duplex behaviour with all
* allowed sizes of SSL records, hence we set the last argument
* to 1 (which means "split the buffer into separate input and
* output areas").
*/
br_ssl_engine_set_buffer(&sc.eng, bearssl_buffer, sizeof bearssl_buffer, 0); br_ssl_engine_set_buffer(&sc.eng, bearssl_buffer, sizeof bearssl_buffer, 0);
// Inject some entropy from the ESP hardware RNG
// This is necessary because we don't support any of the BearSSL methods /*
* Inject some entropy from the ESP hardware RNG
* This is necessary because we don't support any of the BearSSL methods
*/
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
int rand = hwrand(); int rand = hwrand();
br_ssl_engine_inject_entropy(&sc.eng, &rand, 4); br_ssl_engine_inject_entropy(&sc.eng, &rand, 4);
} }
/*
* Reset the client context, for a new handshake. We provide the
* target host name: it will be used for the SNI extension. The
* last parameter is 0: we are not trying to resume a session.
*/
br_ssl_client_reset(&sc, WEB_SERVER, 0); br_ssl_client_reset(&sc, WEB_SERVER, 0);
/*
* Initialise the simplified I/O wrapper context, to use our
* SSL client context, and the two callbacks for socket I/O.
*/
br_sslio_init(&ioc, &sc.eng, sock_read, &fd, sock_write, &fd); br_sslio_init(&ioc, &sc.eng, sock_read, &fd, sock_write, &fd);
printf("done.\r\n"); printf("done.\r\n");
@ -210,16 +211,34 @@ void http_get_task(void *pvParameters)
} }
printf("Connected\r\n"); printf("Connected\r\n");
if (br_sslio_write_all(&ioc, GET_REQUEST, strlen(GET_REQUEST)) != BR_ERR_OK) /*
{ * Note that while the context has, at that point, already
* assembled the ClientHello to send, nothing happened on the
* network yet. Real I/O will occur only with the next call.
*
* We write our simple HTTP request. We test the call
* for an error (-1), but this is not strictly necessary, since
* the error state "sticks": if the context fails for any reason
* (e.g. bad server certificate), then it will remain in failed
* state and all subsequent calls will return -1 as well.
*/
if (br_sslio_write_all(&ioc, GET_REQUEST, strlen(GET_REQUEST)) != BR_ERR_OK) {
close(fd); close(fd);
freeaddrinfo(res); freeaddrinfo(res);
printf("br_sslio_write_all failed: %d\r\n", br_ssl_engine_last_error(&sc.eng)); printf("br_sslio_write_all failed: %d\r\n", br_ssl_engine_last_error(&sc.eng));
failures++; failures++;
continue; continue;
} }
/*
* SSL is a buffered protocol: we make sure that all our request
* bytes are sent onto the wire.
*/
br_sslio_flush(&ioc); br_sslio_flush(&ioc);
/*
* Read and print the server response
*/
for (;;) for (;;)
{ {
int rlen; int rlen;
@ -237,15 +256,29 @@ void http_get_task(void *pvParameters)
} }
} }
/*
* If reading the response failed for any reason, we detect it here
*/
if (br_ssl_engine_last_error(&sc.eng) != BR_ERR_OK) {
close(fd);
freeaddrinfo(res);
printf("failure, error = %d\r\n", br_ssl_engine_last_error(&sc.eng));
failures++;
continue;
}
/*
* Close the connection and start over after a delay
*/
close(fd); close(fd);
freeaddrinfo(res); freeaddrinfo(res);
successes++; successes++;
printf("successes = %d failures = %d\r\n", successes, failures); printf("\r\n\r\nsuccesses = %d failures = %d\r\n", successes, failures);
for(int countdown = 10; countdown >= 0; countdown--) { for(int countdown = 10; countdown >= 0; countdown--) {
printf("%d... ", countdown); printf("%d...\n", countdown);
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
} }
printf("\r\nStarting again!\r\n"); printf("Starting again!\r\n\r\n");
} }
} }