Repository navigation
feat(ocpp): TLS and mutual TLS for the embedded OCPP servers - #1342
Merged
Merged
Conversation
…1341 Add hsem_ocpp_tls_mode (off/tls/mtls, default off) with optional server certificate, server key and client CA paths, shared by both OCPP servers. With no paths, HSEM generates a private CA and a CA-signed server certificate in /ssl/hsem/ocpp/ (or <config>/ssl/hsem/ocpp/ without /ssl), keys 0600, CA never overwritten, server certificate reissued within 30 days of expiry or when the host names change. All crypto and file I/O runs in the executor. In mtls mode the server requires a client certificate from the client CA and refuses the upgrade (HTTP 403) when its CN does not match the CPID path, before any existing session is touched. If TLS cannot be set up, neither server starts: there is no fallback to plain ws://. TLS setting changes reload the entry so the servers restart with them. New service hsem.ocpp_generate_client_certificate signs a client key and certificate (CN = CPID) and returns paths only. Diagnostics gain an ocpp_tls block (mode, expiry, SANs, CA fingerprint or the setup error), and the OCPP status sensor's url attribute uses wss:// with TLS on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riptions - Fixes #1341 Hassfest rejects translation strings that look like HTML. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1342 +/- ##
==========================================
- Coverage 99.23% 99.21% -0.03%
==========================================
Files 260 262 +2
Lines 19424 19787 +363
==========================================
+ Hits 19275 19631 +356
- Misses 149 156 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…CPID - Fixes #1341 Add hsem_ocpp_tls_allow_cn_mismatch (default off, mtls only) for chargers whose certificate is named after their serial number. When on, the CN check is skipped and a warning is logged on every such connection; the certificate must still chain to the client CA and is still required. The setting is part of the reload snapshot and the diagnostics block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/memories.md
Fixes #1341 Co-Authored-By: Claude Opus 5.5 <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.
Summary
Adds TLS and mutual TLS to the embedded OCPP 1.6 servers. The certificates can be supplied by the user or generated by HSEM in Home Assistant's SSL folder.
Fixes #1341
Branch:
feat/1341-ocpp-tlsWhat changed and why
New setting
hsem_ocpp_tls_mode(OCPP step, shared by both servers):off(default, unchangedws://),tls(wss://with a server certificate),mtls(wss://plus a required client certificate). It comes with three optional paths: server certificate, server key, and client CA. Empty paths mean HSEM uses the certificates it generates.Generated certificates (
utils/ocpp_tls.py, new):/ssl/hsem/ocpp/when/sslexists (HA OS and Supervised). Otherwise stored in<config>/ssl/hsem/ocpp/. HSEM only writes inside that subfolder, never directly into/ssl.ca.crtor onlyca.key) is an error, not something HSEM silently replaces.networkintegration), the host name and the internal URL host. It is reissued on start-up when it expires within 30 days, when the names change, or when it wasn't signed by the current CA.0600(the temporary file already has0600). The folders get0700.hass.async_add_executor_job, including the/sslexistence check.Server (
ocpp_server.py):OCPPServertakes an optionalssl_context, built onhomeassistant.util.ssl.server_context_modern()(TLS 1.2+). Inmtlsmode the context usesCERT_REQUIREDwith the client CA. The upgrade is also refused with HTTP 403 when the peer certificate's CN doesn't match the last segment of the CPID path. That check runs before the reconnect handling, so a charger can't use its own valid certificate to take over another charger's session.CN mismatch setting
hsem_ocpp_tls_allow_cn_mismatch(default off,mtlsonly): for chargers whose certificate is named after their serial number. When on, a certificate whose CN differs from the CPID is accepted, and a warning is logged on every such connection. The certificate must still chain to the client CA, and a client certificate is still required. Only the name binding is dropped, so the docs warn that any certificate from that CA can then connect under any CPID.No fallback to
ws://(coordinator_lifecycle.py): TLS is prepared once and shared by both servers. If it fails, neither server starts, and HSEM logsOCPP TLS setup failed, OCPP server(s) not started.TLS changes reload the entry (
__init__.py): option changes are normally applied in place without restarting the OCPP servers. Without this change, switching TLS on would have kept plainws://running until a restart. The TLS settings are now part of the snapshot that decides when the entry reloads.Service
hsem.ocpp_generate_client_certificate(services_ocpp_tls.py, new): signs a client key and certificate (CN=<cpid>) with HSEM's CA and returnscertificate_path,key_pathandca_certificate_path(paths only). The CPID must match^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$because it becomes a file name. The service refuses to overwrite an existing certificate withoutreplace: true. It also refuses when a custom client CA is configured, since HSEM can't sign with a CA whose key it doesn't hold.Diagnostics:
ocpp_tlsblock with mode, the CN mismatch setting, certificate source, server certificate expiry, SANs and the CA's SHA-256 fingerprint, or the setup error. It never contains key material.build_diagnostics_dumponly accepts a real dict there.OCPP status sensor: the
urlattribute now advertiseswss://when TLS is on.Manifest:
networkadded toafter_dependencies(used for the SANs). No new requirement:cryptographyalready ships with Home Assistant.Files
utils/ocpp_tls.py,services_ocpp_tls.pycustom_sensors/ocpp_server.py,custom_sensors/ocpp_sensors.py,coordinator_lifecycle.py,coordinator.py,coordinator_state.py,__init__.py,flows/ocpp.py,custom_sensors/config_reader.py,models/sensor_config.py,const.py,services.py,services.yaml,diagnostics.py,utils/diagnostics.py,manifest.json,translations/{en,da,de,es}.jsondocs/ev-charge-plan-setup.md(new section "Securing the OCPP connection (TLS)"),docs/config-flow-reference.md,docs/services-reference.md,docs/sensors-reference.md,.github/memories.md(module map)Tests
tests/custom_sensors/test_ocpp_server_tls.py: realwss://connections against a runningOCPPServerwith generated certificates. Covered: server-only TLS; plainws://refused; valid client certificate;/ocpp/<id>base-URL path; missing client certificate; certificate from another CA; CN naming another charger (403); and a mismatching certificate not displacing the connected charger. With the CN check removed, the two CN tests fail. With the mismatch setting on: CP1's certificate connects as CP2 (warning logged), a certificate from another CA is still refused, and a missing client certificate is still refused.tests/utils/test_ocpp_tls.py: folder resolution, CA generation/reuse/permissions/half-CA/expiry, server certificate SANs/usage/rotation (expiry, names, replaced CA), client signing, unsafe CPIDs, context building, config-flow file checks, CN parsing and matching, SAN discovery, and the executor hand-off.tests/test_ocpp_tls_wiring.py: flow schema and validation, config reader, server start-up (off / shared context / failure starts nothing / no generation with OCPP off), the service, and diagnostics.tests/test_init_options_reload.py,tests/test_init_setup_order.py: each TLS setting change schedules a reload, and the reload snapshot includes the TLS settings.tests/sensors/test_ocpp_sensors_state.py:wss://URL.Quality gates
./scripts/quality.sh all: passes (7538 passed, 1 skipped, run on the branch rebased onto currentmain; new modules at 97–100 % coverage; mypy, pyright, translations and file-size checks clean)scripts/validate_translations.py: 0 missing, 0 stale, 0 placeholder mismatchesKnown limitations
🤖 Generated with Claude Code