From 66b3dc1cf2109ea8fa1bb9a5f6d2e53e12f90e97 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 16 Jul 2020 16:46:30 +0200 Subject: [PATCH] Increase TLS fingerprint security --- tasmota/WiFiClientSecureLightBearSSL.cpp | 93 +++++++++++++++++++++++- tasmota/WiFiClientSecureLightBearSSL.h | 5 ++ tasmota/xdrv_02_mqtt.ino | 31 ++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/tasmota/WiFiClientSecureLightBearSSL.cpp b/tasmota/WiFiClientSecureLightBearSSL.cpp index d0907788e..0d69ff810 100755 --- a/tasmota/WiFiClientSecureLightBearSSL.cpp +++ b/tasmota/WiFiClientSecureLightBearSSL.cpp @@ -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) diff --git a/tasmota/WiFiClientSecureLightBearSSL.h b/tasmota/WiFiClientSecureLightBearSSL.h index e5908275e..274f1b2dc 100755 --- a/tasmota/WiFiClientSecureLightBearSSL.h +++ b/tasmota/WiFiClientSecureLightBearSSL.h @@ -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; diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index 7a7eae075..a24d51de3 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -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();