Skip to main content

secured_vam_sender_receiver/
secured_vam_sender_receiver.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT)
3
4//! Secured VAM Sender and Receiver Example
5//!
6//! This example demonstrates sending and receiving ETSI TS 103 097-secured
7//! VAMs using the `rustflexstack` security layer and the VRU Awareness
8//! Service.
9//!
10//! Two instances are expected to run simultaneously, one with `--at 1` and the
11//! other with `--at 2`.  Each loads its own Authorization Ticket from the
12//! `certs/` directory (generated by `generate_certificate_chain`), signs
13//! outgoing VAMs, and verifies incoming ones.
14//!
15//! Security is applied as a middleware layer between the GN router and the
16//! link layer — outgoing packets are signed and incoming secured packets are
17//! verified before being forwarded to the GN router.
18//!
19//! # Prerequisites
20//! ```text
21//! cargo run --example generate_certificate_chain --target x86_64-unknown-linux-gnu
22//! ```
23//!
24//! # Running
25//! ```text
26//! # Terminal 1:
27//! sudo cargo run --example secured_vam_sender_receiver --target x86_64-unknown-linux-gnu -- --at 1
28//! # Terminal 2:
29//! sudo cargo run --example secured_vam_sender_receiver --target x86_64-unknown-linux-gnu -- --at 2
30//! ```
31//!
32//! If no interface is given the example falls back to `lo` (loopback), which
33//! is sufficient to test send→receive on a single machine.
34
35use std::env;
36use std::fs;
37use std::path::Path;
38use std::sync::{mpsc, Arc, Mutex};
39use std::thread;
40use std::time::Duration;
41
42use rustflexstack::btp::router::Router as BTPRouter;
43use rustflexstack::facilities::location_service::{GpsFix, LocationService};
44use rustflexstack::facilities::vru_awareness_service::{DeviceData, VruAwarenessService};
45use rustflexstack::geonet::basic_header::{BasicHeader, BasicNH};
46use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST};
47use rustflexstack::geonet::mib::Mib;
48use rustflexstack::geonet::position_vector::LongPositionVector;
49use rustflexstack::geonet::router::Router as GNRouter;
50use rustflexstack::link_layer::raw_link_layer::RawLinkLayer;
51
52use rustflexstack::security::certificate::{Certificate, OwnCertificate};
53use rustflexstack::security::certificate_library::CertificateLibrary;
54use rustflexstack::security::ecdsa_backend::EcdsaBackend;
55use rustflexstack::security::sign_service::SignService;
56use rustflexstack::security::sn_sap::{ReportVerify, SNSignRequest, SNVerifyRequest};
57use rustflexstack::security::verify_service::{verify_message, VerifyEvent};
58
59const ITS_AID_VAM: u64 = 638;
60
61/// Build the security stack: load certificates, create backend, sign & verify
62/// services.  Returns a `SignService` ready for use.
63fn build_security_stack(at_index: usize) -> SignService {
64    let cert_dir = Path::new("certs");
65
66    // ── Load certificates ────────────────────────────────────────────────
67    let root_bytes = fs::read(cert_dir.join("root_ca.cert"))
68        .expect("root_ca.cert not found — run generate_certificate_chain first");
69    let aa_bytes = fs::read(cert_dir.join("aa.cert"))
70        .expect("aa.cert not found — run generate_certificate_chain first");
71
72    let root_ca = Certificate::from_bytes(&root_bytes, None);
73    let aa = Certificate::from_bytes(&aa_bytes, Some(root_ca.clone()));
74
75    // Load both ATs — one is "ours", the other is a known peer
76    let at1_cert_bytes = fs::read(cert_dir.join("at1.cert")).expect("at1.cert not found");
77    let at2_cert_bytes = fs::read(cert_dir.join("at2.cert")).expect("at2.cert not found");
78
79    let at1 = Certificate::from_bytes(&at1_cert_bytes, Some(aa.clone()));
80    let at2 = Certificate::from_bytes(&at2_cert_bytes, Some(aa.clone()));
81
82    // Load our private key
83    let own_key_file = if at_index == 1 { "at1.key" } else { "at2.key" };
84    let key_bytes = fs::read(cert_dir.join(own_key_file))
85        .unwrap_or_else(|_| panic!("{} not found", own_key_file));
86
87    // ── Create backend and import key ────────────────────────────────────
88    let mut backend = EcdsaBackend::new();
89    let key_id = backend.import_signing_key(&key_bytes);
90
91    let own_cert = if at_index == 1 {
92        at1.clone()
93    } else {
94        at2.clone()
95    };
96    let peer_cert = if at_index == 1 {
97        at2.clone()
98    } else {
99        at1.clone()
100    };
101
102    // ── Build certificate library ────────────────────────────────────────
103    let cert_library = CertificateLibrary::new(
104        &backend,
105        vec![root_ca],
106        vec![aa],
107        vec![own_cert.clone(), peer_cert],
108    );
109
110    // ── Create sign service and add own certificate ──────────────────────
111    let mut sign_service = SignService::new(backend, cert_library);
112    let own = OwnCertificate::new(own_cert, key_id);
113    sign_service.add_own_certificate(own);
114
115    sign_service
116}
117
118fn main() {
119    // ── Parse arguments ──────────────────────────────────────────────────
120    let args: Vec<String> = env::args().collect();
121    let mut at_index: usize = 1;
122    let mut iface = "lo".to_string();
123
124    let mut i = 1;
125    while i < args.len() {
126        match args[i].as_str() {
127            "--at" => {
128                i += 1;
129                at_index = args[i].parse::<usize>().expect("--at must be 1 or 2");
130                assert!(at_index == 1 || at_index == 2, "--at must be 1 or 2");
131            }
132            "--iface" | "-i" => {
133                i += 1;
134                iface = args[i].clone();
135            }
136            other => {
137                // Positional argument: interface name
138                iface = other.to_string();
139            }
140        }
141        i += 1;
142    }
143
144    println!("=== Secured VAM Sender/Receiver (VRU Awareness Service) ===");
145    println!("AT index: {}", at_index);
146    println!("Interface: {}", iface);
147
148    // ── Build security stack ─────────────────────────────────────────────
149    let sign_service = build_security_stack(at_index);
150    println!(
151        "Security stack loaded. Own AT HashedId8: {:02x?}",
152        sign_service
153            .cert_library
154            .own_certificates
155            .keys()
156            .next()
157            .unwrap()
158    );
159
160    // Wrap in Arc<Mutex> so it can be shared between threads
161    let sign_service = Arc::new(Mutex::new(sign_service));
162
163    // ── Generate a random locally-administered MAC ───────────────────────
164    let mac = {
165        use std::time::{SystemTime, UNIX_EPOCH};
166        let seed = SystemTime::now()
167            .duration_since(UNIX_EPOCH)
168            .unwrap()
169            .subsec_nanos()
170            ^ (at_index as u32 * 0x1234_5678); // different seed per AT
171        [
172            0x02u8,
173            (seed >> 24) as u8,
174            (seed >> 16) as u8,
175            (seed >> 8) as u8,
176            seed as u8,
177            at_index as u8,
178        ]
179    };
180    println!(
181        "MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
182        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
183    );
184
185    // ── MIB ──────────────────────────────────────────────────────────────
186    let mut mib = Mib::new();
187    mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::Cyclist, MID::new(mac));
188    mib.itsGnBeaconServiceRetransmitTimer = 0;
189
190    // ── Location Service ─────────────────────────────────────────────────
191    let mut loc_svc = LocationService::new();
192    let gn_gps_rx = loc_svc.subscribe();
193    let vru_gps_rx = loc_svc.subscribe();
194
195    // ── Spawn GN router and BTP router ───────────────────────────────────
196    let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None);
197    let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib);
198
199    // ── Wire RawLinkLayer ────────────────────────────────────────────────
200    let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::<Vec<u8>>();
201
202    // ── Security middleware: TX path ─────────────────────────────────────
203    //
204    // Intercept packets from GN router → link layer.
205    // Sign the payload (CommonHeader + extended header + data) and wrap
206    // in a secured GN packet with BasicNH::SecuredPacket.
207    let (secured_ll_tx, secured_ll_rx) = mpsc::channel::<Vec<u8>>();
208    let sign_svc_tx = Arc::clone(&sign_service);
209    thread::spawn(move || {
210        while let Ok(packet) = gn_to_ll_rx.recv() {
211            if packet.len() < 4 {
212                let _ = secured_ll_tx.send(packet);
213                continue;
214            }
215            let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap();
216            let bh = BasicHeader::decode(bh_bytes);
217
218            match bh.nh {
219                BasicNH::CommonHeader if packet.len() > 4 => {
220                    let inner_payload = &packet[4..];
221
222                    let request = SNSignRequest {
223                        tbs_message: inner_payload.to_vec(),
224                        its_aid: ITS_AID_VAM,
225                        permissions: vec![],
226                        generation_location: None,
227                    };
228
229                    let sec_message = {
230                        let mut svc = sign_svc_tx.lock().unwrap();
231                        svc.sign_request(&request).sec_message
232                    };
233
234                    // Build new packet: BasicHeader(nh=SecuredPacket) + sec_message
235                    let mut new_bh = bh;
236                    new_bh.nh = BasicNH::SecuredPacket;
237                    let secured_packet: Vec<u8> = new_bh
238                        .encode()
239                        .iter()
240                        .copied()
241                        .chain(sec_message.iter().copied())
242                        .collect();
243                    let _ = secured_ll_tx.send(secured_packet);
244                }
245                _ => {
246                    // Pass through (e.g. beacons)
247                    let _ = secured_ll_tx.send(packet);
248                }
249            }
250        }
251    });
252
253    // The link layer reads from secured_ll_rx (post-signing)
254    let raw_ll = RawLinkLayer::new(ll_to_gn_tx, secured_ll_rx, &iface, mac);
255    raw_ll.start();
256
257    // ── Security middleware: RX path ─────────────────────────────────────
258    //
259    // Intercept packets from link layer → GN router.
260    // If BasicNH::SecuredPacket, verify and extract, then forward
261    // with BasicNH::CommonHeader.
262    let gn_h_rx = gn_handle.clone();
263    let sign_svc_rx = Arc::clone(&sign_service);
264    thread::spawn(move || {
265        while let Ok(packet) = ll_to_gn_rx.recv() {
266            if packet.len() < 4 {
267                gn_h_rx.send_incoming_packet(packet);
268                continue;
269            }
270            let bh_bytes: [u8; 4] = packet[0..4].try_into().unwrap();
271            let bh = BasicHeader::decode(bh_bytes);
272
273            match bh.nh {
274                BasicNH::SecuredPacket if packet.len() > 4 => {
275                    let sec_message = &packet[4..];
276                    let request = SNVerifyRequest {
277                        message: sec_message.to_vec(),
278                    };
279
280                    let (confirm, _events) = {
281                        let mut svc = sign_svc_rx.lock().unwrap();
282                        let svc = &mut *svc;
283                        let result = verify_message(&request, &svc.backend, &mut svc.cert_library);
284                        // Process VerifyEvents for P2PCD
285                        for event in &result.1 {
286                            match event {
287                                VerifyEvent::UnknownAt(h8) => {
288                                    svc.notify_unknown_at(h8);
289                                }
290                                VerifyEvent::InlineP2pcdRequest(h3s) => {
291                                    svc.notify_inline_p2pcd_request(h3s);
292                                }
293                                VerifyEvent::ReceivedCaCertificate(cert) => {
294                                    svc.notify_received_ca_certificate(cert.as_ref().clone());
295                                }
296                            }
297                        }
298                        result
299                    };
300
301                    if confirm.report == ReportVerify::Success {
302                        println!(
303                            "[SEC RX] Verified OK — ITS-AID={}, cert={:02x?}",
304                            confirm.its_aid,
305                            &confirm.certificate_id[..],
306                        );
307                        // Rebuild the packet: BasicHeader(nh=CommonHeader) + plain_message
308                        let mut new_bh = bh;
309                        new_bh.nh = BasicNH::CommonHeader;
310                        let plain_packet: Vec<u8> = new_bh
311                            .encode()
312                            .iter()
313                            .copied()
314                            .chain(confirm.plain_message.iter().copied())
315                            .collect();
316                        gn_h_rx.send_incoming_packet(plain_packet);
317                    } else {
318                        eprintln!("[SEC RX] Verification failed: {:?}", confirm.report);
319                    }
320                }
321                _ => {
322                    // Non-secured packet — forward directly
323                    gn_h_rx.send_incoming_packet(packet);
324                }
325            }
326        }
327    });
328
329    // ── GN → BTP ─────────────────────────────────────────────────────────
330    let btp_h1 = btp_handle.clone();
331    thread::spawn(move || {
332        while let Ok(ind) = gn_to_btp_rx.recv() {
333            btp_h1.send_gn_data_indication(ind);
334        }
335    });
336
337    // ── BTP → GN ─────────────────────────────────────────────────────────
338    let gn_h2 = gn_handle.clone();
339    thread::spawn(move || {
340        while let Ok(req) = btp_to_gn_rx.recv() {
341            gn_h2.send_gn_data_request(req);
342        }
343    });
344
345    // ── LocationService → GN position vector ─────────────────────────────
346    let gn_h3 = gn_handle.clone();
347    thread::spawn(move || {
348        while let Ok(fix) = gn_gps_rx.recv() {
349            let mut epv = LongPositionVector::decode([0u8; 24]);
350            epv.update_from_gps(
351                fix.latitude,
352                fix.longitude,
353                fix.speed_mps,
354                fix.heading_deg,
355                fix.pai,
356            );
357            gn_h3.update_position_vector(epv);
358        }
359    });
360
361    // ── VRU Awareness Service ────────────────────────────────────────────
362    let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]);
363    let device_data = DeviceData {
364        station_id,
365        station_type: 2, // cyclist
366    };
367
368    let (vru_svc, vam_rx) = VruAwarenessService::new(btp_handle.clone(), device_data);
369    vru_svc.start(vru_gps_rx);
370
371    // ── Decoded VAM printer ──────────────────────────────────────────────
372    thread::spawn(move || {
373        while let Ok(vam) = vam_rx.recv() {
374            let lat = vam
375                .vam
376                .vam_parameters
377                .basic_container
378                .reference_position
379                .latitude
380                .0 as f64
381                / 1e7;
382            let lon = vam
383                .vam
384                .vam_parameters
385                .basic_container
386                .reference_position
387                .longitude
388                .0 as f64
389                / 1e7;
390            println!(
391                "[VAM RX] station={:>10}  lat={:.5}  lon={:.5}",
392                vam.header.0.station_id.0, lat, lon,
393            );
394        }
395    });
396
397    // ── GPS publisher (simulates a VRU GNSS sensor at 10 Hz) ─────────────
398    thread::sleep(Duration::from_millis(100));
399    println!("Publishing GPS fixes @ 10 Hz — Ctrl+C to stop\n");
400
401    loop {
402        thread::sleep(Duration::from_millis(100));
403        // 41.552°N  2.134°E — Parc Tecnològic del Vallès
404        loc_svc.publish(GpsFix {
405            latitude: 41.552,
406            longitude: 2.134,
407            altitude_m: 120.0,
408            speed_mps: 1.5,
409            heading_deg: 90.0,
410            pai: true,
411        });
412    }
413}