initial work towards server-side support

This commit is contained in:
lumi 2017-03-16 20:04:22 +01:00
parent 2d8fffdbfc
commit 4b9f2376af
10 changed files with 759 additions and 260 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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())
}
}

View file

@ -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() {

29
sasl/src/client/mod.rs Normal file
View file

@ -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;

201
sasl/src/common/mod.rs Normal file
View file

@ -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",
}
}
}

155
sasl/src/common/scram.rs Normal file
View file

@ -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())
}
}
}
}
}

View file

@ -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 mod client;
pub mod common;
pub mod server;
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,,",
}
}
/// 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;

View file

@ -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> {
}
}

239
sasl/src/server/mod.rs Normal file
View file

@ -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;
}