summaryrefslogtreecommitdiffhomepage
path: root/oxish
diff options
context:
space:
mode:
Diffstat (limited to 'oxish')
-rw-r--r--oxish/src/authentication.rs100
-rw-r--r--oxish/src/lib.rs2
-rw-r--r--oxish/src/tests.rs11
3 files changed, 58 insertions, 55 deletions
diff --git a/oxish/src/authentication.rs b/oxish/src/authentication.rs
index ee40d89..0f91735 100644
--- a/oxish/src/authentication.rs
+++ b/oxish/src/authentication.rs
@@ -94,7 +94,7 @@ async fn inner<T: AsyncRead + AsyncWrite + Unpin>(
};
conn.send(&service_accept).await?;
- let mut cached_user = None::<User>;
+ let mut cached_user = None::<CachedUser>;
let mut attempts = 6;
loop {
attempts -= 1;
@@ -144,24 +144,24 @@ async fn inner<T: AsyncRead + AsyncWrite + Unpin>(
};
let user = match &mut cached_user {
- Some(user) if &*user.name == user_auth_request.user_name => user,
+ Some(user) if &*user.data.name == user_auth_request.user_name => user,
_ => {
let Ok(name) = Username::try_from(user_auth_request.user_name.to_owned()) else {
conn.send_auth_failed().await?;
continue;
};
- match store.lookup(name, provider) {
- Some(user) => cached_user.insert(user),
- _ => {
- conn.send_auth_failed().await?;
- continue;
- }
- }
+ let Some(user) = store.lookup(name) else {
+ conn.send_auth_failed().await?;
+ continue;
+ };
+
+ let keys = store.keys(&user, provider);
+ cached_user.insert(CachedUser { data: user, keys })
}
};
- let authorized_key = user.authorized_keys.iter().find(|key| {
+ let authorized_key = user.keys.iter().find(|key| {
key.algorithm == public_key.algorithm && key.blob.as_slice() == public_key.key_blob
});
@@ -208,7 +208,7 @@ async fn inner<T: AsyncRead + AsyncWrite + Unpin>(
let message = SignatureData {
session_id: session_id.as_ref(),
- user_name: &user.name,
+ user_name: &user.data.name,
service_name: user_auth_request.service_name,
algorithm: public_key.algorithm,
public_key: public_key.key_blob,
@@ -220,9 +220,9 @@ async fn inner<T: AsyncRead + AsyncWrite + Unpin>(
return Err(ProtoError::Unreachable("must have cached user").into());
};
- info!(user = %user.name, "authentication successful");
+ info!(user = %user.data.name, "authentication successful");
conn.send(&MessageType::UserAuthSuccess).await?;
- break Ok(user);
+ break Ok(user.data);
}
_ => {
conn.send_auth_failed().await?;
@@ -243,7 +243,11 @@ impl DefaultStore {
pub fn new(provider: &dyn CryptoProvider) -> Result<Box<dyn UserStore>, Error> {
Ok(match unsafe { libc::geteuid() } {
0 => Box::new(SystemStore) as Box<dyn UserStore>,
- uid => Box::new(SingleUser(User::lookup(UserLookup::Id(uid), provider)?)),
+ uid => {
+ let data = User::lookup(UserLookup::Id(uid))?;
+ let keys = authorized_keys(&data.home_dir, data.id, provider);
+ Box::new(SingleUser(CachedUser { data, keys }))
+ }
})
}
}
@@ -252,8 +256,8 @@ impl DefaultStore {
struct SystemStore;
impl UserStore for SystemStore {
- fn lookup(&self, name: Username, provider: &dyn CryptoProvider) -> Option<User> {
- match User::lookup(UserLookup::Name(name), provider) {
+ fn lookup(&self, name: Username) -> Option<User> {
+ match User::lookup(UserLookup::Name(name)) {
Ok(user) => Some(user),
Err(error) => {
error!(%error, "failed to get user information");
@@ -262,22 +266,33 @@ impl UserStore for SystemStore {
}
}
+ fn keys(&self, user: &User, provider: &dyn CryptoProvider) -> Vec<AuthorizedKey> {
+ authorized_keys(&user.home_dir, user.id, provider)
+ }
+
fn drop_privileges(&self) -> bool {
true
}
}
/// User store that only contains a single user
-pub(crate) struct SingleUser(User);
+pub(crate) struct SingleUser(CachedUser);
+
+impl SingleUser {
+ #[cfg(test)]
+ pub(crate) fn with_keys(data: User, keys: Vec<AuthorizedKey>) -> Self {
+ Self(CachedUser { data, keys })
+ }
+}
impl UserStore for SingleUser {
- fn lookup(&self, name: Username, _: &dyn CryptoProvider) -> Option<User> {
- match self.0.name == name {
- true => Some(self.0.clone()),
+ fn lookup(&self, name: Username) -> Option<User> {
+ match self.0.data.name == name {
+ true => Some(self.0.data.clone()),
false => {
warn!(
requested = %name,
- authorized = %self.0.name,
+ authorized = %self.0.data.name,
"requested user does not match authorized user",
);
None
@@ -285,26 +300,32 @@ impl UserStore for SingleUser {
}
}
- fn drop_privileges(&self) -> bool {
- false
+ fn keys(&self, _: &User, _: &dyn CryptoProvider) -> Vec<AuthorizedKey> {
+ self.0.keys.clone()
}
-}
-impl From<User> for SingleUser {
- fn from(user: User) -> Self {
- Self(user)
+ fn drop_privileges(&self) -> bool {
+ false
}
}
/// A user store resolves a username to a `User` type containing data used for authentication
pub trait UserStore: Send + Sync + 'static {
/// Lookup a user by name, returning `None` if the user does not exist or cannot be retrieved
- fn lookup(&self, name: Username, provider: &dyn CryptoProvider) -> Option<User>;
+ fn lookup(&self, name: Username) -> Option<User>;
+
+ /// Lookup the authorized keys for a user
+ fn keys(&self, user: &User, provider: &dyn CryptoProvider) -> Vec<AuthorizedKey>;
/// Whether the user store should set the UID of the process to the authenticated user
fn drop_privileges(&self) -> bool;
}
+struct CachedUser {
+ data: User,
+ keys: Vec<AuthorizedKey>,
+}
+
/// User data as retrieved from the system database
#[derive(Clone, Debug)]
pub struct User {
@@ -313,15 +334,10 @@ pub struct User {
pub(crate) gid: u32,
pub(crate) home_dir: PathBuf,
pub(crate) shell: PathBuf,
- /// Cached authorized keys for the user
- ///
- /// Since finding the authorized keys can be somewhat expensive, prefer to cache them
- /// here so we can reuse them across attempts for the same user.
- authorized_keys: Vec<AuthorizedKey>,
}
impl User {
- fn lookup(by: UserLookup, provider: &dyn CryptoProvider) -> Result<Self, Error> {
+ fn lookup(by: UserLookup) -> Result<Self, Error> {
let buf_len = match unsafe { sysconf(_SC_GETPW_R_SIZE_MAX) } {
-1 => 1024,
n => Ord::min(n as usize, 1_048_576),
@@ -414,7 +430,6 @@ impl User {
name,
id,
gid,
- authorized_keys: authorized_keys(&home_dir, id, provider),
home_dir,
shell,
})
@@ -424,20 +439,13 @@ impl User {
///
/// This is primarily intended for testing.
#[cfg(test)]
- pub(crate) fn new(
- name: String,
- id: u32,
- gid: u32,
- home_dir: PathBuf,
- authorized_keys: Vec<AuthorizedKey>,
- ) -> Result<Self, Error> {
+ pub(crate) fn new(name: String, id: u32, gid: u32, home_dir: PathBuf) -> Result<Self, Error> {
Ok(Self {
name: Username::try_from(name)?,
id,
gid,
home_dir,
shell: PathBuf::from("/bin/sh"),
- authorized_keys,
})
}
@@ -597,8 +605,9 @@ fn check_permissions(file: &File, uid: u32, level: &str) -> ControlFlow<()> {
}
}
+/// An authorized public key for a user
#[derive(Clone)]
-pub(crate) struct AuthorizedKey {
+pub struct AuthorizedKey {
algorithm: PublicKeyAlgorithm<'static>,
blob: Vec<u8>,
key: Arc<dyn VerifyingKey>,
@@ -629,7 +638,8 @@ impl AuthorizedKey {
})
}
- pub(crate) fn from_str(s: &str, provider: &dyn CryptoProvider) -> Result<Option<Self>, ()> {
+ /// Build an `AuthorizedKey` from a string in the format used in `authorized_keys`
+ pub fn from_str(s: &str, provider: &dyn CryptoProvider) -> Result<Option<Self>, ()> {
let key = match s.split_once('#') {
Some((contents, _)) => contents,
None => s,
diff --git a/oxish/src/lib.rs b/oxish/src/lib.rs
index 65b3554..9de9891 100644
--- a/oxish/src/lib.rs
+++ b/oxish/src/lib.rs
@@ -48,7 +48,7 @@ pub use graviola::DEFAULT_PROVIDER;
compile_error!("no crypto providers enabled -- enable at least one to fix this error");
mod authentication;
-pub use authentication::{DefaultStore, User, UserStore, Username};
+pub use authentication::{AuthorizedKey, DefaultStore, User, UserStore, Username};
mod session;
pub use session::Session;
mod server;
diff --git a/oxish/src/tests.rs b/oxish/src/tests.rs
index 8740478..b5ca048 100644
--- a/oxish/src/tests.rs
+++ b/oxish/src/tests.rs
@@ -112,14 +112,7 @@ async fn handshake(
let key = AuthorizedKey::from_str(&authorized_key, provider)
.unwrap()
.expect("failed to parse generated public key");
- let user = User::new(
- USER.to_string(),
- 1000,
- 1000,
- PathBuf::from("/var/empty"),
- vec![key],
- )
- .unwrap();
+ let user = User::new(USER.to_string(), 1000, 1000, PathBuf::from("/var/empty")).unwrap();
// Start the server on a loopback port and serve exactly one connection.
let (_, pkcs8) = provider
@@ -129,7 +122,7 @@ async fn handshake(
let addr = listener.local_addr().unwrap();
let server = Server::new(
- Box::new(SingleUser::from(user)),
+ Box::new(SingleUser::with_keys(user, vec![key])),
HostKeys::new([Zeroizing::new(pkcs8)].into_iter(), provider).unwrap(),
session_binary().await,
provider,