diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 76c90bf3..5862c31e 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -8,6 +8,8 @@ use openssl::sign::Signer; use common::Password; +use secret; + use base64; /// Generate a nonce for SCRAM authentication. @@ -19,6 +21,9 @@ pub fn generate_nonce() -> Result { /// A trait which defines the needed methods for SCRAM. pub trait ScramProvider { + /// The kind of secret this `ScramProvider` requires. + type SecretKind: secret::SecretKind; + /// The name of the hash function. fn name() -> &'static str; @@ -37,6 +42,8 @@ pub struct Sha1; impl ScramProvider for Sha1 { // TODO: look at all these unwraps + type SecretKind = secret::Pbkdf2Sha1; + fn name() -> &'static str { "SHA-1" } @@ -98,6 +105,8 @@ pub struct Sha256; impl ScramProvider for Sha256 { // TODO: look at all these unwraps + type SecretKind = secret::Pbkdf2Sha256; + fn name() -> &'static str { "SHA-256" } diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index af40cd3a..c454eef1 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -25,12 +25,15 @@ //! ## More complex usage //! //! ```rust -//! use sasl::server::{Validator, Mechanism as ServerMechanism, Response}; +//! #[macro_use] extern crate sasl; +//! +//! use sasl::server::{Validator, Provider, Mechanism as ServerMechanism, Response}; //! use sasl::server::mechanisms::{Plain as ServerPlain, Scram as ServerScram}; //! use sasl::client::Mechanism as ClientMechanism; //! use sasl::client::mechanisms::{Plain as ClientPlain, Scram as ClientScram}; -//! use sasl::common::{Identity, Credentials, Secret, Password, ChannelBinding}; +//! use sasl::common::{Identity, Credentials, Password, ChannelBinding}; //! use sasl::common::scram::{ScramProvider, Sha1, Sha256}; +//! use sasl::secret; //! //! const USERNAME: &'static str = "user"; //! const PASSWORD: &'static str = "pencil"; @@ -39,41 +42,67 @@ //! //! struct MyValidator; //! -//! impl Validator for MyValidator { -//! fn validate_credentials(&self, creds: &Credentials) -> Result { -//! if creds.identity != Identity::Username(USERNAME.to_owned()) { -//! Err("authentication failure".to_owned()) +//! impl Validator for MyValidator { +//! fn validate(&self, identity: &Identity, value: &secret::PlainValue) -> Result<(), String> { +//! let &secret::PlainValue(ref password) = value; +//! if identity != &Identity::Username(USERNAME.to_owned()) { +//! Err("authentication failed".to_owned()) //! } -//! else if creds.secret != Secret::password_plain(PASSWORD) { -//! Err("authentication failure".to_owned()) +//! else if password != PASSWORD { +//! Err("authentication failed".to_owned()) //! } //! else { -//! Ok(creds.identity.clone()) +//! Ok(()) //! } //! } -//! -//! fn request_pbkdf2(&self) -> Result<(Vec, usize, Vec), String> { -//! Ok( ( SALT.to_vec() -//! , ITERATIONS -//! , S::derive(&Password::Plain(PASSWORD.to_owned()), &SALT, ITERATIONS)? ) ) -//! } //! } //! -//! let mut mech = ServerPlain::new(MyValidator); -//! let expected_response = Response::Success(Identity::Username("user".to_owned()), Vec::new()); -//! assert_eq!(mech.respond(b"\0user\0pencil"), Ok(expected_response)); +//! impl Provider for MyValidator { +//! fn provide(&self, identity: &Identity) -> Result { +//! if identity != &Identity::Username(USERNAME.to_owned()) { +//! Err("authentication failed".to_owned()) +//! } +//! else { +//! let digest = sasl::common::scram::Sha1::derive +//! ( &Password::Plain((PASSWORD.to_owned())) +//! , &SALT[..] +//! , ITERATIONS )?; +//! Ok(secret::Pbkdf2Sha1Value { +//! salt: SALT.to_vec(), +//! iterations: ITERATIONS, +//! digest: digest, +//! }) +//! } +//! } +//! } //! -//! let mut mech = ServerPlain::new(MyValidator); -//! assert_eq!(mech.respond(b"\0user\0marker"), Err("authentication failure".to_owned())); +//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1); //! -//! let creds = Credentials::default() -//! .with_username(USERNAME) -//! .with_password(PASSWORD); +//! impl Provider for MyValidator { +//! fn provide(&self, identity: &Identity) -> Result { +//! if identity != &Identity::Username(USERNAME.to_owned()) { +//! Err("authentication failed".to_owned()) +//! } +//! else { +//! let digest = sasl::common::scram::Sha256::derive +//! ( &Password::Plain((PASSWORD.to_owned())) +//! , &SALT[..] +//! , ITERATIONS )?; +//! Ok(secret::Pbkdf2Sha256Value { +//! salt: SALT.to_vec(), +//! iterations: ITERATIONS, +//! digest: digest, +//! }) +//! } +//! } +//! } //! -//! fn finish(cm: &mut CM, sm: &mut SM) -> Result +//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha256); + +//! +//! fn finish(cm: &mut CM, sm: &mut SM) -> Result //! where CM: ClientMechanism, -//! SM: ServerMechanism, -//! V: Validator { +//! SM: ServerMechanism { //! let init = cm.initial()?; //! println!("C: {}", String::from_utf8_lossy(&init)); //! let mut resp = sm.respond(&init)?; @@ -99,20 +128,32 @@ //! } //! } //! -//! let mut client_mech = ClientPlain::from_credentials(creds.clone()).unwrap(); -//! let mut server_mech = ServerPlain::new(MyValidator); +//! fn main() { +//! let mut mech = ServerPlain::new(MyValidator); +//! let expected_response = Response::Success(Identity::Username("user".to_owned()), Vec::new()); +//! assert_eq!(mech.respond(b"\0user\0pencil"), Ok(expected_response)); //! -//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! let mut mech = ServerPlain::new(MyValidator); +//! assert_eq!(mech.respond(b"\0user\0marker"), Err("authentication failed".to_owned())); //! -//! let mut client_mech = ClientScram::::from_credentials(creds.clone()).unwrap(); -//! let mut server_mech = ServerScram::::new(MyValidator, ChannelBinding::Unsupported); +//! let creds = Credentials::default() +//! .with_username(USERNAME) +//! .with_password(PASSWORD); +//! let mut client_mech = ClientPlain::from_credentials(creds.clone()).unwrap(); +//! let mut server_mech = ServerPlain::new(MyValidator); //! -//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); //! -//! let mut client_mech = ClientScram::::from_credentials(creds.clone()).unwrap(); -//! let mut server_mech = ServerScram::::new(MyValidator, ChannelBinding::Unsupported); +//! let mut client_mech = ClientScram::::from_credentials(creds.clone()).unwrap(); +//! let mut server_mech = ServerScram::::new(MyValidator, ChannelBinding::Unsupported); //! -//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! +//! let mut client_mech = ClientScram::::from_credentials(creds.clone()).unwrap(); +//! let mut server_mech = ServerScram::::new(MyValidator, ChannelBinding::Unsupported); +//! +//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! } //! ``` //! //! # Usage @@ -131,7 +172,9 @@ extern crate openssl; mod error; pub mod client; -pub mod common; +#[macro_use] pub mod server; +pub mod common; +pub mod secret; pub use error::Error; diff --git a/sasl/src/mechanisms/mod.rs b/sasl/src/mechanisms/mod.rs new file mode 100644 index 00000000..408740a5 --- /dev/null +++ b/sasl/src/mechanisms/mod.rs @@ -0,0 +1,5 @@ +mod plain; +mod scram; + +pub use self::plain::Plain; +pub use self::scram::Scram; diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs new file mode 100644 index 00000000..171e2f92 --- /dev/null +++ b/sasl/src/secret.rs @@ -0,0 +1,74 @@ +pub trait SecretKind { + type Value: PartialEq; +} + +pub trait Pbkdf2SecretValue { + fn salt(&self) -> &[u8]; + fn iterations(&self) -> usize; + fn digest(&self) -> &[u8]; +} + +pub struct Plain; + +#[derive(PartialEq)] +pub struct PlainValue(pub String); + +impl SecretKind for Plain { + type Value = PlainValue; +} + +pub struct Pbkdf2Sha1 { + pub salt: Vec, + pub iterations: usize, +} + +#[derive(PartialEq)] +pub struct Pbkdf2Sha1Value { + pub salt: Vec, + pub iterations: usize, + pub digest: Vec, +} + +impl SecretKind for Pbkdf2Sha1 { + type Value = Pbkdf2Sha1Value; +} + +impl Pbkdf2SecretValue for Pbkdf2Sha1Value { + fn salt(&self) -> &[u8] { + &self.salt + } + fn iterations(&self) -> usize { + self.iterations + } + fn digest(&self) -> &[u8] { + &self.digest + } +} + +pub struct Pbkdf2Sha256 { + pub salt: Vec, + pub iterations: usize, +} + +#[derive(PartialEq)] +pub struct Pbkdf2Sha256Value { + pub salt: Vec, + pub iterations: usize, + pub digest: Vec, +} + +impl SecretKind for Pbkdf2Sha256 { + type Value = Pbkdf2Sha256Value; +} + +impl Pbkdf2SecretValue for Pbkdf2Sha256Value { + fn salt(&self) -> &[u8] { + &self.salt + } + fn iterations(&self) -> usize { + self.iterations + } + fn digest(&self) -> &[u8] { + &self.digest + } +} diff --git a/sasl/src/server/mechanisms/mod.rs b/sasl/src/server/mechanisms/mod.rs new file mode 100644 index 00000000..77181025 --- /dev/null +++ b/sasl/src/server/mechanisms/mod.rs @@ -0,0 +1,7 @@ +mod plain; +#[cfg(feature = "scram")] +mod scram; + +pub use self::plain::Plain; +#[cfg(feature = "scram")] +pub use self::scram::Scram; diff --git a/sasl/src/server/mechanisms/plain.rs b/sasl/src/server/mechanisms/plain.rs index 1594256f..f43f68f5 100644 --- a/sasl/src/server/mechanisms/plain.rs +++ b/sasl/src/server/mechanisms/plain.rs @@ -1,13 +1,40 @@ -use server::Mechanism; -use common::{Secret, Credentials, Password}; +use common::Identity; +use secret; +use server::{Mechanism, Response, Validator}; -pub struct Plain { - password: String, +pub struct Plain> { + validator: V, } -impl Mechanism for Plain { - fn name(&self) -> &str { "PLAIN" } - - fn from_initial_message(validator: &V, msg: &[u8]) -> Result<(Self, String), String> { +impl> Plain { + pub fn new(validator: V) -> Plain { + Plain { + validator: validator, + } + } +} + +impl> Mechanism for Plain { + fn name(&self) -> &str { + "PLAIN" + } + + fn respond(&mut self, payload: &[u8]) -> Result { + 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")?; + 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")?; + let ident = Identity::Username(username); + self.validator + .validate(&ident, &secret::PlainValue(password))?; + Ok(Response::Success(ident, Vec::new())) } } diff --git a/sasl/src/server/mechanisms/scram.rs b/sasl/src/server/mechanisms/scram.rs new file mode 100644 index 00000000..d888ebc8 --- /dev/null +++ b/sasl/src/server/mechanisms/scram.rs @@ -0,0 +1,185 @@ +use std::marker::PhantomData; + +use base64; + +use common::scram::{generate_nonce, ScramProvider}; +use common::{parse_frame, xor, ChannelBinding, Identity}; +use secret; +use secret::Pbkdf2SecretValue; +use server::{Mechanism, Provider, Response}; + +enum ScramState { + Init, + SentChallenge { + initial_client_message: Vec, + initial_server_message: Vec, + gs2_header: Vec, + server_nonce: String, + identity: Identity, + salted_password: Vec, + }, + Done, +} + +pub struct Scram +where + S: ScramProvider, + P: Provider, + ::Value: secret::Pbkdf2SecretValue, +{ + name: String, + state: ScramState, + channel_binding: ChannelBinding, + provider: P, + _marker: PhantomData, +} + +impl Scram +where + S: ScramProvider, + P: Provider, + ::Value: secret::Pbkdf2SecretValue, +{ + pub fn new(provider: P, channel_binding: ChannelBinding) -> Scram { + Scram { + name: format!("SCRAM-{}", S::name()), + state: ScramState::Init, + channel_binding: channel_binding, + provider: provider, + _marker: PhantomData, + } + } +} + +impl Mechanism for Scram +where + S: ScramProvider, + P: Provider, + ::Value: secret::Pbkdf2SecretValue, +{ + fn name(&self) -> &str { + &self.name + } + + fn respond(&mut self, payload: &[u8]) -> Result { + let next_state; + let ret; + match self.state { + ScramState::Init => { + // TODO: really ugly, mostly because parse_frame takes a &[u8] and i don't + // want to double validate utf-8 + // + // NEED TO CHANGE THIS THOUGH. IT'S AWFUL. + let mut commas = 0; + let mut idx = 0; + for &b in payload { + idx += 1; + if b == 0x2C { + commas += 1; + if commas >= 2 { + break; + } + } + } + if commas < 2 { + return Err("failed to decode message".to_owned()); + } + let gs2_header = payload[..idx].to_vec(); + let rest = payload[idx..].to_vec(); + // TODO: process gs2 header properly, not this ugly stuff + match self.channel_binding { + ChannelBinding::None | ChannelBinding::Unsupported => { + // Not supported. + if gs2_header[0] != 0x79 { + // ord("y") + return Err("channel binding not supported".to_owned()); + } + } + ref other => { + // Supported. + if gs2_header[0] == 0x79 { + // ord("y") + return Err("channel binding is supported".to_owned()); + } else if !other.supports("tls-unique") { + // TODO: grab the data + return Err("channel binding mechanism incorrect".to_owned()); + } + } + } + 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())?; + let identity = Identity::Username(username.to_owned()); + let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; + let mut server_nonce = String::new(); + server_nonce += client_nonce; + server_nonce += + &generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; + let pbkdf2 = self.provider.provide(&identity)?; + let mut buf = Vec::new(); + buf.extend(b"r="); + buf.extend(server_nonce.bytes()); + buf.extend(b",s="); + buf.extend(base64::encode(pbkdf2.salt()).bytes()); + buf.extend(b",i="); + buf.extend(pbkdf2.iterations().to_string().bytes()); + ret = Response::Proceed(buf.clone()); + next_state = ScramState::SentChallenge { + server_nonce: server_nonce, + identity: identity, + salted_password: pbkdf2.digest().to_vec(), + initial_client_message: rest, + initial_server_message: buf, + gs2_header: gs2_header, + }; + } + ScramState::SentChallenge { + ref server_nonce, + ref identity, + ref salted_password, + ref gs2_header, + ref initial_client_message, + ref initial_server_message, + } => { + let frame = parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; + let mut cb_data: Vec = Vec::new(); + cb_data.extend(gs2_header); + cb_data.extend(self.channel_binding.data()); + let mut client_final_message_bare = Vec::new(); + client_final_message_bare.extend(b"c="); + client_final_message_bare.extend(base64::encode(&cb_data).bytes()); + client_final_message_bare.extend(b",r="); + client_final_message_bare.extend(server_nonce.bytes()); + let client_key = S::hmac(b"Client Key", &salted_password); + let server_key = S::hmac(b"Server Key", &salted_password); + let stored_key = S::hash(&client_key); + let mut auth_message = Vec::new(); + auth_message.extend(initial_client_message); + auth_message.extend(b","); + auth_message.extend(initial_server_message); + auth_message.extend(b","); + auth_message.extend(client_final_message_bare.clone()); + 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 = + base64::decode(sent_proof).map_err(|_| "can't decode proof".to_owned())?; + if client_proof != sent_proof { + return Err("authentication failed".to_owned()); + } + let server_signature = S::hmac(&auth_message, &server_key); + let mut buf = Vec::new(); + buf.extend(b"v="); + buf.extend(base64::encode(&server_signature).bytes()); + ret = Response::Success(identity.clone(), buf); + next_state = ScramState::Done; + } + ScramState::Done => { + return Err("sasl session is already over".to_owned()); + } + } + self.state = next_state; + Ok(ret) + } +} diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index ede741a2..6f9c4fa9 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,16 +1,34 @@ -use common::{Credentials, Identity}; +use common::Identity; +use secret::SecretKind; -#[cfg(feature = "scram")] -use common::scram::ScramProvider; - -pub trait Validator { - fn validate_credentials(&self, credentials: &Credentials) -> Result; - - #[cfg(feature = "scram")] - fn request_pbkdf2(&self) -> Result<(Vec, usize, Vec), String>; +#[macro_export] +macro_rules! impl_validator_using_provider { + ( $type:ty, $secret:ty ) => { + impl $crate::server::Validator<$secret> for $type { + fn validate( + &self, + identity: &$crate::common::Identity, + value: &<$secret as sasl::secret::SecretKind>::Value, + ) -> Result<(), String> { + if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value { + Ok(()) + } else { + Err("authentication failure".to_owned()) + } + } + } + }; } -pub trait Mechanism { +pub trait Provider: Validator { + fn provide(&self, identity: &Identity) -> Result; +} + +pub trait Validator { + fn validate(&self, identity: &Identity, value: &S::Value) -> Result<(), String>; +} + +pub trait Mechanism { fn name(&self) -> &str; fn respond(&mut self, payload: &[u8]) -> Result; } @@ -21,225 +39,4 @@ pub enum Response { Proceed(Vec), } -pub mod mechanisms { - mod plain { - use common::{ChannelBinding, Credentials, Identity, Secret}; - use server::{Mechanism, Response, Validator}; - - pub struct Plain { - validator: V, - } - - impl Plain { - pub fn new(validator: V) -> Plain { - Plain { - validator: validator, - } - } - } - - impl Mechanism for Plain { - fn name(&self) -> &str { - "PLAIN" - } - - fn respond(&mut self, payload: &[u8]) -> Result { - 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")?; - 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")?; - let creds = Credentials { - identity: Identity::Username(username), - secret: Secret::password_plain(password), - channel_binding: ChannelBinding::None, - }; - let ret = self.validator.validate_credentials(&creds)?; - Ok(Response::Success(ret, Vec::new())) - } - } - } - - #[cfg(feature = "scram")] - mod scram { - use std::marker::PhantomData; - - use base64; - - use common::scram::{generate_nonce, ScramProvider}; - use common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Secret}; - use server::{Mechanism, Response, Validator}; - - enum ScramState { - Init, - SentChallenge { - initial_client_message: Vec, - initial_server_message: Vec, - gs2_header: Vec, - server_nonce: String, - username: String, - salted_password: Vec, - }, - Done, - } - - pub struct Scram { - name: String, - state: ScramState, - channel_binding: ChannelBinding, - validator: V, - _marker: PhantomData, - } - - impl Scram { - pub fn new(validator: V, channel_binding: ChannelBinding) -> Scram { - Scram { - name: format!("SCRAM-{}", S::name()), - state: ScramState::Init, - channel_binding: channel_binding, - validator: validator, - _marker: PhantomData, - } - } - } - - impl Mechanism for Scram { - fn name(&self) -> &str { - &self.name - } - - fn respond(&mut self, payload: &[u8]) -> Result { - let next_state; - let ret; - match self.state { - ScramState::Init => { - // TODO: really ugly, mostly because parse_frame takes a &[u8] and i don't - // want to double validate utf-8 - // - // NEED TO CHANGE THIS THOUGH. IT'S AWFUL. - let mut commas = 0; - let mut idx = 0; - for &b in payload { - idx += 1; - if b == 0x2C { - commas += 1; - if commas >= 2 { - break; - } - } - } - if commas < 2 { - return Err("failed to decode message".to_owned()); - } - let gs2_header = payload[..idx].to_vec(); - let rest = payload[idx..].to_vec(); - // TODO: process gs2 header properly, not this ugly stuff - match self.channel_binding { - ChannelBinding::None | ChannelBinding::Unsupported => { - // Not supported. - if gs2_header[0] != 0x79 { - // ord("y") - return Err("channel binding not supported".to_owned()); - } - } - ref other => { - // Supported. - if gs2_header[0] == 0x79 { - // ord("y") - return Err("channel binding is supported".to_owned()); - } else if !other.supports("tls-unique") { - // TODO: grab the data - return Err("channel binding mechanism incorrect".to_owned()); - } - } - } - 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())?; - let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; - let mut server_nonce = String::new(); - server_nonce += client_nonce; - server_nonce += - &generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; - let (salt, iterations, data) = self.validator.request_pbkdf2::()?; - let mut buf = Vec::new(); - buf.extend(b"r="); - buf.extend(server_nonce.bytes()); - buf.extend(b",s="); - buf.extend(base64::encode(&salt).bytes()); - buf.extend(b",i="); - buf.extend(iterations.to_string().bytes()); - ret = Response::Proceed(buf.clone()); - next_state = ScramState::SentChallenge { - server_nonce: server_nonce, - username: username.to_owned(), - salted_password: data, - initial_client_message: rest, - initial_server_message: buf, - gs2_header: gs2_header, - }; - } - ScramState::SentChallenge { - server_nonce: ref server_nonce, - username: ref username, - salted_password: ref salted_password, - gs2_header: ref gs2_header, - initial_client_message: ref initial_client_message, - initial_server_message: ref initial_server_message, - } => { - let frame = - parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; - let mut cb_data: Vec = Vec::new(); - cb_data.extend(gs2_header); - cb_data.extend(self.channel_binding.data()); - let mut client_final_message_bare = Vec::new(); - client_final_message_bare.extend(b"c="); - client_final_message_bare.extend(base64::encode(&cb_data).bytes()); - client_final_message_bare.extend(b",r="); - client_final_message_bare.extend(server_nonce.bytes()); - let client_key = S::hmac(b"Client Key", &salted_password); - let server_key = S::hmac(b"Server Key", &salted_password); - let stored_key = S::hash(&client_key); - let mut auth_message = Vec::new(); - auth_message.extend(initial_client_message); - auth_message.extend(b","); - auth_message.extend(initial_server_message); - auth_message.extend(b","); - auth_message.extend(client_final_message_bare.clone()); - 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 = base64::decode(sent_proof) - .map_err(|_| "can't decode proof".to_owned())?; - if client_proof != sent_proof { - return Err("authentication failed".to_owned()); - } - let server_signature = S::hmac(&auth_message, &server_key); - let mut buf = Vec::new(); - buf.extend(b"v="); - buf.extend(base64::encode(&server_signature).bytes()); - ret = Response::Success(Identity::Username(username.to_owned()), buf); - next_state = ScramState::Done; - } - ScramState::Done => { - return Err("sasl session is already over".to_owned()); - } - } - self.state = next_state; - Ok(ret) - } - } - } - - pub use self::plain::Plain; - #[cfg(feature = "scram")] - pub use self::scram::Scram; -} +pub mod mechanisms;