diff --git a/README.md b/README.md index aff59ca..ea8660b 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,10 @@ SPDX-License-Identifier: GPL-3.0-or-later # SSH Stamp -Sponsored by: - -![nlnet_zero_commons][nlnet_zero_commons] - -# ⚠️ WARNING: Pre-alpha PoC quality, DO NOT use in production. Currently contains highly unsafe business logic auth issues (both password and key management handlers need to be fixed). - -# ⚠️ WARNING: Do not file CVEs reports since deficiencies are very much known at this point in time and they'll be worked on soon as part of [this NLNet SSH-Stamp research and development grant][nlnet-grant] ;) - -Expect panics, lost bytes on the UART and other tricky UX issues, we are working on it, pull-requests are accepted too! +Your everyday SSH secured serial access. ## Description -Your everyday SSH secured serial access. - The **SSH Stamp** is a secure wireless to UART bridge implemented in Rust (no_std, no_alloc and no_unsafe whenever possible) with simplicity and robustness as its main design tenets. @@ -149,6 +139,10 @@ cargo install cargo-cyclonedx cargo cyclonedx -f json --manifest-path ./docs/ ``` +Sponsored by: + +![nlnet_zero_commons][nlnet_zero_commons] + [nlnet-grant]: https://nlnet.nl/project/SSH-Stamp/ [openwrt_mediatek_no_monitor]: https://github.com/openwrt/openwrt/issues/16279 [nlnet_zero_commons]: ./docs/nlnet/zero_commons_logo.svg diff --git a/src/config.rs b/src/config.rs index b606cb5..8a8559f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,36 +1,30 @@ -use log::{debug, info}; +use log::{debug, info, warn}; use core::net::Ipv4Addr; #[cfg(feature = "ipv6")] use core::net::Ipv6Addr; +use core::str::FromStr; use embassy_net::{Ipv4Cidr, StaticConfigV4}; #[cfg(feature = "ipv6")] use embassy_net::{Ipv6Cidr, StaticConfigV6}; use heapless::String; -use bcrypt; -use hmac::{Hmac, Mac}; -use sha2::Sha256; -use subtle::ConstantTimeEq; - use sunset::packets::Ed25519PubKey; -use sunset::{KeyType, Result, sshwire}; +use sunset::{KeyType, Result}; use sunset::{ SignKey, sshwire::{SSHDecode, SSHEncode, SSHSink, SSHSource, WireError, WireResult}, }; +use crate::errors::Error; use crate::settings::{DEFAULT_SSID, DEFAULT_UART_RX_PIN, DEFAULT_UART_TX_PIN, KEY_SLOTS}; #[derive(Debug, PartialEq)] pub struct SSHStampConfig { - //pub first_boot: bool, pub hostkey: SignKey, - /// Authentication - pub password_authentication: bool, - pub admin_pw: Option, - pub admin_keys: [Option; KEY_SLOTS], + /// Authentication: only pubkey-based auth supported + pub pubkeys: [Option; KEY_SLOTS], /// WiFi pub wifi_ssid: String<32>, @@ -46,6 +40,8 @@ pub struct SSHStampConfig { pub ipv6_static: Option, /// UART pub uart_pins: UartPins, + /// True until the device has been provisioned for the first time. + pub first_boot: bool, } #[derive(Debug, PartialEq)] @@ -66,7 +62,7 @@ impl Default for UartPins { impl SSHStampConfig { /// Bump this when the format changes - pub const CURRENT_VERSION: u8 = 6; + pub const CURRENT_VERSION: u8 = 8; /// Creates a new config with default parameters. /// @@ -74,7 +70,6 @@ impl SSHStampConfig { pub fn new() -> Result { let hostkey = SignKey::generate(KeyType::Ed25519, None)?; - // TODO: Those env events come from system's std::env / core::env (if any)... so it shouldn't be unsafe() let wifi_ssid = Self::default_ssid(); let mac = random_mac()?; let wifi_pw = None; @@ -85,11 +80,12 @@ impl SSHStampConfig { uart_pins.rx, uart_pins.tx ); + // No password config fields nor logic: only pubkey auth supported. + // Leave password fields out (except wifi one). + Ok(SSHStampConfig { hostkey, - password_authentication: true, - admin_pw: None, - admin_keys: Default::default(), + pubkeys: Default::default(), wifi_ssid, wifi_pw, mac, @@ -97,21 +93,11 @@ impl SSHStampConfig { #[cfg(feature = "ipv6")] ipv6_static: None, uart_pins, + first_boot: true, }) } - pub fn set_admin_pw(&mut self, pw: Option<&str>) -> Result<()> { - self.admin_pw = pw.map(PwHash::new).transpose()?; - Ok(()) - } - - pub fn check_admin_pw(&mut self, pw: &str) -> bool { - if let Some(ref p) = self.admin_pw { - p.check(pw) - } else { - false - } - } + // Password functions removed; pubkey-only auth supported. pub(crate) fn default_ssid() -> String<32> { let mut s = String::<32>::new(); @@ -119,9 +105,43 @@ impl SSHStampConfig { s } - // pub fn config_change(&mut self, conf: SSHConfig) -> Result<()> { - // ServEvent::ConfigChange(); - // } + pub(crate) fn add_pubkey(&mut self, key_str: &str) -> Result<(), Error> { + // Accept OpenSSH public key format (e.g. "ssh-ed25519 AAAA...") and + // validate it is an Ed25519 key. Insert into the first empty slot or + // overwrite slot 0 if none empty. + + info!( + "Checking pubkey string passed through ENV: {}", + key_str.trim() + ); + + let openssh = ssh_key::PublicKey::from_str(key_str.trim())?; + + info!("Public key format valid, continuing to parse"); + + match openssh.key_data() { + ssh_key::public::KeyData::Ed25519(k) => { + let bytes = k.0; // [u8; 32] + let newk = Ed25519PubKey { + key: sunset::sshwire::Blob(bytes), + }; + + info!("Parsed Ed25519 public key, adding to config"); + for slot in self.pubkeys.iter_mut() { + if slot.is_none() { + *slot = Some(newk); + return Ok(()); + } + } + + warn!("Public key slots full, overwriting the first one"); + // SECURITY: Allow this on FirstAuth ON FIRST BOOT ONLY. + self.pubkeys[0] = Some(newk); + Ok(()) + } + _ => Err(Error::BadKey), + } + } } fn random_mac() -> Result<[u8; 6]> { @@ -253,11 +273,7 @@ impl SSHEncode for SSHStampConfig { fn enc(&self, s: &mut dyn SSHSink) -> WireResult<()> { enc_signkey(&self.hostkey, s)?; - // Authentication - self.password_authentication.enc(s)?; - enc_option(&self.admin_pw, s)?; - - for k in self.admin_keys.iter() { + for k in self.pubkeys.iter() { enc_option(k, s)?; } @@ -273,6 +289,9 @@ impl SSHEncode for SSHStampConfig { self.uart_pins.rx.enc(s)?; self.uart_pins.tx.enc(s)?; + // Persist first-boot marker + self.first_boot.enc(s)?; + Ok(()) } } @@ -284,11 +303,8 @@ impl<'de> SSHDecode<'de> for SSHStampConfig { { let hostkey = dec_signkey(s)?; - // Authentication - let password_authentication = SSHDecode::dec(s)?; - let admin_pw = dec_option(s)?; - let mut admin_keys = [None; KEY_SLOTS]; - for k in admin_keys.iter_mut() { + let mut pubkeys = [None; KEY_SLOTS]; + for k in pubkeys.iter_mut() { *k = dec_option(s)?; } @@ -307,11 +323,11 @@ impl<'de> SSHDecode<'de> for SSHStampConfig { let tx: u8 = SSHDecode::dec(s)?; let uart_pins = UartPins { rx, tx }; + let first_boot = SSHDecode::dec(s)?; + Ok(Self { hostkey, - password_authentication, - admin_pw, - admin_keys, + pubkeys, wifi_ssid, wifi_pw, mac, @@ -319,97 +335,7 @@ impl<'de> SSHDecode<'de> for SSHStampConfig { #[cfg(feature = "ipv6")] ipv6_static, uart_pins, + first_boot, }) } } - -/// Stores a bcrypt password hash. -/// -/// We use bcrypt because it seems the best password hashing option where -/// memory hardness isn't possible (the rp2040 is smaller than CPU or GPU memory). -/// -/// The cost is currently set to 6, taking ~500ms on a 125mhz rp2040. -/// Time converges to roughly 8.6ms * 2**cost -/// -/// Passwords are pre-hashed to avoid bcrypt's 72 byte limit. -/// rust-bcrypt allows nulls in passwords. -/// We use an hmac rather than plain hash to avoid password shucking -/// (an attacker bcrypts known hashes from some other breach, then -/// brute forces the weaker hash for any that match). -//#[derive(Clone, SSHEncode, SSHDecode, PartialEq)] -#[derive(Clone, PartialEq)] -pub struct PwHash { - salt: [u8; 16], - hash: [u8; 24], - cost: u8, -} - -impl PwHash { - const COST: u8 = 6; - /// `pw` must not be empty. - pub fn new(pw: &str) -> Result { - if pw.is_empty() { - return sunset::error::BadUsage.fail(); - } - - let mut salt = [0u8; 16]; - sunset::random::fill_random(&mut salt)?; - let prehash = Self::prehash(pw, &salt); - let cost = Self::COST; - let hash = bcrypt::bcrypt(cost as u32, salt, &prehash); - Ok(Self { salt, hash, cost }) - } - - pub fn check(&self, pw: &str) -> bool { - if pw.is_empty() { - return false; - } - let prehash = Self::prehash(pw, &self.salt); - let check_hash = bcrypt::bcrypt(self.cost as u32, self.salt, &prehash); - check_hash.ct_eq(&self.hash).into() - } - - fn prehash(pw: &str, salt: &[u8]) -> [u8; 32] { - // OK unwrap: can't fail, accepts any length - // TODO: Generalise, not only Espressif esp_hal - let mut prehash = Hmac::::new_from_slice(salt).unwrap(); - prehash.update(pw.as_bytes()); - prehash.finalize().into_bytes().into() - } -} - -impl core::fmt::Debug for PwHash { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("PwHash").finish_non_exhaustive() - } -} - -impl SSHEncode for PwHash { - fn enc(&self, s: &mut dyn SSHSink) -> WireResult<()> { - self.salt.enc(s)?; - self.hash.enc(s)?; - self.cost.enc(s) - } -} - -impl<'de> SSHDecode<'de> for PwHash { - fn dec(s: &mut S) -> WireResult - where - S: SSHSource<'de>, - { - let salt = <[u8; 16]>::dec(s)?; - let hash = <[u8; 24]>::dec(s)?; - let cost = u8::dec(s)?; - Ok(PwHash { salt, hash, cost }) - } -} - -pub fn roundtrip_config() { - // default config - let c1 = SSHStampConfig::new().unwrap(); - let mut buf = [0u8; 1000]; - let l = sshwire::write_ssh(&mut buf, &c1).unwrap(); - let v = &buf[..l]; - let c2: SSHStampConfig = sshwire::read_ssh(v, None).unwrap(); - assert_eq!(c1, c2); -} diff --git a/src/errors.rs b/src/errors.rs index 8b69a87..6baddc6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -9,4 +9,12 @@ pub enum Error { InvalidPin, #[snafu(display("Flash storage error"))] FlashStorageError, + BadKey, + OpenSSHParseError, +} + +impl From for Error { + fn from(_e: ssh_key::Error) -> Error { + Error::OpenSSHParseError + } } diff --git a/src/espressif/buffered_uart.rs b/src/espressif/buffered_uart.rs index 9ae63d7..84231cb 100644 --- a/src/espressif/buffered_uart.rs +++ b/src/espressif/buffered_uart.rs @@ -16,7 +16,6 @@ use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe}; use esp_hal::Async; use esp_hal::gpio::AnyPin; use esp_hal::peripherals::UART1; -use esp_hal::system::software_reset; use esp_hal::uart::{Config, RxConfig, Uart}; use portable_atomic::{AtomicUsize, Ordering}; use static_cell::StaticCell; @@ -133,17 +132,15 @@ impl Default for BufferedUart { pub async fn uart_buffer_disable() -> () { // disable uart buffer - info!("UART buffer disabled"); + info!("UART buffer disabled: WIP"); // TODO: Correctly disable/restart UART buffer and/or send messsage to user over SSH - software_reset(); } // use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; pub async fn uart_disable() -> () { // disable uart - info!("UART disabled"); + info!("UART disabled: WIP"); // TODO: Correctly disable/restart UART and/or send messsage to user over SSH - software_reset(); } /// UART pins for the buffered UART task. diff --git a/src/espressif/net.rs b/src/espressif/net.rs index 899c23d..0aa136e 100644 --- a/src/espressif/net.rs +++ b/src/espressif/net.rs @@ -21,7 +21,6 @@ use embassy_net::{Stack, StackResources, tcp::TcpSocket}; use embassy_time::{Duration, Timer}; use esp_hal::peripherals::WIFI; use esp_hal::rng::Rng; -use esp_hal::system::software_reset; use esp_radio::Controller; use esp_radio::wifi::WifiEvent; use esp_radio::wifi::{AccessPointConfig, ModeConfig, WifiApState, WifiController}; @@ -95,16 +94,14 @@ pub async fn if_up( pub async fn ap_stack_disable() -> () { // drop ap_stack - info!("AP Stack disabled"); + info!("AP Stack disabled: WIP"); // TODO: Correctly disable/restart AP Stack and/or send messsage to user over SSH - software_reset(); } pub async fn tcp_socket_disable() -> () { // drop tcp stack - info!("TCP socket disabled"); + info!("TCP socket disabled: WIP"); // TODO: Correctly disable/restart tcp socket and/or send messsage to user over SSH - software_reset(); } pub async fn accept_requests<'a>( @@ -186,7 +183,8 @@ pub async fn wifi_controller_disable() -> () { // drop wifi controller // esp_wifi::deinit_unchecked() // wifi_controller.deinit_unchecked() - software_reset(); + info!("Disabling wifi: WIP"); + //software_reset(); } use esp_radio::wifi::WifiDevice; diff --git a/src/main.rs b/src/main.rs index 2be296c..43692bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,7 +50,7 @@ cfg_if::cfg_if! { pub async fn peripherals_disable() -> () { // drop peripherals - software_reset(); + info!("Disabling peripherals: WIP"); } pub struct SshStampInit<'a> { @@ -98,9 +98,6 @@ async fn main(spawner: Spawner) -> ! { panic!("Could not acquire flash storage lock"); }; let mut flash_storage = flash_storage_guard.lock().await; - // TODO: Migrate this function/test to embedded-test. - // Quick roundtrip test for SSHStampConfig - // ssh_stamp::config::roundtrip_config(); ssh_stamp::store::load_or_create(&mut flash_storage).await } .expect("Could not load or create SSHStampConfig"); @@ -191,7 +188,8 @@ async fn main(spawner: Spawner) -> ! { } peripherals_disable().await; - // loop{} + // loop {} + log::warn!("End of Main... Reset!!"); software_reset(); } diff --git a/src/serve.rs b/src/serve.rs index c158a52..b9da76f 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -2,27 +2,28 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -use log::{debug, info}; +use log::{debug, info, trace, warn}; use crate::config::SSHStampConfig; +use crate::espressif::buffered_uart::UART_SIGNAL; use crate::settings::UART_BUFFER_SIZE; use crate::store; +use storage::flash; + +use core::fmt::Debug; use core::option::Option::{self, None, Some}; use core::result::Result; -use storage::flash; + // Embassy use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::Channel; -use embassy_sync::mutex::Mutex; -// use embedded_storage::Storage; -use esp_hal::system::software_reset; -use heapless::String; -use sunset_async::SunsetMutex; -// use sunset::sshwire::SSHEncode; -use crate::espressif::buffered_uart::UART_SIGNAL; + +// Sunset use sunset::{ChanHandle, ServEvent, error}; +use sunset_async::SunsetMutex; use sunset_async::{ProgressHolder, SSHServer}; +#[derive(Debug)] pub enum SessionType { Bridge(ChanHandle), #[cfg(feature = "sftp-ota")] @@ -34,56 +35,70 @@ pub async fn connection_loop( chan_pipe: &Channel, config: &SunsetMutex, ) -> Result<(), sunset::Error> { - let username = Mutex::::new(String::<20>::new()); let mut session: Option = None; debug!("Entering connection_loop and prog_loop is next..."); let mut config_changed: bool = false; + + // Will be set in `ev` PubkeyAuth is accepted and cleared once the channel is sent down chan_pipe + let mut auth_checked = false; + loop { let mut ph = ProgressHolder::new(); let ev = serv.progress(&mut ph).await?; - // debug!(&ev); - #[allow(unreachable_patterns)] + + trace!("{:?}", &ev); match ev { // #[cfg(feature = "sftp-ota")] ServEvent::SessionSubsystem(a) => { info!("ServEvent::SessionSubsystem"); - if a.command()?.to_lowercase().as_str() == "sftp" { + + if !auth_checked { + warn!("Unauthenticated SessionSubsystem rejected"); + a.fail()?; + // TODO: Handle this gracefully + // TODO: Provide a message back to the client and the close the session? + //software_reset(); + } else if a.command()?.to_lowercase().as_str() == "sftp" { if let Some(ch) = session.take() { debug_assert!(ch.num() == a.channel()); #[cfg(feature = "sftp-ota")] { a.succeed()?; info!("We got SFTP subsystem"); - let _ = chan_pipe.try_send(SessionType::Sftp(ch)); + match chan_pipe.try_send(SessionType::Sftp(ch)) { + Ok(_) => auth_checked = false, + Err(e) => error!("Could not send the channel: {:?}", e), + }; } #[cfg(not(feature = "sftp-ota"))] { - use log::warn; - warn!("SFTP subsystem requested but not supported in this build"); a.fail()?; } } else { a.fail()?; } - } else { - a.fail()?; } } ServEvent::SessionShell(a) => { info!("ServEvent::SessionShell"); - if let Some(ch) = session.take() { + + if !auth_checked { + warn!("Unauthenticated SessionShell rejected"); + a.fail()?; + // TODO: Handle this gracefully + // TODO: Provide a message back to the client and the close the session? + //software_reset(); + } else if let Some(ch) = session.take() { // Save config after connection successful (SessionEnv completed) if config_changed { + config_changed = false; // TODO: Avoid unnecessary "does not neet to be mutable" warnings for now let config_guard = config.lock().await; let Some(flash_storage_guard) = flash::get_flash_n_buffer() else { panic!("Could not acquire flash storage lock"); }; let mut flash_storage = flash_storage_guard.lock().await; - // TODO: Migrate this function/test to embedded-test. - // Quick roundtrip test for SSHStampConfig - // ssh_stamp::config::roundtrip_config(); let _result = store::save(&mut flash_storage, &config_guard).await; } debug_assert!(ch.num() == a.channel()); @@ -92,30 +107,75 @@ pub async fn connection_loop( // Signal for uart task to configure pins and run. Value is irrelevant. UART_SIGNAL.signal(1); info!("Connection loop: UART_SIGNAL sent"); - let _ = chan_pipe.try_send(SessionType::Bridge(ch)); + match chan_pipe.try_send(SessionType::Bridge(ch)) { + Ok(_) => auth_checked = false, + Err(e) => log::error!("Could not send the channel: {:?}", e), + }; } else { a.fail()?; } } - ServEvent::FirstAuth(ref a) => { + ServEvent::FirstAuth(mut a) => { info!("ServEvent::FirstAuth"); - // record the username - if username.lock().await.push_str(a.username()?).is_err() { - info!("Too long username") + // Allow the "first auth" behaviour only on first-boot-like configs. + // Consider the device in first-boot state when there is no password + // and no stored client pubkeys. + let config_guard = config.lock().await; + + // Disable password auth method regardless. + a.enable_password_auth(false)?; + + // SECURITY: We have no users; enable pubkey auth so the + // provisioner can add a key. + a.enable_pubkey_auth(true)?; + if config_guard.first_boot { + a.allow()?; // SECURITY: Controversial (but necessary to provision?) + } else { + // Not first boot: do not auto-allow; reject the first-auth helper. + info!( + "FirstAuth received but not first-boot, allowing pubkey auth but rejecting + additions of new public keys on already provisioned device" + ); + a.reject()?; } } ServEvent::Hostkeys(h) => { info!("ServEvent::Hostkeys"); let config_guard = config.lock().await; + // Just take it from config as private hostkey is generated on first boot. h.hostkeys(&[&config_guard.hostkey])?; } ServEvent::PasswordAuth(a) => { - info!("ServEvent::PasswordAuth"); - a.allow()?; + warn!("Password auth is not supported, use public key auth instead."); + a.reject()?; } ServEvent::PubkeyAuth(a) => { info!("ServEvent::PubkeyAuth"); - a.allow()?; + let config_guard = config.lock().await; + let client_pubkey = a.pubkey()?; + + match client_pubkey { + sunset::packets::PubKey::Ed25519(presented) => { + let matched = config_guard + .pubkeys + .iter() + .any(|slot| slot.as_ref().is_some_and(|stored| *stored == presented)); + + if matched { + a.allow()?; + auth_checked = true; + } else { + info!("No matching pubkey slot found"); + a.reject()?; + //software_reset(); // TODO: Handle better HSM-flow-wise. + } + } + _ => { + // Only Ed25519 keys supported + a.reject()?; + //software_reset(); // TODO: Handle better HSM-flow-wise. + } + } } ServEvent::OpenSession(a) => { info!("ServEvent::OpenSession"); @@ -134,45 +194,38 @@ pub async fn connection_loop( debug!("ENV name: {}", a.name()?); debug!("ENV value: {}", a.value()?); - // TODO: Logic to serialise/validate env vars? I.e: - // a.name.validate(); // Checks the input variable, sanitizes, assigns a target subsystem - // - // config.change(c): Apply the config change to the relevant subsystem. - // i.e: if UART_TX_PIN or UART_RX_PIN, we update the PinChannel with with_channel() to change pins live. match a.name()? { - "SAVE_CONFIG" => { - if a.value()? == "1" { - debug!("Triggering config save..."); - todo!("Implement config save to flash"); - } + "LANG" => { + // Ignore, but succeed to avoid client-side warnings + // This env variable will always be sent by OpenSSH client. + a.succeed()?; } - // If the env var is UART_TX_PIN or UART_RX_PIN - "UART_TX_PIN" => { - let val = a.value()?; - debug!("Updating UART TX pin to {}", val); - if let Ok(pin_num) = val.parse::() { - let mut config_lock = config.lock().await; - config_lock.uart_pins.tx = pin_num; - config_changed = true; - debug!("TX pin updated"); - } else { - debug!("Invalid TX pin value"); - } - } - "UART_RX_PIN" => { - let val = a.value()?; - debug!("Updating UART RX pin to {}", val); - if let Ok(pin_num) = val.parse::() { - let mut config_lock = config.lock().await; - config_lock.uart_pins.rx = pin_num; + "SSH_STAMP_PUBKEY" => { + let mut config_guard = config.lock().await; + // Only allow adding a pubkey via ENV on first-boot-like configs. + + if !config_guard.first_boot { + warn!("SSH_STAMP_PUBKEY env received but not first-boot; rejecting"); + a.fail()?; + break Ok(()); // TODO: Do better HSM-flow-wise + } else if config_guard.add_pubkey(a.value()?).is_ok() { + info!("Added new pubkey from ENV"); + a.succeed()?; + // Mark that config has changed and clear first_boot so + // future connections are not treated as first-boot. + config_guard.first_boot = false; config_changed = true; - debug!("RX pin updated"); + auth_checked = true; + // Don't immediately allow the new user/key to establish bridge but reboot first? + //software_reset(); // TODO: Do better HSM-flow-wise. } else { - debug!("Invalid RX pin value"); + warn!("Failed to add new pubkey from ENV"); + a.fail()?; } } _ => { - debug!("Unknown/unsupported ENV var"); + warn!("Unsupported environment variable"); + a.fail()?; } } @@ -180,32 +233,38 @@ pub async fn connection_loop( // that serialises current config to flash // Only save once all ENV requests have been recorded? - a.succeed()?; + //a.succeed()?; } ServEvent::SessionPty(a) => { - info!("ServEvent::SessionPty"); - a.succeed()?; + let first_boot = { config.lock().await.first_boot }; + + if auth_checked || first_boot { + info!("ServEvent::SessionPty: Session granted"); + a.succeed()?; + } else { + info!("ServEvent::SessionPty: No auth not session"); + a.fail()?; + } } ServEvent::SessionExec(a) => { a.fail()?; } - ServEvent::Defunct | ServEvent::SessionShell(_) => { + ServEvent::Defunct => { info!("Expected caller to handle event"); error::BadUsage.fail()? } ServEvent::PollAgain => { // info!("ServEvent::PollAgain"); } - _ => (), } } } pub async fn connection_disable() -> () { // disable connection loop - info!("Connection loop disabled"); + info!("Connection loop disabled: WIP"); // TODO: Correctly disable/restart Conection loop and/or send messsage to user over SSH - software_reset(); + // software_reset(); } pub async fn ssh_wait_for_initialisation<'server>( @@ -217,14 +276,13 @@ pub async fn ssh_wait_for_initialisation<'server>( pub async fn ssh_disable() -> () { // drop ssh server - info!("SSH Server disabled"); + info!("SSH Server disabled: WIP"); // TODO: Correctly disable/restart SSH Server and/or send messsage to user over SSH - software_reset(); + // software_reset(); } use crate::espressif::buffered_uart::BufferedUart; use crate::serial::serial_bridge; -use sunset_async::ChanInOut; pub async fn handle_ssh_client<'a, 'b>( uart_buff: &'a BufferedUart, @@ -237,10 +295,9 @@ pub async fn handle_ssh_client<'a, 'b>( match session_type { SessionType::Bridge(ch) => { info!("Handling bridge session"); - let stdio: ChanInOut<'_> = ssh_server.stdio(ch).await?; - let stdio2 = stdio.clone(); + let (stdin, stdout) = ssh_server.stdio(ch).await?.split(); info!("Starting bridge"); - serial_bridge(stdio, stdio2, uart_buff).await? + serial_bridge(stdin, stdout, uart_buff).await? } #[cfg(feature = "sftp-ota")] SessionType::Sftp(ch) => { @@ -258,7 +315,7 @@ pub async fn handle_ssh_client<'a, 'b>( pub async fn bridge_disable() -> () { // disable bridge - info!("Bridge disabled"); + info!("Bridge disabled: WIP"); // TODO: Correctly disable/restart bridge and/or send message to user over SSH - software_reset(); + // software_reset(); } diff --git a/src/settings.rs b/src/settings.rs index 61ca007..f3bdef4 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -8,13 +8,13 @@ use core::net::Ipv4Addr; pub(crate) const DEFAULT_SSID: &str = "ssh-stamp"; //pub(crate) const SSH_SERVER_ID: &str = "SSH-2.0-ssh-stamp-0.1"; pub(crate) const KEY_SLOTS: usize = 1; // TODO: Document whether this a "reasonable default"? Justify why? -//pub(crate) const PASSWORD_AUTHENTICATION: bool = true; pub(crate) const DEFAULT_IP: &Ipv4Addr = &Ipv4Addr::new(192, 168, 4, 1); -pub const UART_BUFFER_SIZE: usize = 4096; // UART settings //pub(crate) const BAUD_RATE: u32 = 115200; //pub(crate) const UART_SETTINGS: &str = "8N1"; +pub const UART_BUFFER_SIZE: usize = 4096; + cfg_if::cfg_if!( // if #[cfg(feature = "esp32")] { if #[cfg(feature = "esp32")] {