Files
lettre/src/authentication.rs
2015-10-08 20:12:07 +02:00

110 lines
3.5 KiB
Rust

//! Provides authentication mecanisms
use std::fmt::{Display, Formatter};
use std::fmt;
use serialize::base64::{self, ToBase64, FromBase64};
use serialize::hex::ToHex;
use crypto::hmac::Hmac;
use crypto::md5::Md5;
use crypto::mac::Mac;
use NUL;
use error::Error;
/// Represents authentication mecanisms
#[derive(PartialEq,Eq,Copy,Clone,Hash,Debug)]
pub enum Mecanism {
/// PLAIN authentication mecanism
/// RFC 4616: https://tools.ietf.org/html/rfc4616
Plain,
/// CRAM-MD5 authentication mecanism
/// RFC 2195: https://tools.ietf.org/html/rfc2195
CramMd5,
}
impl Display for Mecanism {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f,
"{}",
match *self {
Mecanism::Plain => "PLAIN",
Mecanism::CramMd5 => "CRAM-MD5",
})
}
}
impl Mecanism {
/// Does the mecanism supports initial response
pub fn supports_initial_response(&self) -> bool {
match *self {
Mecanism::Plain => true,
Mecanism::CramMd5 => false,
}
}
/// Returns the string to send to the server, using the provided username, password and
/// challenge in some cases
pub fn response(&self,
username: &str,
password: &str,
challenge: Option<&str>)
-> Result<String, Error> {
match *self {
Mecanism::Plain => {
match challenge {
Some(_) => Err(Error::ClientError("This mecanism does not expect a challenge")),
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password)
.as_bytes()
.to_base64(base64::STANDARD)),
}
}
Mecanism::CramMd5 => {
let encoded_challenge = match challenge {
Some(challenge) => challenge,
None => return Err(Error::ClientError("This mecanism does expect a challenge")),
};
let decoded_challenge = match encoded_challenge.from_base64() {
Ok(challenge) => challenge,
Err(error) => return Err(Error::ChallengeParsingError(error)),
};
let mut hmac = Hmac::new(Md5::new(), password.as_bytes());
hmac.input(&decoded_challenge);
Ok(format!("{} {}", username, hmac.result().code().to_hex())
.as_bytes()
.to_base64(base64::STANDARD))
}
}
}
}
#[cfg(test)]
mod test {
use super::Mecanism;
#[test]
fn test_plain() {
let mecanism = Mecanism::Plain;
assert_eq!(mecanism.response("username", "password", None).unwrap(),
"AHVzZXJuYW1lAHBhc3N3b3Jk");
assert!(mecanism.response("username", "password", Some("test")).is_err());
}
#[test]
fn test_cram_md5() {
let mecanism = Mecanism::CramMd5;
assert_eq!(mecanism.response("alice",
"wonderland",
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
.unwrap(),
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
assert!(mecanism.response("alice", "wonderland", Some("tést")).is_err());
assert!(mecanism.response("alice", "wonderland", None).is_err());
}
}