Skip to content

Commit 48072ee

Browse files
dirkxme-no-devP-R-O-C-H-Ylucasssvaz
authored
Support for STARTLS/STARTSSL in-band transport upgrades/renegotation (#9100)
* Split start_ssl_client into two phases; to allow the implementation of protocols that use some sort of in-band STARTTLS or STARTSSL signal to upgrade a plaint text connection to SSL/TLS. Examples of these protocols are XMPP, SMTP and various database TCP connections. * Remove removed setTimeout that was accidentally included (was removed for IDF >=5), bring timeout inline with the other timeouts (ints), fix cert/key checks to look if there is actually something there (all issues caught by the CI/CD on windows-latest * Quell compiler warning; use the right timeout * Newer versions of MBEDTLS make the client key struct private (and most of the x509 struct too), so absent of a non-null pointer we cannot check wether it is populated. Solve this by looking at the version (as 0 is not a valid x509 version). * Fix another \(rightfull\) compiler warning iwth the version pointer * Quell CI/CD runs on non-WiFi supporting hardare * Quell CI/CD runs on non-WiFi supporting hardare * Fix typo in directory name * Apply suggestions from code review Co-authored-by: Jan Procházka <[email protected]> * Rename Files * Remove leftover file --------- Co-authored-by: Me No Dev <[email protected]> Co-authored-by: Jan Procházka <[email protected]> Co-authored-by: Lucas Saavedra Vaz <[email protected]>
1 parent 13fac08 commit 48072ee

File tree

6 files changed

+314
-34
lines changed

6 files changed

+314
-34
lines changed

Diff for: libraries/WiFiClientSecure/examples/WiFiClientSecureProtocolUpgrade/.skip.esp32h2

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* STARTSSL example
2+
3+
Inline upgrading from a clear-text connection to an SSL/TLS connection.
4+
5+
Some protocols such as SMTP, XMPP, Mysql, Postgress and others allow, or require,
6+
that you start the connection without encryption; and then send a command to switch
7+
over to encryption.
8+
9+
E.g. a typical SMTP submission would entail a dialogue such as this:
10+
11+
1. client connects to server in the clear
12+
2. server says hello
13+
3. client sents a EHLO
14+
4. server tells the client that it supports SSL/TLS
15+
5. client sends a 'STARTTLS' to make use of this faciltiy
16+
6. client/server negiotiate a SSL or TLS connection.
17+
7. client sends another EHLO
18+
8. server now tells the client what (else) is supported; such as additional authentication options.
19+
... conversation continues encrypted.
20+
21+
This can be enabled in WiFiClientSecure by telling it to start in plaintext:
22+
23+
client.setPlainStart();
24+
25+
and client is than a plain, TCP, connection (just as WiFiClient would be); until the client calls
26+
the method:
27+
28+
client.startTLS(); // returns zero on error; non zero on success.
29+
30+
After which things switch to TLS/SSL.
31+
*/
32+
33+
#include <WiFiClientSecure.h>
34+
35+
#ifndef WIFI_NETWORK
36+
#define WIFI_NETWORK "YOUR Wifi SSID"
37+
#endif
38+
39+
#ifndef WIFI_PASSWD
40+
#define WIFI_PASSWD "your-secret-password"
41+
#endif
42+
43+
#ifndef SMTP_HOST
44+
#define SMTP_HOST "smtp.gmail.com"
45+
#endif
46+
47+
#ifndef SMTP_PORT
48+
#define SMTP_PORT (587) // Standard (plaintext) submission port
49+
#endif
50+
51+
const char* ssid = WIFI_NETWORK; // your network SSID (name of wifi network)
52+
const char* password = WIFI_PASSWD; // your network password
53+
const char* server = SMTP_HOST; // Server URL
54+
const int submission_port = SMTP_PORT; // submission port.
55+
56+
WiFiClientSecure client;
57+
58+
static bool readAllSMTPLines();
59+
60+
void setup() {
61+
int ret;
62+
//Initialize serial and wait for port to open:
63+
Serial.begin(115200);
64+
delay(100);
65+
66+
Serial.print("Attempting to connect to SSID: ");
67+
Serial.print(ssid);
68+
WiFi.begin(ssid, password);
69+
70+
// attempt to connect to Wifi network:
71+
while (WiFi.status() != WL_CONNECTED) {
72+
Serial.print(".");
73+
// wait 1 second for re-trying
74+
delay(1000);
75+
}
76+
77+
Serial.print("Connected to ");
78+
Serial.println(ssid);
79+
80+
Serial.printf("\nStarting connection to server: %s:%d\n", server, submission_port);
81+
82+
83+
// skip verification for this demo. In production one should at the very least
84+
// enable TOFU; or ideally hardcode a (CA) certificate that is trusted.
85+
client.setInsecure();
86+
87+
// Enable a plain-test start.
88+
client.setPlainStart();
89+
90+
if (!client.connect(server, SMTP_PORT)) {
91+
Serial.println("Connection failed!");
92+
return;
93+
};
94+
95+
Serial.println("Connected to server (in the clear, in plaintest)");
96+
97+
if (!readAllSMTPLines()) goto err;
98+
99+
Serial.println("Sending : EHLO\t\tin the clear");
100+
client.print("EHLO there\r\n");
101+
102+
if (!readAllSMTPLines()) goto err;
103+
104+
Serial.println("Sending : STARTTLS\t\tin the clear");
105+
client.print("STARTTLS\r\n");
106+
107+
if (!readAllSMTPLines()) goto err;
108+
109+
Serial.println("Upgrading connection to TLS");
110+
if ((ret=client.startTLS()) <= 0) {
111+
Serial.printf("Upgrade connection failed: err %d\n", ret);
112+
goto err;
113+
}
114+
115+
Serial.println("Sending : EHLO again\t\tover the now encrypted connection");
116+
client.print("EHLO again\r\n");
117+
118+
if (!readAllSMTPLines()) goto err;
119+
120+
// normally, as this point - we'd be authenticating and then be submitting
121+
// an email. This has been left out of this example.
122+
123+
Serial.println("Sending : QUIT\t\t\tover the now encrypted connection");
124+
client.print("QUIT\r\n");
125+
126+
if (!readAllSMTPLines()) goto err;
127+
128+
Serial.println("Completed OK\n");
129+
err:
130+
Serial.println("Closing connection");
131+
client.stop();
132+
}
133+
134+
// SMTP command repsponse start with three digits and a space;
135+
// or, for continuation, with three digits and a '-'.
136+
static bool readAllSMTPLines() {
137+
String s = "";
138+
int i;
139+
140+
// blocking read; we cannot rely on a timeout
141+
// of a WiFiClientSecure read; as it is non
142+
// blocking.
143+
const unsigned long timeout = 15 * 1000;
144+
unsigned long start = millis(); // the timeout is for the entire CMD block response; not per character/line.
145+
while (1) {
146+
while ((i = client.available()) == 0 && millis() - start < timeout) {
147+
/* .. wait */
148+
};
149+
if (i == 0) {
150+
Serial.println("Timeout reading SMTP response");
151+
return false;
152+
};
153+
if (i < 0)
154+
break;
155+
156+
i = client.read();
157+
if (i < 0)
158+
break;
159+
160+
if (i > 31 && i < 128) s += (char)i;
161+
if (i == 0x0A) {
162+
Serial.print("Receiving: ");
163+
Serial.println(s);
164+
if (s.charAt(3) == ' ')
165+
return true;
166+
s = "";
167+
}
168+
}
169+
Serial.printf("Error reading SMTP command response line: %d\n", i);
170+
return false;
171+
}
172+
173+
void loop() {
174+
// do nothing
175+
}
176+

Diff for: libraries/WiFiClientSecure/src/WiFiClientSecure.cpp

+48-13
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,40 @@ int WiFiClientSecure::connect(const char *host, uint16_t port, const char *CA_ce
140140
int WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *host, const char *CA_cert, const char *cert, const char *private_key)
141141
{
142142
int ret = start_ssl_client(sslclient, ip, port, host, _timeout, CA_cert, _use_ca_bundle, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos);
143+
144+
if (ret >=0 && ! _stillinPlainStart)
145+
ret = ssl_starttls_handshake(sslclient);
146+
else
147+
log_i("Actual TLS start posponed.");
148+
143149
_lastError = ret;
150+
144151
if (ret < 0) {
145-
log_e("start_ssl_client: %d", ret);
152+
log_e("start_ssl_client: connect failed: %d", ret);
146153
stop();
147154
return 0;
148155
}
149156
_connected = true;
150157
return 1;
151158
}
152159

160+
int WiFiClientSecure::startTLS()
161+
{
162+
int ret = 1;
163+
if (_stillinPlainStart) {
164+
log_i("startTLS: starting TLS/SSL on this dplain connection");
165+
ret = ssl_starttls_handshake(sslclient);
166+
if (ret < 0) {
167+
log_e("startTLS: %d", ret);
168+
stop();
169+
return 0;
170+
};
171+
_stillinPlainStart = false;
172+
} else
173+
log_i("startTLS: ignoring StartTLS - as we should be secure already");
174+
return 1;
175+
}
176+
153177
int WiFiClientSecure::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey) {
154178
return connect(ip.toString().c_str(), port, pskIdent, psKey);
155179
}
@@ -164,7 +188,7 @@ int WiFiClientSecure::connect(const char *host, uint16_t port, const char *pskId
164188
int ret = start_ssl_client(sslclient, address, port, host, _timeout, NULL, false, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos);
165189
_lastError = ret;
166190
if (ret < 0) {
167-
log_e("start_ssl_client: %d", ret);
191+
log_e("start_ssl_client: connect failed %d", ret);
168192
stop();
169193
return 0;
170194
}
@@ -189,17 +213,18 @@ int WiFiClientSecure::read()
189213
{
190214
uint8_t data = -1;
191215
int res = read(&data, 1);
192-
if (res < 0) {
193-
return res;
194-
}
195-
return data;
216+
return res < 0 ? res: data;
196217
}
197218

198219
size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)
199220
{
200221
if (!_connected) {
201222
return 0;
202223
}
224+
225+
if (_stillinPlainStart)
226+
return send_net_data(sslclient, buf, size);
227+
203228
if(_lastWriteTimeout != _timeout){
204229
struct timeval timeout_tv;
205230
timeout_tv.tv_sec = _timeout / 1000;
@@ -209,9 +234,9 @@ size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)
209234
_lastWriteTimeout = _timeout;
210235
}
211236
}
212-
213237
int res = send_ssl_data(sslclient, buf, size);
214238
if (res < 0) {
239+
log_e("Closing connection on failed write");
215240
stop();
216241
res = 0;
217242
}
@@ -220,6 +245,9 @@ size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)
220245

221246
int WiFiClientSecure::read(uint8_t *buf, size_t size)
222247
{
248+
if(_stillinPlainStart)
249+
return get_net_receive(sslclient, buf, size);
250+
223251
if(_lastReadTimeout != _timeout){
224252
if(fd() >= 0){
225253
struct timeval timeout_tv;
@@ -232,7 +260,7 @@ int WiFiClientSecure::read(uint8_t *buf, size_t size)
232260
}
233261
}
234262

235-
int peeked = 0;
263+
int peeked = 0, res = -1;
236264
int avail = available();
237265
if ((!buf && size) || avail <= 0) {
238266
return -1;
@@ -251,9 +279,10 @@ int WiFiClientSecure::read(uint8_t *buf, size_t size)
251279
buf++;
252280
peeked = 1;
253281
}
254-
255-
int res = get_ssl_receive(sslclient, buf, size);
282+
res = get_ssl_receive(sslclient, buf, size);
283+
256284
if (res < 0) {
285+
log_e("Closing connection on failed read");
257286
stop();
258287
return peeked?peeked:res;
259288
}
@@ -262,12 +291,17 @@ int WiFiClientSecure::read(uint8_t *buf, size_t size)
262291

263292
int WiFiClientSecure::available()
264293
{
265-
int peeked = (_peek >= 0);
294+
if (_stillinPlainStart)
295+
return peek_net_receive(sslclient,0);
296+
297+
int peeked = (_peek >= 0), res = -1;
266298
if (!_connected) {
267299
return peeked;
268300
}
269-
int res = data_to_read(sslclient);
270-
if (res < 0) {
301+
res = data_to_read(sslclient);
302+
303+
if (res < 0 && !_stillinPlainStart) {
304+
log_e("Closing connection on failed available check");
271305
stop();
272306
return peeked?peeked:res;
273307
}
@@ -403,3 +437,4 @@ int WiFiClientSecure::fd() const
403437
{
404438
return sslclient->socket;
405439
}
440+

Diff for: libraries/WiFiClientSecure/src/WiFiClientSecure.h

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class WiFiClientSecure : public WiFiClient
3434
int _peek = -1;
3535
int _timeout;
3636
bool _use_insecure;
37+
bool _stillinPlainStart = false;
3738
const char *_CA_cert;
3839
const char *_cert;
3940
const char *_private_key;
@@ -78,6 +79,17 @@ class WiFiClientSecure : public WiFiClient
7879
bool verify(const char* fingerprint, const char* domain_name);
7980
void setHandshakeTimeout(unsigned long handshake_timeout);
8081
void setAlpnProtocols(const char **alpn_protos);
82+
83+
// Certain protocols start in plain-text; and then have the client
84+
// give some STARTSSL command to `upgrade' the connection to TLS
85+
// or SSL. Setting PlainStart to true (the default is false) enables
86+
// this. It is up to the application code to then call 'startTLS()'
87+
// at the right point to initialise the SSL or TLS upgrade.
88+
89+
void setPlainStart() { _stillinPlainStart = true; };
90+
bool stillInPlainStart() { return _stillinPlainStart; };
91+
int startTLS();
92+
8193
const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); };
8294
bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); };
8395
int fd() const;

0 commit comments

Comments
 (0)