Files

85 lines
5.3 KiB
Markdown
Raw Permalink Normal View History

# independent-verifier-01: signed CAM/DENM checked by c-its
An independent implementation (c-its, Rust, https://github.com/TheEnbyperor/c-its,
commit e3bb3b82480d6df4237e2a8c35ea0dd7eade25b4 of 2026-09-07; rasn/rasn-its
codecs, RustCrypto p256) verifies what the host SUT signs with a lab credential
chain issued by `vidf_issue`. Nothing of the verification is ours: c-its
decodes the GeoNetworking and TS 103 097 structures with its own ASN.1
modules, checks the message signature, every certificate signature up to the
root it is given, the chain lengths, the end-entity permissions against the
issuers' `certIssuePermissions` and finally the CAM/DENM content against the
ticket's SSP (`security_authorized`).
## Material (public parts only)
| File | Content |
| --- | --- |
| `LAB_RCA.oer` | self-signed root, `vidf_issue root` from a throwaway P-256 key (`openssl ecparam -genkey`), CPOC-shaped `certIssuePermissions` (minChainLength 2, eeType app+enrol; second group psid 623 013E/FFC1) |
| `LAB_AA.oer` | AA under the root, appPermissions psid 623 ssp 0130 |
| `LAB_AT.oer` | standard ticket: psid 36 (01FFFC), 37 (01FFFFFF), 141 (no SSP), 638 (01) |
| `LAB_AT_NOMGMT.oer` | the same without psid 141 |
| `sut-standard.pcap`, `sut-nomgmt.pcap` | three CAM carriers (certificate, digest, certificate) and one DENM carrier (GBC) each, recorded with `tools/capture_pcap.py` |
| `c-its-standard.jsonl`, `c-its-nomgmt.jsonl` | c-its-pcap output, one JSON object per frame |
Private keys (`*.vkey`, the PEM) are not part of the evidence. The lab root is
not the project's root certificate; the procedure is the one the project root
would go through (`vidf_issue root --key <encrypted PEM>`, pass phrase prompted
by OpenSSL, never on the command line).
## Commands
```
vidf_issue root --key lab-root.pem --name "vanetza-idf lab root" --id LAB_RCA --out chain
vidf_issue authority --issuer chain/LAB_RCA.oer --issuer-key lab-root.pem --name "vanetza-idf lab AA" --id LAB_AA --out chain
vidf_issue ticket --issuer chain/LAB_AA.oer --issuer-key chain/LAB_AA.vkey --id LAB_AT \
--permission 36:01FFFC --permission 37:01FFFFFF --permission 141 --permission 638:01 --out chain
vidf_issue ticket ... --id LAB_AT_NOMGMT --permission 36:01FFFC --permission 37:01FFFFFF --permission 638:01 --out chain
python tools/capture_pcap.py --sut vidf_sut --pool chain --root LAB_RCA --aa LAB_AA --at LAB_AT --out sut-standard.pcap
python tools/capture_pcap.py --sut vidf_sut --pool chain --root LAB_RCA --aa LAB_AA --at LAB_AT_NOMGMT --out sut-nomgmt.pcap
mkdir ctl && cp chain/LAB_RCA.oer ctl/root-LAB_RCA.oer && cp chain/LAB_AA.oer ctl/aa-LAB_AA.oer
RUST_LOG=info c-its-pcap sut-standard.pcap > c-its-standard.jsonl
RUST_LOG=info c-its-pcap sut-nomgmt.pcap > c-its-nomgmt.jsonl
```
c-its was built on Windows with `cargo build --release --features build-binary
--bin c-its-pcap` (cargo 1.98.1). Its `ieee80211` dependency enables `defmt`
by default, which needs an embedded logger and does not link on a host; the
local checkout sets `ieee80211 = { version = "0.5.9", default-features = false,
features = ["crypto"] }` (a build change, no functional change).
## Result
| Capture | Frames | Message signature | Certificate signatures (root, AA, AT) | Chain lengths / eeType | `ee_permissions_valid` | `validated_chain` | `security_authorized` |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `sut-nomgmt.pcap` | 3 CAM + 1 DENM | verifies (4/4) | verify (4/4) | root [2,2] app+enrol, AA [1,1] app | true | chain 0 (root → AA → AT) | **true** for the CAMs and the DENM |
| `sut-standard.pcap` | 3 CAM + 1 DENM | verifies (4/4) | verify (4/4) | as above | false | none | false |
The digest-signed CAM (second frame) is resolved by c-its from the certificate
it saw in the first frame; the DENM carries the certificate (TS 103 097 clause
7.1.2) and the generationLocation.
The difference between the two captures is psid 141 (GN-MGMT) alone: the
standard ticket lists it without an SSP, which IEEE Std 1609.2 permits and the
ETSI test-suite tickets do as well (`CERT_IUT_A_AT`: `<ssp aid="GN-MGMT">`
empty), and which is consistent with an issuer range `all`. c-its maps an
appPermissions entry without SSP to `AppPermission::Unsupported`
(`src/security/perms.rs`, `_ => Self::Unsupported`), and
`CertSubjectAuthorization::authorizes` returns false for every unsupported
entry (`src/security/certs.rs`), so c-its cannot validate the permissions of
any ticket that carries GN-MGMT that way. The second capture removes the
entry to show that everything else c-its checks passes.
## What this found on our side
The first attempt (not retained) used the previous lab profile: root and AA
with the default chain length (1) and a bitmap range `00/FF` for GN-MGMT, the
AA with the end-entity SSP 01C0 for psid 623. c-its verified all signatures
but reported `ee_permissions_valid: false` because a root whose
`certIssuePermissions` permit a chain of length 1 does not authorise a ticket
two certificates below it (IEEE Std 1609.2 6.4.28). The lab issuer and the
test pool now follow the EU CCMS CPOC Protocol Release 3.0 root profile
(minChainLength 2, eeType app+enrol, the two psid 623 groups) and TS 102 941
V2.2.1 Table B.6 for the CA-side SSPs; the library's own verifier gained the
chain consistency checks it lacked (`docs/idf/validation.md`, "Chain
consistency").