From 14b70e9328e78c94ba0c0042766a11854ac389de Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 4 Dec 2015 19:02:46 +0300 Subject: [PATCH] Allow setting client side TLS key and certificate --- .../examples/HTTPSRequest/HTTPSRequest.ino | 4 +- .../ESP8266WiFi/src/WiFiClientSecure.cpp | 74 ++++++++++++++++++ libraries/ESP8266WiFi/src/WiFiClientSecure.h | 16 ++++ tools/sdk/lib/libaxtls.a | Bin 556130 -> 554486 bytes 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WiFi/examples/HTTPSRequest/HTTPSRequest.ino b/libraries/ESP8266WiFi/examples/HTTPSRequest/HTTPSRequest.ino index 975fe13ef..6d10d5115 100644 --- a/libraries/ESP8266WiFi/examples/HTTPSRequest/HTTPSRequest.ino +++ b/libraries/ESP8266WiFi/examples/HTTPSRequest/HTTPSRequest.ino @@ -4,7 +4,7 @@ * This example demonstrates how to use * WiFiClientSecure class to access HTTPS API. * We fetch and display the status of - * esp8266/Arduino project continous integration + * esp8266/Arduino project continuous integration * build. * * Created by Ivan Grokhotkov, 2015. @@ -54,7 +54,7 @@ void setup() { Serial.println("certificate doesn't match"); } - String url = "/repos/esp8266/Arduino/commits/esp8266/status"; + String url = "/repos/esp8266/Arduino/commits/master/status"; Serial.print("requesting URL: "); Serial.println(url); diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp b/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp index 6d4365221..247701acd 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp @@ -50,6 +50,17 @@ extern "C" #define SSL_DEBUG_OPTS 0 #endif +uint8_t* default_private_key = 0; +uint32_t default_private_key_len = 0; +static bool default_private_key_dynamic = false; +// +uint8_t* default_certificate = 0; +uint32_t default_certificate_len = 0; +static bool default_certificate_dynamic = false; + +static void clear_private_key(); +static void clear_certificate(); + class SSLContext { public: @@ -70,6 +81,9 @@ public: if (_ssl_ctx_refcnt == 0) { ssl_ctx_free(_ssl_ctx); } + + clear_private_key(); + clear_certificate(); } void ref() { @@ -337,6 +351,66 @@ bool WiFiClientSecure::verify(const char* fp, const char* url) { return true; } +void WiFiClientSecure::setCertificate(const uint8_t* cert_data, size_t size) { + clear_certificate(); + default_certificate = (uint8_t*) cert_data; + default_certificate_len = size; +} + +void WiFiClientSecure::setPrivateKey(const uint8_t* pk, size_t size) { + clear_private_key(); + default_private_key = (uint8_t*) pk; + default_private_key_len = size; +} + +bool WiFiClientSecure::loadCertificate(Stream& stream, size_t size) { + clear_certificate(); + default_certificate = new uint8_t[size]; + if (!default_certificate) { + return false; + } + if (stream.readBytes(default_certificate, size) != size) { + delete[] default_certificate; + return false; + } + default_certificate_dynamic = true; + default_certificate_len = size; + return true; +} + +bool WiFiClientSecure::loadPrivateKey(Stream& stream, size_t size) { + clear_private_key(); + default_private_key = new uint8_t[size]; + if (!default_private_key) { + return false; + } + if (stream.readBytes(default_private_key, size) != size) { + delete[] default_private_key; + return false; + } + default_private_key_dynamic = true; + default_private_key_len = size; + return true; +} + +static void clear_private_key() { + if (default_private_key && default_private_key_dynamic) { + delete[] default_private_key; + default_private_key_dynamic = false; + } + default_private_key = 0; + default_private_key_len = 0; +} + +static void clear_certificate() { + if (default_certificate && default_certificate_dynamic) { + delete[] default_certificate; + default_certificate_dynamic = false; + } + default_certificate = 0; + default_certificate_len = 0; +} + extern "C" int ax_port_read(int fd, uint8_t* buffer, size_t count) { ClientContext* _client = reinterpret_cast(fd); if (_client->state() != ESTABLISHED && !_client->getSize()) { diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecure.h b/libraries/ESP8266WiFi/src/WiFiClientSecure.h index 8ce11f939..07b111e1a 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecure.h +++ b/libraries/ESP8266WiFi/src/WiFiClientSecure.h @@ -48,6 +48,22 @@ public: int peek() override; void stop() override; + void setCertificate(const uint8_t* cert_data, size_t size); + void setPrivateKey(const uint8_t* pk, size_t size); + + bool loadCertificate(Stream& stream, size_t size); + bool loadPrivateKey(Stream& stream, size_t size); + + template + bool loadCertificate(TFile& file) { + return loadCertificate(file, file.size()); + } + + template + bool loadPrivateKey(TFile& file) { + return loadPrivateKey(file, file.size()); + } + protected: int _connectSSL(); diff --git a/tools/sdk/lib/libaxtls.a b/tools/sdk/lib/libaxtls.a index b74d97cc434d721102ac4874ea72865d8ff219f8..9e29c802bd67ed9c543db86b405a38974432558f 100644 GIT binary patch delta 19157 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 delta 21805 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