Skip to content

Support for STARTLS/STARTSSL in-band transport upgrades/renegotation #9100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c8e01dc
Split start_ssl_client into two phases; to allow the implementation o…
dirkx Jan 14, 2024
cd244e1
Remove removed setTimeout that was accidentally included (was removed…
dirkx Jan 14, 2024
2718994
Quell compiler warning; use the right timeout
dirkx Jan 14, 2024
ad1a199
Newer versions of MBEDTLS make the client key struct private (and mos…
dirkx Jan 14, 2024
241818c
Fix another \(rightfull\) compiler warning iwth the version pointer
dirkx Jan 14, 2024
c3bbe29
Merge branch 'master' into feat_two_stage_ssltls_protocol_support
me-no-dev Jan 16, 2024
5b71abe
Quell CI/CD runs on non-WiFi supporting hardare
dirkx Jan 17, 2024
0bc34a5
Merge branch 'feat_two_stage_ssltls_protocol_support' of github.com:d…
dirkx Jan 17, 2024
933285e
Quell CI/CD runs on non-WiFi supporting hardare
dirkx Jan 17, 2024
9f88f19
Fix typo in directory name
dirkx Jan 17, 2024
fce9637
Merge branch 'master' into feat_two_stage_ssltls_protocol_support
dirkx Jan 17, 2024
5829954
Merge branch 'master' into feat_two_stage_ssltls_protocol_support
P-R-O-C-H-Y Jan 29, 2024
4e5ba8f
Apply suggestions from code review
lucasssvaz Feb 7, 2024
9268adb
Merge branch 'master' into feat_two_stage_ssltls_protocol_support
lucasssvaz Feb 7, 2024
71210cf
Rename Files
lucasssvaz Feb 7, 2024
f9349ca
Remove leftover file
lucasssvaz Feb 8, 2024
346c3c8
Merge branch 'master' into feat_two_stage_ssltls_protocol_support
lucasssvaz Feb 9, 2024
067d2cd
Merge branch 'master' into feat_two_stage_ssltls_protocol_support
lucasssvaz Feb 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/* STARTSSL example

Inline upgrading from a clear-text connection to an SSL/TLS connection.

Some protocols such as SMTP, XMPP, Mysql, Postgress and others allow, or require,
that you start the connection without encryption; and then send a command to switch
over to encryption.

E.g. a typical SMTP submission would entail a dialogue such as this:

1. client connects to server in the clear
2. server says hello
3. client sents a EHLO
4. server tells the client that it supports SSL/TLS
5. client sends a 'STARTTLS' to make use of this faciltiy
6. client/server negiotiate a SSL or TLS connection.
7. client sends another EHLO
8. server now tells the client what (else) is supported; such as additional authentication options.
... conversation continues encrypted.

This can be enabled in WiFiClientSecure by telling it to start in plaintext:

client.setPlainStart();

and client is than a plain, TCP, connection (just as WiFiClient would be); until the client calls
the method:

client.startTLS(); // returns zero on error; non zero on success.

After which things switch to TLS/SSL.
*/

#include <WiFiClientSecure.h>

#ifndef WIFI_NETWORK
#define WIFI_NETWORK "YOUR Wifi SSID"
#endif

#ifndef WIFI_PASSWD
#define WIFI_PASSWD "your-secret-password"
#endif

#ifndef SMTP_HOST
#define SMTP_HOST "smtp.gmail.com"
#endif

#ifndef SMTP_PORT
#define SMTP_PORT (587) // Standard (plaintext) submission port
#endif

const char* ssid = WIFI_NETWORK; // your network SSID (name of wifi network)
const char* password = WIFI_PASSWD; // your network password
const char* server = SMTP_HOST; // Server URL
const int submission_port = SMTP_PORT; // submission port.

WiFiClientSecure client;

static bool readAllSMTPLines();

void setup() {
int ret;
//Initialize serial and wait for port to open:
Serial.begin(115200);
delay(100);

Serial.print("Attempting to connect to SSID: ");
Serial.print(ssid);
WiFi.begin(ssid, password);

// attempt to connect to Wifi network:
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
// wait 1 second for re-trying
delay(1000);
}

Serial.print("Connected to ");
Serial.println(ssid);

Serial.printf("\nStarting connection to server: %s:%d\n", server, submission_port);


// skip verification for this demo. In production one should at the very least
// enable TOFU; or ideally hardcode a (CA) certificate that is trusted.
client.setInsecure();

// Enable a plain-test start.
client.setPlainStart();

if (!client.connect(server, SMTP_PORT)) {
Serial.println("Connection failed!");
return;
};

Serial.println("Connected to server (in the clear, in plaintest)");

if (!readAllSMTPLines()) goto err;

Serial.println("Sending : EHLO\t\tin the clear");
client.print("EHLO there\r\n");

if (!readAllSMTPLines()) goto err;

Serial.println("Sending : STARTTLS\t\tin the clear");
client.print("STARTTLS\r\n");

if (!readAllSMTPLines()) goto err;

Serial.println("Upgrading connection to TLS");
if ((ret=client.startTLS()) <= 0) {
Serial.printf("Upgrade connection failed: err %d\n", ret);
goto err;
}

Serial.println("Sending : EHLO again\t\tover the now encrypted connection");
client.print("EHLO again\r\n");

if (!readAllSMTPLines()) goto err;

// normally, as this point - we'd be authenticating and then be submitting
// an email. This has been left out of this example.

Serial.println("Sending : QUIT\t\t\tover the now encrypted connection");
client.print("QUIT\r\n");

if (!readAllSMTPLines()) goto err;

Serial.println("Completed OK\n");
err:
Serial.println("Closing connection");
client.stop();
}

// SMTP command repsponse start with three digits and a space;
// or, for continuation, with three digits and a '-'.
static bool readAllSMTPLines() {
String s = "";
int i;

// blocking read; we cannot rely on a timeout
// of a WiFiClientSecure read; as it is non
// blocking.
const unsigned long timeout = 15 * 1000;
unsigned long start = millis(); // the timeout is for the entire CMD block response; not per character/line.
while (1) {
while ((i = client.available()) == 0 && millis() - start < timeout) {
/* .. wait */
};
if (i == 0) {
Serial.println("Timeout reading SMTP response");
return false;
};
if (i < 0)
break;

i = client.read();
if (i < 0)
break;

if (i > 31 && i < 128) s += (char)i;
if (i == 0x0A) {
Serial.print("Receiving: ");
Serial.println(s);
if (s.charAt(3) == ' ')
return true;
s = "";
}
}
Serial.printf("Error reading SMTP command response line: %d\n", i);
return false;
}

void loop() {
// do nothing
}

61 changes: 48 additions & 13 deletions libraries/WiFiClientSecure/src/WiFiClientSecure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,40 @@ int WiFiClientSecure::connect(const char *host, uint16_t port, const char *CA_ce
int WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *host, const char *CA_cert, const char *cert, const char *private_key)
{
int ret = start_ssl_client(sslclient, ip, port, host, _timeout, CA_cert, _use_ca_bundle, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos);

if (ret >=0 && ! _stillinPlainStart)
ret = ssl_starttls_handshake(sslclient);
else
log_i("Actual TLS start posponed.");

_lastError = ret;

if (ret < 0) {
log_e("start_ssl_client: %d", ret);
log_e("start_ssl_client: connect failed: %d", ret);
stop();
return 0;
}
_connected = true;
return 1;
}

int WiFiClientSecure::startTLS()
{
int ret = 1;
if (_stillinPlainStart) {
log_i("startTLS: starting TLS/SSL on this dplain connection");
ret = ssl_starttls_handshake(sslclient);
if (ret < 0) {
log_e("startTLS: %d", ret);
stop();
return 0;
};
_stillinPlainStart = false;
} else
log_i("startTLS: ignoring StartTLS - as we should be secure already");
return 1;
}

int WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey) {
return connect(ip.toString().c_str(), port, pskIdent, psKey);
}
Expand All @@ -167,7 +191,7 @@ int WiFiClientSecure::connect(const char *host, uint16_t port, const char *pskId
int ret = start_ssl_client(sslclient, address, port, host, _timeout, NULL, false, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos);
_lastError = ret;
if (ret < 0) {
log_e("start_ssl_client: %d", ret);
log_e("start_ssl_client: connect failed %d", ret);
stop();
return 0;
}
Expand All @@ -192,17 +216,18 @@ int WiFiClientSecure::read()
{
uint8_t data = -1;
int res = read(&data, 1);
if (res < 0) {
return res;
}
return data;
return res < 0 ? res: data;
}

size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)
{
if (!_connected) {
return 0;
}

if (_stillinPlainStart)
return send_net_data(sslclient, buf, size);

if(_lastWriteTimeout != _timeout){
struct timeval timeout_tv;
timeout_tv.tv_sec = _timeout / 1000;
Expand All @@ -212,9 +237,9 @@ size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)
_lastWriteTimeout = _timeout;
}
}

int res = send_ssl_data(sslclient, buf, size);
if (res < 0) {
log_e("Closing connection on failed write");
stop();
res = 0;
}
Expand All @@ -223,6 +248,9 @@ size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)

int WiFiClientSecure::read(uint8_t *buf, size_t size)
{
if(_stillinPlainStart)
return get_net_receive(sslclient, buf, size);

if(_lastReadTimeout != _timeout){
if(fd() >= 0){
struct timeval timeout_tv;
Expand All @@ -235,7 +263,7 @@ int WiFiClientSecure::read(uint8_t *buf, size_t size)
}
}

int peeked = 0;
int peeked = 0, res = -1;
int avail = available();
if ((!buf && size) || avail <= 0) {
return -1;
Expand All @@ -254,9 +282,10 @@ int WiFiClientSecure::read(uint8_t *buf, size_t size)
buf++;
peeked = 1;
}

int res = get_ssl_receive(sslclient, buf, size);
res = get_ssl_receive(sslclient, buf, size);

if (res < 0) {
log_e("Closing connection on failed read");
stop();
return peeked?peeked:res;
}
Expand All @@ -265,12 +294,17 @@ int WiFiClientSecure::read(uint8_t *buf, size_t size)

int WiFiClientSecure::available()
{
int peeked = (_peek >= 0);
if (_stillinPlainStart)
return peek_net_receive(sslclient,0);

int peeked = (_peek >= 0), res = -1;
if (!_connected) {
return peeked;
}
int res = data_to_read(sslclient);
if (res < 0) {
res = data_to_read(sslclient);

if (res < 0 && !_stillinPlainStart) {
log_e("Closing connection on failed available check");
stop();
return peeked?peeked:res;
}
Expand Down Expand Up @@ -406,3 +440,4 @@ int WiFiClientSecure::fd() const
{
return sslclient->socket;
}

12 changes: 12 additions & 0 deletions libraries/WiFiClientSecure/src/WiFiClientSecure.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class WiFiClientSecure : public WiFiClient
int _peek = -1;
int _timeout;
bool _use_insecure;
bool _stillinPlainStart = false;
const char *_CA_cert;
const char *_cert;
const char *_private_key;
Expand Down Expand Up @@ -78,6 +79,17 @@ class WiFiClientSecure : public WiFiClient
bool verify(const char* fingerprint, const char* domain_name);
void setHandshakeTimeout(unsigned long handshake_timeout);
void setAlpnProtocols(const char **alpn_protos);

// Certain protocols start in plain-text; and then have the client
// give some STARTSSL command to `upgrade' the connection to TLS
// or SSL. Setting PlainStart to true (the default is false) enables
// this. It is up to the application code to then call 'startTLS()'
// at the right point to initialise the SSL or TLS upgrade.

void setPlainStart() { _stillinPlainStart = true; };
bool stillInPlainStart() { return _stillinPlainStart; };
int startTLS();

const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); };
bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); };
int fd() const;
Expand Down
Loading