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:
commit
5550148149
10 changed files with 365 additions and 107 deletions
|
@ -1,6 +1,6 @@
|
|||
//! Provides the SASL "ANONYMOUS" mechanism.
|
||||
|
||||
use crate::client::Mechanism;
|
||||
use crate::client::{Mechanism, MechanismError};
|
||||
use crate::common::{Credentials, Secret};
|
||||
|
||||
/// A struct for the SASL ANONYMOUS mechanism.
|
||||
|
@ -21,11 +21,11 @@ impl Mechanism for Anonymous {
|
|||
"ANONYMOUS"
|
||||
}
|
||||
|
||||
fn from_credentials(credentials: Credentials) -> Result<Anonymous, String> {
|
||||
fn from_credentials(credentials: Credentials) -> Result<Anonymous, MechanismError> {
|
||||
if let Secret::None = credentials.secret {
|
||||
Ok(Anonymous)
|
||||
} else {
|
||||
Err("the anonymous sasl mechanism requires no credentials".to_owned())
|
||||
Err(MechanismError::AnonymousRequiresNoCredentials)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Provides the SASL "PLAIN" mechanism.
|
||||
|
||||
use crate::client::Mechanism;
|
||||
use crate::client::{Mechanism, MechanismError};
|
||||
use crate::common::{Credentials, Identity, Password, Secret};
|
||||
|
||||
/// A struct for the SASL PLAIN mechanism.
|
||||
|
@ -27,24 +27,24 @@ impl Mechanism for 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 Identity::Username(username) = credentials.identity {
|
||||
Ok(Plain::new(username, password))
|
||||
} else {
|
||||
Err("PLAIN requires a username".to_owned())
|
||||
Err(MechanismError::PlainRequiresUsername)
|
||||
}
|
||||
} 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();
|
||||
auth.push(0);
|
||||
auth.extend(self.username.bytes());
|
||||
auth.push(0);
|
||||
auth.extend(self.password.bytes());
|
||||
Ok(auth)
|
||||
auth
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use base64;
|
||||
|
||||
use crate::client::Mechanism;
|
||||
use crate::client::{Mechanism, MechanismError};
|
||||
use crate::common::scram::{generate_nonce, ScramProvider};
|
||||
use crate::common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret};
|
||||
|
||||
|
@ -80,20 +80,20 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
&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 Identity::Username(username) = credentials.identity {
|
||||
Scram::new(username, password, credentials.channel_binding)
|
||||
.map_err(|_| "can't generate nonce".to_owned())
|
||||
.map_err(|_| MechanismError::CannotGenerateNonce)
|
||||
} else {
|
||||
Err("SCRAM requires a username".to_owned())
|
||||
Err(MechanismError::ScramRequiresUsername)
|
||||
}
|
||||
} 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();
|
||||
gs2_header.extend(self.channel_binding.header());
|
||||
let mut bare = Vec::new();
|
||||
|
@ -108,10 +108,10 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
initial_message: bare,
|
||||
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 ret;
|
||||
match self.state {
|
||||
|
@ -120,13 +120,13 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
ref gs2_header,
|
||||
} => {
|
||||
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 salt = frame.get("s").and_then(|v| base64::decode(v).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 salt = salt.ok_or_else(|| "no server salt".to_owned())?;
|
||||
let iterations = iterations.ok_or_else(|| "no server iterations".to_owned())?;
|
||||
let server_nonce = server_nonce.ok_or_else(|| MechanismError::NoServerNonce)?;
|
||||
let salt = salt.ok_or_else(|| MechanismError::NoServerSalt)?;
|
||||
let iterations = iterations.ok_or_else(|| MechanismError::NoServerIterations)?;
|
||||
// TODO: SASLprep
|
||||
let mut client_final_message_bare = Vec::new();
|
||||
client_final_message_bare.extend(b"c=");
|
||||
|
@ -159,15 +159,15 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
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;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn success(&mut self, data: &[u8]) -> Result<(), String> {
|
||||
let frame = parse_frame(data).map_err(|_| "can't decode success response".to_owned())?;
|
||||
fn success(&mut self, data: &[u8]) -> Result<(), MechanismError> {
|
||||
let frame = parse_frame(data).map_err(|_| MechanismError::CannotDecodeSuccessResponse)?;
|
||||
match self.state {
|
||||
ScramState::GotServerData {
|
||||
ref server_signature,
|
||||
|
@ -176,13 +176,13 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
|
|||
if sig == *server_signature {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("invalid signature in success response".to_owned())
|
||||
Err(MechanismError::InvalidSignatureInSuccessResponse)
|
||||
}
|
||||
} 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 mut mechanism =
|
||||
Scram::<Sha1>::new_with_nonce(username, password, client_nonce.to_owned());
|
||||
let init = mechanism.initial().unwrap();
|
||||
let init = mechanism.initial();
|
||||
assert_eq!(
|
||||
String::from_utf8(init.clone()).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 mut mechanism =
|
||||
Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned());
|
||||
let init = mechanism.initial().unwrap();
|
||||
let init = mechanism.initial();
|
||||
assert_eq!(
|
||||
String::from_utf8(init.clone()).unwrap(),
|
||||
String::from_utf8(client_init[..].to_owned()).unwrap()
|
||||
|
|
|
@ -1,4 +1,80 @@
|
|||
use crate::common::scram::DeriveError;
|
||||
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.
|
||||
pub trait Mechanism {
|
||||
|
@ -6,22 +82,22 @@ pub trait Mechanism {
|
|||
fn name(&self) -> &str;
|
||||
|
||||
/// Creates this mechanism from `Credentials`.
|
||||
fn from_credentials(credentials: Credentials) -> Result<Self, String>
|
||||
fn from_credentials(credentials: Credentials) -> Result<Self, MechanismError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Provides initial payload of the SASL mechanism.
|
||||
fn initial(&mut self) -> Result<Vec<u8>, String> {
|
||||
Ok(Vec::new())
|
||||
fn initial(&mut self) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use getrandom::{getrandom, Error as RngError};
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac};
|
||||
use pbkdf2::pbkdf2;
|
||||
use sha1::{Digest, Sha1 as Sha1_hash};
|
||||
use sha2::Sha256 as Sha256_hash;
|
||||
|
@ -17,6 +17,29 @@ pub fn generate_nonce() -> Result<String, RngError> {
|
|||
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.
|
||||
pub trait ScramProvider {
|
||||
/// The kind of secret this `ScramProvider` requires.
|
||||
|
@ -29,10 +52,10 @@ pub trait ScramProvider {
|
|||
fn hash(data: &[u8]) -> Vec<u8>;
|
||||
|
||||
/// 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.
|
||||
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
|
||||
|
@ -52,12 +75,9 @@ impl ScramProvider for Sha1 {
|
|||
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>;
|
||||
let mut mac = match HmacSha1::new_varkey(key) {
|
||||
Ok(mac) => mac,
|
||||
Err(err) => return Err(format!("{}", err)),
|
||||
};
|
||||
let mut mac = HmacSha1::new_varkey(key)?;
|
||||
mac.input(data);
|
||||
let result = mac.result();
|
||||
let mut vec = Vec::with_capacity(Sha1_hash::output_size());
|
||||
|
@ -65,7 +85,7 @@ impl ScramProvider for Sha1 {
|
|||
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 {
|
||||
Password::Plain(ref plain) => {
|
||||
let mut result = vec![0; 20];
|
||||
|
@ -79,17 +99,16 @@ impl ScramProvider for Sha1 {
|
|||
ref data,
|
||||
} => {
|
||||
if method != Self::name() {
|
||||
Err(format!(
|
||||
"incompatible hashing method, {} is not {}",
|
||||
method,
|
||||
Self::name()
|
||||
Err(DeriveError::IncompatibleHashingMethod(
|
||||
method.to_string(),
|
||||
Self::name().to_string(),
|
||||
))
|
||||
} else if my_salt == &salt {
|
||||
Err(format!("incorrect salt"))
|
||||
Err(DeriveError::IncorrectSalt)
|
||||
} else if my_iterations == iterations {
|
||||
Err(format!(
|
||||
"incompatible iteration count, {} is not {}",
|
||||
my_iterations, iterations
|
||||
Err(DeriveError::IncompatibleIterationCount(
|
||||
my_iterations,
|
||||
iterations,
|
||||
))
|
||||
} else {
|
||||
Ok(data.to_vec())
|
||||
|
@ -116,12 +135,9 @@ impl ScramProvider for Sha256 {
|
|||
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>;
|
||||
let mut mac = match HmacSha256::new_varkey(key) {
|
||||
Ok(mac) => mac,
|
||||
Err(err) => return Err(format!("{}", err)),
|
||||
};
|
||||
let mut mac = HmacSha256::new_varkey(key)?;
|
||||
mac.input(data);
|
||||
let result = mac.result();
|
||||
let mut vec = Vec::with_capacity(Sha256_hash::output_size());
|
||||
|
@ -129,7 +145,7 @@ impl ScramProvider for Sha256 {
|
|||
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 {
|
||||
Password::Plain(ref plain) => {
|
||||
let mut result = vec![0; 32];
|
||||
|
@ -143,17 +159,16 @@ impl ScramProvider for Sha256 {
|
|||
ref data,
|
||||
} => {
|
||||
if method != Self::name() {
|
||||
Err(format!(
|
||||
"incompatible hashing method, {} is not {}",
|
||||
method,
|
||||
Self::name()
|
||||
Err(DeriveError::IncompatibleHashingMethod(
|
||||
method.to_string(),
|
||||
Self::name().to_string(),
|
||||
))
|
||||
} else if my_salt == &salt {
|
||||
Err(format!("incorrect salt"))
|
||||
Err(DeriveError::IncorrectSalt)
|
||||
} else if my_iterations == iterations {
|
||||
Err(format!(
|
||||
"incompatible iteration count, {} is not {}",
|
||||
my_iterations, iterations
|
||||
Err(DeriveError::IncompatibleIterationCount(
|
||||
my_iterations,
|
||||
iterations,
|
||||
))
|
||||
} else {
|
||||
Ok(data.to_vec())
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
//!
|
||||
//! 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");
|
||||
//! ```
|
||||
|
@ -28,8 +28,9 @@
|
|||
//! #[macro_use] extern crate sasl;
|
||||
//!
|
||||
//! 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::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::common::{Identity, Credentials, Password, ChannelBinding};
|
||||
//! use sasl::common::scram::{ScramProvider, Sha1, Sha256};
|
||||
|
@ -43,13 +44,13 @@
|
|||
//! struct 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;
|
||||
//! if identity != &Identity::Username(USERNAME.to_owned()) {
|
||||
//! Err("authentication failed".to_owned())
|
||||
//! Err(ValidatorError::AuthenticationFailed)
|
||||
//! }
|
||||
//! else if password != PASSWORD {
|
||||
//! Err("authentication failed".to_owned())
|
||||
//! Err(ValidatorError::AuthenticationFailed)
|
||||
//! }
|
||||
//! else {
|
||||
//! Ok(())
|
||||
|
@ -58,9 +59,9 @@
|
|||
//! }
|
||||
//!
|
||||
//! 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()) {
|
||||
//! Err("authentication failed".to_owned())
|
||||
//! Err(ProviderError::AuthenticationFailed)
|
||||
//! }
|
||||
//! else {
|
||||
//! let digest = sasl::common::scram::Sha1::derive
|
||||
|
@ -79,9 +80,9 @@
|
|||
//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1);
|
||||
//!
|
||||
//! 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()) {
|
||||
//! Err("authentication failed".to_owned())
|
||||
//! Err(ProviderError::AuthenticationFailed)
|
||||
//! }
|
||||
//! else {
|
||||
//! let digest = sasl::common::scram::Sha256::derive
|
||||
|
@ -99,10 +100,28 @@
|
|||
//!
|
||||
//! 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,
|
||||
//! SM: ServerMechanism {
|
||||
//! let init = cm.initial()?;
|
||||
//! let init = cm.initial();
|
||||
//! println!("C: {}", String::from_utf8_lossy(&init));
|
||||
//! let mut resp = sm.respond(&init)?;
|
||||
//! loop {
|
||||
|
@ -133,7 +152,7 @@
|
|||
//! assert_eq!(mech.respond(b"\0user\0pencil"), Ok(expected_response));
|
||||
//!
|
||||
//! 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()
|
||||
//! .with_username(USERNAME)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#[cfg(feature = "scram")]
|
||||
use crate::common::scram::DeriveError;
|
||||
|
||||
pub trait Secret {}
|
||||
|
||||
pub trait Pbkdf2Secret {
|
||||
|
@ -20,7 +23,11 @@ pub struct Pbkdf2Sha1 {
|
|||
|
||||
impl Pbkdf2Sha1 {
|
||||
#[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::Password;
|
||||
let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?;
|
||||
|
@ -55,7 +62,11 @@ pub struct Pbkdf2Sha256 {
|
|||
|
||||
impl Pbkdf2Sha256 {
|
||||
#[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::Password;
|
||||
let digest = Sha256::derive(&Password::Plain(password.to_owned()), salt, iterations)?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::Identity;
|
||||
use crate::secret;
|
||||
use crate::server::{Mechanism, Response, Validator};
|
||||
use crate::server::{Mechanism, MechanismError, Response, Validator};
|
||||
|
||||
pub struct Plain<V: Validator<secret::Plain>> {
|
||||
validator: V,
|
||||
|
@ -19,19 +19,19 @@ impl<V: Validator<secret::Plain>> Mechanism for Plain<V> {
|
|||
"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);
|
||||
sp.next();
|
||||
let username = sp
|
||||
.next()
|
||||
.ok_or_else(|| "no username specified".to_owned())?;
|
||||
let username =
|
||||
String::from_utf8(username.to_vec()).map_err(|_| "error decoding username")?;
|
||||
.ok_or_else(|| MechanismError::NoUsernameSpecified)?;
|
||||
let username = String::from_utf8(username.to_vec())
|
||||
.map_err(|_| MechanismError::ErrorDecodingUsername)?;
|
||||
let password = sp
|
||||
.next()
|
||||
.ok_or_else(|| "no password specified".to_owned())?;
|
||||
let password =
|
||||
String::from_utf8(password.to_vec()).map_err(|_| "error decoding password")?;
|
||||
.ok_or_else(|| MechanismError::NoPasswordSpecified)?;
|
||||
let password = String::from_utf8(password.to_vec())
|
||||
.map_err(|_| MechanismError::ErrorDecodingPassword)?;
|
||||
let ident = Identity::Username(username);
|
||||
self.validator.validate(&ident, &secret::Plain(password))?;
|
||||
Ok(Response::Success(ident, Vec::new()))
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::common::scram::{generate_nonce, ScramProvider};
|
|||
use crate::common::{parse_frame, xor, ChannelBinding, Identity};
|
||||
use crate::secret;
|
||||
use crate::secret::Pbkdf2Secret;
|
||||
use crate::server::{Mechanism, Provider, Response};
|
||||
use crate::server::{Mechanism, MechanismError, Provider, Response};
|
||||
|
||||
enum ScramState {
|
||||
Init,
|
||||
|
@ -61,7 +61,7 @@ where
|
|||
&self.name
|
||||
}
|
||||
|
||||
fn respond(&mut self, payload: &[u8]) -> Result<Response, String> {
|
||||
fn respond(&mut self, payload: &[u8]) -> Result<Response, MechanismError> {
|
||||
let next_state;
|
||||
let ret;
|
||||
match self.state {
|
||||
|
@ -82,7 +82,7 @@ where
|
|||
}
|
||||
}
|
||||
if commas < 2 {
|
||||
return Err("failed to decode message".to_owned());
|
||||
return Err(MechanismError::FailedToDecodeMessage);
|
||||
}
|
||||
let gs2_header = payload[..idx].to_vec();
|
||||
let rest = payload[idx..].to_vec();
|
||||
|
@ -92,29 +92,29 @@ where
|
|||
// Not supported.
|
||||
if gs2_header[0] != 0x79 {
|
||||
// ord("y")
|
||||
return Err("channel binding not supported".to_owned());
|
||||
return Err(MechanismError::ChannelBindingNotSupported);
|
||||
}
|
||||
}
|
||||
ref other => {
|
||||
// Supported.
|
||||
if gs2_header[0] == 0x79 {
|
||||
// ord("y")
|
||||
return Err("channel binding is supported".to_owned());
|
||||
return Err(MechanismError::ChannelBindingIsSupported);
|
||||
} else if !other.supports("tls-unique") {
|
||||
// TODO: grab the data
|
||||
return Err("channel binding mechanism incorrect".to_owned());
|
||||
return Err(MechanismError::ChannelBindingMechanismIncorrect);
|
||||
}
|
||||
}
|
||||
}
|
||||
let frame =
|
||||
parse_frame(&rest).map_err(|_| "can't decode initial message".to_owned())?;
|
||||
let username = frame.get("n").ok_or_else(|| "no username".to_owned())?;
|
||||
parse_frame(&rest).map_err(|_| MechanismError::CannotDecodeInitialMessage)?;
|
||||
let username = frame.get("n").ok_or_else(|| MechanismError::NoUsername)?;
|
||||
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();
|
||||
server_nonce += client_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 mut buf = Vec::new();
|
||||
buf.extend(b"r=");
|
||||
|
@ -141,7 +141,8 @@ where
|
|||
ref initial_client_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();
|
||||
cb_data.extend(gs2_header);
|
||||
cb_data.extend(self.channel_binding.data());
|
||||
|
@ -161,11 +162,11 @@ where
|
|||
let stored_key = S::hash(&client_key);
|
||||
let client_signature = S::hmac(&auth_message, &stored_key)?;
|
||||
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 =
|
||||
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 {
|
||||
return Err("authentication failed".to_owned());
|
||||
return Err(MechanismError::AuthenticationFailed);
|
||||
}
|
||||
let server_signature = S::hmac(&auth_message, &server_key)?;
|
||||
let mut buf = Vec::new();
|
||||
|
@ -175,7 +176,7 @@ where
|
|||
next_state = ScramState::Done;
|
||||
}
|
||||
ScramState::Done => {
|
||||
return Err("sasl session is already over".to_owned());
|
||||
return Err(MechanismError::SaslSessionAlreadyOver);
|
||||
}
|
||||
}
|
||||
self.state = next_state;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::common::scram::DeriveError;
|
||||
use crate::common::Identity;
|
||||
use crate::secret::Secret;
|
||||
use std::fmt;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_validator_using_provider {
|
||||
|
@ -9,11 +11,11 @@ macro_rules! impl_validator_using_provider {
|
|||
&self,
|
||||
identity: &$crate::common::Identity,
|
||||
value: &$secret,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), ValidatorError> {
|
||||
if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value {
|
||||
Ok(())
|
||||
} 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> {
|
||||
fn provide(&self, identity: &Identity) -> Result<S, String>;
|
||||
fn provide(&self, identity: &Identity) -> Result<S, ProviderError>;
|
||||
}
|
||||
|
||||
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, "can’t 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, "can’t decode response"),
|
||||
MechanismError::InvalidKeyLength(err) => write!(fmt, "invalid key length: {}", err),
|
||||
MechanismError::NoProof => write!(fmt, "no proof"),
|
||||
MechanismError::CannotDecodeProof => write!(fmt, "can’t 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 {
|
||||
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)]
|
||||
|
|
Loading…
Reference in a new issue