TLS, PKI and Plug & Charge
Transport security is the most frequent cause of a session that connects but never exchanges a protocol message. This page states exactly what ChargeLink Native requires per protocol and adapter, how to generate and check material, and what the Plug & Charge (PnC) surfaces do.
Requirements per protocol
| Protocol | TLS | Version | Client (vehicle) certificate | Adapter |
|---|---|---|---|---|
| ISO 15118-20 | mandatory for every external session | TLS 1.3 only; plaintext and TLS 1.2 are rejected before SupportedAppProtocol. Named groups secp521r1 and x448 only, cipher suites TLS_AES_256_GCM_SHA384 and TLS_CHACHA20_POLY1305_SHA256 only; no session tickets or resumption | required and verified against the V2G root (ISO 15118-20 Annex B chain, ECDSA secp521r1 with SHA-512) | rustls (--tls --require-ca) |
| ISO 15118-2 | optional | SChannel: TLS 1.2 only; rustls: 1.2 or 1.3; the iso2-evcc lane: TLS 1.2 on OpenSSL with the cipher suites of ISO 15118-2 | not required; --require-ca enables verification on rustls | rustls or Windows SChannel (--external-evcc-windows-tls --tls-pfx) |
| DIN 70121 | none | — | — | — |
| gRPC Integration API | none (every call requires the per-start access token) | — | — | keep on 127.0.0.1; see Production Security |
ISO 15118-20 named groups and cipher suites
Both ChargeLink roles use the named groups ISO 15118-20 requires (see Table 7 of the standard): secp521r1 and x448. The EVCC offers both with a key share for each and only the cipher suites TLS_AES_256_GCM_SHA384 and TLS_CHACHA20_POLY1305_SHA256; the SECC chooses its own first preference among those groups the EV offers, with a HelloRetryRequest where needed, and refuses a client that offers neither with handshake_failure before any V2G message. Session tickets and resumption are off.
| Option (EVSE and EVCC commands) | Effect |
|---|---|
--iso20-curve-profile secp521r1|x448 | The group offered first (default secp521r1). The bench runs secp521r1. |
--allow-legacy-tls-named-groups | Adds secp256r1 last, for peers that offer only P-256 or x25519. Recorded as a legacy claim boundary in the evidence; on the SECC it also requires --allow-engineering-tls-identity. Not conformant; engineering use only. |
The offered groups, key shares and suites, the negotiated group, the handshake kind and the policy are recorded in evse_listener_status.json, tls_handshake_diagnostics.json and evcc_loopback_status.json. A charger and a vehicle from different ChargeLink releases refuse each other: PC1 and PC2 must run the same release. The ISO 15118-2 TLS 1.2 roles and the x448 key agreement use a static OpenSSL 3.5.9 linked into chargelink.exe; its configuration loading is disabled.
Material you need
| File | EVSE role | EVCC role |
|---|---|---|
| V2G root CA (PEM) | --tls-cafile | --tls-cafile |
| SECC certificate chain and private key (PEM) | --tls-cert, --tls-key | — |
| Vehicle certificate chain and private key (PEM) | — | --tls-cert, --tls-key |
| SECC identity as PKCS#12 (PFX) | --tls-pfx with --tls-pfx-password-env (SChannel only) | — |
Both machines must be provisioned from the same root. The SECC's DNS name is used for SNI only; ISO 15118-20 identity is validated as an Annex B chain, so a missing subjectAltName does not fail the session and the EVCC's --tls-server-name (default chargelink-secc.local) is cosmetic under the strict SNI policy.
Production PKI for ISO 15118-20 must satisfy Annex B: ECDSA on secp521r1 with SHA-512 signatures, the prescribed extended key usages and authority information access, and subject key identifiers computed by method 2. ChargeLink checks the DER profile and the chain signature. Ed448 chains are modelled but fail closed; revocation checking (CRL/OCSP) is exercised by fixtures and negative campaigns but there is no OCSP responder in the engineering lanes.
Generate engineering material
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse tls-material-init --output-dir "$Data\pki\dev" --protocol iso15118-20 --profile-id iso15118_20_dc_dev_tls
Get-ChildItem "$Data\pki\dev"
Writes evse_server_cert.pem (the SECC chain: leaf, CSO sub-CA2, CSO sub-CA1), evse_server_key.pem (PKCS#8 leaf key), evse_ca_bundle.pem (the engineering V2G root) and TLS_SECURITY_BOUNDARY.md. The material is generated fresh on every run: new keys and certificates each time, never compiled into ChargeLink, ECDSA with SHA-512 on secp521r1 in the ISO 15118-20 Annex B shape. Every certificate carries the subject organisation "ChargeLink ENGINEERING ONLY - not for production". Use --overwrite to regenerate into an existing directory.
The SECC refuses engineering material by default. A server certificate with the engineering marker is rejected at evse start unless you pass --allow-engineering-tls-identity. That flag is for development and test only and must never appear in a production command line. Because the root changes with every generation, copy evse_ca_bundle.pem to every peer (the EVCC on PC2, the vehicle simulator) again after each run of tls-material-init.
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse start --protocol iso15118-20 --profile-id iso15118_20_dc --tls --require-ca --allow-engineering-tls-identity `
--tls-cert "$Data\pki\dev\evse_server_cert.pem" --tls-key "$Data\pki\dev\evse_server_key.pem" --tls-cafile "$Data\pki\dev\evse_ca_bundle.pem" `
--external-evcc-session --host $Advertise --tcp-port 50118
Complete engineering bench PKI for two PCs
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse tls-material-init --bench-pki --output-dir "$Data\pki\engineering-pki"
Get-ChildItem -Recurse "$Data\pki\engineering-pki"
With --bench-pki one command generates, with new keys every run, the whole engineering PKI of the two-PC ISO 15118-20 bench in the Annex B structure: pc1\secc-cert-chain.pem, pc1\secc-key.pem and pc1\v2g-root-ca.pem for the charger, pc2\vehicle-cert-chain.pem, pc2\vehicle-key.pem and pc2\v2g-root-ca.pem for the vehicle, plus engineering-pki-manifest.json (fingerprints, validity, SHA-256 of every file; no key) and README-ENGINEERING-ONLY.txt. The result reports the generated SECCID and EVCCID, engineering_only true and pc1_requires_allow_engineering_tls_identity true. The root and sub-CA keys are discarded when the command ends, so nothing else can be issued under that root; the output folder must not exist yet; every file is readable by the owner and SYSTEM only. --pfx-password-env NAME also writes pc1\secc-identity.pfx for the Windows SChannel path. Copy the three pc2 files to PC2 after every generation.
Check material before a session
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse tls-pki-policy
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse tls-pki-doctor --protocol iso15118-20 --profile-id iso15118_20_dc `
--tls --require-ca --tls-cert "$Pki\secc-chain.pem" --tls-key "$Pki\secc-key.pem" --tls-cafile "$Pki\v2g-root-ca.pem" --write-artifacts
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse certificate-flow --protocol iso15118-20 --profile-id iso15118_20_dc `
--tls --tls-cert "$Pki\secc-chain.pem" --tls-key "$Pki\secc-key.pem" --tls-cafile "$Pki\v2g-root-ca.pem"
chargelink --runtime-root "$Data\runtime" --artifact-root "$Data\artifacts" --json evse tls-listener-preflight --protocol iso15118-20 --profile-id iso15118_20_dc `
--tls-cert "$Pki\secc-chain.pem" --tls-key "$Pki\secc-key.pem" --tls-cafile "$Pki\v2g-root-ca.pem" --require-ca `
--host $Advertise --tcp-port 0 --write-artifacts
The doctor reports key/certificate match, chain validity, validity period, EKU and the profile's TLS expectations. The listener preflight prepares (and, with a non-zero port, binds) the rustls server configuration without accepting peers. Always check the clock (w32tm /query /status): an expired or not-yet-valid certificate is the most common false alarm.
Secrets handling
- Never pass
--tls-pfx-passwordon the command line; use--tls-pfx-password-env NAMEand set the variable in the session ($env:NAME = Read-Host 'PFX password'). An unset variable is a configuration error, not a silent fallback. - Private keys are classified
sensitive_credentialwithexport_allowed=false; they never enter support bundles. Certificate bodies are not exported either; diagnostics carry fingerprints and metadata. - Restrict the ACL of
C:\ChargeLinkData\pki(see Production Security).
Plug & Charge
| Surface | Behaviour |
|---|---|
| ISO 15118-20 AuthorizationSetup | The EVSE advertises EIM unless a PnC fixture manifest is configured through CHARGELINK_ISO20_PNC_AUTH_SETUP_FIXTURE_MANIFEST. Generate the manifest with chargelink --json exi iso20-pnc-auth-setup-artifacts --run-id pnc-fixture; the result prints the exact variable to set. A PnC-named profile alone does not advertise PnC on the wire. |
| ISO 15118-2 PaymentDetails / CertificateInstallation | Reference responses built from fixed engineering certificates (AC PnC lane). No CertificateUpdate or MeteringReceipt handling in the live lane. |
| Deterministic TLS/PKI/PnC campaigns | 12 campaigns with transcript hashes: valid contract, expired contract, untrusted root, wrong EKU, revoked contract, malformed certificate, OCSP soft-fail, redaction boundary, ISO 15118-2 contract install and update, TLS hostname mismatch, private-key export block. See Deterministic Simulation. |
Security package v2gtp-sdp-tls-security | 24 executable engineering cases covering SDP, TCP, TLS and V2GTP negatives plus PnC chain checks and redaction validation. |
None of these surfaces connects to a mobility operator or certificate pool. They validate message sequences and certificate policy with engineering material.
Troubleshooting a TLS failure
Symptom (from tls_handshake_diagnostics.json or the probe status) | Likely cause | Action |
|---|---|---|
| Handshake rejected before SupportedAppProtocol on ISO 15118-20 | Peer offered TLS 1.2 or no client certificate | Peer must use TLS 1.3 with a vehicle certificate; do not use the SChannel or dev adapters for ISO 15118-20. |
handshake_failure at once, the diagnostics list offered groups without secp521r1 or x448 | The peer offers only P-256 or x25519 (neither secp521r1 nor x448) | Fix the peer, or for engineering use start both roles with --allow-legacy-tls-named-groups (plus --allow-engineering-tls-identity on the SECC). |
| A ChargeLink charger and vehicle refuse each other after an upgrade | Different ChargeLink releases on PC1 and PC2 | Deploy the same release on both PCs; mixed versions fail closed by design. |
| Unknown CA / chain validation failed | Different roots on the two machines | Provision both from one root; run the PKI doctor with the same --tls-cafile on both. |
| Certificate expired or before its validity period | Clock or validity period | w32tm /resync; check notBefore/notAfter in the doctor output. |
| Wrong EKU / Annex B profile check failed | Certificate issued for another purpose or wrong curve | Reissue with the Annex B profile (P-521, SHA-512, correct EKU). |
evse start refuses the server certificate; the message names engineering TLS material | The chain came from tls-material-init and carries the engineering marker | Development or test bench: add --allow-engineering-tls-identity. Production: replace the material with operator-issued certificates. |
| Unknown CA on the peer right after regenerating engineering material | tls-material-init creates a new root every run | Copy the new evse_ca_bundle.pem to the peer again. |
| Handshake passes with dev material, fails with production material | Production chain has intermediate certificates the file does not contain | Supply the full chain in --tls-cert; the leaf alone is not enough. |

