diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index ac3e6ec..5843b84 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -8,6 +8,8 @@ use openssl::error::ErrorStack; 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; @@ -36,6 +41,8 @@ pub trait ScramProvider { pub struct Sha1; impl ScramProvider for Sha1 { // TODO: look at all these unwraps + type SecretKind = secret::Pbkdf2Sha1; + fn name() -> &'static str { "SHA-1" } fn hash(data: &[u8]) -> Vec { @@ -78,6 +85,8 @@ impl ScramProvider for Sha1 { // TODO: look at all these unwraps pub struct Sha256; impl ScramProvider for Sha256 { // TODO: look at all these unwraps + type SecretKind = secret::Pbkdf2Sha256; + fn name() -> &'static str { "SHA-256" } fn hash(data: &[u8]) -> Vec { diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 631605a..ac2b76f 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,8 @@ extern crate openssl; mod error; pub mod client; -pub mod server; +#[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 0000000..408740a --- /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 0000000..47e8223 --- /dev/null +++ b/sasl/src/secret.rs @@ -0,0 +1,62 @@ +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 0000000..5b99350 --- /dev/null +++ b/sasl/src/server/mechanisms/mod.rs @@ -0,0 +1,5 @@ +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 1594256..1bf10aa 100644 --- a/sasl/src/server/mechanisms/plain.rs +++ b/sasl/src/server/mechanisms/plain.rs @@ -1,13 +1,31 @@ -use server::Mechanism; -use common::{Secret, Credentials, Password}; +use server::{Validator, Response, Mechanism}; +use common::Identity; +use secret; -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 0000000..7a817b7 --- /dev/null +++ b/sasl/src/server/mechanisms/scram.rs @@ -0,0 +1,168 @@ +use std::marker::PhantomData; + +use base64; + +use server::{Provider, Response, Mechanism}; +use common::{Identity, ChannelBinding, parse_frame, xor}; +use common::scram::{ScramProvider, generate_nonce}; +use secret; +use secret::Pbkdf2SecretValue; + +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 f51a6bc..9436fdb 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,16 +1,32 @@ -use common::{Identity, Credentials}; +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,205 +37,4 @@ pub enum Response { Proceed(Vec), } -pub mod mechanisms { - mod plain { - use server::{Validator, Response, Mechanism}; - use common::{Identity, ChannelBinding, Credentials, Secret}; - - 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 server::{Validator, Response, Mechanism}; - use common::{Identity, ChannelBinding, Credentials, Secret, parse_frame, xor}; - use common::scram::{ScramProvider, generate_nonce}; - - 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;