1use log::{debug, warn};
18
19use core::net::Ipv4Addr;
20#[cfg(feature = "ipv6")]
21use core::net::Ipv6Addr;
22use core::str::FromStr;
23use embassy_net::{Ipv4Cidr, StaticConfigV4};
24#[cfg(feature = "ipv6")]
25use embassy_net::{Ipv6Cidr, StaticConfigV6};
26use heapless::String;
27use ssh_key::PublicKey;
28use ssh_key::public::KeyData;
29use ssh_stamp_hal::UartParams;
30
31use sunset::packets::Ed25519PubKey;
32use sunset::{KeyType, Result};
33use sunset::{
34 SignKey,
35 sshwire::{Blob, SSHDecode, SSHEncode, SSHSink, SSHSource, WireError, WireResult},
36};
37
38use crate::errors::Error;
39use crate::settings::{KEY_SLOTS, WIFI_PASSWORD_CHARS};
40
41#[derive(Debug, PartialEq)]
42pub struct SSHStampConfig {
43 pub hostkey: SignKey,
44
45 pub pubkeys: [Option<Ed25519PubKey>; KEY_SLOTS],
47
48 pub wifi_ap_ssid: String<32>,
51 pub wifi_ap_pw: String<63>,
52 pub wifi_ap_band: u8,
54 pub wifi_sta_ssid: String<32>,
56 pub wifi_sta_pw: String<63>,
57 pub mac: [u8; 6],
62 pub ipv4_static: Option<StaticConfigV4>,
64 #[cfg(feature = "ipv6")]
65 pub ipv6_static: Option<StaticConfigV6>,
66 pub uart_pins: UartPins,
68 pub uart_params: UartParams,
71 pub first_login: bool,
73}
74
75#[derive(Debug, PartialEq)]
82pub struct UartPins {
83 pub rx: u8,
84 pub tx: u8,
85}
86
87const MAC_RANDOM_SENTINEL: [u8; 6] = [0xFF; 6];
88
89impl SSHStampConfig {
90 pub const CURRENT_VERSION: u8 = 12;
92
93 #[must_use]
95 pub fn is_mac_random(&self) -> bool {
96 self.mac == MAC_RANDOM_SENTINEL
97 }
98
99 pub fn resolve_mac(&self) -> Result<[u8; 6]> {
103 if self.is_mac_random() {
104 random_mac()
105 } else {
106 Ok(self.mac)
107 }
108 }
109
110 pub fn new(default_mac: [u8; 6], uart_pins: UartPins) -> Result<Self> {
122 let hostkey = SignKey::generate(KeyType::Ed25519, None)?;
123
124 let wifi_ap_ssid = Self::generate_wifi_ssid()?;
126 let wifi_ap_pw = Self::generate_wifi_password()?;
127 let wifi_ap_band = 0; let wifi_sta_ssid = String::<32>::new();
130 let wifi_sta_pw = String::<63>::new();
131 let mac = default_mac;
132
133 debug!(
134 "SSH Stamp Config new() - RX Pin: {} TX Pin: {}",
135 uart_pins.rx, uart_pins.tx
136 );
137
138 Ok(SSHStampConfig {
139 hostkey,
140 pubkeys: Default::default(),
141 wifi_ap_ssid,
142 wifi_ap_pw,
143 wifi_ap_band,
144 wifi_sta_ssid,
145 wifi_sta_pw,
146 mac,
147 ipv4_static: None,
148 #[cfg(feature = "ipv6")]
149 ipv6_static: None,
150 uart_pins,
151 uart_params: UartParams::default(),
152 first_login: true,
153 })
154 }
155
156 pub(crate) fn generate_wifi_ssid() -> Result<String<32>> {
157 let mut rnd = [0u8; 16];
158 getrandom::fill(&mut rnd).map_err(|_| sunset::Error::msg("RNG failed"))?;
159 let mut ssid = String::<32>::new();
160 for &byte in &rnd {
161 let _ = ssid.push(WIFI_PASSWORD_CHARS[(byte as usize) % 62] as char);
162 }
163 Ok(ssid)
164 }
165
166 pub(crate) fn generate_wifi_password() -> Result<String<63>> {
167 let mut rnd = [0u8; 24];
168 getrandom::fill(&mut rnd).map_err(|_| sunset::Error::msg("RNG failed"))?;
169 let mut pw = String::<63>::new();
170 for &byte in &rnd {
171 let _ = pw.push(WIFI_PASSWORD_CHARS[(byte as usize) % 62] as char);
172 }
173 Ok(pw)
174 }
175
176 pub(crate) fn add_pubkey(&mut self, key_str: &str) -> Result<(), Error> {
179 debug!(
184 "Checking pubkey string passed through ENV: {}",
185 key_str.trim()
186 );
187
188 let openssh = PublicKey::from_str(key_str.trim())?;
189
190 debug!("Public key format valid, continuing to parse");
191
192 match openssh.key_data() {
193 KeyData::Ed25519(k) => {
194 let bytes = k.0; let newk = Ed25519PubKey { key: Blob(bytes) };
196
197 debug!("Parsed Ed25519 public key, adding to config");
198 for slot in &mut self.pubkeys {
199 if slot.is_none() {
200 *slot = Some(newk);
201 return Ok(());
202 }
203 }
204
205 warn!("Public key slots full, overwriting the first one");
206 self.pubkeys[0] = Some(newk);
208 Ok(())
209 }
210 _ => Err(Error::BadKey),
211 }
212 }
213}
214
215fn random_mac() -> Result<[u8; 6]> {
216 let mut mac = [0u8; 6];
217 getrandom::fill(&mut mac).map_err(|_| sunset::Error::msg("RNG failed"))?;
218 mac[0] = (mac[0] & 0xfc) | 0x02;
220 Ok(mac)
221}
222
223fn enc_signkey(k: &SignKey, s: &mut dyn SSHSink) -> WireResult<()> {
225 match k {
227 SignKey::Ed25519(k) => k.to_bytes().enc(s),
228 SignKey::AgentEd25519(_) => Err(WireError::UnknownVariant),
229 }
230}
231
232fn dec_signkey<'de, S>(s: &mut S) -> WireResult<SignKey>
233where
234 S: SSHSource<'de>,
235{
236 let k: ed25519_dalek::SecretKey = SSHDecode::dec(s)?;
237 let k = ed25519_dalek::SigningKey::from_bytes(&k);
238 Ok(SignKey::Ed25519(k))
239}
240
241pub(crate) fn enc_option<T: SSHEncode>(v: Option<&T>, s: &mut dyn SSHSink) -> WireResult<()> {
243 v.is_some().enc(s)?;
244 if let Some(v) = v {
245 v.enc(s)?;
246 }
247 Ok(())
248}
249
250pub(crate) fn dec_option<'de, S, T: SSHDecode<'de>>(s: &mut S) -> WireResult<Option<T>>
251where
252 S: SSHSource<'de>,
253{
254 bool::dec(s)?.then(|| SSHDecode::dec(s)).transpose()
255}
256
257fn enc_ipv4_config(v: Option<&StaticConfigV4>, s: &mut dyn SSHSink) -> WireResult<()> {
258 v.is_some().enc(s)?;
259 if let Some(v) = v {
260 v.address.address().to_bits().enc(s)?;
261 debug!("enc_ipv4_config: prefix = {}", v.address.prefix_len());
262 v.address.prefix_len().enc(s)?;
263 let gw = v.gateway.as_ref().map(|g| g.to_bits());
265 enc_option(gw.as_ref(), s)?;
266 }
267 Ok(())
268}
269
270#[cfg(feature = "ipv6")]
271fn enc_ipv6_config(v: Option<&StaticConfigV6>, s: &mut dyn SSHSink) -> WireResult<()> {
272 v.is_some().enc(s)?;
273 if let Some(v) = v {
274 v.address.address().octets().enc(s)?;
275 v.address.prefix_len().enc(s)?;
276 let gw = v.gateway.as_ref().map(core::net::Ipv6Addr::octets);
277 enc_option(gw.as_ref(), s)?;
278 }
279 Ok(())
280}
281
282fn dec_ipv4_config<'de, S>(s: &mut S) -> WireResult<Option<StaticConfigV4>>
283where
284 S: SSHSource<'de>,
285{
286 let opt = bool::dec(s)?;
287 opt.then(|| {
288 let ad: u32 = SSHDecode::dec(s)?;
289 let ad = Ipv4Addr::from_bits(ad);
290 let prefix: u8 = SSHDecode::dec(s)?;
291 if prefix > 32 {
292 return Err(WireError::PacketWrong);
294 }
295 let gw: Option<u32> = dec_option(s)?;
296 let gateway = gw.map(Ipv4Addr::from_bits);
297 Ok(StaticConfigV4 {
298 address: Ipv4Cidr::new(ad, prefix),
299 gateway,
300 dns_servers: Default::default(),
303 })
304 })
305 .transpose()
306}
307
308#[cfg(feature = "ipv6")]
309fn dec_ipv6_config<'de, S>(s: &mut S) -> WireResult<Option<StaticConfigV6>>
310where
311 S: SSHSource<'de>,
312{
313 let opt = bool::dec(s)?;
314 opt.then(|| {
315 let ad: [u8; 16] = SSHDecode::dec(s)?;
316 let ad = Ipv6Addr::from(ad);
317 let prefix = SSHDecode::dec(s)?;
318 if prefix > 32 {
319 return Err(WireError::PacketWrong);
321 }
322 let gw: Option<[u8; 16]> = dec_option(s)?;
323 let gateway = gw.map(Ipv6Addr::from);
324 Ok(StaticConfigV6 {
325 address: Ipv6Cidr::new(ad, prefix),
326 gateway,
327 dns_servers: Default::default(),
328 })
329 })
330 .transpose()
331}
332
333impl SSHEncode for SSHStampConfig {
334 fn enc(&self, s: &mut dyn SSHSink) -> WireResult<()> {
335 enc_signkey(&self.hostkey, s)?;
336
337 for k in &self.pubkeys {
338 enc_option(k.as_ref(), s)?;
339 }
340
341 self.wifi_ap_ssid.as_str().enc(s)?;
343 self.wifi_ap_pw.as_str().enc(s)?;
344 self.wifi_ap_band.enc(s)?;
345 self.wifi_sta_ssid.as_str().enc(s)?;
347 self.wifi_sta_pw.as_str().enc(s)?;
348 self.mac.enc(s)?;
349
350 enc_ipv4_config(self.ipv4_static.as_ref(), s)?;
351 #[cfg(feature = "ipv6")]
352 enc_ipv6_config(self.ipv6_static.as_ref(), s)?;
353
354 self.uart_pins.rx.enc(s)?;
356 self.uart_pins.tx.enc(s)?;
357
358 self.uart_params.baud.enc(s)?;
360 self.uart_params.data_bits.enc(s)?;
361 (self.uart_params.parity as u8).enc(s)?;
362 self.uart_params.stop_bits.enc(s)?;
363
364 self.first_login.enc(s)?;
366
367 Ok(())
368 }
369}
370
371impl<'de> SSHDecode<'de> for SSHStampConfig {
372 fn dec<S>(s: &mut S) -> WireResult<Self>
373 where
374 S: SSHSource<'de>,
375 {
376 let hostkey = dec_signkey(s)?;
377
378 let mut pubkeys = [None; KEY_SLOTS];
379 for k in &mut pubkeys {
380 *k = dec_option(s)?;
381 }
382
383 let wifi_ap_ssid_str: &str = SSHDecode::dec(s)?;
385 let wifi_ap_ssid = String::try_from(wifi_ap_ssid_str).map_err(|_| WireError::BadString)?;
386 let wifi_ap_pw_str: &str = SSHDecode::dec(s)?;
387 let wifi_ap_pw = String::try_from(wifi_ap_pw_str).map_err(|_| WireError::BadString)?;
388 let wifi_ap_band: u8 = SSHDecode::dec(s)?;
389 let wifi_sta_ssid_str: &str = SSHDecode::dec(s)?;
391 let wifi_sta_ssid =
392 String::try_from(wifi_sta_ssid_str).map_err(|_| WireError::BadString)?;
393 let wifi_sta_pw_str: &str = SSHDecode::dec(s)?;
394 let wifi_sta_pw = String::try_from(wifi_sta_pw_str).map_err(|_| WireError::BadString)?;
395
396 let mac = SSHDecode::dec(s)?;
397
398 let ipv4_static = dec_ipv4_config(s)?;
399 #[cfg(feature = "ipv6")]
400 let ipv6_static = dec_ipv6_config(s)?;
401
402 let rx: u8 = SSHDecode::dec(s)?;
405 let tx: u8 = SSHDecode::dec(s)?;
406 let uart_pins = UartPins { rx, tx };
407
408 let baud: u32 = SSHDecode::dec(s)?;
410 let data_bits: u8 = SSHDecode::dec(s)?;
411 let parity: u8 = SSHDecode::dec(s)?;
412 let stop_bits: u8 = SSHDecode::dec(s)?;
413 let uart_params = UartParams {
414 baud,
415 data_bits,
416 parity: parity.into(),
417 stop_bits,
418 };
419
420 let first_login = SSHDecode::dec(s)?;
421
422 Ok(Self {
423 hostkey,
424 pubkeys,
425 wifi_ap_ssid,
426 wifi_ap_pw,
427 wifi_ap_band,
428 wifi_sta_ssid,
429 wifi_sta_pw,
430 mac,
431 ipv4_static,
432 #[cfg(feature = "ipv6")]
433 ipv6_static,
434 uart_pins,
435 uart_params,
436 first_login,
437 })
438 }
439}