From f6627d887b2b3f0f94ddcbf651bc8ab6c7fc3dc3 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 29 Sep 2020 19:21:34 +0200 Subject: [PATCH] proper error messages --- Cargo.toml | 1 + src/error.rs | 33 ++++++++++++++++++++++++- src/lib.rs | 68 +++++++++++++++++++++++++++++++++------------------- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14d75cd..3674cf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ ring = "0.13.5" failure = "0.1.5" rpassword = "4.0.1" libcryptsetup-rs = "0.4.1" +pamsm = { version = "0.4.1", features = ["libpam"] } structopt = "0.3.2" [profile.release] diff --git a/src/error.rs b/src/error.rs index 03d9741..aa7c4a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,10 @@ use ctap::FidoError; use libcryptsetup_rs::LibcryptErr; +use pamsm::PamError; use std::io; use std::io::ErrorKind; use std::string::FromUtf8Error; use Fido2LuksError::*; - pub type Fido2LuksResult = Result; #[derive(Debug, Fail)] @@ -29,6 +29,10 @@ pub enum Fido2LuksError { WrongSecret, #[fail(display = "not an utf8 string")] StringEncodingError { cause: FromUtf8Error }, + #[fail(display = "elevated privileges required")] + MissingPrivileges, + #[fail(display = "{}", cause)] + Configuration { cause: ConfigurationError }, } impl Fido2LuksError { @@ -50,6 +54,20 @@ pub enum AskPassError { IO(io::Error), #[fail(display = "provided passwords don't match")] Mismatch, + #[fail(display = "unable to retrieve password: {}", _0)] + Pam(PamError), +} + +impl From for AskPassError { + fn from(e: PamError) -> Self { + AskPassError::Pam(e) + } +} + +impl From for AskPassError { + fn from(e: io::Error) -> Self { + AskPassError::IO(e) + } } #[derive(Debug, Fail)] @@ -112,3 +130,16 @@ impl From for Fido2LuksError { StringEncodingError { cause: e } } } +#[derive(Debug, Fail)] +pub enum ConfigurationError { + #[fail(display = "config is missing some values: {:?}", _0)] + Missing(Vec), + #[fail(display = "config attribute {} contains an invalid value: {}", _1, _0)] + InvalidValue(String, String), +} + +impl From for Fido2LuksError { + fn from(cause: ConfigurationError) -> Fido2LuksError { + Fido2LuksError::Configuration { cause } + } +} diff --git a/src/lib.rs b/src/lib.rs index c2f3a86..a52c3b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,6 @@ extern crate failure; extern crate ctap_hmac as ctap; #[macro_use] extern crate serde_derive; -#[macro_use] -extern crate pamsm; use crate::cli_args::{CommaSeparated, HexEncoded}; use crate::device::*; use crate::error::*; @@ -14,7 +12,6 @@ use failure::_core::time::Duration; use pamsm::PamLibExt; use pamsm::*; use std::collections::{HashMap, HashSet}; -use std::ffi::CStr; use std::path::Path; use std::str::FromStr; use sudo::{self, RunningAs}; @@ -45,15 +42,19 @@ impl PamFido2Luks { }) .collect(); - let credentials = args - .get("credentials") - .map(|creds| { - >::from_str(creds) - .expect("Invalid credentials") - .0 //TODO: proper error handling - }) - .unwrap_or_default(); + let credentials = match args.get("credentials").map(|creds| { + >::from_str(creds) + .map(|cs| cs.0) + .map_err(|_| ConfigurationError::InvalidValue("credentials".into(), creds.into())) + }) { + Some(creds) => creds?, + _ => Vec::new(), + }; let pin = args.get("pin"); + let pin_prefix = args + .get("pin-prefix") + .map(|p| p.parse::().unwrap_or_default()) + .unwrap_or_default(); let device = args .get("device") .map(|device| device.replace("%user%", user.as_str())); @@ -61,7 +62,7 @@ impl PamFido2Luks { .get("name") .map(|name| name.replace("%user%", user.as_str())); - let attempts = args + let mut attempts = args .get("attempts") .and_then(|a| a.parse::().ok()) .unwrap_or(3); @@ -73,10 +74,7 @@ impl PamFido2Luks { } // root required to mount luks match sudo::check() { - RunningAs::User => { - //err - unimplemented!("no root") - } + RunningAs::User => return Err(Fido2LuksError::MissingPrivileges), _ => { sudo::escalate_if_needed().unwrap(); } @@ -100,32 +98,54 @@ impl PamFido2Luks { .collect(); let credentials: Vec<&FidoCredential> = credentials.iter().collect(); if !credentials.is_empty() { - for _ in 0..attempts { - let salt = util::sha256(&[password().expect("Password").as_bytes()]); + loop { + let (pin, pass) = if pin_prefix { + let password = password() + .map_err(|e| Fido2LuksError::AskPassError { cause: e.into() })?; + let mut parts = password.split(":"); + ( + parts.next().map(|p| p.to_string()).or(pin.cloned()), + parts.collect::>().join(":"), + ) + } else { + ( + pin.cloned(), + password() + .map_err(|e| Fido2LuksError::AskPassError { cause: e.into() })?, + ) + }; + + let salt = util::sha256(&[pass.as_bytes()]); let secret = util::sha256(&[ &salt, &perform_challenge( &credentials[..], &salt, Duration::from_secs(15), - pin.map(AsRef::as_ref), + pin.as_ref().map(String::as_str), )? .0[..], ]); - device.activate(name.as_str(), &secret[..], None)?; + match device.activate(name.as_str(), &secret[..], None) { + Ok(_) => return Ok(()), + _ if attempts > 0 => { + attempts -= 1; + continue; + } + Err(e) => break Err(e), + } } - Ok(()) } else { - unimplemented!("custom error") + Err(ConfigurationError::Missing(vec!["credentials".into()]).into()) } } else { - unimplemented!("custom error") + Ok(()) } } } impl PamServiceModule for PamFido2Luks { - fn authenticate(pamh: Pam, flag: PamFlag, args: Vec) -> PamError { + fn authenticate(pamh: Pam, _flag: PamFlag, args: Vec) -> PamError { let user = match pamh.get_cached_user() { Err(_) => return dbg!(PamError::AUTH_ERR), Ok(p) => p.map(|s| s.to_str().map(str::to_string).unwrap()),