Increase TLS fingerprint security

This commit is contained in:
Theo Arends 2020-07-16 16:46:30 +02:00
parent be7aa3adf4
commit 66b3dc1cf2
3 changed files with 128 additions and 1 deletions

View File

@ -695,18 +695,56 @@ extern "C" {
xc->done_cert = true; // first cert already processed
}
// **** Start patch Castellucci
/*
static void pubkeyfingerprint_pubkey_fingerprint(br_sha1_context *shactx, br_rsa_public_key rsakey) {
br_sha1_init(shactx);
br_sha1_update(shactx, "ssh-rsa", 7); // tag
br_sha1_update(shactx, rsakey.e, rsakey.elen); // exponent
br_sha1_update(shactx, rsakey.n, rsakey.nlen); // modulus
}
*/
// If `compat` id false, adds a u32be length prefixed value to the sha1 state.
// If `compat` is true, the length will be omitted for compatibility with
// data from older versions of Tasmota.
static void sha1_update_len(br_sha1_context *shactx, const void *msg, uint32_t len, bool compat) {
uint8_t buf[] = {0, 0, 0, 0};
if (!compat) {
buf[0] = (len >> 24) & 0xff;
buf[1] = (len >> 16) & 0xff;
buf[2] = (len >> 8) & 0xff;
buf[3] = (len >> 0) & 0xff;
br_sha1_update(shactx, buf, 4); // length
}
br_sha1_update(shactx, msg, len); // message
}
// Update the received fingerprint based on the certificate's public key.
// If `compat` is true, an insecure version of the fingerprint will be
// calcualted for compatibility with older versions of Tasmota. Normally,
// `compat` should be false.
static void pubkeyfingerprint_pubkey_fingerprint(br_x509_pubkeyfingerprint_context *xc, bool compat) {
br_rsa_public_key rsakey = xc->ctx.pkey.key.rsa;
br_sha1_context shactx;
br_sha1_init(&shactx);
sha1_update_len(&shactx, "ssh-rsa", 7, compat); // tag
sha1_update_len(&shactx, rsakey.e, rsakey.elen, compat); // exponent
sha1_update_len(&shactx, rsakey.n, rsakey.nlen, compat); // modulus
br_sha1_out(&shactx, xc->pubkey_recv_fingerprint); // copy to fingerprint
}
// **** End patch Castellucci
// Callback when complete chain has been parsed.
// Return 0 on validation success, !0 on validation error
static unsigned pubkeyfingerprint_end_chain(const br_x509_class **ctx) {
br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx;
// **** Start patch Castellucci
/*
br_sha1_context sha1_context;
pubkeyfingerprint_pubkey_fingerprint(&sha1_context, xc->ctx.pkey.key.rsa);
br_sha1_out(&sha1_context, xc->pubkey_recv_fingerprint); // copy to fingerprint
@ -723,6 +761,59 @@ extern "C" {
// Default (no validation at all) or no errors in prior checks = success.
return 0;
}
*/
// set fingerprint status byte to zero
// FIXME: find a better way to pass this information
xc->pubkey_recv_fingerprint[20] = 0;
// Try matching using the the new fingerprint algorithm
pubkeyfingerprint_pubkey_fingerprint(xc, false);
if (!xc->fingerprint_all) {
if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint1, 20)) {
return 0;
}
if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint2, 20)) {
return 0;
}
// No match under new algorithm, do some basic checking on the key.
//
// RSA keys normally have an e value of 65537, which is three bytes long.
// Other e values are suspicious, but if the modulus is a standard size
// (multiple of 512 bits/64 bytes), any public exponent up to eight bytes
// long will be allowed.
//
// A legitimate key could possibly be marked as bad by this check, but
// the user would have had to really worked at making a strange key.
if (!(xc->ctx.pkey.key.rsa.elen == 3
&& xc->ctx.pkey.key.rsa.e[0] == 1
&& xc->ctx.pkey.key.rsa.e[1] == 0
&& xc->ctx.pkey.key.rsa.e[2] == 1)) {
if (xc->ctx.pkey.key.rsa.nlen & 63 != 0 || xc->ctx.pkey.key.rsa.elen > 8) {
return 2; // suspicious key, return error
}
}
// try the old algorithm and potentially mark for update
pubkeyfingerprint_pubkey_fingerprint(xc, true);
if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint1, 20)) {
xc->pubkey_recv_fingerprint[20] |= 1; // mark for update
}
if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint2, 20)) {
xc->pubkey_recv_fingerprint[20] |= 2; // mark for update
}
if (!xc->pubkey_recv_fingerprint[20]) {
return 1; // not marked for update because no match, error
}
// the old fingerprint format matched, recompute new one for update
pubkeyfingerprint_pubkey_fingerprint(xc, false);
return 0;
} else {
// Default (no validation at all) or no errors in prior checks = success.
return 0;
}
// **** End patch Castellucci
}
// Return the public key from the validator (set by x509_minimal)

View File

@ -121,7 +121,12 @@ class WiFiClientSecure_light : public WiFiClient {
bool _fingerprint_any; // accept all fingerprints
const uint8_t *_fingerprint1; // fingerprint1 to be checked against
const uint8_t *_fingerprint2; // fingerprint2 to be checked against
// **** Start patch Castellucci
/*
uint8_t _recv_fingerprint[20]; // fingerprint received
*/
uint8_t _recv_fingerprint[21]; // fingerprint received
// **** End patch Castellucci
unsigned char *_recvapp_buf;
size_t _recvapp_len;

View File

@ -637,6 +637,8 @@ void MqttReconnect(void)
AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("MFLN not supported by TLS server"));
}
#ifndef USE_MQTT_TLS_CA_CERT // don't bother with fingerprints if using CA validation
// **** Start patch Castellucci
/*
// create a printable version of the fingerprint received
char buf_fingerprint[64];
ToHex_P((unsigned char *)tlsClient->getRecvPubKeyFingerprint(), 20, buf_fingerprint, sizeof(buf_fingerprint), ' ');
@ -665,6 +667,35 @@ void MqttReconnect(void)
SettingsSaveAll(); // save settings
}
}
*/
const uint8_t *recv_fingerprint = tlsClient->getRecvPubKeyFingerprint();
// create a printable version of the fingerprint received
char buf_fingerprint[64];
ToHex_P(recv_fingerprint, 20, buf_fingerprint, sizeof(buf_fingerprint), ' ');
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Server fingerprint: %s"), buf_fingerprint);
bool learned = false;
// If the fingerprint slot is marked for update, we'll do so.
// Otherwise, if the fingerprint slot had the magic trust-on-first-use
// value, we will save the current fingerprint there, but only if the other fingerprint slot
// *didn't* match it.
if (recv_fingerprint[20] & 0x1 || (learn_fingerprint1 && 0 != memcmp(recv_fingerprint, Settings.mqtt_fingerprint[1], 20))) {
memcpy(Settings.mqtt_fingerprint[0], recv_fingerprint, 20);
learned = true;
}
// As above, but for the other slot.
if (recv_fingerprint[20] & 0x2 || (learn_fingerprint2 && 0 != memcmp(recv_fingerprint, Settings.mqtt_fingerprint[0], 20))) {
memcpy(Settings.mqtt_fingerprint[1], recv_fingerprint, 20);
learned = true;
}
if (learned) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Fingerprint learned: %s"), buf_fingerprint);
SettingsSaveAll(); // save settings
}
// **** End patch Castellucci
#endif // !USE_MQTT_TLS_CA_CERT
#endif // USE_MQTT_TLS
MqttConnected();