Skip to content

Commit 00f9624

Browse files
chemicstryme-no-dev
authored andcommitted
Port SSL fingerprint checking from ESP8266 WiFiClientSecure to ESP32 (#1397)
1 parent 0ea9ea4 commit 00f9624

File tree

4 files changed

+157
-0
lines changed

4 files changed

+157
-0
lines changed

libraries/WiFiClientSecure/src/WiFiClientSecure.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ void WiFiClientSecure::setPrivateKey (const char *private_key)
210210
_private_key = private_key;
211211
}
212212

213+
bool WiFiClientSecure::verify(const char* fp, const char* domain_name)
214+
{
215+
if (!sslclient)
216+
return false;
217+
218+
return verify_ssl_fingerprint(sslclient, fp, domain_name);
219+
}
220+
213221
int WiFiClientSecure::lastError(char *buf, const size_t size)
214222
{
215223
if (!_lastError) {

libraries/WiFiClientSecure/src/WiFiClientSecure.h

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class WiFiClientSecure : public WiFiClient
5858
void setCACert(const char *rootCA);
5959
void setCertificate(const char *client_ca);
6060
void setPrivateKey (const char *private_key);
61+
bool verify(const char* fingerprint, const char* domain_name);
6162

6263
operator bool()
6364
{

libraries/WiFiClientSecure/src/ssl_client.cpp

+146
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
#include <lwip/sockets.h>
1313
#include <lwip/sys.h>
1414
#include <lwip/netdb.h>
15+
#include <mbedtls/sha256.h>
16+
#include <mbedtls/oid.h>
17+
#include <algorithm>
18+
#include <string>
1519
#include "ssl_client.h"
1620

1721

@@ -262,3 +266,145 @@ int get_ssl_receive(sslclient_context *ssl_client, uint8_t *data, int length)
262266
//log_v( "%d bytes read", ret); //for low level debug
263267
return ret;
264268
}
269+
270+
static bool parseHexNibble(char pb, uint8_t* res)
271+
{
272+
if (pb >= '0' && pb <= '9') {
273+
*res = (uint8_t) (pb - '0'); return true;
274+
} else if (pb >= 'a' && pb <= 'f') {
275+
*res = (uint8_t) (pb - 'a' + 10); return true;
276+
} else if (pb >= 'A' && pb <= 'F') {
277+
*res = (uint8_t) (pb - 'A' + 10); return true;
278+
}
279+
return false;
280+
}
281+
282+
// Compare a name from certificate and domain name, return true if they match
283+
static bool matchName(const std::string& name, const std::string& domainName)
284+
{
285+
size_t wildcardPos = name.find('*');
286+
if (wildcardPos == std::string::npos) {
287+
// Not a wildcard, expect an exact match
288+
return name == domainName;
289+
}
290+
291+
size_t firstDotPos = name.find('.');
292+
if (wildcardPos > firstDotPos) {
293+
// Wildcard is not part of leftmost component of domain name
294+
// Do not attempt to match (rfc6125 6.4.3.1)
295+
return false;
296+
}
297+
if (wildcardPos != 0 || firstDotPos != 1) {
298+
// Matching of wildcards such as baz*.example.com and b*z.example.com
299+
// is optional. Maybe implement this in the future?
300+
return false;
301+
}
302+
size_t domainNameFirstDotPos = domainName.find('.');
303+
if (domainNameFirstDotPos == std::string::npos) {
304+
return false;
305+
}
306+
return domainName.substr(domainNameFirstDotPos) == name.substr(firstDotPos);
307+
}
308+
309+
// Verifies certificate provided by the peer to match specified SHA256 fingerprint
310+
bool verify_ssl_fingerprint(sslclient_context *ssl_client, const char* fp, const char* domain_name)
311+
{
312+
// Convert hex string to byte array
313+
uint8_t fingerprint_local[32];
314+
int len = strlen(fp);
315+
int pos = 0;
316+
for (size_t i = 0; i < sizeof(fingerprint_local); ++i) {
317+
while (pos < len && ((fp[pos] == ' ') || (fp[pos] == ':'))) {
318+
++pos;
319+
}
320+
if (pos > len - 2) {
321+
log_d("pos:%d len:%d fingerprint too short", pos, len);
322+
return false;
323+
}
324+
uint8_t high, low;
325+
if (!parseHexNibble(fp[pos], &high) || !parseHexNibble(fp[pos+1], &low)) {
326+
log_d("pos:%d len:%d invalid hex sequence: %c%c", pos, len, fp[pos], fp[pos+1]);
327+
return false;
328+
}
329+
pos += 2;
330+
fingerprint_local[i] = low | (high << 4);
331+
}
332+
333+
// Get certificate provided by the peer
334+
const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx);
335+
336+
if (!crt)
337+
{
338+
log_d("could not fetch peer certificate");
339+
return false;
340+
}
341+
342+
// Calculate certificate's SHA256 fingerprint
343+
uint8_t fingerprint_remote[32];
344+
mbedtls_sha256_context sha256_ctx;
345+
mbedtls_sha256_init(&sha256_ctx);
346+
mbedtls_sha256_starts(&sha256_ctx, false);
347+
mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len);
348+
mbedtls_sha256_finish(&sha256_ctx, fingerprint_remote);
349+
350+
// Check if fingerprints match
351+
if (memcmp(fingerprint_local, fingerprint_remote, 32))
352+
{
353+
log_d("fingerprint doesn't match");
354+
return false;
355+
}
356+
357+
// Additionally check if certificate has domain name if provided
358+
if (domain_name)
359+
return verify_ssl_dn(ssl_client, domain_name);
360+
else
361+
return true;
362+
}
363+
364+
// Checks if peer certificate has specified domain in CN or SANs
365+
bool verify_ssl_dn(sslclient_context *ssl_client, const char* domain_name)
366+
{
367+
log_d("domain name: '%s'", (domain_name)?domain_name:"(null)");
368+
std::string domain_name_str(domain_name);
369+
std::transform(domain_name_str.begin(), domain_name_str.end(), domain_name_str.begin(), ::tolower);
370+
371+
// Get certificate provided by the peer
372+
const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx);
373+
374+
// Check for domain name in SANs
375+
const mbedtls_x509_sequence* san = &crt->subject_alt_names;
376+
while (san != nullptr)
377+
{
378+
std::string san_str((const char*)san->buf.p, san->buf.len);
379+
std::transform(san_str.begin(), san_str.end(), san_str.begin(), ::tolower);
380+
381+
if (matchName(san_str, domain_name_str))
382+
return true;
383+
384+
log_d("SAN '%s': no match", san_str.c_str());
385+
386+
// Fetch next SAN
387+
san = san->next;
388+
}
389+
390+
// Check for domain name in CN
391+
const mbedtls_asn1_named_data* common_name = &crt->subject;
392+
while (common_name != nullptr)
393+
{
394+
// While iterating through DN objects, check for CN object
395+
if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid))
396+
{
397+
std::string common_name_str((const char*)common_name->val.p, common_name->val.len);
398+
399+
if (matchName(common_name_str, domain_name_str))
400+
return true;
401+
402+
log_d("CN '%s': not match", common_name_str.c_str());
403+
}
404+
405+
// Fetch next DN object
406+
common_name = common_name->next;
407+
}
408+
409+
return false;
410+
}

libraries/WiFiClientSecure/src/ssl_client.h

+2
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@ void stop_ssl_socket(sslclient_context *ssl_client, const char *rootCABuff, cons
3232
int data_to_read(sslclient_context *ssl_client);
3333
int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, uint16_t len);
3434
int get_ssl_receive(sslclient_context *ssl_client, uint8_t *data, int length);
35+
bool verify_ssl_fingerprint(sslclient_context *ssl_client, const char* fp, const char* domain_name);
36+
bool verify_ssl_dn(sslclient_context *ssl_client, const char* domain_name);
3537

3638
#endif

0 commit comments

Comments
 (0)