diff --git a/Cargo.toml b/Cargo.toml index fdc4df9..9e0aab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pam-ssh-agent" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/nresare/pam-ssh-agent" diff --git a/README.md b/README.md index 7d1038d..2123ec6 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,16 @@ remote `ssh-agent`. One scenario that this module can be used in is to grant escalated privileges on a remote system with the `sudo` command where the identity of the user is confirmed by their ability to provide a signature made with a local ssh-agent and a private key that never leaves the -designated hardware. +designated hardware. I use the [Secretive](https://github.com/maxgoedjen/secretive) app on +macOS for this purpose. This project is re-implementation of the [pam_ssh_agent_auth](https://github.com/jbeverly/pam_ssh_agent_auth) module but does not share any code with that project. The eventual goal of this module is to be -functionally equivalent and a drop-in replacement instead of pam_ssh_agent_auth. +functionally equivalent and a drop-in replacement for `pam_ssh_agent_auth`. + +This project is currently in a usable state, and has been tested with Ubuntu 22.04. As of now, +the path expansion patterns that pam_ssh_agent_auth provides are not implemented. In other +words a single authorized_keys file is expected to be used. ## Project goals @@ -19,24 +24,30 @@ Since this is security sensitive software and a bug could easily result in undue escalation, the main goal of this project is to be robust and easy to follow for would-be reviewers. -The implementation leans heavily on modules available in the Rust ecosystem that implements +The implementation leans heavily on crates available in the Rust ecosystem that implements the different parts needed for the overall functionality, most notably the pam, ssh-key, and ssh-agent-client-rs crates. Using upstream libraries directly is intended to make it easier to ensure that implementation issues with security implication gets addressed in a -timely manner. A secondary benefit is that supporting a wide range of algorithms is easier. - -## Current state +timely manner. A secondary benefit is that it is easier to support a wide range of algorithms. -The pam module integration is in a very rough state, but at least it can be used as a proof -of concept. To try it out, these are the rough steps: +## Usage * use `debuild -b` to build a `.deb` package with the shared object and install it with `dpkg` -* install `doas`, to ensure that you have a differnt way of elevating your privileges. You will - need to add a `permit` line in /etc/doas.conf for it to work -* Replace the common-auth include in `/etc/pam.d/sudo` with `auth required pam_ssh_agent.so` +* install `doas`, to ensure that you have a different way of elevating your privileges than sudo. + You will need to add a `permit` line in `/etc/doas.conf` for it to work +* Replace the `common-auth` include in `/etc/pam.d/sudo` with `auth required pam_ssh_agent.so` * Configure `sudo` to not drop the `SSH_AUTH_SOCK` environment variable by adding `Defaults env_keep += "SSH_AUTH_SOCK` to the file `/etc/sudoers.d/ssh_agent_env` -* Add the public key from you to `/etc/sudo_ssh_keys` +* Add the public key that your ssh-agent knows about to `/etc/security/authorized_keys` + +## Configuration options + +PAM modules can be configured using space separated options after `pam_ssh_agent.so` in the applicable +configuration file in `/etc/pam.d`. pam_ssh_agent currently understands the following options + +* `debug` This will increase log output to the AUTHPRIV syslog facility +* `file=/file/name` This will modify the file holding the authorized public keys instead of the + default `/etc/security/authorized_keys`. ## License diff --git a/debian/changelog b/debian/changelog index 87af826..257ca8e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +pam-ssh-agent (0.3.0) UNRELEASED; urgency=medium + + * 0.3.0 + + -- Noa Resare Sun, 13 Aug 2023 12:17:15 +0000 + pam-ssh-agent (0.2.0) UNRELEASED; urgency=medium * 0.2.0 diff --git a/src/args.rs b/src/args.rs index 739e8be..c93c434 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,5 +1,6 @@ use std::ffi::CStr; +/// Argument parsing. #[derive(Debug, Eq, PartialEq)] pub struct Args { pub debug: bool, @@ -7,6 +8,7 @@ pub struct Args { } impl Args { + /// Parses args and returns an Args instance with the parsed arguments pub fn parse(args: Vec<&CStr>) -> Self { let mut debug = false; let mut file: String = "/etc/security/authorized_keys".into(); diff --git a/src/auth.rs b/src/auth.rs index 99dae37..dbf9789 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -9,6 +9,12 @@ use std::path::Path; const CHALLENGE_SIZE: usize = 32; +/// Finds the first key, if any, that the ssh-agent knows about that is also present +/// in the file referenced by keys_file_path, sends a random message to be signed and +/// verifies the signature with the public key. +/// +/// Returns Ok(true) if a key was found and the signature was correct, Ok(false) if no +/// key was found, and Err if agent communication or signature verification failed. pub fn authenticate( keys_file_path: &str, mut agent: impl SSHAgent, @@ -29,8 +35,7 @@ pub fn authenticate( fn sign_and_verify(key: PublicKey, mut agent: impl SSHAgent) -> Result { let mut data: [u8; CHALLENGE_SIZE] = [0_u8; CHALLENGE_SIZE]; - getrandom(data.as_mut_slice()).expect("Failed to obtain random data to sign"); - + getrandom(data.as_mut_slice())?; let sig = agent.sign(&key, data.as_ref())?; key.key_data().verify(data.as_ref(), &sig)?; diff --git a/src/keys.rs b/src/keys.rs index 757f4be..49c67de 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -11,7 +11,7 @@ impl KeyHolder { pub(crate) fn from_file(path: &Path) -> Result { let keys = Vec::from_iter( AuthorizedKeys::read_file(path) - .context(format!("Failed to read from '{:?}'", path))? + .context(format!("Failed to read from {:?}", path))? .into_iter() .map(|e| e.public_key().key_data().to_owned()), ); diff --git a/src/lib.rs b/src/lib.rs index 56a1fce..a636734 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,13 @@ struct PamSshAgent; pam::pam_hooks!(PamSshAgent); impl PamHooks for PamSshAgent { + /// The authentication method called by pam to authenticate the user. This method + /// will return PAM_SUCCESS if the ssh-agent available through the unix socket path + /// in the PAM_AUTH_SOCK environment variable is able to correctly sign a random + /// message with the private key corresponding to one of the public keys in in + /// /etc/security/authorized_key. Otherwise this function returns PAM_AUTH_ERR. + /// + /// This method logs diagnostic output to the AUTHPRIV facility. fn sm_authenticate( pam_handle: &mut PamHandle, args: Vec<&CStr>, @@ -68,6 +75,8 @@ fn do_authenticate(log: &mut impl Log, args: &Args) -> Result<()> { } } +/// Fetch the name of the current service, i.e. the software that uses pam for authentication +/// using the PamHandle::get_item() method. fn get_service(pam_handle: &PamHandle) -> String { let service = match pam_handle.get_item::() { Ok(Some(service)) => service,