diff options
Diffstat (limited to 'oxish/src')
| -rw-r--r-- | oxish/src/authentication.rs | 153 |
1 files changed, 77 insertions, 76 deletions
diff --git a/oxish/src/authentication.rs b/oxish/src/authentication.rs index da64a36..ff238eb 100644 --- a/oxish/src/authentication.rs +++ b/oxish/src/authentication.rs @@ -19,7 +19,7 @@ use std::{ fd::{AsRawFd, FromRawFd}, unix::ffi::OsStrExt, }, - path::{Path, PathBuf}, + path::PathBuf, str, }; @@ -272,7 +272,7 @@ impl DefaultStore { 0 => Box::new(SystemStore) as Box<dyn UserStore>, uid => { let data = User::lookup(UserLookup::Id(uid))?; - let keys = authorized_keys(&data.home_dir, data.id, provider); + let keys = data.authorized_keys(provider); Box::new(SingleUser(CachedUser { data, keys })) } }) @@ -294,7 +294,7 @@ impl UserStore for SystemStore { } fn keys(&self, user: &User, provider: &dyn CryptoProvider) -> Vec<AuthorizedKey> { - authorized_keys(&user.home_dir, user.id, provider) + user.authorized_keys(provider) } fn drop_privileges(&self) -> bool { @@ -468,6 +468,80 @@ impl User { }) } + /// Read and parse the `authorized_keys` file for a user + /// + /// This is pretty finicky because we need to check that + /// + /// - None of the path components have group or other write permissions + /// - Each of the path components are owned by root or the target user + /// - Avoid TOCTOU issues when opening each path component + fn authorized_keys(&self, provider: &dyn CryptoProvider) -> Vec<AuthorizedKey> { + let home_dir = &self.home_dir; + let home = match File::open(home_dir) { + Ok(file) => file, + Err(error) => { + warn!(%error, ?home_dir, "failed to open home directory"); + return Vec::new(); + } + }; + + match check_permissions(&home, self.id, "home directory") { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => { + warn!(?home_dir, "bad permissions on home directory"); + return Vec::new(); + } + }; + + let ssh_dir = match open_in_dir(&home, ".ssh", O_DIRECTORY) { + Ok(file) => file, + Err(error) => { + warn!(%error, ?home_dir, "failed to open .ssh directory"); + return Vec::new(); + } + }; + + match check_permissions(&ssh_dir, self.id, ".ssh directory") { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => { + warn!(?home_dir, "bad permissions on .ssh directory"); + return Vec::new(); + } + }; + + let mut key_file = match open_in_dir(&ssh_dir, "authorized_keys", O_RDONLY) { + Ok(file) => file, + Err(error) => { + warn!(%error, ?home_dir, "failed to open authorized keys file"); + return Vec::new(); + } + }; + + match check_permissions(&key_file, self.id, "authorized keys file") { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => { + warn!(?home_dir, "bad permissions on authorized keys file"); + return Vec::new(); + } + }; + + let mut contents = String::new(); + if let Err(error) = key_file.read_to_string(&mut contents) { + warn!(%error, ?home_dir, "failed to read authorized keys file"); + return Vec::new(); + }; + + let mut keys = Vec::new(); + for (line, key) in contents.lines().enumerate() { + match AuthorizedKey::from_str(key, provider) { + Some(key) => keys.push(key), + None => debug!(line = line + 1, "no valid authorized key found on line"), + } + } + + keys + } + const FAKE_HOME: *const c_char = c"/var/empty".as_ptr().cast::<c_char>(); const DEFAULT_SHELL: *const c_char = c"/bin/sh".as_ptr().cast::<c_char>(); } @@ -527,79 +601,6 @@ enum UserLookup { Id(u32), } -/// Read and parse the `authorized_keys` file for a user -/// -/// This is pretty finicky because we need to check that -/// -/// - None of the path components have group or other write permissions -/// - Each of the path components are owned by root or the target user -/// - Avoid TOCTOU issues when opening each path component -fn authorized_keys(home_dir: &Path, uid: u32, provider: &dyn CryptoProvider) -> Vec<AuthorizedKey> { - let home = match File::open(home_dir) { - Ok(file) => file, - Err(error) => { - warn!(%error, ?home_dir, "failed to open home directory"); - return Vec::new(); - } - }; - - match check_permissions(&home, uid, "home directory") { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => { - warn!(?home_dir, "bad permissions on home directory"); - return Vec::new(); - } - }; - - let ssh_dir = match open_in_dir(&home, ".ssh", O_DIRECTORY) { - Ok(file) => file, - Err(error) => { - warn!(%error, ?home_dir, "failed to open .ssh directory"); - return Vec::new(); - } - }; - - match check_permissions(&ssh_dir, uid, ".ssh directory") { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => { - warn!(?home_dir, "bad permissions on .ssh directory"); - return Vec::new(); - } - }; - - let mut key_file = match open_in_dir(&ssh_dir, "authorized_keys", O_RDONLY) { - Ok(file) => file, - Err(error) => { - warn!(%error, ?home_dir, "failed to open authorized keys file"); - return Vec::new(); - } - }; - - match check_permissions(&key_file, uid, "authorized keys file") { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => { - warn!(?home_dir, "bad permissions on authorized keys file"); - return Vec::new(); - } - }; - - let mut contents = String::new(); - if let Err(error) = key_file.read_to_string(&mut contents) { - warn!(%error, ?home_dir, "failed to read authorized keys file"); - return Vec::new(); - }; - - let mut keys = Vec::new(); - for (line, key) in contents.lines().enumerate() { - match AuthorizedKey::from_str(key, provider) { - Some(key) => keys.push(key), - None => debug!(line = line + 1, "no valid authorized key found on line"), - } - } - - keys -} - fn open_in_dir(dir: &File, name: &str, flags: libc::c_int) -> Result<File, io::Error> { let c_name = CString::new(name)?; match unsafe { libc::openat(dir.as_raw_fd(), c_name.as_ptr(), flags) } { |
