TLS and PKI
Transport security is the most frequent cause of a session that connects but never exchanges a protocol message. This page states exactly what ChargeLink requires per protocol and adapter, what your device under test (DUT) must trust, and how to generate and check material.
Requirements per protocol
| Protocol | TLS | Version | Client (vehicle) certificate | Adapter |
|---|---|---|---|---|
| ISO 15118-20 (and MCS) | mandatory for every session with a DUT | 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) | built-in (--tls --require-ca) |
| ISO 15118-2 | optional | Charger role: Windows SChannel TLS 1.2 only, built-in adapter 1.2 or 1.3. Vehicle role (iso2-evcc): TLS 1.2 with the cipher suites of ISO 15118-2 | not required; --require-ca enables verification on the built-in adapter | built-in 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: secp521r1 and x448. As vehicle, ChargeLink offers both with a key share for each and only the cipher suites TLS_AES_256_GCM_SHA384 and TLS_CHACHA20_POLY1305_SHA256. As charger, ChargeLink chooses its own first preference among those groups your DUT offers, with a HelloRetryRequest where needed, and refuses a DUT that offers neither with handshake_failure before any V2G message. Session tickets and resumption are off.
| Option (charger and vehicle commands) | Effect |
|---|---|
--iso20-curve-profile secp521r1|x448 | The group offered first (default secp521r1). The NI VeriStand bench uses secp521r1. |
--allow-legacy-tls-named-groups | Adds secp256r1 last, for DUTs that offer only P-256 or x25519. Recorded in the evidence; in the charger role 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. ChargeLink reads no system TLS configuration files. When both sides of a session are ChargeLink (Self-test without a device under test), they must come from the same release: mixed releases refuse each other.
Material you need
| File | ChargeLink as charger | ChargeLink as vehicle |
|---|---|---|
| V2G root CA (PEM) | --tls-cafile: the root your DUT vehicle's certificate chains to | --tls-cafile: the root your DUT charger's certificate chains to |
| 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) | — |
ChargeLink and your DUT must be provisioned from the same root: your DUT must trust the root of ChargeLink's certificate, and ChargeLink must trust the root of your DUT's certificate. 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 vehicle role'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 refused. Revocation (CRL/OCSP) is covered by the offline TLS/PKI campaigns; ChargeLink includes no OCSP responder.
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 charger 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, load evse_ca_bundle.pem into your DUT (or any other peer) 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 PKI for both sides
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, an engineering charger identity and an engineering vehicle identity under one root, in the Annex B structure. The output folder holds the charger-side certificate folder with secc-cert-chain.pem, secc-key.pem and v2g-root-ca.pem, and the vehicle-side certificate folder with vehicle-cert-chain.pem, vehicle-key.pem and v2g-root-ca.pem (the folder names are listed under Names shown as printed), 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 charger\secc-identity.pfx for the Windows SChannel path.
ChargeLink uses the files of its own role; the files of the other side are for your DUT, if your DUT accepts imported certificates and keys. Load them into your DUT again 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 TLS server configuration without accepting peers. In the vehicle role, evcc tls-handshake-probe checks your charger's handshake and certificate policy (ChargeLink as Vehicle). 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 never copied into evidence and 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 is not provided
ISO 15118-20 and ISO 15118-2 define Plug & Charge (Contract authorization with contract certificates). ChargeLink does not provide it: every charger session offers EIM (External Payment) only, and evse start refuses a profile whose ID names Plug & Charge (pnc, contract or plug) with exit code 70 (CL-UNSUPPORTED-001) before any socket opens. The TLS material on this page secures the transport only.
The deterministic TLS/PKI campaigns (valid, expired, revoked and malformed certificates, untrusted root, wrong EKU, OCSP soft-fail, redaction boundary, TLS hostname mismatch, private-key export block) are offline simulations of certificate policy, not a charging session; see Deterministic Simulation.
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 | Your DUT offered TLS 1.2 or no client certificate | The DUT 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 | Your DUT offers only P-256 or x25519 (neither secp521r1 nor x448) | Fix the DUT configuration, or for engineering use start ChargeLink with --allow-legacy-tls-named-groups (plus --allow-engineering-tls-identity in the charger role). |
| A ChargeLink charger and a ChargeLink vehicle refuse each other after an upgrade | Different ChargeLink releases on the two sides | Deploy the same release on both sides; mixed versions fail closed by design. |
| Unknown CA / chain validation failed | ChargeLink and your DUT use different roots | Provision both from one root; run the PKI doctor with the same --tls-cafile your DUT trusts. |
| 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 your DUT right after regenerating engineering material | tls-material-init creates a new root every run | Load the new root (evse_ca_bundle.pem or v2g-root-ca.pem) into your DUT 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. |

