initial work towards server-side support

lumi created

Change summary

sasl/src/client/mechanisms/anonymous.rs |   5 
sasl/src/client/mechanisms/mod.rs       |   2 
sasl/src/client/mechanisms/plain.rs     |  11 
sasl/src/client/mechanisms/scram.rs     | 149 +---------------
sasl/src/client/mod.rs                  |  29 +++
sasl/src/common/mod.rs                  | 201 ++++++++++++++++++++++
sasl/src/common/scram.rs                | 155 +++++++++++++++++
sasl/src/lib.rs                         | 215 +++++++++++------------
sasl/src/server/mechanisms/plain.rs     |  13 +
sasl/src/server/mod.rs                  | 239 +++++++++++++++++++++++++++
10 files changed, 759 insertions(+), 260 deletions(-)

Detailed changes

sasl/src/mechanisms/anonymous.rs → sasl/src/client/mechanisms/anonymous.rs 🔗

@@ -1,8 +1,7 @@
 //! Provides the SASL "ANONYMOUS" mechanism.
 
-use Credentials;
-use Mechanism;
-use Secret;
+use client::Mechanism;
+use common::{Credentials, Secret};
 
 /// A struct for the SASL ANONYMOUS mechanism.
 pub struct Anonymous;

sasl/src/mechanisms/mod.rs → sasl/src/client/mechanisms/mod.rs 🔗

@@ -6,4 +6,4 @@ mod scram;
 
 pub use self::anonymous::Anonymous;
 pub use self::plain::Plain;
-pub use self::scram::{Scram, ScramProvider, Sha1, Sha256};
+pub use self::scram::Scram;

sasl/src/mechanisms/plain.rs → sasl/src/client/mechanisms/plain.rs 🔗

@@ -1,8 +1,7 @@
 //! Provides the SASL "PLAIN" mechanism.
 
-use Credentials;
-use Mechanism;
-use Secret;
+use client::Mechanism;
+use common::{Credentials, Identity, Password, Secret};
 
 /// A struct for the SASL PLAIN mechanism.
 pub struct Plain {
@@ -29,14 +28,14 @@ impl Mechanism for Plain {
     }
 
     fn from_credentials(credentials: Credentials) -> Result<Plain, String> {
-        if let Secret::Password(password) = credentials.secret {
-            if let Some(username) = credentials.username {
+        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())
             }
         } else {
-            Err("PLAIN requires a password".to_owned())
+            Err("PLAIN requires a plaintext password".to_owned())
         }
     }
 

sasl/src/mechanisms/scram.rs → sasl/src/client/mechanisms/scram.rs 🔗

@@ -2,140 +2,14 @@
 
 use base64;
 
-use ChannelBinding;
-use Credentials;
-use Mechanism;
-use Secret;
+use client::Mechanism;
+use common::scram::{generate_nonce, ScramProvider};
+use common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret};
 
 use error::Error;
 
-use openssl::error::ErrorStack;
-use openssl::hash::hash;
-use openssl::hash::MessageDigest;
-use openssl::pkcs5::pbkdf2_hmac;
-use openssl::pkey::PKey;
-use openssl::rand::rand_bytes;
-use openssl::sign::Signer;
-
 use std::marker::PhantomData;
 
-use std::collections::HashMap;
-
-use std::string::FromUtf8Error;
-
-#[cfg(test)]
-#[test]
-fn xor_works() {
-    assert_eq!(
-        xor(
-            &[135, 94, 53, 134, 73, 233, 140, 221, 150, 12, 96, 111, 54, 66, 11, 76],
-            &[163, 9, 122, 180, 107, 44, 22, 252, 248, 134, 112, 82, 84, 122, 56, 209]
-        ),
-        &[36, 87, 79, 50, 34, 197, 154, 33, 110, 138, 16, 61, 98, 56, 51, 157]
-    );
-}
-
-fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
-    assert_eq!(a.len(), b.len());
-    let mut ret = Vec::with_capacity(a.len());
-    for (a, b) in a.into_iter().zip(b) {
-        ret.push(a ^ b);
-    }
-    ret
-}
-
-fn parse_frame(frame: &[u8]) -> Result<HashMap<String, String>, FromUtf8Error> {
-    let inner = String::from_utf8(frame.to_owned())?;
-    let mut ret = HashMap::new();
-    for s in inner.split(',') {
-        let mut tmp = s.splitn(2, '=');
-        let key = tmp.next();
-        let val = tmp.next();
-        match (key, val) {
-            (Some(k), Some(v)) => {
-                ret.insert(k.to_owned(), v.to_owned());
-            }
-            _ => (),
-        }
-    }
-    Ok(ret)
-}
-
-fn generate_nonce() -> Result<String, ErrorStack> {
-    let mut data = vec![0; 32];
-    rand_bytes(&mut data)?;
-    Ok(base64::encode(&data))
-}
-
-/// A trait which defines the needed methods for SCRAM.
-pub trait ScramProvider {
-    /// The name of the hash function.
-    fn name() -> &'static str;
-
-    /// A function which hashes the data using the hash function.
-    fn hash(data: &[u8]) -> Vec<u8>;
-
-    /// A function which performs an HMAC using the hash function.
-    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8>;
-
-    /// A function which does PBKDF2 key derivation using the hash function.
-    fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec<u8>;
-}
-
-/// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS
-pub struct Sha1;
-
-impl ScramProvider for Sha1 {
-    // TODO: look at all these unwraps
-    fn name() -> &'static str {
-        "SHA-1"
-    }
-
-    fn hash(data: &[u8]) -> Vec<u8> {
-        hash(MessageDigest::sha1(), data).unwrap()
-    }
-
-    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8> {
-        let pkey = PKey::hmac(key).unwrap();
-        let mut signer = Signer::new(MessageDigest::sha1(), &pkey).unwrap();
-        signer.update(data).unwrap();
-        signer.finish().unwrap()
-    }
-
-    fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec<u8> {
-        let mut result = vec![0; 20];
-        pbkdf2_hmac(data, salt, iterations, MessageDigest::sha1(), &mut result).unwrap();
-        result
-    }
-}
-
-/// A `ScramProvider` which provides SCRAM-SHA-256 and SCRAM-SHA-256-PLUS
-pub struct Sha256;
-
-impl ScramProvider for Sha256 {
-    // TODO: look at all these unwraps
-    fn name() -> &'static str {
-        "SHA-256"
-    }
-
-    fn hash(data: &[u8]) -> Vec<u8> {
-        hash(MessageDigest::sha256(), data).unwrap()
-    }
-
-    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8> {
-        let pkey = PKey::hmac(key).unwrap();
-        let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
-        signer.update(data).unwrap();
-        signer.finish().unwrap()
-    }
-
-    fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec<u8> {
-        let mut result = vec![0; 32];
-        pbkdf2_hmac(data, salt, iterations, MessageDigest::sha256(), &mut result).unwrap();
-        result
-    }
-}
-
 enum ScramState {
     Init,
     SentInitialMessage {
@@ -151,7 +25,7 @@ enum ScramState {
 pub struct Scram<S: ScramProvider> {
     name: String,
     username: String,
-    password: String,
+    password: Password,
     client_nonce: String,
     state: ScramState,
     channel_binding: ChannelBinding,
@@ -164,7 +38,7 @@ impl<S: ScramProvider> Scram<S> {
     ///
     /// It is recommended that instead you use a `Credentials` struct and turn it into the
     /// requested mechanism using `from_credentials`.
-    pub fn new<N: Into<String>, P: Into<String>>(
+    pub fn new<N: Into<String>, P: Into<Password>>(
         username: N,
         password: P,
         channel_binding: ChannelBinding,
@@ -183,7 +57,7 @@ impl<S: ScramProvider> Scram<S> {
     // Used for testing.
     #[doc(hidden)]
     #[cfg(test)]
-    pub fn new_with_nonce<N: Into<String>, P: Into<String>>(
+    pub fn new_with_nonce<N: Into<String>, P: Into<Password>>(
         username: N,
         password: P,
         nonce: String,
@@ -208,7 +82,7 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
 
     fn from_credentials(credentials: Credentials) -> Result<Scram<S>, String> {
         if let Secret::Password(password) = credentials.secret {
-            if let Some(username) = credentials.username {
+            if let Identity::Username(username) = credentials.identity {
                 Scram::new(username, password, credentials.channel_binding)
                     .map_err(|_| "can't generate nonce".to_owned())
             } else {
@@ -262,7 +136,7 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
                 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 salted_password = S::derive(self.password.as_bytes(), &salt, iterations);
+                let salted_password = S::derive(&self.password, &salt, iterations)?;
                 let client_key = S::hmac(b"Client Key", &salted_password);
                 let server_key = S::hmac(b"Server Key", &salted_password);
                 let mut auth_message = Vec::new();
@@ -271,6 +145,7 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
                 auth_message.extend(challenge);
                 auth_message.push(b',');
                 auth_message.extend(&client_final_message_bare);
+                println!("_ {}", String::from_utf8_lossy(&auth_message));
                 let stored_key = S::hash(&client_key);
                 let client_signature = S::hmac(&auth_message, &stored_key);
                 let client_proof = xor(&client_key, &client_signature);
@@ -315,9 +190,9 @@ impl<S: ScramProvider> Mechanism for Scram<S> {
 
 #[cfg(test)]
 mod tests {
-    use Mechanism;
-
-    use super::*;
+    use client::mechanisms::Scram;
+    use client::Mechanism;
+    use common::scram::{Sha1, Sha256};
 
     #[test]
     fn scram_sha1_works() {

sasl/src/client/mod.rs 🔗

@@ -0,0 +1,29 @@
+use common::Credentials;
+
+/// A trait which defines SASL mechanisms.
+pub trait Mechanism {
+    /// The name of the mechanism.
+    fn name(&self) -> &str;
+
+    /// Creates this mechanism from `Credentials`.
+    fn from_credentials(credentials: Credentials) -> Result<Self, String>
+    where
+        Self: Sized;
+
+    /// Provides initial payload of the SASL mechanism.
+    fn initial(&mut self) -> Result<Vec<u8>, String> {
+        Ok(Vec::new())
+    }
+
+    /// Creates a response to the SASL challenge.
+    fn response(&mut self, _challenge: &[u8]) -> Result<Vec<u8>, String> {
+        Ok(Vec::new())
+    }
+
+    /// Verifies the server success response, if there is one.
+    fn success(&mut self, _data: &[u8]) -> Result<(), String> {
+        Ok(())
+    }
+}
+
+pub mod mechanisms;

sasl/src/common/mod.rs 🔗

@@ -0,0 +1,201 @@
+use std::collections::HashMap;
+
+use std::convert::From;
+
+use std::string::FromUtf8Error;
+
+pub mod scram;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Identity {
+    None,
+    Username(String),
+}
+
+impl From<String> for Identity {
+    fn from(s: String) -> Identity {
+        Identity::Username(s)
+    }
+}
+
+impl<'a> From<&'a str> for Identity {
+    fn from(s: &'a str) -> Identity {
+        Identity::Username(s.to_owned())
+    }
+}
+
+/// A struct containing SASL credentials.
+#[derive(Clone, Debug)]
+pub struct Credentials {
+    /// The requested identity.
+    pub identity: Identity,
+    /// The secret used to authenticate.
+    pub secret: Secret,
+    /// Channel binding data, for *-PLUS mechanisms.
+    pub channel_binding: ChannelBinding,
+}
+
+impl Default for Credentials {
+    fn default() -> Credentials {
+        Credentials {
+            identity: Identity::None,
+            secret: Secret::None,
+            channel_binding: ChannelBinding::Unsupported,
+        }
+    }
+}
+
+impl Credentials {
+    /// Creates a new Credentials with the specified username.
+    pub fn with_username<N: Into<String>>(mut self, username: N) -> Credentials {
+        self.identity = Identity::Username(username.into());
+        self
+    }
+
+    /// Creates a new Credentials with the specified plaintext password.
+    pub fn with_password<P: Into<String>>(mut self, password: P) -> Credentials {
+        self.secret = Secret::password_plain(password);
+        self
+    }
+
+    /// Creates a new Credentials with the specified chanel binding.
+    pub fn with_channel_binding(mut self, channel_binding: ChannelBinding) -> Credentials {
+        self.channel_binding = channel_binding;
+        self
+    }
+}
+
+/// Represents a SASL secret, like a password.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Secret {
+    /// No extra data needed.
+    None,
+    /// Password required.
+    Password(Password),
+}
+
+impl Secret {
+    pub fn password_plain<S: Into<String>>(password: S) -> Secret {
+        Secret::Password(Password::Plain(password.into()))
+    }
+
+    pub fn password_pbkdf2<S: Into<String>>(
+        method: S,
+        salt: Vec<u8>,
+        iterations: usize,
+        data: Vec<u8>,
+    ) -> Secret {
+        Secret::Password(Password::Pbkdf2 {
+            method: method.into(),
+            salt: salt,
+            iterations: iterations,
+            data: data,
+        })
+    }
+}
+
+/// Represents a password.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Password {
+    /// A plaintext password.
+    Plain(String),
+    /// A password digest derived using PBKDF2.
+    Pbkdf2 {
+        method: String,
+        salt: Vec<u8>,
+        iterations: usize,
+        data: Vec<u8>,
+    },
+}
+
+impl From<String> for Password {
+    fn from(s: String) -> Password {
+        Password::Plain(s)
+    }
+}
+
+impl<'a> From<&'a str> for Password {
+    fn from(s: &'a str) -> Password {
+        Password::Plain(s.to_owned())
+    }
+}
+
+#[cfg(test)]
+#[test]
+fn xor_works() {
+    assert_eq!(
+        xor(
+            &[135, 94, 53, 134, 73, 233, 140, 221, 150, 12, 96, 111, 54, 66, 11, 76],
+            &[163, 9, 122, 180, 107, 44, 22, 252, 248, 134, 112, 82, 84, 122, 56, 209]
+        ),
+        &[36, 87, 79, 50, 34, 197, 154, 33, 110, 138, 16, 61, 98, 56, 51, 157]
+    );
+}
+
+#[doc(hidden)]
+pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
+    assert_eq!(a.len(), b.len());
+    let mut ret = Vec::with_capacity(a.len());
+    for (a, b) in a.into_iter().zip(b) {
+        ret.push(a ^ b);
+    }
+    ret
+}
+
+#[doc(hidden)]
+pub fn parse_frame(frame: &[u8]) -> Result<HashMap<String, String>, FromUtf8Error> {
+    let inner = String::from_utf8(frame.to_owned())?;
+    let mut ret = HashMap::new();
+    for s in inner.split(',') {
+        let mut tmp = s.splitn(2, '=');
+        let key = tmp.next();
+        let val = tmp.next();
+        match (key, val) {
+            (Some(k), Some(v)) => {
+                ret.insert(k.to_owned(), v.to_owned());
+            }
+            _ => (),
+        }
+    }
+    Ok(ret)
+}
+
+/// Channel binding configuration.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ChannelBinding {
+    /// No channel binding data.
+    None,
+    /// Advertise that the client does not think the server supports channel binding.
+    Unsupported,
+    /// p=tls-unique channel binding data.
+    TlsUnique(Vec<u8>),
+}
+
+impl ChannelBinding {
+    /// Return the gs2 header for this channel binding mechanism.
+    pub fn header(&self) -> &[u8] {
+        match *self {
+            ChannelBinding::None => b"n,,",
+            ChannelBinding::Unsupported => b"y,,",
+            ChannelBinding::TlsUnique(_) => b"p=tls-unique,,",
+        }
+    }
+
+    /// Return the channel binding data for this channel binding mechanism.
+    pub fn data(&self) -> &[u8] {
+        match *self {
+            ChannelBinding::None => &[],
+            ChannelBinding::Unsupported => &[],
+            ChannelBinding::TlsUnique(ref data) => data,
+        }
+    }
+
+    /// Checks whether this channel binding mechanism is supported.
+    pub fn supports(&self, mechanism: &str) -> bool {
+        match *self {
+            ChannelBinding::None => false,
+            ChannelBinding::Unsupported => false,
+            ChannelBinding::TlsUnique(_) => mechanism == "tls-unique",
+        }
+    }
+}

sasl/src/common/scram.rs 🔗

@@ -0,0 +1,155 @@
+use openssl::error::ErrorStack;
+use openssl::hash::hash;
+use openssl::hash::MessageDigest;
+use openssl::pkcs5::pbkdf2_hmac;
+use openssl::pkey::PKey;
+use openssl::rand::rand_bytes;
+use openssl::sign::Signer;
+
+use common::Password;
+
+use base64;
+
+/// Generate a nonce for SCRAM authentication.
+pub fn generate_nonce() -> Result<String, ErrorStack> {
+    let mut data = vec![0; 32];
+    rand_bytes(&mut data)?;
+    Ok(base64::encode(&data))
+}
+
+/// A trait which defines the needed methods for SCRAM.
+pub trait ScramProvider {
+    /// The name of the hash function.
+    fn name() -> &'static str;
+
+    /// A function which hashes the data using the hash function.
+    fn hash(data: &[u8]) -> Vec<u8>;
+
+    /// A function which performs an HMAC using the hash function.
+    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8>;
+
+    /// A function which does PBKDF2 key derivation using the hash function.
+    fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, String>;
+}
+
+/// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS
+pub struct Sha1;
+
+impl ScramProvider for Sha1 {
+    // TODO: look at all these unwraps
+    fn name() -> &'static str {
+        "SHA-1"
+    }
+
+    fn hash(data: &[u8]) -> Vec<u8> {
+        hash(MessageDigest::sha1(), data).unwrap()
+    }
+
+    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8> {
+        let pkey = PKey::hmac(key).unwrap();
+        let mut signer = Signer::new(MessageDigest::sha1(), &pkey).unwrap();
+        signer.update(data).unwrap();
+        signer.finish().unwrap()
+    }
+
+    fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, String> {
+        match *password {
+            Password::Plain(ref plain) => {
+                let mut result = vec![0; 20];
+                pbkdf2_hmac(
+                    plain.as_bytes(),
+                    salt,
+                    iterations,
+                    MessageDigest::sha1(),
+                    &mut result,
+                )
+                .unwrap();
+                Ok(result)
+            }
+            Password::Pbkdf2 {
+                ref method,
+                salt: ref my_salt,
+                iterations: my_iterations,
+                ref data,
+            } => {
+                if method != Self::name() {
+                    Err(format!(
+                        "incompatible hashing method, {} is not {}",
+                        method,
+                        Self::name()
+                    ))
+                } else if my_salt == &salt {
+                    Err(format!("incorrect salt"))
+                } else if my_iterations == iterations {
+                    Err(format!(
+                        "incompatible iteration count, {} is not {}",
+                        my_iterations, iterations
+                    ))
+                } else {
+                    Ok(data.to_vec())
+                }
+            }
+        }
+    }
+}
+
+/// A `ScramProvider` which provides SCRAM-SHA-256 and SCRAM-SHA-256-PLUS
+pub struct Sha256;
+
+impl ScramProvider for Sha256 {
+    // TODO: look at all these unwraps
+    fn name() -> &'static str {
+        "SHA-256"
+    }
+
+    fn hash(data: &[u8]) -> Vec<u8> {
+        hash(MessageDigest::sha256(), data).unwrap()
+    }
+
+    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8> {
+        let pkey = PKey::hmac(key).unwrap();
+        let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
+        signer.update(data).unwrap();
+        signer.finish().unwrap()
+    }
+
+    fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result<Vec<u8>, String> {
+        match *password {
+            Password::Plain(ref plain) => {
+                let mut result = vec![0; 32];
+                pbkdf2_hmac(
+                    plain.as_bytes(),
+                    salt,
+                    iterations,
+                    MessageDigest::sha256(),
+                    &mut result,
+                )
+                .unwrap();
+                Ok(result)
+            }
+            Password::Pbkdf2 {
+                ref method,
+                salt: ref my_salt,
+                iterations: my_iterations,
+                ref data,
+            } => {
+                if method != Self::name() {
+                    Err(format!(
+                        "incompatible hashing method, {} is not {}",
+                        method,
+                        Self::name()
+                    ))
+                } else if my_salt == &salt {
+                    Err(format!("incorrect salt"))
+                } else if my_iterations == iterations {
+                    Err(format!(
+                        "incompatible iteration count, {} is not {}",
+                        my_iterations, iterations
+                    ))
+                } else {
+                    Ok(data.to_vec())
+                }
+            }
+        }
+    }
+}

sasl/src/lib.rs 🔗

@@ -1,12 +1,15 @@
-#![deny(missing_docs)]
+//#![deny(missing_docs)]
 
 //! This crate provides a framework for SASL authentication and a few authentication mechanisms.
 //!
 //! # Examples
 //!
+//! ## Simple client-sided usage
+//!
 //! ```rust
-//! use sasl::{Credentials, Mechanism, Error};
-//! use sasl::mechanisms::Plain;
+//! use sasl::client::Mechanism;
+//! use sasl::common::Credentials;
+//! use sasl::client::mechanisms::Plain;
 //!
 //! let creds = Credentials::default()
 //!                         .with_username("user")
@@ -19,7 +22,98 @@
 //! assert_eq!(initial_data, b"\0user\0pencil");
 //! ```
 //!
-//! You may look at the tests of `mechanisms/scram.rs` for examples of more advanced usage.
+//! ## More complex usage
+//!
+//! ```rust
+//! use sasl::server::{Validator, 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::scram::{ScramProvider, Sha1, Sha256};
+//!
+//! const USERNAME: &'static str = "user";
+//! const PASSWORD: &'static str = "pencil";
+//! const SALT: [u8; 8] = [35, 71, 92, 105, 212, 219, 114, 93];
+//! const ITERATIONS: usize = 4096;
+//!
+//! struct MyValidator;
+//!
+//! impl Validator for MyValidator {
+//!     fn validate_credentials(&self, creds: &Credentials) -> Result<Identity, String> {
+//!         if creds.identity != Identity::Username(USERNAME.to_owned()) {
+//!             Err("authentication failure".to_owned())
+//!         }
+//!         else if creds.secret != Secret::password_plain(PASSWORD) {
+//!             Err("authentication failure".to_owned())
+//!         }
+//!         else {
+//!             Ok(creds.identity.clone())
+//!         }
+//!     }
+//!
+//!     fn request_pbkdf2<S: ScramProvider>(&self) -> Result<(Vec<u8>, usize, Vec<u8>), 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));
+//!
+//! let mut mech = ServerPlain::new(MyValidator);
+//! assert_eq!(mech.respond(b"\0user\0marker"), Err("authentication failure".to_owned()));
+//!
+//! let creds = Credentials::default()
+//!                         .with_username(USERNAME)
+//!                         .with_password(PASSWORD);
+//!
+//! fn finish<CM, SM, V>(cm: &mut CM, sm: &mut SM) -> Result<Identity, String>
+//!     where CM: ClientMechanism,
+//!           SM: ServerMechanism<V>,
+//!           V: Validator {
+//!     let init = cm.initial()?;
+//!     println!("C: {}", String::from_utf8_lossy(&init));
+//!     let mut resp = sm.respond(&init)?;
+//!     loop {
+//!         let msg;
+//!         match resp {
+//!             Response::Proceed(ref data) => {
+//!                 println!("S: {}", String::from_utf8_lossy(&data));
+//!                 msg = cm.response(data)?;
+//!                 println!("C: {}", String::from_utf8_lossy(&msg));
+//!             },
+//!             _ => break,
+//!         }
+//!         resp = sm.respond(&msg)?;
+//!     }
+//!     if let Response::Success(ret, fin) = resp {
+//!         println!("S: {}", String::from_utf8_lossy(&fin));
+//!         cm.success(&fin)?;
+//!         Ok(ret)
+//!     }
+//!     else {
+//!         unreachable!();
+//!     }
+//! }
+//!
+//! 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())));
+//!
+//! let mut client_mech = ClientScram::<Sha1>::from_credentials(creds.clone()).unwrap();
+//! let mut server_mech = ServerScram::<Sha1, _>::new(MyValidator, ChannelBinding::Unsupported);
+//!
+//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned())));
+//!
+//! let mut client_mech = ClientScram::<Sha256>::from_credentials(creds.clone()).unwrap();
+//! let mut server_mech = ServerScram::<Sha256, _>::new(MyValidator, ChannelBinding::Unsupported);
+//!
+//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned())));
+//! ```
 //!
 //! # Usage
 //!
@@ -34,113 +128,8 @@ extern crate openssl;
 
 mod error;
 
-pub use error::Error;
-
-/// A struct containing SASL credentials.
-#[derive(Clone, Debug)]
-pub struct Credentials {
-    /// The requested username.
-    pub username: Option<String>,
-    /// The secret used to authenticate.
-    pub secret: Secret,
-    /// Channel binding data, for *-PLUS mechanisms.
-    pub channel_binding: ChannelBinding,
-}
-
-impl Default for Credentials {
-    fn default() -> Credentials {
-        Credentials {
-            username: None,
-            secret: Secret::None,
-            channel_binding: ChannelBinding::None,
-        }
-    }
-}
-
-impl Credentials {
-    /// Creates a new Credentials with the specified username.
-    pub fn with_username<N: Into<String>>(mut self, username: N) -> Credentials {
-        self.username = Some(username.into());
-        self
-    }
-
-    /// Creates a new Credentials with the specified password.
-    pub fn with_password<P: Into<String>>(mut self, password: P) -> Credentials {
-        self.secret = Secret::Password(password.into());
-        self
-    }
-
-    /// Creates a new Credentials with the specified chanel binding.
-    pub fn with_channel_binding(mut self, channel_binding: ChannelBinding) -> Credentials {
-        self.channel_binding = channel_binding;
-        self
-    }
-}
-
-/// Channel binding configuration.
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum ChannelBinding {
-    /// No channel binding data.
-    None,
-    /// Advertise that the client does not think the server supports channel binding.
-    Unsupported,
-    /// p=tls-unique channel binding data.
-    TlsUnique(Vec<u8>),
-}
-
-impl ChannelBinding {
-    /// Return the gs2 header for this channel binding mechanism.
-    pub fn header(&self) -> &[u8] {
-        match *self {
-            ChannelBinding::None => b"n,,",
-            ChannelBinding::Unsupported => b"y,,",
-            ChannelBinding::TlsUnique(_) => b"p=tls-unique,,",
-        }
-    }
+pub mod client;
+pub mod common;
+pub mod server;
 
-    /// Return the channel binding data for this channel binding mechanism.
-    pub fn data(&self) -> &[u8] {
-        match *self {
-            ChannelBinding::None => &[],
-            ChannelBinding::Unsupported => &[],
-            ChannelBinding::TlsUnique(ref data) => data,
-        }
-    }
-}
-
-/// Represents a SASL secret, like a password.
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Secret {
-    /// No extra data needed.
-    None,
-    /// Password required.
-    Password(String),
-}
-
-/// A trait which defines SASL mechanisms.
-pub trait Mechanism {
-    /// The name of the mechanism.
-    fn name(&self) -> &str;
-
-    /// Creates this mechanism from `Credentials`.
-    fn from_credentials(credentials: Credentials) -> Result<Self, String>
-    where
-        Self: Sized;
-
-    /// Provides initial payload of the SASL mechanism.
-    fn initial(&mut self) -> Result<Vec<u8>, String> {
-        Ok(Vec::new())
-    }
-
-    /// Creates a response to the SASL challenge.
-    fn response(&mut self, _challenge: &[u8]) -> Result<Vec<u8>, String> {
-        Ok(Vec::new())
-    }
-
-    /// Verifies the server success response, if there is one.
-    fn success(&mut self, _data: &[u8]) -> Result<(), String> {
-        Ok(())
-    }
-}
-
-pub mod mechanisms;
+pub use error::Error;

sasl/src/server/mechanisms/plain.rs 🔗

@@ -0,0 +1,13 @@
+use server::Mechanism;
+use common::{Secret, Credentials, Password};
+
+pub struct Plain {
+    password: String,
+}
+
+impl<V: Validator> Mechanism<V> for Plain {
+    fn name(&self) -> &str { "PLAIN" }
+
+    fn from_initial_message(validator: &V, msg: &[u8]) -> Result<(Self, String), String> {
+    }
+}

sasl/src/server/mod.rs 🔗

@@ -0,0 +1,239 @@
+use common::scram::ScramProvider;
+use common::{Credentials, Identity};
+
+pub trait Validator {
+    fn validate_credentials(&self, credentials: &Credentials) -> Result<Identity, String>;
+    fn request_pbkdf2<S: ScramProvider>(&self) -> Result<(Vec<u8>, usize, Vec<u8>), String>;
+}
+
+pub trait Mechanism<V: Validator> {
+    fn name(&self) -> &str;
+    fn respond(&mut self, payload: &[u8]) -> Result<Response, String>;
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Response {
+    Success(Identity, Vec<u8>),
+    Proceed(Vec<u8>),
+}
+
+pub mod mechanisms {
+    mod plain {
+        use common::{ChannelBinding, Credentials, Identity, Secret};
+        use server::{Mechanism, Response, Validator};
+
+        pub struct Plain<V: Validator> {
+            validator: V,
+        }
+
+        impl<V: Validator> Plain<V> {
+            pub fn new(validator: V) -> Plain<V> {
+                Plain {
+                    validator: validator,
+                }
+            }
+        }
+
+        impl<V: Validator> Mechanism<V> for Plain<V> {
+            fn name(&self) -> &str {
+                "PLAIN"
+            }
+
+            fn respond(&mut self, payload: &[u8]) -> Result<Response, String> {
+                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()))
+            }
+        }
+    }
+
+    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<u8>,
+                initial_server_message: Vec<u8>,
+                gs2_header: Vec<u8>,
+                server_nonce: String,
+                username: String,
+                salted_password: Vec<u8>,
+            },
+            Done,
+        }
+
+        pub struct Scram<S: ScramProvider, V: Validator> {
+            name: String,
+            state: ScramState,
+            channel_binding: ChannelBinding,
+            validator: V,
+            _marker: PhantomData<S>,
+        }
+
+        impl<S: ScramProvider, V: Validator> Scram<S, V> {
+            pub fn new(validator: V, channel_binding: ChannelBinding) -> Scram<S, V> {
+                Scram {
+                    name: format!("SCRAM-{}", S::name()),
+                    state: ScramState::Init,
+                    channel_binding: channel_binding,
+                    validator: validator,
+                    _marker: PhantomData,
+                }
+            }
+        }
+
+        impl<S: ScramProvider, V: Validator> Mechanism<V> for Scram<S, V> {
+            fn name(&self) -> &str {
+                &self.name
+            }
+
+            fn respond(&mut self, payload: &[u8]) -> Result<Response, String> {
+                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::<S>()?;
+                        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<u8> = 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;
+    pub use self::scram::Scram;
+}