Castell · SkyV2X

Integration guide

Carregar el castell.

The live endpoints, from trust anchor to misbehaviour report, with copy-paste examples.

Trust anchor pinning

Pin the Root CA by its HashedId8, not by URL.

$ shell341 bytes COER
# Root CA certificate (COER)
curl https://pki.skyv2x.com/trustanchor -o root.cert

# HashedId8 = last 8 bytes of SHA-256 (SHA-384 for Brainpool P-384 certificates, such as the TLM)
openssl dgst -sha256 -binary root.cert | tail -c 8 | xxd -p -u
# → D5CC48BA56F87FCA

The same value is published in /capabilities under trust_anchors.root_ca.hashedId8, next to the TLM and MA.

Enrolment — get an EC

The EA must know the station's canonical key and permissions before the first request, and only a subscriber (a manufacturer or operator) registers stations. For testing, a sandbox token needs no approval:

$ curlsandbox token, then register once per station
curl -X POST https://pki.skyv2x.com/subscribers/sandbox/token
# → {"subscriber_id": "sandbox-…", "token": "cst_…", "max_profile": {…}}

curl -X POST https://pki.skyv2x.com/subscribers/me/stations \
  -H 'Authorization: Bearer cst_…' -H 'Content-Type: application/json' \
  -d '{"canonical_public_key": "02…", "curve": "ecdsaNistP256"}'
# → {"its_id": "b2f42bd8e5bdde59", "app_permissions": {"36": "01fffc", …}}
# optional: "its_id" to name the station, "app_permissions" to narrow the profile, "public_key_pem" instead of hex

Organisations register at POST /subscribers and receive their token and maximum profile after review.

  • Outer: ECIES to the EA encryption key, wrapping a SignedData signed by the canonical key (signer self).
  • Inner: SignedData(InnerEcRequest) signed by the new verification key (proof of possession).
  • Refusals come back encrypted: unknownits (not registered), invalidsignature (outer key ≠ registered key), deniedpermissions.
$ pythonreference client
python3 its_client.py --server https://pki.skyv2x.com

# [2/4] sandbox subscriber · station registered
# [3/4] EC issued · HashedId8 f63341bcf0e4f7c7 · 216 B · validity 8760 h

Authorization — get an AT

With an EC, request Authorization Tickets from the AA. The AA only sees the SharedAtRequest and an HMAC keyTag; the EA validates the EC and confirms the permissions: those requested, intersected with the station's profile.

$ pythonsame run
# [4/4] AT issued · HashedId8 0e66d4c7df963d9a · 161 B · subject none · validity 168 h
#       PSIDs 36 (CAM) · 37 (DENM) · 141 (GN-MGMT) · 1618 (MR)

One AT per request; the pool and its rotation live in the ITS-S.

Subset chain audit — /conformance

The chain audited the way a strict receiver does it: every AT through its AA to the Root CA, plus the EA, MA and TLM edges.

$ shell/conformance JSON
curl https://pki.skyv2x.com/conformance | jq '{
  rca: .rca.hashed_id8,
  all_checks_ok,
  real_gaps_count,
  ssp_real_fails_count,
  checks: [.chain_checks[] | {child: .child_name, ok, certissue_gaps, appperm_gaps}]
}'
  • real_gaps_count — PSIDs a certificate claims that its issuer does not delegate. Target 0.
  • ssp_real_fails_count — SSP values outside the issuer's range. Target 0.
  • expected_gaps_by_design, ssp_known_fails — declared exceptions. Both empty.

Verify a signed CAM

Verifying a CAM means AT → AA → Root CA. The CTL lists the EAs and AAs under our Root CA; the ECTL, signed by the TLM (Brainpool P-384), lists the Root CAs.

$ shelltrust list pull
# ECTL, signed by the TLM
curl https://pki.skyv2x.com/getectl -o ectl.bin

# CTL and CRL of our Root CA
RCA_HID=$(openssl dgst -sha256 -binary root.cert | tail -c 8 | xxd -p -u)
curl https://pki.skyv2x.com/getctl/$RCA_HID -o rca-ctl.bin
curl https://pki.skyv2x.com/getcrl/$RCA_HID -o crl.bin

Testbed refresh windows: ECTL 90 days, CTL 30, CRL 7.

Python end-to-end client

The endpoints are plain HTTPS. ECIES, the HMAC keyTag and OER encoding belong to your stack.

$ pythonsketch
import requests, hashlib

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
caps = requests.get(f"{BASE}/capabilities").json()
assert rca_hid8.upper() == caps["trust_anchors"]["root_ca"]["hashedId8"]

# 3. Enrolment and authorization: ECIES + keyTag + OER (TS 102 941 Annex C)

Misbehaviour reporting

Encrypt the Misbehaviour Report to the MA encryption key and POST it. Castell verifies the reporter's AT (PSID 1618), the signature and the freshness window; forged or replayed reports are rejected.

$ shellTS 103 759
# MA certificate (carries the encryption key)
curl https://pki.skyv2x.com/ma -o ma.cert

# Report signed by the AT, encrypted to the MA (ECIES + AES-CCM)
curl -X POST -H "Content-Type: application/x-its-mr" \
     --data-binary @mr.bin \
     https://pki.skyv2x.com/mr
# → 202, report_id in the body

Standard upload paths: POST /uploadMR-v1, /uploadMR-v1/Signed, /uploadMR-v1/SignedAndEncrypted (empty-body responses). /mr returns the decoded summary.