Castell · SkyV2X

ETSI TS 103 097 · cert format

The C-ITS certificate
and message format

ETSI TS 103 097 specifies the certificate format and the SignedData / EncryptedData wrappers every C-ITS message uses. It's the layer between IEEE 1609.2 cryptography and the application messages above. Castell signs to it.

Anatomy of a TS 103 097 signed message SignedData ts 103 097 §5 · outer wrapper hashAlgorithm sha256 tbsData to-be-signed payload + headerInfo payload.unsecuredData <CAM bytes> · application message bytes headerInfo.psid 36 CAM service headerInfo.generationTime 706970579606066 microseconds since 2004-01-01 headerInfo.generationLocation { lat, lon } optional, ITS reference frame headerInfo.requestedCertificate absent cert-by-value not requested signer CHOICE digest 1A2B3C4D5E6F7081 HashedId8 of the AT certificate (example) signature CHOICE ecdsaNistP256Signature r = 0x8f c3 ad ... 1c s = 0x4e b7 a2 ... 9d
A signed CAM, taken apart. SignedData → tbsData → signer → signature.

What ETSI TS 103 097 specifies

TS 103 097 is the wire-level cryptographic contract for C-ITS. It defines:

The spec builds on IEEE 1609.2 cryptographic primitives. Where 1609.2 defines how you sign with ECDSA on a given curve, TS 103 097 defines what a signed CAM looks like on the wire and what its issuer cert looks like.

Explicit vs implicit certificates

TS 103 097 inherits two cert flavours from 1609.2.

Explicit certs

The public key sits directly in the cert body, as a PublicVerificationKey CHOICE holding the curve and the encoded point. Verification is the plain ECDSA flow. Hash the to-be-signed bytes, run ecdsa-verify with the carried key, accept or reject.

Implicit certs

The cert body carries a reconstructionValue instead of a key. The verifier derives the actual public key by combining that value with the issuer's public key, an ECQV (Elliptic Curve Qu-Vanstone) reconstruction. Implicit certs are smaller on the wire, a single point rather than a full pubkey, at the cost of one reconstruction step before any signature can be verified.

Castell issues Explicit only. Implicit is not implemented, a deliberate scope choice. Explicit certs cover the integrator workflows the testbed serves, and implicit reconstruction adds real complexity for marginal wire savings. It also sidesteps the IEEE 1609.2 P-384 implicit constraint, covered on the multi-curve page.

How Castell signs to TS 103 097

Wire version

Castell declares conformance to TS 103 097 v1.3.1 cert format COER. v1.3.1 is the wire-stable cert encoding the deployed ITS-G5 ecosystem produces today. Newer versions (v2.1.1, v2.2.1) add fields and CHOICE entries while staying backward compatible with the v1.3.1 subset. Pinning to v1.3.1 lets anything in the wild parse Castell's certs without spec version negotiation.

What gets signed

Every artefact Castell publishes is signed end-to-end:

PSID and SSP: what each cert can sign

Every cert carries an appPermissions field. It enumerates the PSIDs (Provider Service Identifiers) the cert may claim, each with an SSP (Service-Specific Permissions) that constrains what the cert can do within that PSID. A signed CAM must reference a PSID the signing cert holds permission for. Receivers reject signatures whose PSID isn't covered.

Castell's AT-2 release (v0.6.1, see changelog) widened the AA's certIssue permission set to cover every Day-1 service (CAM, DENM, SPATEM, MAPEM, IVIM, SREM, SSEM, VAM, CPM, GN-MGMT). A stack running VAM (PSID 638), for one, can now request an AT from Castell without tripping strict subset-chain validation.

generationTime and the freshness window

Every signed message carries generationTime. A receiver rejects signatures whose generationTime falls outside an acceptable window from local time, typically ±5 minutes. Castell enforces this on incoming requests too. See Castell v0.8.3 in the changelog for the default window and the rationale.

Decoding TS 103 097 messages

Castell exposes two debug endpoints that parse incoming TS 103 097 wire bytes and return their structure as JSON. They are public on purpose, so any integrator can check what their stack produces:

# Decode an EC request you just built — see what the wire looks like
curl -X POST https://pki.skyv2x.com/v221/ec-request-decode \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @your-ec-request.coer

# Same for AT request (v2.2.1 strict decode)
curl -X POST https://pki.skyv2x.com/v221/at-request-decode \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @your-at-request.coer

The decoded JSON shows every CHOICE tag, every option field, the signer reference and the PSIDs/SSPs the signing cert holds. It's the fastest way to debug a v2.2.1 conformance failure without standing up your own parser.

Related material