fix(cas): anchor the legacy CAS certificate chain so non-EU regions work - #12
Open
rtammekivi wants to merge 1 commit into
Open
fix(cas): anchor the legacy CAS certificate chain so non-EU regions work#12rtammekivi wants to merge 1 commit into
rtammekivi wants to merge 1 commit into
Conversation
Live video fails outside the EU with "CAS TLS handshake failed". CAS is reached at sysConf[15]:[16], which is the account's home region — Global (auto-route) does not change it, and a cross-region login is refused with meta 1100, so no config choice avoids it. Only eucas was migrated to GlobalSign. uscas, sgpcas and sacas all still serve one Entrust-issued *.ezvizlife.com certificate that expired 2019-06-17, chaining through Entrust L1K and G2. The expiry alone was never a problem: _is_certificate_expired_error exists precisely because the vendor app tolerates it, and OpenSSL reported verify_code 10, so the fallback fired and video worked. The 2024 Entrust distrust removed those roots from public trust stores — the HA 2026.8.1 image carries 119 roots and no Entrust. The chain can no longer anchor, so verification now fails as verify_code 19 (self-signed certificate in certificate chain) before expiry is ever considered, the fallback's trigger never occurs, and the handshake is refused. Proven by swapping only the trust store against an unchanged sgpcas: code 19 with the image's roots, code 10 with the Entrust root trusted. Carry the Entrust G2 root (notAfter 2030-12-07) and trust it for CAS connections only, restoring the chain so the one tolerated defect is expiry again. Verified against the live endpoints from inside the HA container: eucas verifies outright, sgpcas/uscas/sacas reach the expiry fallback with the chain anchored, a hostname mismatch is still rejected, and the fallback still refuses a non-expired certificate. Bundling the CA is deliberately preferred over widening the fallback to any verification failure: that would accept any self-signed certificate carrying a *.ezvizlife.com SAN, since the fallback handshake itself is unverified. Pinning the leaf is not viable either — eucas and sgpcas serve unrelated certificates and the eucas one expires 2026-12-14. Also stop discarding the underlying SSLError, which is what made this undiagnosable from the log, and keep the CAS error visible when the 0.10.5 re-login hold-off suppresses the retry in get_control_key. Upstream ezviz_hp7 has the same rot at pylocalapi/cas.py#L127-L133. Refs: #9 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Live video fails for every non-EU account with:
Reported in #9 (Australia). Reproduced here without the reporter's account.
Not a station, network or clock problem
The station IP in that message is context from
camera.py:266, not the endpoint that failed._send_all— the first byte of any CAS request — runs afterwrap_socket, so the connection dies before the station key is ever requested. The socket that failed is to a cloud host on port 6500.CAS is resolved from
sysConf[15]:[16], which follows the account's home region. "Global (auto-route)" does not change that — it logs in withmeta 200and still returns the home region'ssysConf— and a deliberate cross-region login is refused withmeta 1100and no session. There is no config-level workaround.Only EU was migrated
eucasuscassgpcassacasThe three failing endpoints serve one identical certificate — same serial, same 2019 expiry, chaining L1K → G2 → Entrust Root (2006).
This is rot, not a latent bug
The expiry was never the problem.
_is_certificate_expired_errorexists precisely because the vendor app tolerates an expired CAS certificate, OpenSSL reportedverify_code 10, the fallback fired, and video worked.The 2024 Entrust distrust removed those roots from public trust stores. The HA 2026.8.1 image carries 119 roots, zero Entrust. The chain can no longer anchor, so verification now fails as
verify_code 19before expiry is considered, and the fallback's trigger condition never occurs.Proven by changing only the trust store, against an unchanged
sgpcas:The fix
Carry the Entrust G2 root and trust it for CAS connections only, restoring the chain so the single tolerated defect is expiry again.
_is_certificate_expired_errorand the expiry assertion are untouched.Verified against the live endpoints, patched code, inside the HA container:
EU is unaffected — it verifies outright and never reaches the fallback.
Also included:
cas.pyno longer discards the underlyingSSLError(that omission is what made this undiagnosable from the log), andget_control_keykeeps the CAS error visible when the 0.10.5 re-login hold-off suppresses the retry.Alternatives rejected
CERT_NONE, so the only surviving check is the SAN — any self-signed certificate carrying*.ezvizlife.comwould be accepted. No real security.eucasandsgpcasserve unrelated certificates from different CAs, so a pin needs an entry per region, and theeucascertificate expires 2026-12-14.Worth a deliberate decision
This ships a publicly-distrusted CA root in the repo. It is scoped to the CAS context — never added to the system store, used for one hostname pattern on one port, and the peer certificate's SAN is still checked by hand on the fallback path. That is a real trade-off and should be an explicit choice, not a detail discovered later.
Refs: #9rather thanFixes: the handshake is confirmed fixed, but the key fetch end-to-end can only be confirmed by the reporter.Upstream
ezviz_hp7carries the same rot atpylocalapi/cas.py#L127-L133— every non-EU user of that integration is broken identically.🤖 Generated with Claude Code