Merge branch 'no-string-error' into 'master'

Use structs for errors instead of plain strings

Closes #3

See merge request lumi/sasl-rs!8
This commit is contained in:
lumi 2020-06-07 12:26:12 +00:00
commit 5550148149
10 changed files with 365 additions and 107 deletions

View file

@ -1,6 +1,6 @@
//! Provides the SASL "ANONYMOUS" mechanism. //! Provides the SASL "ANONYMOUS" mechanism.
use crate::client::Mechanism; use crate::client::{Mechanism, MechanismError};
use crate::common::{Credentials, Secret}; use crate::common::{Credentials, Secret};
/// A struct for the SASL ANONYMOUS mechanism. /// A struct for the SASL ANONYMOUS mechanism.
@ -21,11 +21,11 @@ impl Mechanism for Anonymous {
"ANONYMOUS" "ANONYMOUS"
} }
fn from_credentials(credentials: Credentials) -> Result<Anonymous, String> { fn from_credentials(credentials: Credentials) -> Result<Anonymous, MechanismError> {
if let Secret::None = credentials.secret { if let Secret::None = credentials.secret {
Ok(Anonymous) Ok(Anonymous)
} else { } else {
Err("the anonymous sasl mechanism requires no credentials".to_owned()) Err(MechanismError::AnonymousRequiresNoCredentials)
} }
} }
} }

View file

@ -1,6 +1,6 @@
//! Provides the SASL "PLAIN" mechanism. //! Provides the SASL "PLAIN" mechanism.
use crate::client::Mechanism; use crate::client::{Mechanism, MechanismError};
use crate::common::{Credentials, Identity, Password, Secret}; use crate::common::{Credentials, Identity, Password, Secret};
/// A struct for the SASL PLAIN mechanism. /// A struct for the SASL PLAIN mechanism.
@ -27,24 +27,24 @@ impl Mechanism for Plain {
"PLAIN" "PLAIN"
} }
fn from_credentials(credentials: Credentials) -> Result<Plain, String> { fn from_credentials(credentials: Credentials) -> Result<Plain, MechanismError> {
if let Secret::Password(Password::Plain(password)) = credentials.secret { if let Secret::Password(Password::Plain(password)) = credentials.secret {
if let Identity::Username(username) = credentials.identity { if let Identity::Username(username) = credentials.identity {
Ok(Plain::new(username, password)) Ok(Plain::new(username, password))
} else { } else {
Err("PLAIN requires a username".to_owned()) Err(MechanismError::PlainRequiresUsername)
} }
} else { } else {
Err("PLAIN requires a plaintext password".to_owned()) Err(MechanismError::PlainRequiresPlaintextPassword)
} }
} }
fn initial(&mut self) -> Result<Vec<u8>, String> { fn initial(&mut self) -> Vec<u8> {
let mut auth = Vec::new(); let mut auth = Vec::new();
auth.push(0); auth.push(0);
auth.extend(self.username.bytes()); auth.extend(self.username.bytes());
auth.push(0); auth.push(0);
auth.extend(self.password.bytes()); auth.extend(self.password.bytes());
Ok(auth) auth
} }
} }

View file

@ -2,7 +2,7 @@
use base64; use base64;
use crate::client::Mechanism; use crate::client::{Mechanism, MechanismError};
use crate::common::scram::{generate_nonce, ScramProvider}; use crate::common::scram::{generate_nonce, ScramProvider};
use crate::common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret}; use crate::common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret};
@ -80,20 +80,20 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
&self.name &self.name
} }
fn from_credentials(credentials: Credentials) -> Result<Scram<S>, String> { fn from_credentials(credentials: Credentials) -> Result<Scram<S>, MechanismError> {
if let Secret::Password(password) = credentials.secret { if let Secret::Password(password) = credentials.secret {
if let Identity::Username(username) = credentials.identity { if let Identity::Username(username) = credentials.identity {
Scram::new(username, password, credentials.channel_binding) Scram::new(username, password, credentials.channel_binding)
.map_err(|_| "can't generate nonce".to_owned()) .map_err(|_| MechanismError::CannotGenerateNonce)
} else { } else {
Err("SCRAM requires a username".to_owned()) Err(MechanismError::ScramRequiresUsername)
} }
} else { } else {
Err("SCRAM requires a password".to_owned()) Err(MechanismError::ScramRequiresPassword)
} }
} }
fn initial(&mut self) -> Result<Vec<u8>, String> { fn initial(&mut self) -> Vec<u8> {
let mut gs2_header = Vec::new(); let mut gs2_header = Vec::new();
gs2_header.extend(self.channel_binding.header()); gs2_header.extend(self.channel_binding.header());
let mut bare = Vec::new(); let mut bare = Vec::new();
@ -108,10 +108,10 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
initial_message: bare, initial_message: bare,
gs2_header: gs2_header, gs2_header: gs2_header,
}; };
Ok(data) data
} }
fn response(&mut self, challenge: &[u8]) -> Result<Vec<u8>, String> { fn response(&mut self, challenge: &[u8]) -> Result<Vec<u8>, MechanismError> {
let next_state; let next_state;
let ret; let ret;
match self.state { match self.state {
@ -120,13 +120,13 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
ref gs2_header, ref gs2_header,
} => { } => {
let frame = let frame =
parse_frame(challenge).map_err(|_| "can't decode challenge".to_owned())?; parse_frame(challenge).map_err(|_| MechanismError::CannotDecodeChallenge)?;
let server_nonce = frame.get("r"); let server_nonce = frame.get("r");
let salt = frame.get("s").and_then(|v| base64::decode(v).ok()); let salt = frame.get("s").and_then(|v| base64::decode(v).ok());
let iterations = frame.get("i").and_then(|v| v.parse().ok()); let iterations = frame.get("i").and_then(|v| v.parse().ok());
let server_nonce = server_nonce.ok_or_else(|| "no server nonce".to_owned())?; let server_nonce = server_nonce.ok_or_else(|| MechanismError::NoServerNonce)?;
let salt = salt.ok_or_else(|| "no server salt".to_owned())?; let salt = salt.ok_or_else(|| MechanismError::NoServerSalt)?;
let iterations = iterations.ok_or_else(|| "no server iterations".to_owned())?; let iterations = iterations.ok_or_else(|| MechanismError::NoServerIterations)?;
// TODO: SASLprep // TODO: SASLprep
let mut client_final_message_bare = Vec::new(); let mut client_final_message_bare = Vec::new();
client_final_message_bare.extend(b"c="); client_final_message_bare.extend(b"c=");
@ -159,15 +159,15 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
ret = client_final_message; ret = client_final_message;
} }
_ => { _ => {
return Err("not in the right state to receive this response".to_owned()); return Err(MechanismError::InvalidState);
} }
} }
self.state = next_state; self.state = next_state;
Ok(ret) Ok(ret)
} }
fn success(&mut self, data: &[u8]) -> Result<(), String> { fn success(&mut self, data: &[u8]) -> Result<(), MechanismError> {
let frame = parse_frame(data).map_err(|_| "can't decode success response".to_owned())?; let frame = parse_frame(data).map_err(|_| MechanismError::CannotDecodeSuccessResponse)?;
match self.state { match self.state {
ScramState::GotServerData { ScramState::GotServerData {
ref server_signature, ref server_signature,
@ -176,13 +176,13 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
if sig == *server_signature { if sig == *server_signature {
Ok(()) Ok(())
} else { } else {
Err("invalid signature in success response".to_owned()) Err(MechanismError::InvalidSignatureInSuccessResponse)
} }
} else { } else {
Err("no signature in success response".to_owned()) Err(MechanismError::NoSignatureInSuccessResponse)
} }
} }
_ => Err("not in the right state to get a success response".to_owned()), _ => Err(MechanismError::InvalidState),
} }
} }
} }
@ -206,7 +206,7 @@ mod tests {
let server_final = b"v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; let server_final = b"v=rmF9pqV8S7suAoZWja4dJRkFsKQ=";
let mut mechanism = let mut mechanism =
Scram::<Sha1>::new_with_nonce(username, password, client_nonce.to_owned()); Scram::<Sha1>::new_with_nonce(username, password, client_nonce.to_owned());
let init = mechanism.initial().unwrap(); let init = mechanism.initial();
assert_eq!( assert_eq!(
String::from_utf8(init.clone()).unwrap(), String::from_utf8(init.clone()).unwrap(),
String::from_utf8(client_init[..].to_owned()).unwrap() String::from_utf8(client_init[..].to_owned()).unwrap()
@ -231,7 +231,7 @@ mod tests {
let server_final = b"v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="; let server_final = b"v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";
let mut mechanism = let mut mechanism =
Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned()); Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned());
let init = mechanism.initial().unwrap(); let init = mechanism.initial();
assert_eq!( assert_eq!(
String::from_utf8(init.clone()).unwrap(), String::from_utf8(init.clone()).unwrap(),
String::from_utf8(client_init[..].to_owned()).unwrap() String::from_utf8(client_init[..].to_owned()).unwrap()

View file

@ -1,4 +1,80 @@
use crate::common::scram::DeriveError;
use crate::common::Credentials; use crate::common::Credentials;
use hmac::crypto_mac::InvalidKeyLength;
use std::fmt;
#[derive(Debug, PartialEq)]
pub enum MechanismError {
AnonymousRequiresNoCredentials,
PlainRequiresUsername,
PlainRequiresPlaintextPassword,
CannotGenerateNonce,
ScramRequiresUsername,
ScramRequiresPassword,
CannotDecodeChallenge,
NoServerNonce,
NoServerSalt,
NoServerIterations,
DeriveError(DeriveError),
InvalidKeyLength(InvalidKeyLength),
InvalidState,
CannotDecodeSuccessResponse,
InvalidSignatureInSuccessResponse,
NoSignatureInSuccessResponse,
}
impl From<DeriveError> for MechanismError {
fn from(err: DeriveError) -> MechanismError {
MechanismError::DeriveError(err)
}
}
impl From<InvalidKeyLength> for MechanismError {
fn from(err: InvalidKeyLength) -> MechanismError {
MechanismError::InvalidKeyLength(err)
}
}
impl fmt::Display for MechanismError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"{}",
match self {
MechanismError::AnonymousRequiresNoCredentials =>
"ANONYMOUS mechanism requires no credentials",
MechanismError::PlainRequiresUsername => "PLAIN requires a username",
MechanismError::PlainRequiresPlaintextPassword =>
"PLAIN requires a plaintext password",
MechanismError::CannotGenerateNonce => "can't generate nonce",
MechanismError::ScramRequiresUsername => "SCRAM requires a username",
MechanismError::ScramRequiresPassword => "SCRAM requires a password",
MechanismError::CannotDecodeChallenge => "can't decode challenge",
MechanismError::NoServerNonce => "no server nonce",
MechanismError::NoServerSalt => "no server salt",
MechanismError::NoServerIterations => "no server iterations",
MechanismError::DeriveError(err) => return write!(fmt, "derive error: {}", err),
MechanismError::InvalidKeyLength(err) =>
return write!(fmt, "invalid key length: {}", err),
MechanismError::InvalidState => "not in the right state to receive this response",
MechanismError::CannotDecodeSuccessResponse => "can't decode success response",
MechanismError::InvalidSignatureInSuccessResponse =>
"invalid signature in success response",
MechanismError::NoSignatureInSuccessResponse => "no signature in success response",
}
)
}
}
impl std::error::Error for MechanismError {}
/// A trait which defines SASL mechanisms. /// A trait which defines SASL mechanisms.
pub trait Mechanism { pub trait Mechanism {
@ -6,22 +82,22 @@ pub trait Mechanism {
fn name(&self) -> &str; fn name(&self) -> &str;
/// Creates this mechanism from `Credentials`. /// Creates this mechanism from `Credentials`.
fn from_credentials(credentials: Credentials) -> Result<Self, String> fn from_credentials(credentials: Credentials) -> Result<Self, MechanismError>
where where
Self: Sized; Self: Sized;
/// Provides initial payload of the SASL mechanism. /// Provides initial payload of the SASL mechanism.
fn initial(&mut self) -> Result<Vec<u8>, String> { fn initial(&mut self) -> Vec<u8> {
Ok(Vec::new()) Vec::new()
} }
/// Creates a response to the SASL challenge. /// Creates a response to the SASL challenge.
fn response(&mut self, _challenge: &[u8]) -> Result<Vec<u8>, String> { fn response(&mut self, _challenge: &[u8]) -> Result<Vec<u8>, MechanismError> {
Ok(Vec::new()) Ok(Vec::new())
} }
/// Verifies the server success response, if there is one. /// Verifies the server success response, if there is one.
fn success(&mut self, _data: &[u8]) -> Result<(), String> { fn success(&mut self, _data: &[u8]) -> Result<(), MechanismError> {
Ok(()) Ok(())
} }
} }

View file

@ -1,5 +1,5 @@
use getrandom::{getrandom, Error as RngError}; use getrandom::{getrandom, Error as RngError};
use hmac::{Hmac, Mac}; use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac};
use pbkdf2::pbkdf2; use pbkdf2::pbkdf2;
use sha1::{Digest, Sha1 as Sha1_hash}; use sha1::{Digest, Sha1 as Sha1_hash};
use sha2::Sha256 as Sha256_hash; use sha2::Sha256 as Sha256_hash;
@ -17,6 +17,29 @@ pub fn generate_nonce() -> Result<String, RngError> {
Ok(base64::encode(&data)) Ok(base64::encode(&data))
} }
#[derive(Debug, PartialEq)]
pub enum DeriveError {
IncompatibleHashingMethod(String, String),
IncorrectSalt,
IncompatibleIterationCount(usize, usize),
}
impl std::fmt::Display for DeriveError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DeriveError::IncompatibleHashingMethod(one, two) => {
write!(fmt, "incompatible hashing method, {} is not {}", one, two)
}
DeriveError::IncorrectSalt => write!(fmt, "incorrect salt"),
DeriveError::IncompatibleIterationCount(one, two) => {
write!(fmt, "incompatible iteration count, {} is not {}", one, two)
}
}
}
}
impl std::error::Error for DeriveError {}
/// A trait which defines the needed methods for SCRAM. /// A trait which defines the needed methods for SCRAM.
pub trait ScramProvider { pub trait ScramProvider {
/// The kind of secret this `ScramProvider` requires. /// The kind of secret this `ScramProvider` requires.
@ -29,10 +52,10 @@ pub trait ScramProvider {
fn hash(data: &[u8]) -> Vec<u8>; fn hash(data: &[u8]) -> Vec<u8>;
/// A function which performs an HMAC using the hash function. /// A function which performs an HMAC using the hash function.
fn hmac(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String>; fn hmac(data: &[u8], key: &[u8]) -> Result<Vec<u8>, InvalidKeyLength>;
/// A function which does PBKDF2 key derivation using the hash function. /// A function which does PBKDF2 key derivation using the hash function.
fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, String>; fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, DeriveError>;
} }
/// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS /// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS
@ -52,12 +75,9 @@ impl ScramProvider for Sha1 {
vec vec
} }
fn hmac(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> { fn hmac(data: &[u8], key: &[u8]) -> Result<Vec<u8>, InvalidKeyLength> {
type HmacSha1 = Hmac<Sha1_hash>; type HmacSha1 = Hmac<Sha1_hash>;
let mut mac = match HmacSha1::new_varkey(key) { let mut mac = HmacSha1::new_varkey(key)?;
Ok(mac) => mac,
Err(err) => return Err(format!("{}", err)),
};
mac.input(data); mac.input(data);
let result = mac.result(); let result = mac.result();
let mut vec = Vec::with_capacity(Sha1_hash::output_size()); let mut vec = Vec::with_capacity(Sha1_hash::output_size());
@ -65,7 +85,7 @@ impl ScramProvider for Sha1 {
Ok(vec) Ok(vec)
} }
fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, String> { fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, DeriveError> {
match *password { match *password {
Password::Plain(ref plain) => { Password::Plain(ref plain) => {
let mut result = vec![0; 20]; let mut result = vec![0; 20];
@ -79,17 +99,16 @@ impl ScramProvider for Sha1 {
ref data, ref data,
} => { } => {
if method != Self::name() { if method != Self::name() {
Err(format!( Err(DeriveError::IncompatibleHashingMethod(
"incompatible hashing method, {} is not {}", method.to_string(),
method, Self::name().to_string(),
Self::name()
)) ))
} else if my_salt == &salt { } else if my_salt == &salt {
Err(format!("incorrect salt")) Err(DeriveError::IncorrectSalt)
} else if my_iterations == iterations { } else if my_iterations == iterations {
Err(format!( Err(DeriveError::IncompatibleIterationCount(
"incompatible iteration count, {} is not {}", my_iterations,
my_iterations, iterations iterations,
)) ))
} else { } else {
Ok(data.to_vec()) Ok(data.to_vec())
@ -116,12 +135,9 @@ impl ScramProvider for Sha256 {
vec vec
} }
fn hmac(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> { fn hmac(data: &[u8], key: &[u8]) -> Result<Vec<u8>, InvalidKeyLength> {
type HmacSha256 = Hmac<Sha256_hash>; type HmacSha256 = Hmac<Sha256_hash>;
let mut mac = match HmacSha256::new_varkey(key) { let mut mac = HmacSha256::new_varkey(key)?;
Ok(mac) => mac,
Err(err) => return Err(format!("{}", err)),
};
mac.input(data); mac.input(data);
let result = mac.result(); let result = mac.result();
let mut vec = Vec::with_capacity(Sha256_hash::output_size()); let mut vec = Vec::with_capacity(Sha256_hash::output_size());
@ -129,7 +145,7 @@ impl ScramProvider for Sha256 {
Ok(vec) Ok(vec)
} }
fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, String> { fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, DeriveError> {
match *password { match *password {
Password::Plain(ref plain) => { Password::Plain(ref plain) => {
let mut result = vec![0; 32]; let mut result = vec![0; 32];
@ -143,17 +159,16 @@ impl ScramProvider for Sha256 {
ref data, ref data,
} => { } => {
if method != Self::name() { if method != Self::name() {
Err(format!( Err(DeriveError::IncompatibleHashingMethod(
"incompatible hashing method, {} is not {}", method.to_string(),
method, Self::name().to_string(),
Self::name()
)) ))
} else if my_salt == &salt { } else if my_salt == &salt {
Err(format!("incorrect salt")) Err(DeriveError::IncorrectSalt)
} else if my_iterations == iterations { } else if my_iterations == iterations {
Err(format!( Err(DeriveError::IncompatibleIterationCount(
"incompatible iteration count, {} is not {}", my_iterations,
my_iterations, iterations iterations,
)) ))
} else { } else {
Ok(data.to_vec()) Ok(data.to_vec())

View file

@ -17,7 +17,7 @@
//! //!
//! let mut mechanism = Plain::from_credentials(creds).unwrap(); //! let mut mechanism = Plain::from_credentials(creds).unwrap();
//! //!
//! let initial_data = mechanism.initial().unwrap(); //! let initial_data = mechanism.initial();
//! //!
//! assert_eq!(initial_data, b"\0user\0pencil"); //! assert_eq!(initial_data, b"\0user\0pencil");
//! ``` //! ```
@ -28,8 +28,9 @@
//! #[macro_use] extern crate sasl; //! #[macro_use] extern crate sasl;
//! //!
//! use sasl::server::{Validator, Provider, Mechanism as ServerMechanism, Response}; //! use sasl::server::{Validator, Provider, Mechanism as ServerMechanism, Response};
//! use sasl::server::{ValidatorError, ProviderError, MechanismError as ServerMechanismError};
//! use sasl::server::mechanisms::{Plain as ServerPlain, Scram as ServerScram}; //! use sasl::server::mechanisms::{Plain as ServerPlain, Scram as ServerScram};
//! use sasl::client::Mechanism as ClientMechanism; //! use sasl::client::{Mechanism as ClientMechanism, MechanismError as ClientMechanismError};
//! use sasl::client::mechanisms::{Plain as ClientPlain, Scram as ClientScram}; //! use sasl::client::mechanisms::{Plain as ClientPlain, Scram as ClientScram};
//! use sasl::common::{Identity, Credentials, Password, ChannelBinding}; //! use sasl::common::{Identity, Credentials, Password, ChannelBinding};
//! use sasl::common::scram::{ScramProvider, Sha1, Sha256}; //! use sasl::common::scram::{ScramProvider, Sha1, Sha256};
@ -43,13 +44,13 @@
//! struct MyValidator; //! struct MyValidator;
//! //!
//! impl Validator<secret::Plain> for MyValidator { //! impl Validator<secret::Plain> for MyValidator {
//! fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), String> { //! fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), ValidatorError> {
//! let &secret::Plain(ref password) = value; //! let &secret::Plain(ref password) = value;
//! if identity != &Identity::Username(USERNAME.to_owned()) { //! if identity != &Identity::Username(USERNAME.to_owned()) {
//! Err("authentication failed".to_owned()) //! Err(ValidatorError::AuthenticationFailed)
//! } //! }
//! else if password != PASSWORD { //! else if password != PASSWORD {
//! Err("authentication failed".to_owned()) //! Err(ValidatorError::AuthenticationFailed)
//! } //! }
//! else { //! else {
//! Ok(()) //! Ok(())
@ -58,9 +59,9 @@
//! } //! }
//! //!
//! impl Provider<secret::Pbkdf2Sha1> for MyValidator { //! impl Provider<secret::Pbkdf2Sha1> for MyValidator {
//! fn provide(&self, identity: &Identity) -> Result<secret::Pbkdf2Sha1, String> { //! fn provide(&self, identity: &Identity) -> Result<secret::Pbkdf2Sha1, ProviderError> {
//! if identity != &Identity::Username(USERNAME.to_owned()) { //! if identity != &Identity::Username(USERNAME.to_owned()) {
//! Err("authentication failed".to_owned()) //! Err(ProviderError::AuthenticationFailed)
//! } //! }
//! else { //! else {
//! let digest = sasl::common::scram::Sha1::derive //! let digest = sasl::common::scram::Sha1::derive
@ -79,9 +80,9 @@
//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1); //! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1);
//! //!
//! impl Provider<secret::Pbkdf2Sha256> for MyValidator { //! impl Provider<secret::Pbkdf2Sha256> for MyValidator {
//! fn provide(&self, identity: &Identity) -> Result<secret::Pbkdf2Sha256, String> { //! fn provide(&self, identity: &Identity) -> Result<secret::Pbkdf2Sha256, ProviderError> {
//! if identity != &Identity::Username(USERNAME.to_owned()) { //! if identity != &Identity::Username(USERNAME.to_owned()) {
//! Err("authentication failed".to_owned()) //! Err(ProviderError::AuthenticationFailed)
//! } //! }
//! else { //! else {
//! let digest = sasl::common::scram::Sha256::derive //! let digest = sasl::common::scram::Sha256::derive
@ -99,10 +100,28 @@
//! //!
//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha256); //! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha256);
//! //!
//! fn finish<CM, SM>(cm: &mut CM, sm: &mut SM) -> Result<Identity, String> //! #[derive(Debug, PartialEq)]
//! enum MechanismError {
//! Client(ClientMechanismError),
//! Server(ServerMechanismError),
//! }
//!
//! impl From<ClientMechanismError> for MechanismError {
//! fn from(err: ClientMechanismError) -> MechanismError {
//! MechanismError::Client(err)
//! }
//! }
//!
//! impl From<ServerMechanismError> for MechanismError {
//! fn from(err: ServerMechanismError) -> MechanismError {
//! MechanismError::Server(err)
//! }
//! }
//!
//! fn finish<CM, SM>(cm: &mut CM, sm: &mut SM) -> Result<Identity, MechanismError>
//! where CM: ClientMechanism, //! where CM: ClientMechanism,
//! SM: ServerMechanism { //! SM: ServerMechanism {
//! let init = cm.initial()?; //! let init = cm.initial();
//! println!("C: {}", String::from_utf8_lossy(&init)); //! println!("C: {}", String::from_utf8_lossy(&init));
//! let mut resp = sm.respond(&init)?; //! let mut resp = sm.respond(&init)?;
//! loop { //! loop {
@ -133,7 +152,7 @@
//! assert_eq!(mech.respond(b"\0user\0pencil"), Ok(expected_response)); //! assert_eq!(mech.respond(b"\0user\0pencil"), Ok(expected_response));
//! //!
//! let mut mech = ServerPlain::new(MyValidator); //! let mut mech = ServerPlain::new(MyValidator);
//! assert_eq!(mech.respond(b"\0user\0marker"), Err("authentication failed".to_owned())); //! assert_eq!(mech.respond(b"\0user\0marker"), Err(ServerMechanismError::ValidatorError(ValidatorError::AuthenticationFailed)));
//! //!
//! let creds = Credentials::default() //! let creds = Credentials::default()
//! .with_username(USERNAME) //! .with_username(USERNAME)

View file

@ -1,3 +1,6 @@
#[cfg(feature = "scram")]
use crate::common::scram::DeriveError;
pub trait Secret {} pub trait Secret {}
pub trait Pbkdf2Secret { pub trait Pbkdf2Secret {
@ -20,7 +23,11 @@ pub struct Pbkdf2Sha1 {
impl Pbkdf2Sha1 { impl Pbkdf2Sha1 {
#[cfg(feature = "scram")] #[cfg(feature = "scram")]
pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result<Pbkdf2Sha1, String> { pub fn derive(
password: &str,
salt: &[u8],
iterations: usize,
) -> Result<Pbkdf2Sha1, DeriveError> {
use crate::common::scram::{ScramProvider, Sha1}; use crate::common::scram::{ScramProvider, Sha1};
use crate::common::Password; use crate::common::Password;
let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?; let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?;
@ -55,7 +62,11 @@ pub struct Pbkdf2Sha256 {
impl Pbkdf2Sha256 { impl Pbkdf2Sha256 {
#[cfg(feature = "scram")] #[cfg(feature = "scram")]
pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result<Pbkdf2Sha256, String> { pub fn derive(
password: &str,
salt: &[u8],
iterations: usize,
) -> Result<Pbkdf2Sha256, DeriveError> {
use crate::common::scram::{ScramProvider, Sha256}; use crate::common::scram::{ScramProvider, Sha256};
use crate::common::Password; use crate::common::Password;
let digest = Sha256::derive(&Password::Plain(password.to_owned()), salt, iterations)?; let digest = Sha256::derive(&Password::Plain(password.to_owned()), salt, iterations)?;

View file

@ -1,6 +1,6 @@
use crate::common::Identity; use crate::common::Identity;
use crate::secret; use crate::secret;
use crate::server::{Mechanism, Response, Validator}; use crate::server::{Mechanism, MechanismError, Response, Validator};
pub struct Plain<V: Validator<secret::Plain>> { pub struct Plain<V: Validator<secret::Plain>> {
validator: V, validator: V,
@ -19,19 +19,19 @@ impl<V: Validator<secret::Plain>> Mechanism for Plain<V> {
"PLAIN" "PLAIN"
} }
fn respond(&mut self, payload: &[u8]) -> Result<Response, String> { fn respond(&mut self, payload: &[u8]) -> Result<Response, MechanismError> {
let mut sp = payload.split(|&b| b == 0); let mut sp = payload.split(|&b| b == 0);
sp.next(); sp.next();
let username = sp let username = sp
.next() .next()
.ok_or_else(|| "no username specified".to_owned())?; .ok_or_else(|| MechanismError::NoUsernameSpecified)?;
let username = let username = String::from_utf8(username.to_vec())
String::from_utf8(username.to_vec()).map_err(|_| "error decoding username")?; .map_err(|_| MechanismError::ErrorDecodingUsername)?;
let password = sp let password = sp
.next() .next()
.ok_or_else(|| "no password specified".to_owned())?; .ok_or_else(|| MechanismError::NoPasswordSpecified)?;
let password = let password = String::from_utf8(password.to_vec())
String::from_utf8(password.to_vec()).map_err(|_| "error decoding password")?; .map_err(|_| MechanismError::ErrorDecodingPassword)?;
let ident = Identity::Username(username); let ident = Identity::Username(username);
self.validator.validate(&ident, &secret::Plain(password))?; self.validator.validate(&ident, &secret::Plain(password))?;
Ok(Response::Success(ident, Vec::new())) Ok(Response::Success(ident, Vec::new()))

View file

@ -6,7 +6,7 @@ use crate::common::scram::{generate_nonce, ScramProvider};
use crate::common::{parse_frame, xor, ChannelBinding, Identity}; use crate::common::{parse_frame, xor, ChannelBinding, Identity};
use crate::secret; use crate::secret;
use crate::secret::Pbkdf2Secret; use crate::secret::Pbkdf2Secret;
use crate::server::{Mechanism, Provider, Response}; use crate::server::{Mechanism, MechanismError, Provider, Response};
enum ScramState { enum ScramState {
Init, Init,
@ -61,7 +61,7 @@ where
&self.name &self.name
} }
fn respond(&mut self, payload: &[u8]) -> Result<Response, String> { fn respond(&mut self, payload: &[u8]) -> Result<Response, MechanismError> {
let next_state; let next_state;
let ret; let ret;
match self.state { match self.state {
@ -82,7 +82,7 @@ where
} }
} }
if commas < 2 { if commas < 2 {
return Err("failed to decode message".to_owned()); return Err(MechanismError::FailedToDecodeMessage);
} }
let gs2_header = payload[..idx].to_vec(); let gs2_header = payload[..idx].to_vec();
let rest = payload[idx..].to_vec(); let rest = payload[idx..].to_vec();
@ -92,29 +92,29 @@ where
// Not supported. // Not supported.
if gs2_header[0] != 0x79 { if gs2_header[0] != 0x79 {
// ord("y") // ord("y")
return Err("channel binding not supported".to_owned()); return Err(MechanismError::ChannelBindingNotSupported);
} }
} }
ref other => { ref other => {
// Supported. // Supported.
if gs2_header[0] == 0x79 { if gs2_header[0] == 0x79 {
// ord("y") // ord("y")
return Err("channel binding is supported".to_owned()); return Err(MechanismError::ChannelBindingIsSupported);
} else if !other.supports("tls-unique") { } else if !other.supports("tls-unique") {
// TODO: grab the data // TODO: grab the data
return Err("channel binding mechanism incorrect".to_owned()); return Err(MechanismError::ChannelBindingMechanismIncorrect);
} }
} }
} }
let frame = let frame =
parse_frame(&rest).map_err(|_| "can't decode initial message".to_owned())?; parse_frame(&rest).map_err(|_| MechanismError::CannotDecodeInitialMessage)?;
let username = frame.get("n").ok_or_else(|| "no username".to_owned())?; let username = frame.get("n").ok_or_else(|| MechanismError::NoUsername)?;
let identity = Identity::Username(username.to_owned()); let identity = Identity::Username(username.to_owned());
let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; let client_nonce = frame.get("r").ok_or_else(|| MechanismError::NoNonce)?;
let mut server_nonce = String::new(); let mut server_nonce = String::new();
server_nonce += client_nonce; server_nonce += client_nonce;
server_nonce += server_nonce +=
&generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; &generate_nonce().map_err(|_| MechanismError::FailedToGenerateNonce)?;
let pbkdf2 = self.provider.provide(&identity)?; let pbkdf2 = self.provider.provide(&identity)?;
let mut buf = Vec::new(); let mut buf = Vec::new();
buf.extend(b"r="); buf.extend(b"r=");
@ -141,7 +141,8 @@ where
ref initial_client_message, ref initial_client_message,
ref initial_server_message, ref initial_server_message,
} => { } => {
let frame = parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; let frame =
parse_frame(payload).map_err(|_| MechanismError::CannotDecodeResponse)?;
let mut cb_data: Vec<u8> = Vec::new(); let mut cb_data: Vec<u8> = Vec::new();
cb_data.extend(gs2_header); cb_data.extend(gs2_header);
cb_data.extend(self.channel_binding.data()); cb_data.extend(self.channel_binding.data());
@ -161,11 +162,11 @@ where
let stored_key = S::hash(&client_key); let stored_key = S::hash(&client_key);
let client_signature = S::hmac(&auth_message, &stored_key)?; let client_signature = S::hmac(&auth_message, &stored_key)?;
let client_proof = xor(&client_key, &client_signature); let client_proof = xor(&client_key, &client_signature);
let sent_proof = frame.get("p").ok_or_else(|| "no proof".to_owned())?; let sent_proof = frame.get("p").ok_or_else(|| MechanismError::NoProof)?;
let sent_proof = let sent_proof =
base64::decode(sent_proof).map_err(|_| "can't decode proof".to_owned())?; base64::decode(sent_proof).map_err(|_| MechanismError::CannotDecodeProof)?;
if client_proof != sent_proof { if client_proof != sent_proof {
return Err("authentication failed".to_owned()); return Err(MechanismError::AuthenticationFailed);
} }
let server_signature = S::hmac(&auth_message, &server_key)?; let server_signature = S::hmac(&auth_message, &server_key)?;
let mut buf = Vec::new(); let mut buf = Vec::new();
@ -175,7 +176,7 @@ where
next_state = ScramState::Done; next_state = ScramState::Done;
} }
ScramState::Done => { ScramState::Done => {
return Err("sasl session is already over".to_owned()); return Err(MechanismError::SaslSessionAlreadyOver);
} }
} }
self.state = next_state; self.state = next_state;

View file

@ -1,5 +1,7 @@
use crate::common::scram::DeriveError;
use crate::common::Identity; use crate::common::Identity;
use crate::secret::Secret; use crate::secret::Secret;
use std::fmt;
#[macro_export] #[macro_export]
macro_rules! impl_validator_using_provider { macro_rules! impl_validator_using_provider {
@ -9,11 +11,11 @@ macro_rules! impl_validator_using_provider {
&self, &self,
identity: &$crate::common::Identity, identity: &$crate::common::Identity,
value: &$secret, value: &$secret,
) -> Result<(), String> { ) -> Result<(), ValidatorError> {
if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value { if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value {
Ok(()) Ok(())
} else { } else {
Err("authentication failure".to_owned()) Err(ValidatorError::AuthenticationFailed)
} }
} }
} }
@ -21,16 +23,150 @@ macro_rules! impl_validator_using_provider {
} }
pub trait Provider<S: Secret>: Validator<S> { pub trait Provider<S: Secret>: Validator<S> {
fn provide(&self, identity: &Identity) -> Result<S, String>; fn provide(&self, identity: &Identity) -> Result<S, ProviderError>;
} }
pub trait Validator<S: Secret> { pub trait Validator<S: Secret> {
fn validate(&self, identity: &Identity, value: &S) -> Result<(), String>; fn validate(&self, identity: &Identity, value: &S) -> Result<(), ValidatorError>;
}
#[derive(Debug, PartialEq)]
pub enum ProviderError {
AuthenticationFailed,
DeriveError(DeriveError),
}
#[derive(Debug, PartialEq)]
pub enum ValidatorError {
AuthenticationFailed,
ProviderError(ProviderError),
}
#[derive(Debug, PartialEq)]
pub enum MechanismError {
NoUsernameSpecified,
ErrorDecodingUsername,
NoPasswordSpecified,
ErrorDecodingPassword,
ValidatorError(ValidatorError),
FailedToDecodeMessage,
ChannelBindingNotSupported,
ChannelBindingIsSupported,
ChannelBindingMechanismIncorrect,
CannotDecodeInitialMessage,
NoUsername,
NoNonce,
FailedToGenerateNonce,
ProviderError(ProviderError),
CannotDecodeResponse,
InvalidKeyLength(hmac::crypto_mac::InvalidKeyLength),
NoProof,
CannotDecodeProof,
AuthenticationFailed,
SaslSessionAlreadyOver,
}
impl From<DeriveError> for ProviderError {
fn from(err: DeriveError) -> ProviderError {
ProviderError::DeriveError(err)
}
}
impl From<ProviderError> for ValidatorError {
fn from(err: ProviderError) -> ValidatorError {
ValidatorError::ProviderError(err)
}
}
impl From<ProviderError> for MechanismError {
fn from(err: ProviderError) -> MechanismError {
MechanismError::ProviderError(err)
}
}
impl From<ValidatorError> for MechanismError {
fn from(err: ValidatorError) -> MechanismError {
MechanismError::ValidatorError(err)
}
}
impl From<hmac::crypto_mac::InvalidKeyLength> for MechanismError {
fn from(err: hmac::crypto_mac::InvalidKeyLength) -> MechanismError {
MechanismError::InvalidKeyLength(err)
}
}
impl fmt::Display for ProviderError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "provider error")
}
}
impl fmt::Display for ValidatorError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "validator error")
}
}
impl fmt::Display for MechanismError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
MechanismError::NoUsernameSpecified => write!(fmt, "no username specified"),
MechanismError::ErrorDecodingUsername => write!(fmt, "error decoding username"),
MechanismError::NoPasswordSpecified => write!(fmt, "no password specified"),
MechanismError::ErrorDecodingPassword => write!(fmt, "error decoding password"),
MechanismError::ValidatorError(err) => write!(fmt, "validator error: {}", err),
MechanismError::FailedToDecodeMessage => write!(fmt, "failed to decode message"),
MechanismError::ChannelBindingNotSupported => {
write!(fmt, "channel binding not supported")
}
MechanismError::ChannelBindingIsSupported => {
write!(fmt, "channel binding is supported")
}
MechanismError::ChannelBindingMechanismIncorrect => {
write!(fmt, "channel binding mechanism is incorrect")
}
MechanismError::CannotDecodeInitialMessage => {
write!(fmt, "cant decode initial message")
}
MechanismError::NoUsername => write!(fmt, "no username"),
MechanismError::NoNonce => write!(fmt, "no nonce"),
MechanismError::FailedToGenerateNonce => write!(fmt, "failed to generate nonce"),
MechanismError::ProviderError(err) => write!(fmt, "provider error: {}", err),
MechanismError::CannotDecodeResponse => write!(fmt, "cant decode response"),
MechanismError::InvalidKeyLength(err) => write!(fmt, "invalid key length: {}", err),
MechanismError::NoProof => write!(fmt, "no proof"),
MechanismError::CannotDecodeProof => write!(fmt, "cant decode proof"),
MechanismError::AuthenticationFailed => write!(fmt, "authentication failed"),
MechanismError::SaslSessionAlreadyOver => write!(fmt, "SASL session already over"),
}
}
}
impl Error for ProviderError {}
impl Error for ValidatorError {}
use std::error::Error;
impl Error for MechanismError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
MechanismError::ValidatorError(err) => Some(err),
MechanismError::ProviderError(err) => Some(err),
// TODO: figure out how to enable the std feature on this crate.
//MechanismError::InvalidKeyLength(err) => Some(err),
_ => None,
}
}
} }
pub trait Mechanism { pub trait Mechanism {
fn name(&self) -> &str; fn name(&self) -> &str;
fn respond(&mut self, payload: &[u8]) -> Result<Response, String>; fn respond(&mut self, payload: &[u8]) -> Result<Response, MechanismError>;
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]