Castell · SkyV2X

Integration guide

Carregar el castell.

From raw curl to a production client. Reference walkthroughs, copy-paste examples, real endpoints. Build the trust chain end to end.

Trust anchor pinning

Every V2X stack needs a root of trust. Pin the SkyV2X RCA by its HashedId8 and SHA-256, never by URL alone.

$ shell191 bytes COER
# Fetch the RCA cert (COER, TS 103 097 v1.3.1)
curl https://pki.skyv2x.com/trustanchor -o root.cert

# Verify HashedId8 — the canonical identity (TS 103 097)
openssl dgst -sha256 -binary root.cert | tail -c 8 | xxd -u -c 8
# → 8E0674D074994268

In production C-ITS, an OEM ships its devices with the trust anchor burned into a TPM or HSM. For early integration testing, file-based pinning is acceptable.

Enrolment — get an EC

An ITS-S authenticates itself to the EA (Enrolment Authority) with its canonical key, and receives an Enrolment Credential (EC) tied to that canonical identity.

Wire format per TS 102 941.2:

  • Outer: EciesP256EncryptedKey targeting EA encryption pubkey
  • Inner: SignedData(InnerEcRequest) signed by the canonical key
  • Payload: InnerEcRequest { itsId, certificateFormat, publicKeys, requestedSubjectAttributes }

Quickest path — use a reference Python client (see Python section below for the minimal version):

$ pythonreference client
python3 its_client.py --server https://pki.skyv2x.com

# Output (truncated):
# [3/4] EnrolmentRequest → EC issued
#   ✓ EC HashedId8 = 4fdb002bb5e37c98
#   ✓ EC issuer    = sha256AndDigest(EA HashedId8)
#   ✓ EC subject   = name = canonical_id_hex

Authorization — get an AT

With an EC, request Authorization Tickets (AT) from the AA. ATs are pseudonymous — the AA cannot link the AT back to the EC (privacy boundary per TS 102 941.3).

Privacy guarantee: EC.publicKey stays inside an envelope encrypted to EA. AA only sees the SharedAtRequest + an HMAC keyTag. AA forwards to EA for validation, never sees the EC.

$ pythonContinued from
# The reference client does both EC and AT in one run:
# [4/4] AuthorizationRequest → AT issued
#   ✓ AT HashedId8 = 2468d5f305e35cc6
#   ✓ AT subject   = none (privacy)
#   ✓ AT validity  = ('hours', 8760)
#   ✓ PSIDs        = [36 (CAM), 37 (DENM), 141 (GN-MGMT)]

A real ITS-S maintains a pool of ~20 ATs rotating every ~5 min for unlinkability. Our testbed issues 1 AT per request; rotation logic belongs in the ITS-S, not the PKI.

Subset chain audit — /conformance

Castell publishes its subset chain audit as a public endpoint. Apply ultra-strict per IEEE 1609.2 §5.1.2 — recursive from each AT through its AA to the RCA, plus EA and MA edges.

$ shell/conformance JSON
curl https://pki.skyv2x.com/conformance | jq '{
  rca: .rca.hashed_id8,
  all_checks_ok,
  real_gaps_count,
  expected_gaps_by_design,
  checks: [.chain_checks[] | {child: .child_name, ok, certissue_gaps, appperm_gaps}]
}'

Response fields:

  • real_gaps_count — gaps that require fixing. Target: 0.
  • expected_gaps_by_design — edges that are gaps by spec (e.g. ma.cert: MA is a separate service per TS 103 759 §8.2, not delegated through RCA certIssuePermissions).
  • chain_checks[].appperm_gaps / certissue_gaps — PSIDs the child claims that the issuer does not authorize.

Receiver discipline: if your stack only validates AT → AA and skips the AA → RCA edge, it will accept frames that a fully compliant validator rejects. Castell's contract: real_gaps_count == 0 means no AT in our pool will be rejected by a recursive-strict validator on permission grounds.

Verify a signed CAM

Any V2X stack receiving a CAM signed by an AT must verify the chain AT → AA → RCA. The CTL gives you the AAs you trust under our RCA.

$ shellTrust list pull
# 1. ECTL (TLM-signed) — list of trusted RCAs
curl https://pki.skyv2x.com/getectl -o ectl.bin

# 2. RCA-CTL — list of trusted EAs/AAs under our RCA
RCA_HID=$(openssl dgst -sha256 -binary root.cert | tail -c 8 | xxd -p)
curl https://pki.skyv2x.com/getctl/$RCA_HID -o rca-ctl.bin

# 3. CRL — revoked sub-CAs
curl https://pki.skyv2x.com/getcrl/$RCA_HID -o crl.bin

Refresh ECTL every 90 days, RCA-CTL every 30 days, CRL every 7 days (testbed defaults — production CCMS uses shorter windows).

Python end-to-end client

Minimal client using any HTTP library plus your ASN.1 OER library of choice. The sketch below uses pure stdlib for the wire calls; ECIES, HMAC keyTag and OER encoding are deliberately left to your stack.

$ pythonreference sketch
import requests, hashlib
from cryptography.hazmat.primitives.asymmetric import ec

BASE = "https://pki.skyv2x.com"

# 1. Trust anchor
rca = requests.get(f"{BASE}/trustanchor").content
rca_hid8 = hashlib.sha256(rca).digest()[-8:].hex()

# 2. Discovery — capabilities JSON
caps = requests.get(f"{BASE}/capabilities").json()
assert rca_hid8.upper() == caps["trust_anchors"]["root_ca"]["hashedId8"]

# 3. Enrolment — full EC + AT flow needs ECIES + HMAC keyTag
#    + ASN.1 OER encoding. Wire format and field-level spec in
#    TS 102 941 Annex C.

The endpoints above are stable. The client-side cryptography (ECIES wrapper, HMAC keyTag, OER encoding) is what you implement — Castell only verifies signatures and decrypts requests on the server side.

Misbehaviour reporting

If your ITS-S detects misbehaviour (TS 103 759 detectors), encrypt the Misbehaviour Report (MR) to the MA pubkey and POST it.

$ shellTS 103 759
# 1. Get the MA cert (includes encryption pubkey)
curl https://pki.skyv2x.com/ma -o ma.cert

# 2. Build MR per TS 103 759 (Asr* schemas from ETSI Forge)
# 3. Encrypt to MA pubkey (ECIES + AES-CCM)
# 4. POST
curl -X POST -H "Content-Type: application/x-its-mr" \
     --data-binary @mr.bin \
     https://pki.skyv2x.com/mr

# Response: 202 Accepted, report_id = SHA-256(payload)[:16]

v0.1 testbed validates only that the MR decrypts correctly. Full TS 103 759 ASN.1 parse (AsrCam / AsrDenm) ships in v0.2.