-
Notifications
You must be signed in to change notification settings - Fork 13.3k
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
BearSSL client: warn user on misconfiguration, allow flash string for fingerprint #4833
Conversation
… fingerprint +update HTTPSRequest.ino example
@@ -19,6 +19,8 @@ | |||
#include <ESP8266WiFi.h> | |||
#include <WiFiClientSecure.h> | |||
|
|||
#define DEPRECATED_SSL_WITH_AXTLS 0 // use 1 for axTLS, 0 for BearSSL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you are making BearSLL the default here, you may also just drop axTLS part of the example entirely. After all, the intent is that axTLS is still supported (for existing sketches), but all new users should try go with BearSSL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. What about deferring that to when namespace is by default switched to BearSSL. We will have to revisit all examples and also remove these now useless / misleading verify()/verifyCertChain() api methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guarantee there are bugs in both the BearSSL wrapper and library, so let's hold off talk of deprecating things for a bit, please. Maybe a simple debug message in the BearSSL::verify()
method would cover the case discussed here?
return c >= '0' && c <= '9'? c - '0' | ||
: c >= 'A' && c <= 'F'? c - 'A' + 10 | ||
: c >= 'a' && c <= 'f'? c - 'a' + 10 | ||
: 255; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this 255 is an error condition....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, given the way this is written 255 is not an error but could be a space (expected to occur every other iteration in the while() lop in the setFP function) or an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
255 means no hex number. 255 is a separator detector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this code 255 doesn't mean "space", it means "none of the above".
My comment is aimed at: what happens if the fingerprint string has a typo, i.e.: an invalid char. The above check is strict in that it checks for specific chars that are allowed. If there is a separator allowed, then maybe that should be explicitly allowed as well? Then, in case of an invalid char, an error is returned, which gets checked below.
const char* _fingerprint = fingerprint.c_str(); | ||
|
||
while (idx < 20 && (c = *_fingerprint++) && (d = *_fingerprint++)) { | ||
c = htoi(c); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... then the return should likely be checked here, in case the user-provided fingerprint is bad. Also, the return for setFingerprint could be a bool to indicate success/failure.
@@ -768,6 +768,11 @@ bool WiFiClientSecure::_installClientX509Validator() { | |||
bool WiFiClientSecure::_connectSSL(const char* hostName) { | |||
_freeSSL(); | |||
_oom_err = false; | |||
#ifdef DEBUG_ESP_SSL | |||
if (!_use_insecure && !_use_fingerprint && !_use_self_signed && !_knownkey && !_sk) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens with this condition in the normal use case, i.e.: a certificate signed by an authority, which has been verified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll fail. Needs to check that there is no CertStore and no CA list in the object as well.
@@ -104,9 +105,14 @@ class WiFiClientSecure : public WiFiClient { | |||
static bool probeMaxFragmentLength(const char *hostname, uint16_t port, uint16_t len); | |||
static bool probeMaxFragmentLength(const String host, uint16_t port, uint16_t len); | |||
|
|||
// AXTLS compatbile wrappers | |||
bool verify(const char* fingerprint, const char* domain_name) { (void) fingerprint; (void) domain_name; return false; } // Can't handle this case, need app code changes | |||
// AXTLS compatible wrappers | |||
bool verifyCertChain(const char* domain_name) { (void)domain_name; return connected(); } // If we're connected, the cert passed validation during handshake |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to verify a certificate chain before connection? How does verification work with axTLS?
My question is aimed at whether a connection should be initiated here in case it is not established.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it doesn't make sense before a connection. You get a cert chain as part of a connection handshake. In axTLS this is just stuffed away and not examined unless you call .verify() at some later point. In BearSSL it's verified as part of the connection setup and if it fails there is no connection. If you connected, verified, then disconnected, there is no guarantee you'd connect to the same server on the next real .connect().
The only reason verify()
was there in the initial BearSSL was for compatibility with axTLS code (at least compilation...it would fail when run if there was no prior setFingerprint()
type call. If you want to rename it to something else, I'd say drop it as it's always going to be a no-op in BearSSL. Suggest dropping this or using original method code with a return connected()
instead of return false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@earlephilhower I'm a bit confused. My question was for verifyCertChain()
, which already returns connected().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the right direction, a few things needed IMO to clean it up.
@@ -0,0 +1,36 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please just put these couple methods inside the main WiFiClientSecureBearSSL.cpp file? I don't see a good reason for these to live outside that file.
: 255; | ||
} | ||
|
||
void WiFiClientSecure::setFingerprint(const String& fingerprint) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a C string ("12 34 56 ..."
) silently promote to a String&? Maybe using a char *
would be better WRT memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The advantage of const String&
is that it will take a progmem or ram char*. It will be released right after setFingerprint(fp);
which does not any allocation, so ram won't be holed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that in this case a C string promotes to a temp String object, which is then passed in by reference.
int idx = 0; | ||
const char* _fingerprint = fingerprint.c_str(); | ||
|
||
while (idx < 20 && (c = *_fingerprint++) && (d = *_fingerprint++)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming we're sscanf'ing manually here, can we use c=pgm_read_byte(_fingerprint++) && d=pgm_read_byte...
instead? Everything else in BearSSL silently "just works" with PROGMEM strings/arrays (except for write()
which I really should have just use memcpy_p
and be done with it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can always assume fingerprint is in progmem. We would have to have two functions{,_P}. String is already here, takes care of progmem, and is released when we are done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As below, no assumption is needed. RAM and flash can be read w/pgm_read*. It's slower than plain pointer reads, true, but when compared to the actual SSL connection it's absolutely not noticeable (and this is only run one time before connection, not in a loop for every step of the process).
return c >= '0' && c <= '9'? c - '0' | ||
: c >= 'A' && c <= 'F'? c - 'A' + 10 | ||
: c >= 'a' && c <= 'f'? c - 'a' + 10 | ||
: 255; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, given the way this is written 255 is not an error but could be a space (expected to occur every other iteration in the while() lop in the setFP function) or an error.
@@ -768,6 +768,11 @@ bool WiFiClientSecure::_installClientX509Validator() { | |||
bool WiFiClientSecure::_connectSSL(const char* hostName) { | |||
_freeSSL(); | |||
_oom_err = false; | |||
#ifdef DEBUG_ESP_SSL | |||
if (!_use_insecure && !_use_fingerprint && !_use_self_signed && !_knownkey && !_sk) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll fail. Needs to check that there is no CertStore and no CA list in the object as well.
while (idx < 20 && (c = *_fingerprint++) && (d = *_fingerprint++)) { | ||
c = htoi(c); | ||
d = htoi(d); | ||
if (c > 15 || d > 15) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest the following to be more pedantic about verification (forgive no indenting, doing this in the review window in proportional font):
while (idx < 20 && (c = pgm_read_byte(_fingerprint++)) && (d = pgm_read_byte(_fingerprint++))) {
c = htoi(c);
d = htoi(d);
// Skip 0 or more spaces for all but last entry (could drop while and only check for 1 ' ' as well)
while ((idx < 19) && pgm_read_byte(_fingerprint) && (pgm_read_byte(_fingerprint)==' ')) { _fingerprint++; }
if ((c>15) || (d>15)) { return false; }
fp[idx++] = (c<<4)|d;
}
if ((idx != 20) || pgm_read_byte(_fingerprint)) { return false; } // Some garbage left over at end of line
return setFingerprint(fp);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this code fingerprint must be in PROGMEM right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, no. That would be true on the AVR which is Harvard architecture (completely separate program and RAM) and you need special instructions to read flash. ESP8266 has von Neumann, unified address space, but has alignment requirements in certain regions.
pgm_read_*() is always safe and just ensures reads are done on a 32-bit boundary (by shifting/masking after the fact).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, maybe I was too influenced by AVR. Also confused by __FlashStringHelper
and FPSTR
that only String
can understand. Thanks for the details!
Then in the general case when performance is not in the loop we can always use pgm_*
.
I should have read memcpy_P()
's code before :)
@@ -67,6 +67,7 @@ class WiFiClientSecure : public WiFiClient { | |||
_knownkey_usages = usages; | |||
} | |||
// Only check SHA1 fingerprint of certificate | |||
void setFingerprint(const String& fingerprint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above about making this eat char *
instead. Strings on this machine are evil and beat up the heap like mad. IMO they need to be rewritten to use handles like old MacOS to be easier w/o MMU, but that's another can of worms.
@@ -208,6 +214,8 @@ class WiFiClientSecure : public WiFiClient { | |||
static std::shared_ptr<uint8_t> _bearssl_stack; | |||
// The local copy, only used to enable a reference count | |||
std::shared_ptr<uint8_t> _local_bearssl_stack; | |||
|
|||
void WiFiClientSecure_verify__is_unavailable_with_BearSSL__use_setFingerprint_instead (void); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting way to cause an error on compile.
The purpose of this PR was to "shoot in the can of worm" :) axTLS and BearSSL's api are not the same. What I suggest is:
Since @earlephilhower is going to update BearSSL in #4882, I'm OK to close this PR and let his magic do the job. I can take care of the examples.ino. |
Agree w/ you, @d-a-v, except for
I'm fine with doing non-heroic things (already implemented easy ones), so maybe we can go for "should try to be compatible, except when it becomes too onerous?" |
Agreed ! |
Add a method allowing a user to send in a character string for the fingerprint, like axTLS supported. Implements part of PR esp8266#4833 from @d-a-v with changes requested in discussion.
Print a warning when in debug mode when a BearSSL connection tries to connect without having any defined authentication methods, since it will fail. Completely remove the empty axTLS compatibilty method "::verify(char *fp, char *name)" because it can't be done w/BearSSL w/o code changes, and always failed. Better to have a compile failure when we know at compile time the app won't do what is expected. Completes the changes started by @d-a-v in PR esp8266#4833
Add a method allowing a user to send in a character string for the fingerprint, like axTLS supported. Implements part of PR esp8266#4833 from @d-a-v with changes requested in discussion.
Print a warning when in debug mode when a BearSSL connection tries to connect without having any defined authentication methods, since it will fail. Completely remove the empty axTLS compatibilty method "::verify(char *fp, char *name)" because it can't be done w/BearSSL w/o user code changes, and always failed. Better to have a compile failure when we know at compile time the app won't do what is expected. Completes the changes started by @d-a-v in PR #4833
+update HTTPSRequest.ino example
edit:
I'm not sure we should keep BearSSL's
::verify(fingerprint,host)
.