The camera does present a self-signed 1024-bit RSA certificate
(CN=TPRI-DEVICE), and OpenSSL rejects it under the default SECLEVEL=2
with EE certificate key too weak. However this is not what breaks
SslAesTransport, which sets verify_mode = ssl.CERT_NONE and never
validates the certificate. The reported error is SSLV3_ALERT_HANDSHAKE_FAILURE,
not CERTIFICATE_VERIFY_FAILED — the failure happens during cipher
negotiation, before any certificate handling.
Suggested fix
Add the ECDHE suites to CIPHERS in SslAesTransport (and likely the
same for KlapTransport / LinkieTransport, which use the same pattern):
Independent confirmation on a second model — Tapo C225 (PTZ), SMART.IPCAMERA, login_version: 3 — python-kasa 0.10.2, Python 3.14, OpenSSL 3.5.7, HA 2026.7.4 (HAOS). Same failure, same fix.
Adding two data points that go slightly beyond the original report.
1. Each shipped cipher tested individually, rather than as a list — every one is rejected, so it is not one bad entry:
Offered
Result
AES256-GCM-SHA384
FAIL handshake_failure
AES256-SHA256
FAIL handshake_failure
AES128-GCM-SHA256
FAIL handshake_failure
AES128-SHA256
FAIL handshake_failure
AES256-SHA
FAIL handshake_failure
shipped list + any ECDHE
OK — TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
DEFAULT
OK — ECDHE-RSA-AES128-GCM-SHA256
2. Explicitly ruling out the security-level explanation. This one keeps resurfacing in the downstream HA issues, so it is worth nailing down: appending @SECLEVEL=0 and @SECLEVEL=1 to the shipped list still fails identically. verify_mode=CERT_NONE is already set in _create_ssl_context, so the 1024-bit certificate is never validated. Security level is not the blocker — the cipher intersection is empty.
A useful diagnostic trap for anyone triaging this: a plain ssl.create_default_context() connects to the camera fine from the very same container, because it offers ECDHE. "TLS works when I test it manually" therefore does not clear the library — the reproduction has to use SslAesTransport.CIPHERS itself.
Applying exactly the fix proposed here (appending ECDHE-RSA-AES128-GCM-SHA256 and ECDHE-RSA-AES256-GCM-SHA384, keeping the static-RSA entries for older firmware) takes the config entry from a permanent setup_retry loop to loaded, with all 13 PTZ/detection entities populating. Cipher selection is server-preference, so older devices are unaffected.
Happy to raise this as a PR if that would help — it is a one-line change to the CIPHERS list.
Related downstream reports, all open and all the same root cause: home-assistant/core#165645, #164045, #167714, #156926, #162498. #1727 here is the same thing on C225 V2 firmware 1.3.1.
Another affected model: Tapo C200, HW 3.0, FW 1.9.1 Build 260326 Rel.26771n (SMART.IPCAMERA, login_version: 3), python-kasa 0.10.2, Home Assistant 2026.8.3 (Container), Python 3.14.
Same root cause and same fix. Two additions that may help scope it.
1. Every shipped cipher tested individually against the C200
Not one bad entry — the whole list is rejected, and both ECDHE suites are accepted:
Cipher offered
C200 (FW 1.9.1)
AES256-GCM-SHA384
FAIL handshake_failure
AES256-SHA256
FAIL handshake_failure
AES128-GCM-SHA256
FAIL handshake_failure
AES128-SHA256
FAIL handshake_failure
AES256-SHA
FAIL handshake_failure
ECDHE-RSA-AES128-GCM-SHA256
OK, TLSv1.2
ECDHE-RSA-AES256-GCM-SHA384
OK, TLSv1.2
2. The discriminator is the key exchange, not the certificate type
I have a C210 (HW 3.0, FW 1.2.6) on the same LAN that still connects fine with the current CIPHERS. Probing it shows it serves two different certificates depending on the negotiated suite:
The C200 presents only the RSA-1024 certificate and offers only ECDHE-RSA-*.
So "works" vs "handshake_failure" is not about whether the device has an ECDSA certificate — it is about whether the firmware still offers static-RSA key exchange at all. Firmware that has dropped it fails with the current list regardless of model, which fits the spread reported here and in #1736.
3. SECLEVEL is not involved
The RSA-1024 certificate would normally trip OpenSSL's default SECLEVEL=2, so that is a tempting explanation, but it is not the cause here. _create_ssl_context() already sets verify_mode = ssl.CERT_NONE, and appending the ECDHE suites works with SECLEVEL left at the default 2. No security-level downgrade is needed.
With this the C200 pairs and exposes the full feature set including PanTilt, while the C210 continues to negotiate AES256-GCM-SHA384 exactly as before — so appending does not disturb devices that currently work.
Models reported affected so far across this issue and #1736: C120 1.9.2, C200 1.9.1, C201, C220 1.5.4, C225, TC70.
The commit is in no release tag. The newest release is 0.10.2 from 2025-02-12, which predates it, and that is the version Home Assistant pins (python-kasa[speedups]==0.10.2). So every affected user here is hitting a bug that was fixed half a year ago but has never shipped.
That reframes this issue and #1736: no code change is needed — the blocker is purely that no release has been cut. Anyone reading this thread and considering a PR can skip it.
For anyone stuck in the meantime, the runtime workaround that avoids editing site-packages (which is wiped on every Home Assistant image update) is to append the cipher from a small custom integration, since CIPHERS is read as self.CIPHERS inside _create_ssl_context() on every connection:
This is version-agnostic and becomes a no-op automatically once a release containing #1654 lands. Verified against a Tapo C200 (HW 3.0, FW 1.9.1) on python-kasa 0.10.2 / Home Assistant 2026.8.3: the camera pairs and exposes the full feature set including PanTilt, and an existing C210 on the same install continues to negotiate AES256-GCM-SHA384 unchanged.
This should already be fixed by the newest release, but if not, feel free to reopen. #1798 aims to make the transports more robust also for potential future firmware updates, please feel free to test and let me know if you have any issues!
Activity
Nino6689 commented on Aug 6, 2026
Independent confirmation on a second model — Tapo C225 (PTZ), SMART.IPCAMERA, login_version: 3 — python-kasa 0.10.2, Python 3.14, OpenSSL 3.5.7, HA 2026.7.4 (HAOS). Same failure, same fix.
Adding two data points that go slightly beyond the original report.
1. Each shipped cipher tested individually, rather than as a list — every one is rejected, so it is not one bad entry:
2. Explicitly ruling out the security-level explanation. This one keeps resurfacing in the downstream HA issues, so it is worth nailing down: appending @SECLEVEL=0 and @SECLEVEL=1 to the shipped list still fails identically. verify_mode=CERT_NONE is already set in _create_ssl_context, so the 1024-bit certificate is never validated. Security level is not the blocker — the cipher intersection is empty.
A useful diagnostic trap for anyone triaging this: a plain ssl.create_default_context() connects to the camera fine from the very same container, because it offers ECDHE. "TLS works when I test it manually" therefore does not clear the library — the reproduction has to use SslAesTransport.CIPHERS itself.
Applying exactly the fix proposed here (appending ECDHE-RSA-AES128-GCM-SHA256 and ECDHE-RSA-AES256-GCM-SHA384, keeping the static-RSA entries for older firmware) takes the config entry from a permanent setup_retry loop to loaded, with all 13 PTZ/detection entities populating. Cipher selection is server-preference, so older devices are unaffected.
Happy to raise this as a PR if that would help — it is a one-line change to the CIPHERS list.
Related downstream reports, all open and all the same root cause: home-assistant/core#165645, #164045, #167714, #156926, #162498. #1727 here is the same thing on C225 V2 firmware 1.3.1.
Aho-K commented on Aug 30, 2026
Another affected model: Tapo C200, HW 3.0, FW 1.9.1 Build 260326 Rel.26771n (SMART.IPCAMERA, login_version: 3), python-kasa 0.10.2, Home Assistant 2026.8.3 (Container), Python 3.14.
Same root cause and same fix. Two additions that may help scope it.
1. Every shipped cipher tested individually against the C200
Not one bad entry — the whole list is rejected, and both ECDHE suites are accepted:
2. The discriminator is the key exchange, not the certificate type
I have a C210 (HW 3.0, FW 1.2.6) on the same LAN that still connects fine with the current CIPHERS. Probing it shows it serves two different certificates depending on the negotiated suite:
The C200 presents only the RSA-1024 certificate and offers only ECDHE-RSA-*.
So "works" vs "handshake_failure" is not about whether the device has an ECDSA certificate — it is about whether the firmware still offers static-RSA key exchange at all. Firmware that has dropped it fails with the current list regardless of model, which fits the spread reported here and in #1736.
3. SECLEVEL is not involved
The RSA-1024 certificate would normally trip OpenSSL's default SECLEVEL=2, so that is a tempting explanation, but it is not the cause here. _create_ssl_context() already sets verify_mode = ssl.CERT_NONE, and appending the ECDHE suites works with SECLEVEL left at the default 2. No security-level downgrade is needed.
Confirmed stopgap
Appending rather than replacing:
With this the C200 pairs and exposes the full feature set including PanTilt, while the C210 continues to negotiate AES256-GCM-SHA384 exactly as before — so appending does not disturb devices that currently work.
Models reported affected so far across this issue and #1736: C120 1.9.2, C200 1.9.1, C201, C220 1.5.4, C225, TC70.
Aho-K commented on Aug 30, 2026
Correction / follow-up to my previous comment: this is already fixed on main, and has been for six months.
ECDHE-RSA-AES128-GCM-SHA256 was added to SslAesTransport.CIPHERS in b22917a ("Allow SSL connections to older devices", #1654), merged 2026-02-21:
The commit is in no release tag. The newest release is 0.10.2 from 2025-02-12, which predates it, and that is the version Home Assistant pins (python-kasa[speedups]==0.10.2). So every affected user here is hitting a bug that was fixed half a year ago but has never shipped.
That reframes this issue and #1736: no code change is needed — the blocker is purely that no release has been cut. Anyone reading this thread and considering a PR can skip it.
For anyone stuck in the meantime, the runtime workaround that avoids editing site-packages (which is wiped on every Home Assistant image update) is to append the cipher from a small custom integration, since CIPHERS is read as self.CIPHERS inside _create_ssl_context() on every connection:
This is version-agnostic and becomes a no-op automatically once a release containing #1654 lands. Verified against a Tapo C200 (HW 3.0, FW 1.9.1) on python-kasa 0.10.2 / Home Assistant 2026.8.3: the camera pairs and exposes the full feature set including PanTilt, and an existing C210 on the same install continues to negotiate AES256-GCM-SHA384 unchanged.
rytilahti commented on Oct 12, 2026
This should already be fixed by the newest release, but if not, feel free to reopen. #1798 aims to make the transports more robust also for potential future firmware updates, please feel free to test and let me know if you have any issues!