From 353b2579eac3d3c1b9cfa45f086d18cbbf12a84b Mon Sep 17 00:00:00 2001 From: lumi Date: Mon, 27 Feb 2017 16:08:09 +0100 Subject: [PATCH 01/53] initial commit --- sasl/.gitignore | 2 + sasl/Cargo.toml | 8 + sasl/src/error.rs | 13 ++ sasl/src/lib.rs | 48 ++++ sasl/src/mechanisms/anonymous.rs | 27 +++ sasl/src/mechanisms/mod.rs | 8 + sasl/src/mechanisms/plain.rs | 42 ++++ sasl/src/mechanisms/scram.rs | 374 +++++++++++++++++++++++++++++++ 8 files changed, 522 insertions(+) create mode 100644 sasl/.gitignore create mode 100644 sasl/Cargo.toml create mode 100644 sasl/src/error.rs create mode 100644 sasl/src/lib.rs create mode 100644 sasl/src/mechanisms/anonymous.rs create mode 100644 sasl/src/mechanisms/mod.rs create mode 100644 sasl/src/mechanisms/plain.rs create mode 100644 sasl/src/mechanisms/scram.rs diff --git a/sasl/.gitignore b/sasl/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a9d37c560c6ab8d4afbf47eda643e8c42e857716 --- /dev/null +++ b/sasl/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..aa97ef16a1d0e6bd5b69a91f53b340267272b0e1 --- /dev/null +++ b/sasl/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sasl" +version = "0.1.0" +authors = ["lumi "] + +[dependencies] +openssl = "0.9.7" +base64 = "0.4.0" diff --git a/sasl/src/error.rs b/sasl/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..4192300a1f4cdf97a514a52f2013feb3d8dd4a99 --- /dev/null +++ b/sasl/src/error.rs @@ -0,0 +1,13 @@ +use openssl::error::ErrorStack; + +#[derive(Debug)] +pub enum Error { + OpenSslErrorStack(ErrorStack), + SaslError(String), +} + +impl From for Error { + fn from(err: ErrorStack) -> Error { + Error::OpenSslErrorStack(err) + } +} diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..48a57b4ab58f2d3df6cc327db699a9ed8c35cc69 --- /dev/null +++ b/sasl/src/lib.rs @@ -0,0 +1,48 @@ +//! Provides the `SaslMechanism` trait and some implementations. + +extern crate base64; +extern crate openssl; + +pub mod error; + +/// A struct containing SASL credentials. +pub struct SaslCredentials { + pub username: String, + pub secret: SaslSecret, + pub channel_binding: Option>, +} + +/// Represents a SASL secret, like a password. +pub enum SaslSecret { + /// No extra data needed. + None, + /// Password required. + Password(String), +} + +pub trait SaslMechanism { + /// The name of the mechanism. + fn name(&self) -> &str; + + /// Creates this mechanism from `SaslCredentials`. + fn from_credentials(credentials: SaslCredentials) -> Result + where + Self: Sized; + + /// Provides initial payload of the SASL mechanism. + fn initial(&mut self) -> Result, String> { + Ok(Vec::new()) + } + + /// Creates a response to the SASL challenge. + fn response(&mut self, _challenge: &[u8]) -> Result, 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; diff --git a/sasl/src/mechanisms/anonymous.rs b/sasl/src/mechanisms/anonymous.rs new file mode 100644 index 0000000000000000000000000000000000000000..31a6f14b60bbdd6633a5d99150a8676d1dbdbb94 --- /dev/null +++ b/sasl/src/mechanisms/anonymous.rs @@ -0,0 +1,27 @@ +//! Provides the SASL "ANONYMOUS" mechanism. + +use SaslCredentials; +use SaslMechanism; +use SaslSecret; + +pub struct Anonymous; + +impl Anonymous { + pub fn new() -> Anonymous { + Anonymous + } +} + +impl SaslMechanism for Anonymous { + fn name(&self) -> &str { + "ANONYMOUS" + } + + fn from_credentials(credentials: SaslCredentials) -> Result { + if let SaslSecret::None = credentials.secret { + Ok(Anonymous) + } else { + Err("the anonymous sasl mechanism requires no credentials".to_owned()) + } + } +} diff --git a/sasl/src/mechanisms/mod.rs b/sasl/src/mechanisms/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0e4205c8d8cf372565a575c7827c7547446ea675 --- /dev/null +++ b/sasl/src/mechanisms/mod.rs @@ -0,0 +1,8 @@ +///! Provides a few SASL mechanisms. +mod anonymous; +mod plain; +mod scram; + +pub use self::anonymous::Anonymous; +pub use self::plain::Plain; +pub use self::scram::{Scram, ScramProvider, Sha1, Sha256}; diff --git a/sasl/src/mechanisms/plain.rs b/sasl/src/mechanisms/plain.rs new file mode 100644 index 0000000000000000000000000000000000000000..c273d97f6741eaba61fe7d876760c5a254fb08e5 --- /dev/null +++ b/sasl/src/mechanisms/plain.rs @@ -0,0 +1,42 @@ +//! Provides the SASL "PLAIN" mechanism. + +use SaslCredentials; +use SaslMechanism; +use SaslSecret; + +pub struct Plain { + username: String, + password: String, +} + +impl Plain { + pub fn new, P: Into>(username: N, password: P) -> Plain { + Plain { + username: username.into(), + password: password.into(), + } + } +} + +impl SaslMechanism for Plain { + fn name(&self) -> &str { + "PLAIN" + } + + fn from_credentials(credentials: SaslCredentials) -> Result { + if let SaslSecret::Password(password) = credentials.secret { + Ok(Plain::new(credentials.username, password)) + } else { + Err("PLAIN requires a password".to_owned()) + } + } + + fn initial(&mut self) -> Result, String> { + let mut auth = Vec::new(); + auth.push(0); + auth.extend(self.username.bytes()); + auth.push(0); + auth.extend(self.password.bytes()); + Ok(auth) + } +} diff --git a/sasl/src/mechanisms/scram.rs b/sasl/src/mechanisms/scram.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2fcbc18270685723f9e672629624b74abd56d38 --- /dev/null +++ b/sasl/src/mechanisms/scram.rs @@ -0,0 +1,374 @@ +//! Provides the SASL "SCRAM-*" mechanisms and a way to implement more. + +use base64; + +use SaslCredentials; +use SaslMechanism; +use SaslSecret; + +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 { + 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, 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 { + let mut data = vec![0; 32]; + rand_bytes(&mut data)?; + Ok(base64::encode(&data)) +} + +pub trait ScramProvider { + fn name() -> &'static str; + fn hash(data: &[u8]) -> Vec; + fn hmac(data: &[u8], key: &[u8]) -> Vec; + fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec; +} + +pub struct Sha1; + +impl ScramProvider for Sha1 { + // TODO: look at all these unwraps + fn name() -> &'static str { + "SHA-1" + } + + fn hash(data: &[u8]) -> Vec { + hash(MessageDigest::sha1(), data).unwrap() + } + + fn hmac(data: &[u8], key: &[u8]) -> Vec { + 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 { + let mut result = vec![0; 20]; + pbkdf2_hmac(data, salt, iterations, MessageDigest::sha1(), &mut result).unwrap(); + result + } +} + +pub struct Sha256; + +impl ScramProvider for Sha256 { + // TODO: look at all these unwraps + fn name() -> &'static str { + "SHA-256" + } + + fn hash(data: &[u8]) -> Vec { + hash(MessageDigest::sha256(), data).unwrap() + } + + fn hmac(data: &[u8], key: &[u8]) -> Vec { + 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 { + let mut result = vec![0; 32]; + pbkdf2_hmac(data, salt, iterations, MessageDigest::sha256(), &mut result).unwrap(); + result + } +} + +enum ScramState { + Init, + SentInitialMessage { + initial_message: Vec, + gs2_header: Vec, + }, + GotServerData { + server_signature: Vec, + }, +} + +pub struct Scram { + name: String, + username: String, + password: String, + client_nonce: String, + state: ScramState, + channel_binding: Option>, + _marker: PhantomData, +} + +impl Scram { + pub fn new, P: Into>( + username: N, + password: P, + ) -> Result, Error> { + Ok(Scram { + name: format!("SCRAM-{}", S::name()), + username: username.into(), + password: password.into(), + client_nonce: generate_nonce()?, + state: ScramState::Init, + channel_binding: None, + _marker: PhantomData, + }) + } + + pub fn new_with_nonce, P: Into>( + username: N, + password: P, + nonce: String, + ) -> Scram { + Scram { + name: format!("SCRAM-{}", S::name()), + username: username.into(), + password: password.into(), + client_nonce: nonce, + state: ScramState::Init, + channel_binding: None, + _marker: PhantomData, + } + } + + pub fn new_with_channel_binding, P: Into>( + username: N, + password: P, + channel_binding: Vec, + ) -> Result, Error> { + Ok(Scram { + name: format!("SCRAM-{}-PLUS", S::name()), + username: username.into(), + password: password.into(), + client_nonce: generate_nonce()?, + state: ScramState::Init, + channel_binding: Some(channel_binding), + _marker: PhantomData, + }) + } +} + +impl SaslMechanism for Scram { + fn name(&self) -> &str { + // TODO: this is quite the workaround… + &self.name + } + + fn from_credentials(credentials: SaslCredentials) -> Result, String> { + if let SaslSecret::Password(password) = credentials.secret { + if let Some(binding) = credentials.channel_binding { + Scram::new_with_channel_binding(credentials.username, password, binding) + .map_err(|_| "can't generate nonce".to_owned()) + } else { + Scram::new(credentials.username, password) + .map_err(|_| "can't generate nonce".to_owned()) + } + } else { + Err("SCRAM requires a password".to_owned()) + } + } + + fn initial(&mut self) -> Result, String> { + let mut gs2_header = Vec::new(); + if let Some(_) = self.channel_binding { + gs2_header.extend(b"p=tls-unique,,"); + } else { + gs2_header.extend(b"n,,"); + } + let mut bare = Vec::new(); + bare.extend(b"n="); + bare.extend(self.username.bytes()); + bare.extend(b",r="); + bare.extend(self.client_nonce.bytes()); + let mut data = Vec::new(); + data.extend(&gs2_header); + data.extend(bare.clone()); + self.state = ScramState::SentInitialMessage { + initial_message: bare, + gs2_header: gs2_header, + }; + Ok(data) + } + + fn response(&mut self, challenge: &[u8]) -> Result, String> { + let next_state; + let ret; + match self.state { + ScramState::SentInitialMessage { + ref initial_message, + ref gs2_header, + } => { + let frame = + parse_frame(challenge).map_err(|_| "can't decode challenge".to_owned())?; + let server_nonce = frame.get("r"); + let salt = frame.get("s").and_then(|v| base64::decode(v).ok()); + let iterations = frame.get("i").and_then(|v| v.parse().ok()); + let server_nonce = server_nonce.ok_or_else(|| "no server nonce".to_owned())?; + let salt = salt.ok_or_else(|| "no server salt".to_owned())?; + let iterations = iterations.ok_or_else(|| "no server iterations".to_owned())?; + // TODO: SASLprep + let mut client_final_message_bare = Vec::new(); + client_final_message_bare.extend(b"c="); + let mut cb_data: Vec = Vec::new(); + cb_data.extend(gs2_header); + if let Some(ref cb) = self.channel_binding { + cb_data.extend(cb); + } + 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 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(); + auth_message.extend(initial_message); + auth_message.push(b','); + auth_message.extend(challenge); + auth_message.push(b','); + auth_message.extend(&client_final_message_bare); + 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 server_signature = S::hmac(&auth_message, &server_key); + let mut client_final_message = Vec::new(); + client_final_message.extend(&client_final_message_bare); + client_final_message.extend(b",p="); + client_final_message.extend(base64::encode(&client_proof).bytes()); + next_state = ScramState::GotServerData { + server_signature: server_signature, + }; + ret = client_final_message; + } + _ => { + return Err("not in the right state to receive this response".to_owned()); + } + } + self.state = next_state; + Ok(ret) + } + + fn success(&mut self, data: &[u8]) -> Result<(), String> { + let frame = parse_frame(data).map_err(|_| "can't decode success response".to_owned())?; + match self.state { + ScramState::GotServerData { + ref server_signature, + } => { + if let Some(sig) = frame.get("v").and_then(|v| base64::decode(&v).ok()) { + if sig == *server_signature { + Ok(()) + } else { + Err("invalid signature in success response".to_owned()) + } + } else { + Err("no signature in success response".to_owned()) + } + } + _ => Err("not in the right state to get a success response".to_owned()), + } + } +} + +#[cfg(test)] +mod tests { + use SaslMechanism; + + use super::*; + + #[test] + fn scram_sha1_works() { + // Source: https://wiki.xmpp.org/web/SASLandSCRAM-SHA-1 + let username = "user"; + let password = "pencil"; + let client_nonce = "fyko+d2lbbFgONRv9qkxdawL"; + let client_init = b"n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL"; + let server_init = b"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096"; + let client_final = + b"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="; + let server_final = b"v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; + let mut mechanism = + Scram::::new_with_nonce(username, password, client_nonce.to_owned()); + let init = mechanism.initial().unwrap(); + assert_eq!( + String::from_utf8(init.clone()).unwrap(), + String::from_utf8(client_init[..].to_owned()).unwrap() + ); // depends on ordering… + let resp = mechanism.response(&server_init[..]).unwrap(); + assert_eq!( + String::from_utf8(resp.clone()).unwrap(), + String::from_utf8(client_final[..].to_owned()).unwrap() + ); // again, depends on ordering… + mechanism.success(&server_final[..]).unwrap(); + } + + #[test] + fn scram_sha256_works() { + // Source: RFC 7677 + let username = "user"; + let password = "pencil"; + let client_nonce = "rOprNGfwEbeRWgbNEkqO"; + let client_init = b"n,,n=user,r=rOprNGfwEbeRWgbNEkqO"; + let server_init = b"r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096"; + let client_final = b"c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ="; + let server_final = b"v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="; + let mut mechanism = + Scram::::new_with_nonce(username, password, client_nonce.to_owned()); + let init = mechanism.initial().unwrap(); + assert_eq!( + String::from_utf8(init.clone()).unwrap(), + String::from_utf8(client_init[..].to_owned()).unwrap() + ); // depends on ordering… + let resp = mechanism.response(&server_init[..]).unwrap(); + assert_eq!( + String::from_utf8(resp.clone()).unwrap(), + String::from_utf8(client_final[..].to_owned()).unwrap() + ); // again, depends on ordering… + mechanism.success(&server_final[..]).unwrap(); + } +} From 6628d12e1343e10f4bc65cdbd012e95d34cb4f87 Mon Sep 17 00:00:00 2001 From: lumi Date: Mon, 27 Feb 2017 16:46:14 +0100 Subject: [PATCH 02/53] add license info, prepare for pushing to crates.io --- sasl/COPYING | 675 ++++++++++++++++++++++++++++++++++++++++++++ sasl/COPYING.LESSER | 166 +++++++++++ sasl/Cargo.toml | 10 + sasl/README.md | 30 ++ 4 files changed, 881 insertions(+) create mode 100644 sasl/COPYING create mode 100644 sasl/COPYING.LESSER create mode 100644 sasl/README.md diff --git a/sasl/COPYING b/sasl/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..a737dcfed5db21fb99a8fcb812e995937d38401b --- /dev/null +++ b/sasl/COPYING @@ -0,0 +1,675 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/sasl/COPYING.LESSER b/sasl/COPYING.LESSER new file mode 100644 index 0000000000000000000000000000000000000000..5f5ff16a4a0f6104fadb6a5beef527573e46b425 --- /dev/null +++ b/sasl/COPYING.LESSER @@ -0,0 +1,166 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index aa97ef16a1d0e6bd5b69a91f53b340267272b0e1..b2dc08752d9190e84ddae67346cc1ce5366b7da5 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -2,6 +2,16 @@ name = "sasl" version = "0.1.0" authors = ["lumi "] +description = "A crate for SASL authentication. Still needs a bunch of documenation." +homepage = "https://gitlab.com/lumi/sasl-rs" +repository = "https://gitlab.com/lumi/sasl-rs" +documentation = "https://docs.rs/sasl" +readme = "README.md" +keywords = ["sasl", "authentication"] +license = "LGPL-3.0+" + +[badges] +gitlab = { repository = "lumi/sasl-rs" } [dependencies] openssl = "0.9.7" diff --git a/sasl/README.md b/sasl/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5524494f5596f786000f914d6bae6e7403eeec7d --- /dev/null +++ b/sasl/README.md @@ -0,0 +1,30 @@ +sasl-rs +======= + +What's this? +------------ + +A crate which handles SASL authentication. + +What license is it under? +------------------------- + +LGPLv3 or later. See `COPYING` and `COPYING.LESSER`. + +License yadda yadda. +-------------------- + + Copyright 2017, sasl-rs contributors. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . From c56676a6010841ef0eee6469782cc129345f8d8b Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 28 Feb 2017 13:05:17 +0100 Subject: [PATCH 03/53] #![deny(missing_docs)] and lots of documentation --- sasl/src/error.rs | 3 +++ sasl/src/lib.rs | 43 +++++++++++++++++++++++++++++--- sasl/src/mechanisms/anonymous.rs | 5 ++++ sasl/src/mechanisms/mod.rs | 3 ++- sasl/src/mechanisms/plain.rs | 5 ++++ sasl/src/mechanisms/scram.rs | 28 +++++++++++++++++++++ 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/sasl/src/error.rs b/sasl/src/error.rs index 4192300a1f4cdf97a514a52f2013feb3d8dd4a99..284406bd8037eb94a9361f923d0d6c4748608b4d 100644 --- a/sasl/src/error.rs +++ b/sasl/src/error.rs @@ -1,8 +1,11 @@ use openssl::error::ErrorStack; +/// A wrapper enum for things that could go wrong in this crate. #[derive(Debug)] pub enum Error { + /// An error in OpenSSL. OpenSslErrorStack(ErrorStack), + /// An error in a SASL mechanism. SaslError(String), } diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 48a57b4ab58f2d3df6cc327db699a9ed8c35cc69..fc81505024e0aed4931c8eefc4c24fd80bff06f2 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -1,14 +1,50 @@ -//! Provides the `SaslMechanism` trait and some implementations. +#![deny(missing_docs)] + +//! This crate provides a framework for SASL authentication and a few authentication mechanisms. +//! +//! # Examples +//! +//! ```rust +//! use sasl::{SaslCredentials, SaslSecret, SaslMechanism, Error}; +//! use sasl::mechanisms::Plain; +//! +//! let creds = SaslCredentials { +//! username: "user".to_owned(), +//! secret: SaslSecret::Password("pencil".to_owned()), +//! channel_binding: None, +//! }; +//! +//! let mut mechanism = Plain::from_credentials(creds).unwrap(); +//! +//! let initial_data = mechanism.initial().unwrap(); +//! +//! assert_eq!(initial_data, b"\0user\0pencil"); +//! ``` +//! +//! You may look at the tests of `mechanisms/scram.rs` for examples of more advanced usage. +//! +//! # Usage +//! +//! You can use this in your crate by adding this under `dependencies` in your `Cargo.toml`: +//! +//! ```toml,ignore +//! sasl = "*" +//! ``` extern crate base64; extern crate openssl; -pub mod error; +mod error; + +pub use error::Error; /// A struct containing SASL credentials. pub struct SaslCredentials { - pub username: String, + /// The requested username. + pub username: String, // TODO: change this since some mechanisms do not use it + /// The secret used to authenticate. pub secret: SaslSecret, + /// Optionally, channel binding data, for *-PLUS mechanisms. pub channel_binding: Option>, } @@ -20,6 +56,7 @@ pub enum SaslSecret { Password(String), } +/// A trait which defines SASL mechanisms. pub trait SaslMechanism { /// The name of the mechanism. fn name(&self) -> &str; diff --git a/sasl/src/mechanisms/anonymous.rs b/sasl/src/mechanisms/anonymous.rs index 31a6f14b60bbdd6633a5d99150a8676d1dbdbb94..ecfe14df574611f0cc9f3f542adb96a02e2a598d 100644 --- a/sasl/src/mechanisms/anonymous.rs +++ b/sasl/src/mechanisms/anonymous.rs @@ -4,9 +4,14 @@ use SaslCredentials; use SaslMechanism; use SaslSecret; +/// A struct for the SASL ANONYMOUS mechanism. pub struct Anonymous; impl Anonymous { + /// Constructs a new struct for authenticating using the SASL ANONYMOUS mechanism. + /// + /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// requested mechanism using `from_credentials`. pub fn new() -> Anonymous { Anonymous } diff --git a/sasl/src/mechanisms/mod.rs b/sasl/src/mechanisms/mod.rs index 0e4205c8d8cf372565a575c7827c7547446ea675..2ccd1d7914405fe34d6e5152db752cdf3057fc0b 100644 --- a/sasl/src/mechanisms/mod.rs +++ b/sasl/src/mechanisms/mod.rs @@ -1,4 +1,5 @@ -///! Provides a few SASL mechanisms. +//! Provides a few SASL mechanisms. + mod anonymous; mod plain; mod scram; diff --git a/sasl/src/mechanisms/plain.rs b/sasl/src/mechanisms/plain.rs index c273d97f6741eaba61fe7d876760c5a254fb08e5..d77b70ccf113bde9780585340585304892a45d14 100644 --- a/sasl/src/mechanisms/plain.rs +++ b/sasl/src/mechanisms/plain.rs @@ -4,12 +4,17 @@ use SaslCredentials; use SaslMechanism; use SaslSecret; +/// A struct for the SASL PLAIN mechanism. pub struct Plain { username: String, password: String, } impl Plain { + /// Constructs a new struct for authenticating using the SASL PLAIN mechanism. + /// + /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// requested mechanism using `from_credentials`. pub fn new, P: Into>(username: N, password: P) -> Plain { Plain { username: username.into(), diff --git a/sasl/src/mechanisms/scram.rs b/sasl/src/mechanisms/scram.rs index c2fcbc18270685723f9e672629624b74abd56d38..757e8cc2a7ecb6688572bdeb960437a3688f9cd7 100644 --- a/sasl/src/mechanisms/scram.rs +++ b/sasl/src/mechanisms/scram.rs @@ -66,13 +66,22 @@ fn generate_nonce() -> Result { 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; + + /// A function which performs an HMAC using the hash function. fn hmac(data: &[u8], key: &[u8]) -> Vec; + + /// A function which does PBKDF2 key derivation using the hash function. fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec; } +/// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS pub struct Sha1; impl ScramProvider for Sha1 { @@ -99,6 +108,7 @@ impl ScramProvider for Sha1 { } } +/// A `ScramProvider` which provides SCRAM-SHA-256 and SCRAM-SHA-256-PLUS pub struct Sha256; impl ScramProvider for Sha256 { @@ -136,6 +146,7 @@ enum ScramState { }, } +/// A struct for the SASL SCRAM-* and SCRAM-*-PLUS mechanisms. pub struct Scram { name: String, username: String, @@ -147,6 +158,10 @@ pub struct Scram { } impl Scram { + /// Constructs a new struct for authenticating using the SASL SCRAM-* mechanism. + /// + /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// requested mechanism using `from_credentials`. pub fn new, P: Into>( username: N, password: P, @@ -162,6 +177,12 @@ impl Scram { }) } + /// Constructs a new struct for authenticating using the SASL SCRAM-* mechanism. + /// + /// This one takes a nonce instead of generating it. + /// + /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// requested mechanism using `from_credentials`. pub fn new_with_nonce, P: Into>( username: N, password: P, @@ -178,11 +199,18 @@ impl Scram { } } + /// Constructs a new struct for authenticating using the SASL SCRAM-*-PLUS mechanism. + /// + /// This means that this function will also take the channel binding data. + /// + /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// requested mechanism using `from_credentials`. pub fn new_with_channel_binding, P: Into>( username: N, password: P, channel_binding: Vec, ) -> Result, Error> { + // TODO: channel binding modes other than tls-unique Ok(Scram { name: format!("SCRAM-{}-PLUS", S::name()), username: username.into(), From 4a8b405f3b26bfb74901e3146da496d44a189dbe Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 28 Feb 2017 13:06:03 +0100 Subject: [PATCH 04/53] increment version --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index b2dc08752d9190e84ddae67346cc1ce5366b7da5..7e0657125cb3019b49dccf4dbfbf653cf6014b37 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.1.0" +version = "0.1.1" authors = ["lumi "] description = "A crate for SASL authentication. Still needs a bunch of documenation." homepage = "https://gitlab.com/lumi/sasl-rs" From 88ea00baa7bf4d64a5d77a3cfd765a80f488958f Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 7 Mar 2017 15:02:38 +0100 Subject: [PATCH 05/53] cleaned up channel binding logic, cleaned up SaslCredentials, updated documentation --- sasl/Cargo.toml | 2 +- sasl/README.md | 5 +++ sasl/src/lib.rs | 75 +++++++++++++++++++++++++++++++----- sasl/src/mechanisms/plain.rs | 6 ++- sasl/src/mechanisms/scram.rs | 60 ++++++++--------------------- 5 files changed, 92 insertions(+), 56 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 7e0657125cb3019b49dccf4dbfbf653cf6014b37..93e8271be109d64a263a9b93d37e6268b459d426 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -2,7 +2,7 @@ name = "sasl" version = "0.1.1" authors = ["lumi "] -description = "A crate for SASL authentication. Still needs a bunch of documenation." +description = "A crate for SASL authentication." homepage = "https://gitlab.com/lumi/sasl-rs" repository = "https://gitlab.com/lumi/sasl-rs" documentation = "https://docs.rs/sasl" diff --git a/sasl/README.md b/sasl/README.md index 5524494f5596f786000f914d6bae6e7403eeec7d..0b66d0fff538bf38e91574ec3b7eac1c93e14652 100644 --- a/sasl/README.md +++ b/sasl/README.md @@ -6,6 +6,11 @@ What's this? A crate which handles SASL authentication. +Can I see an example? +--------------------- + +Look at the documentation [here](https://docs.rs/sasl). + What license is it under? ------------------------- diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index fc81505024e0aed4931c8eefc4c24fd80bff06f2..15dbd95d7eb6a0883a821fe90abedd55fa6e6b0b 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -5,14 +5,12 @@ //! # Examples //! //! ```rust -//! use sasl::{SaslCredentials, SaslSecret, SaslMechanism, Error}; +//! use sasl::{SaslCredentials, SaslMechanism, Error}; //! use sasl::mechanisms::Plain; //! -//! let creds = SaslCredentials { -//! username: "user".to_owned(), -//! secret: SaslSecret::Password("pencil".to_owned()), -//! channel_binding: None, -//! }; +//! let creds = SaslCredentials::default() +//! .with_username("user") +//! .with_password("pencil"); //! //! let mut mechanism = Plain::from_credentials(creds).unwrap(); //! @@ -39,16 +37,75 @@ mod error; pub use error::Error; /// A struct containing SASL credentials. +#[derive(Clone, Debug)] pub struct SaslCredentials { /// The requested username. - pub username: String, // TODO: change this since some mechanisms do not use it + pub username: Option, /// The secret used to authenticate. pub secret: SaslSecret, - /// Optionally, channel binding data, for *-PLUS mechanisms. - pub channel_binding: Option>, + /// Channel binding data, for *-PLUS mechanisms. + pub channel_binding: ChannelBinding, +} + +impl Default for SaslCredentials { + fn default() -> SaslCredentials { + SaslCredentials { + username: None, + secret: SaslSecret::None, + channel_binding: ChannelBinding::None, + } + } +} + +impl SaslCredentials { + /// Creates a new SaslCredentials with the specified username. + pub fn with_username>(mut self, username: N) -> SaslCredentials { + self.username = Some(username.into()); + self + } + + /// Creates a new SaslCredentials with the specified password. + pub fn with_password>(mut self, password: P) -> SaslCredentials { + self.secret = SaslSecret::Password(password.into()); + self + } + + /// Creates a new SaslCredentials with the specified chanel binding. + pub fn with_channel_binding(mut self, channel_binding: ChannelBinding) -> SaslCredentials { + self.channel_binding = channel_binding; + self + } +} + +/// Channel binding configuration. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ChannelBinding { + /// No channel binding data. + None, + /// p=tls-unique channel binding data. + TlsUnique(Vec), +} + +impl ChannelBinding { + /// Return the gs2 header for this channel binding mechanism. + pub fn header(&self) -> &[u8] { + match *self { + ChannelBinding::None => b"n,,", + 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::TlsUnique(ref data) => data, + } + } } /// Represents a SASL secret, like a password. +#[derive(Clone, Debug, PartialEq, Eq)] pub enum SaslSecret { /// No extra data needed. None, diff --git a/sasl/src/mechanisms/plain.rs b/sasl/src/mechanisms/plain.rs index d77b70ccf113bde9780585340585304892a45d14..2b4a27071a4d1bdcd1e83532408a030914c8a12c 100644 --- a/sasl/src/mechanisms/plain.rs +++ b/sasl/src/mechanisms/plain.rs @@ -30,7 +30,11 @@ impl SaslMechanism for Plain { fn from_credentials(credentials: SaslCredentials) -> Result { if let SaslSecret::Password(password) = credentials.secret { - Ok(Plain::new(credentials.username, password)) + if let Some(username) = credentials.username { + Ok(Plain::new(username, password)) + } else { + Err("PLAIN requires a username".to_owned()) + } } else { Err("PLAIN requires a password".to_owned()) } diff --git a/sasl/src/mechanisms/scram.rs b/sasl/src/mechanisms/scram.rs index 757e8cc2a7ecb6688572bdeb960437a3688f9cd7..7b7551a0026cd011e99797ff2f5153b9d19947f2 100644 --- a/sasl/src/mechanisms/scram.rs +++ b/sasl/src/mechanisms/scram.rs @@ -2,6 +2,7 @@ use base64; +use ChannelBinding; use SaslCredentials; use SaslMechanism; use SaslSecret; @@ -153,18 +154,20 @@ pub struct Scram { password: String, client_nonce: String, state: ScramState, - channel_binding: Option>, + channel_binding: ChannelBinding, _marker: PhantomData, } impl Scram { - /// Constructs a new struct for authenticating using the SASL SCRAM-* mechanism. + /// Constructs a new struct for authenticating using the SASL SCRAM-* and SCRAM-*-PLUS + /// mechanisms, depending on the passed channel binding. /// /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the /// requested mechanism using `from_credentials`. pub fn new, P: Into>( username: N, password: P, + channel_binding: ChannelBinding, ) -> Result, Error> { Ok(Scram { name: format!("SCRAM-{}", S::name()), @@ -172,17 +175,14 @@ impl Scram { password: password.into(), client_nonce: generate_nonce()?, state: ScramState::Init, - channel_binding: None, + channel_binding: channel_binding, _marker: PhantomData, }) } - /// Constructs a new struct for authenticating using the SASL SCRAM-* mechanism. - /// - /// This one takes a nonce instead of generating it. - /// - /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the - /// requested mechanism using `from_credentials`. + // Used for testing. + #[doc(hidden)] + #[cfg(test)] pub fn new_with_nonce, P: Into>( username: N, password: P, @@ -194,33 +194,10 @@ impl Scram { password: password.into(), client_nonce: nonce, state: ScramState::Init, - channel_binding: None, + channel_binding: ChannelBinding::None, _marker: PhantomData, } } - - /// Constructs a new struct for authenticating using the SASL SCRAM-*-PLUS mechanism. - /// - /// This means that this function will also take the channel binding data. - /// - /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the - /// requested mechanism using `from_credentials`. - pub fn new_with_channel_binding, P: Into>( - username: N, - password: P, - channel_binding: Vec, - ) -> Result, Error> { - // TODO: channel binding modes other than tls-unique - Ok(Scram { - name: format!("SCRAM-{}-PLUS", S::name()), - username: username.into(), - password: password.into(), - client_nonce: generate_nonce()?, - state: ScramState::Init, - channel_binding: Some(channel_binding), - _marker: PhantomData, - }) - } } impl SaslMechanism for Scram { @@ -231,12 +208,11 @@ impl SaslMechanism for Scram { fn from_credentials(credentials: SaslCredentials) -> Result, String> { if let SaslSecret::Password(password) = credentials.secret { - if let Some(binding) = credentials.channel_binding { - Scram::new_with_channel_binding(credentials.username, password, binding) + if let Some(username) = credentials.username { + Scram::new(username, password, credentials.channel_binding) .map_err(|_| "can't generate nonce".to_owned()) } else { - Scram::new(credentials.username, password) - .map_err(|_| "can't generate nonce".to_owned()) + Err("SCRAM requires a username".to_owned()) } } else { Err("SCRAM requires a password".to_owned()) @@ -245,11 +221,7 @@ impl SaslMechanism for Scram { fn initial(&mut self) -> Result, String> { let mut gs2_header = Vec::new(); - if let Some(_) = self.channel_binding { - gs2_header.extend(b"p=tls-unique,,"); - } else { - gs2_header.extend(b"n,,"); - } + gs2_header.extend(self.channel_binding.header()); let mut bare = Vec::new(); bare.extend(b"n="); bare.extend(self.username.bytes()); @@ -286,9 +258,7 @@ impl SaslMechanism for Scram { client_final_message_bare.extend(b"c="); let mut cb_data: Vec = Vec::new(); cb_data.extend(gs2_header); - if let Some(ref cb) = self.channel_binding { - cb_data.extend(cb); - } + cb_data.extend(self.channel_binding.data()); 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()); From 62be423594a197a6267e080192c93616728cf274 Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 7 Mar 2017 15:07:39 +0100 Subject: [PATCH 06/53] version bump --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 93e8271be109d64a263a9b93d37e6268b459d426..790b809175eb3b51ca92ffccce0488d39f3979b2 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.1.1" +version = "0.2.0" authors = ["lumi "] description = "A crate for SASL authentication." homepage = "https://gitlab.com/lumi/sasl-rs" From 2d8fffdbfcf8c8f99ea7f753cf00163318a10c9e Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 7 Mar 2017 17:02:57 +0100 Subject: [PATCH 07/53] clean up naming, add advertising that the client thinks channel binding is unsupported --- sasl/Cargo.toml | 4 +-- sasl/src/lib.rs | 48 +++++++++++++++++--------------- sasl/src/mechanisms/anonymous.rs | 14 +++++----- sasl/src/mechanisms/plain.rs | 14 +++++----- sasl/src/mechanisms/scram.rs | 16 +++++------ 5 files changed, 50 insertions(+), 46 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 790b809175eb3b51ca92ffccce0488d39f3979b2..2f9571124f3ec161ec48eee4dbe180ba9407ca1c 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "sasl" -version = "0.2.0" +version = "0.3.0" authors = ["lumi "] -description = "A crate for SASL authentication." +description = "A crate for SASL authentication. Currently only does the client side." homepage = "https://gitlab.com/lumi/sasl-rs" repository = "https://gitlab.com/lumi/sasl-rs" documentation = "https://docs.rs/sasl" diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 15dbd95d7eb6a0883a821fe90abedd55fa6e6b0b..441e360903d3aee03cfccac53b3d03804ee83f0a 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -5,12 +5,12 @@ //! # Examples //! //! ```rust -//! use sasl::{SaslCredentials, SaslMechanism, Error}; +//! use sasl::{Credentials, Mechanism, Error}; //! use sasl::mechanisms::Plain; //! -//! let creds = SaslCredentials::default() -//! .with_username("user") -//! .with_password("pencil"); +//! let creds = Credentials::default() +//! .with_username("user") +//! .with_password("pencil"); //! //! let mut mechanism = Plain::from_credentials(creds).unwrap(); //! @@ -38,40 +38,40 @@ pub use error::Error; /// A struct containing SASL credentials. #[derive(Clone, Debug)] -pub struct SaslCredentials { +pub struct Credentials { /// The requested username. pub username: Option, /// The secret used to authenticate. - pub secret: SaslSecret, + pub secret: Secret, /// Channel binding data, for *-PLUS mechanisms. pub channel_binding: ChannelBinding, } -impl Default for SaslCredentials { - fn default() -> SaslCredentials { - SaslCredentials { +impl Default for Credentials { + fn default() -> Credentials { + Credentials { username: None, - secret: SaslSecret::None, + secret: Secret::None, channel_binding: ChannelBinding::None, } } } -impl SaslCredentials { - /// Creates a new SaslCredentials with the specified username. - pub fn with_username>(mut self, username: N) -> SaslCredentials { +impl Credentials { + /// Creates a new Credentials with the specified username. + pub fn with_username>(mut self, username: N) -> Credentials { self.username = Some(username.into()); self } - /// Creates a new SaslCredentials with the specified password. - pub fn with_password>(mut self, password: P) -> SaslCredentials { - self.secret = SaslSecret::Password(password.into()); + /// Creates a new Credentials with the specified password. + pub fn with_password>(mut self, password: P) -> Credentials { + self.secret = Secret::Password(password.into()); self } - /// Creates a new SaslCredentials with the specified chanel binding. - pub fn with_channel_binding(mut self, channel_binding: ChannelBinding) -> SaslCredentials { + /// 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 } @@ -82,6 +82,8 @@ impl SaslCredentials { 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), } @@ -91,6 +93,7 @@ impl ChannelBinding { pub fn header(&self) -> &[u8] { match *self { ChannelBinding::None => b"n,,", + ChannelBinding::Unsupported => b"y,,", ChannelBinding::TlsUnique(_) => b"p=tls-unique,,", } } @@ -99,6 +102,7 @@ impl ChannelBinding { pub fn data(&self) -> &[u8] { match *self { ChannelBinding::None => &[], + ChannelBinding::Unsupported => &[], ChannelBinding::TlsUnique(ref data) => data, } } @@ -106,7 +110,7 @@ impl ChannelBinding { /// Represents a SASL secret, like a password. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum SaslSecret { +pub enum Secret { /// No extra data needed. None, /// Password required. @@ -114,12 +118,12 @@ pub enum SaslSecret { } /// A trait which defines SASL mechanisms. -pub trait SaslMechanism { +pub trait Mechanism { /// The name of the mechanism. fn name(&self) -> &str; - /// Creates this mechanism from `SaslCredentials`. - fn from_credentials(credentials: SaslCredentials) -> Result + /// Creates this mechanism from `Credentials`. + fn from_credentials(credentials: Credentials) -> Result where Self: Sized; diff --git a/sasl/src/mechanisms/anonymous.rs b/sasl/src/mechanisms/anonymous.rs index ecfe14df574611f0cc9f3f542adb96a02e2a598d..cf1daf6ab3f91718cc6101ddb2c78992f4845e4c 100644 --- a/sasl/src/mechanisms/anonymous.rs +++ b/sasl/src/mechanisms/anonymous.rs @@ -1,8 +1,8 @@ //! Provides the SASL "ANONYMOUS" mechanism. -use SaslCredentials; -use SaslMechanism; -use SaslSecret; +use Credentials; +use Mechanism; +use Secret; /// A struct for the SASL ANONYMOUS mechanism. pub struct Anonymous; @@ -10,20 +10,20 @@ pub struct Anonymous; impl Anonymous { /// Constructs a new struct for authenticating using the SASL ANONYMOUS mechanism. /// - /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// It is recommended that instead you use a `Credentials` struct and turn it into the /// requested mechanism using `from_credentials`. pub fn new() -> Anonymous { Anonymous } } -impl SaslMechanism for Anonymous { +impl Mechanism for Anonymous { fn name(&self) -> &str { "ANONYMOUS" } - fn from_credentials(credentials: SaslCredentials) -> Result { - if let SaslSecret::None = credentials.secret { + fn from_credentials(credentials: Credentials) -> Result { + if let Secret::None = credentials.secret { Ok(Anonymous) } else { Err("the anonymous sasl mechanism requires no credentials".to_owned()) diff --git a/sasl/src/mechanisms/plain.rs b/sasl/src/mechanisms/plain.rs index 2b4a27071a4d1bdcd1e83532408a030914c8a12c..58e0f2416b62bfc15d364c43b170301d25a8f054 100644 --- a/sasl/src/mechanisms/plain.rs +++ b/sasl/src/mechanisms/plain.rs @@ -1,8 +1,8 @@ //! Provides the SASL "PLAIN" mechanism. -use SaslCredentials; -use SaslMechanism; -use SaslSecret; +use Credentials; +use Mechanism; +use Secret; /// A struct for the SASL PLAIN mechanism. pub struct Plain { @@ -13,7 +13,7 @@ pub struct Plain { impl Plain { /// Constructs a new struct for authenticating using the SASL PLAIN mechanism. /// - /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// It is recommended that instead you use a `Credentials` struct and turn it into the /// requested mechanism using `from_credentials`. pub fn new, P: Into>(username: N, password: P) -> Plain { Plain { @@ -23,13 +23,13 @@ impl Plain { } } -impl SaslMechanism for Plain { +impl Mechanism for Plain { fn name(&self) -> &str { "PLAIN" } - fn from_credentials(credentials: SaslCredentials) -> Result { - if let SaslSecret::Password(password) = credentials.secret { + fn from_credentials(credentials: Credentials) -> Result { + if let Secret::Password(password) = credentials.secret { if let Some(username) = credentials.username { Ok(Plain::new(username, password)) } else { diff --git a/sasl/src/mechanisms/scram.rs b/sasl/src/mechanisms/scram.rs index 7b7551a0026cd011e99797ff2f5153b9d19947f2..1a075e455ab3bceeedc411816d0d4f6ebeaa8555 100644 --- a/sasl/src/mechanisms/scram.rs +++ b/sasl/src/mechanisms/scram.rs @@ -3,9 +3,9 @@ use base64; use ChannelBinding; -use SaslCredentials; -use SaslMechanism; -use SaslSecret; +use Credentials; +use Mechanism; +use Secret; use error::Error; @@ -162,7 +162,7 @@ impl Scram { /// Constructs a new struct for authenticating using the SASL SCRAM-* and SCRAM-*-PLUS /// mechanisms, depending on the passed channel binding. /// - /// It is recommended that instead you use a `SaslCredentials` struct and turn it into the + /// It is recommended that instead you use a `Credentials` struct and turn it into the /// requested mechanism using `from_credentials`. pub fn new, P: Into>( username: N, @@ -200,14 +200,14 @@ impl Scram { } } -impl SaslMechanism for Scram { +impl Mechanism for Scram { fn name(&self) -> &str { // TODO: this is quite the workaround… &self.name } - fn from_credentials(credentials: SaslCredentials) -> Result, String> { - if let SaslSecret::Password(password) = credentials.secret { + fn from_credentials(credentials: Credentials) -> Result, String> { + if let Secret::Password(password) = credentials.secret { if let Some(username) = credentials.username { Scram::new(username, password, credentials.channel_binding) .map_err(|_| "can't generate nonce".to_owned()) @@ -315,7 +315,7 @@ impl SaslMechanism for Scram { #[cfg(test)] mod tests { - use SaslMechanism; + use Mechanism; use super::*; From 4b9f2376af08a829922d16567bddef49b0487f6a Mon Sep 17 00:00:00 2001 From: lumi Date: Thu, 16 Mar 2017 20:04:22 +0100 Subject: [PATCH 08/53] initial work towards server-side support --- 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(-) rename sasl/src/{ => client}/mechanisms/anonymous.rs (93%) rename sasl/src/{ => client}/mechanisms/mod.rs (70%) rename sasl/src/{ => client}/mechanisms/plain.rs (79%) rename sasl/src/{ => client}/mechanisms/scram.rs (69%) create mode 100644 sasl/src/client/mod.rs create mode 100644 sasl/src/common/mod.rs create mode 100644 sasl/src/common/scram.rs create mode 100644 sasl/src/server/mechanisms/plain.rs create mode 100644 sasl/src/server/mod.rs diff --git a/sasl/src/mechanisms/anonymous.rs b/sasl/src/client/mechanisms/anonymous.rs similarity index 93% rename from sasl/src/mechanisms/anonymous.rs rename to sasl/src/client/mechanisms/anonymous.rs index cf1daf6ab3f91718cc6101ddb2c78992f4845e4c..d95245a8b05bb09dbbdd78c72573f32e55892ffd 100644 --- a/sasl/src/mechanisms/anonymous.rs +++ b/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; diff --git a/sasl/src/mechanisms/mod.rs b/sasl/src/client/mechanisms/mod.rs similarity index 70% rename from sasl/src/mechanisms/mod.rs rename to sasl/src/client/mechanisms/mod.rs index 2ccd1d7914405fe34d6e5152db752cdf3057fc0b..6dba5581a1ef4f6c67329edf6902d1e917aa37b9 100644 --- a/sasl/src/mechanisms/mod.rs +++ b/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; diff --git a/sasl/src/mechanisms/plain.rs b/sasl/src/client/mechanisms/plain.rs similarity index 79% rename from sasl/src/mechanisms/plain.rs rename to sasl/src/client/mechanisms/plain.rs index 58e0f2416b62bfc15d364c43b170301d25a8f054..786978da1f3eaee817ece5a624d818f0f17a9672 100644 --- a/sasl/src/mechanisms/plain.rs +++ b/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 { - 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()) } } diff --git a/sasl/src/mechanisms/scram.rs b/sasl/src/client/mechanisms/scram.rs similarity index 69% rename from sasl/src/mechanisms/scram.rs rename to sasl/src/client/mechanisms/scram.rs index 1a075e455ab3bceeedc411816d0d4f6ebeaa8555..5f43a91342a0f41db2fd042d82f72dbcd72cc2d3 100644 --- a/sasl/src/mechanisms/scram.rs +++ b/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 { - 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, 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 { - 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; - - /// A function which performs an HMAC using the hash function. - fn hmac(data: &[u8], key: &[u8]) -> Vec; - - /// A function which does PBKDF2 key derivation using the hash function. - fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec; -} - -/// 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 { - hash(MessageDigest::sha1(), data).unwrap() - } - - fn hmac(data: &[u8], key: &[u8]) -> Vec { - 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 { - 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 { - hash(MessageDigest::sha256(), data).unwrap() - } - - fn hmac(data: &[u8], key: &[u8]) -> Vec { - 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 { - 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 { name: String, username: String, - password: String, + password: Password, client_nonce: String, state: ScramState, channel_binding: ChannelBinding, @@ -164,7 +38,7 @@ impl Scram { /// /// It is recommended that instead you use a `Credentials` struct and turn it into the /// requested mechanism using `from_credentials`. - pub fn new, P: Into>( + pub fn new, P: Into>( username: N, password: P, channel_binding: ChannelBinding, @@ -183,7 +57,7 @@ impl Scram { // Used for testing. #[doc(hidden)] #[cfg(test)] - pub fn new_with_nonce, P: Into>( + pub fn new_with_nonce, P: Into>( username: N, password: P, nonce: String, @@ -208,7 +82,7 @@ impl Mechanism for Scram { fn from_credentials(credentials: Credentials) -> Result, 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 Mechanism for Scram { 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 Mechanism for Scram { 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 Mechanism for Scram { #[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() { diff --git a/sasl/src/client/mod.rs b/sasl/src/client/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..d8655d543cea5816c55d63317dca9ffbdfb94efa --- /dev/null +++ b/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 + where + Self: Sized; + + /// Provides initial payload of the SASL mechanism. + fn initial(&mut self) -> Result, String> { + Ok(Vec::new()) + } + + /// Creates a response to the SASL challenge. + fn response(&mut self, _challenge: &[u8]) -> Result, 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; diff --git a/sasl/src/common/mod.rs b/sasl/src/common/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8e2016a71bd74b9c39b413050d0b5ed4b7a66b80 --- /dev/null +++ b/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 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>(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>(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>(password: S) -> Secret { + Secret::Password(Password::Plain(password.into())) + } + + pub fn password_pbkdf2>( + method: S, + salt: Vec, + iterations: usize, + data: Vec, + ) -> 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, + iterations: usize, + data: Vec, + }, +} + +impl From 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 { + 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, 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), +} + +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", + } + } +} diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs new file mode 100644 index 0000000000000000000000000000000000000000..76c90bf3f03f7f14138d9614fbd8fe9d182545c4 --- /dev/null +++ b/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 { + 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; + + /// A function which performs an HMAC using the hash function. + fn hmac(data: &[u8], key: &[u8]) -> Vec; + + /// A function which does PBKDF2 key derivation using the hash function. + fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result, 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 { + hash(MessageDigest::sha1(), data).unwrap() + } + + fn hmac(data: &[u8], key: &[u8]) -> Vec { + 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, 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 { + hash(MessageDigest::sha256(), data).unwrap() + } + + fn hmac(data: &[u8], key: &[u8]) -> Vec { + 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, 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()) + } + } + } + } +} diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 441e360903d3aee03cfccac53b3d03804ee83f0a..b05f680372067c2a03adefa762d294f35c573599 100644 --- a/sasl/src/lib.rs +++ b/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 { +//! 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(&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)); +//! +//! 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: &mut CM, sm: &mut SM) -> Result +//! where CM: ClientMechanism, +//! SM: ServerMechanism, +//! 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::::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()))); +//! +//! 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 //! @@ -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, - /// 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>(mut self, username: N) -> Credentials { - self.username = Some(username.into()); - self - } - - /// Creates a new Credentials with the specified password. - pub fn with_password>(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), -} - -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 - where - Self: Sized; - - /// Provides initial payload of the SASL mechanism. - fn initial(&mut self) -> Result, String> { - Ok(Vec::new()) - } - - /// Creates a response to the SASL challenge. - fn response(&mut self, _challenge: &[u8]) -> Result, 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; diff --git a/sasl/src/server/mechanisms/plain.rs b/sasl/src/server/mechanisms/plain.rs new file mode 100644 index 0000000000000000000000000000000000000000..1594256f3af787840868af01367f0b9663f9fd82 --- /dev/null +++ b/sasl/src/server/mechanisms/plain.rs @@ -0,0 +1,13 @@ +use server::Mechanism; +use common::{Secret, Credentials, Password}; + +pub struct Plain { + password: String, +} + +impl Mechanism for Plain { + fn name(&self) -> &str { "PLAIN" } + + fn from_initial_message(validator: &V, msg: &[u8]) -> Result<(Self, String), String> { + } +} diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d28d5e839b25604b7956307734f5910f714b81f --- /dev/null +++ b/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; + fn request_pbkdf2(&self) -> Result<(Vec, usize, Vec), String>; +} + +pub trait Mechanism { + fn name(&self) -> &str; + fn respond(&mut self, payload: &[u8]) -> Result; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Response { + Success(Identity, Vec), + Proceed(Vec), +} + +pub mod mechanisms { + mod plain { + use common::{ChannelBinding, Credentials, Identity, Secret}; + use server::{Mechanism, Response, Validator}; + + pub struct Plain { + validator: V, + } + + impl Plain { + pub fn new(validator: V) -> Plain { + Plain { + validator: validator, + } + } + } + + impl Mechanism for Plain { + fn name(&self) -> &str { + "PLAIN" + } + + fn respond(&mut self, payload: &[u8]) -> Result { + let mut sp = payload.split(|&b| b == 0); + sp.next(); + let username = sp + .next() + .ok_or_else(|| "no username specified".to_owned())?; + let username = + String::from_utf8(username.to_vec()).map_err(|_| "error decoding username")?; + let password = sp + .next() + .ok_or_else(|| "no password specified".to_owned())?; + let password = + String::from_utf8(password.to_vec()).map_err(|_| "error decoding password")?; + let creds = Credentials { + identity: Identity::Username(username), + secret: Secret::password_plain(password), + channel_binding: ChannelBinding::None, + }; + let ret = self.validator.validate_credentials(&creds)?; + Ok(Response::Success(ret, Vec::new())) + } + } + } + + mod scram { + use std::marker::PhantomData; + + use base64; + + use common::scram::{generate_nonce, ScramProvider}; + use common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Secret}; + use server::{Mechanism, Response, Validator}; + + enum ScramState { + Init, + SentChallenge { + initial_client_message: Vec, + initial_server_message: Vec, + gs2_header: Vec, + server_nonce: String, + username: String, + salted_password: Vec, + }, + Done, + } + + pub struct Scram { + name: String, + state: ScramState, + channel_binding: ChannelBinding, + validator: V, + _marker: PhantomData, + } + + impl Scram { + pub fn new(validator: V, channel_binding: ChannelBinding) -> Scram { + Scram { + name: format!("SCRAM-{}", S::name()), + state: ScramState::Init, + channel_binding: channel_binding, + validator: validator, + _marker: PhantomData, + } + } + } + + impl Mechanism for Scram { + fn name(&self) -> &str { + &self.name + } + + fn respond(&mut self, payload: &[u8]) -> Result { + let next_state; + let ret; + match self.state { + ScramState::Init => { + // TODO: really ugly, mostly because parse_frame takes a &[u8] and i don't + // want to double validate utf-8 + // + // NEED TO CHANGE THIS THOUGH. IT'S AWFUL. + let mut commas = 0; + let mut idx = 0; + for &b in payload { + idx += 1; + if b == 0x2C { + commas += 1; + if commas >= 2 { + break; + } + } + } + if commas < 2 { + return Err("failed to decode message".to_owned()); + } + let gs2_header = payload[..idx].to_vec(); + let rest = payload[idx..].to_vec(); + // TODO: process gs2 header properly, not this ugly stuff + match self.channel_binding { + ChannelBinding::None | ChannelBinding::Unsupported => { + // Not supported. + if gs2_header[0] != 0x79 { + // ord("y") + return Err("channel binding not supported".to_owned()); + } + } + ref other => { + // Supported. + if gs2_header[0] == 0x79 { + // ord("y") + return Err("channel binding is supported".to_owned()); + } else if !other.supports("tls-unique") { + // TODO: grab the data + return Err("channel binding mechanism incorrect".to_owned()); + } + } + } + let frame = parse_frame(&rest) + .map_err(|_| "can't decode initial message".to_owned())?; + let username = frame.get("n").ok_or_else(|| "no username".to_owned())?; + let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; + let mut server_nonce = String::new(); + server_nonce += client_nonce; + server_nonce += + &generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; + let (salt, iterations, data) = self.validator.request_pbkdf2::()?; + let mut buf = Vec::new(); + buf.extend(b"r="); + buf.extend(server_nonce.bytes()); + buf.extend(b",s="); + buf.extend(base64::encode(&salt).bytes()); + buf.extend(b",i="); + buf.extend(iterations.to_string().bytes()); + ret = Response::Proceed(buf.clone()); + next_state = ScramState::SentChallenge { + server_nonce: server_nonce, + username: username.to_owned(), + salted_password: data, + initial_client_message: rest, + initial_server_message: buf, + gs2_header: gs2_header, + }; + } + ScramState::SentChallenge { + server_nonce: ref server_nonce, + username: ref username, + salted_password: ref salted_password, + gs2_header: ref gs2_header, + initial_client_message: ref initial_client_message, + initial_server_message: ref initial_server_message, + } => { + let frame = + parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; + let mut cb_data: Vec = Vec::new(); + cb_data.extend(gs2_header); + cb_data.extend(self.channel_binding.data()); + let mut client_final_message_bare = Vec::new(); + client_final_message_bare.extend(b"c="); + client_final_message_bare.extend(base64::encode(&cb_data).bytes()); + client_final_message_bare.extend(b",r="); + client_final_message_bare.extend(server_nonce.bytes()); + let client_key = S::hmac(b"Client Key", &salted_password); + let server_key = S::hmac(b"Server Key", &salted_password); + let stored_key = S::hash(&client_key); + let mut auth_message = Vec::new(); + auth_message.extend(initial_client_message); + auth_message.extend(b","); + auth_message.extend(initial_server_message); + auth_message.extend(b","); + auth_message.extend(client_final_message_bare.clone()); + let stored_key = S::hash(&client_key); + let client_signature = S::hmac(&auth_message, &stored_key); + let client_proof = xor(&client_key, &client_signature); + let sent_proof = frame.get("p").ok_or_else(|| "no proof".to_owned())?; + let sent_proof = base64::decode(sent_proof) + .map_err(|_| "can't decode proof".to_owned())?; + if client_proof != sent_proof { + return Err("authentication failed".to_owned()); + } + let server_signature = S::hmac(&auth_message, &server_key); + let mut buf = Vec::new(); + buf.extend(b"v="); + buf.extend(base64::encode(&server_signature).bytes()); + ret = Response::Success(Identity::Username(username.to_owned()), buf); + next_state = ScramState::Done; + } + ScramState::Done => { + return Err("sasl session is already over".to_owned()); + } + } + self.state = next_state; + Ok(ret) + } + } + } + + pub use self::plain::Plain; + pub use self::scram::Scram; +} From a8f8744e21d6c5ff68c9cbf17ba0df45cf9b39f5 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Sat, 25 Mar 2017 14:44:22 +0100 Subject: [PATCH 09/53] isolate scram behind a compilation feature --- sasl/Cargo.toml | 9 ++++++++- sasl/src/client/mechanisms/mod.rs | 4 ++++ sasl/src/common/mod.rs | 1 + sasl/src/error.rs | 3 +++ sasl/src/lib.rs | 2 ++ sasl/src/server/mod.rs | 8 +++++++- 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 2f9571124f3ec161ec48eee4dbe180ba9407ca1c..e4b6c4866cbe511e6492a9dc020ca27c9c558996 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -13,6 +13,13 @@ license = "LGPL-3.0+" [badges] gitlab = { repository = "lumi/sasl-rs" } +[features] +default = ["scram"] +scram = ["openssl"] + [dependencies] -openssl = "0.9.7" base64 = "0.4.0" + +[dependencies.openssl] +version = "0.9.7" +optional = true diff --git a/sasl/src/client/mechanisms/mod.rs b/sasl/src/client/mechanisms/mod.rs index 6dba5581a1ef4f6c67329edf6902d1e917aa37b9..bde2fcbf0fd6df9d727478dc2e3afae2143f0b6b 100644 --- a/sasl/src/client/mechanisms/mod.rs +++ b/sasl/src/client/mechanisms/mod.rs @@ -2,8 +2,12 @@ mod anonymous; mod plain; + +#[cfg(feature = "scram")] mod scram; pub use self::anonymous::Anonymous; pub use self::plain::Plain; + +#[cfg(feature = "scram")] pub use self::scram::Scram; diff --git a/sasl/src/common/mod.rs b/sasl/src/common/mod.rs index 8e2016a71bd74b9c39b413050d0b5ed4b7a66b80..21444f786e396a3f131671b75739b07b874bcde2 100644 --- a/sasl/src/common/mod.rs +++ b/sasl/src/common/mod.rs @@ -4,6 +4,7 @@ use std::convert::From; use std::string::FromUtf8Error; +#[cfg(feature = "scram")] pub mod scram; #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/sasl/src/error.rs b/sasl/src/error.rs index 284406bd8037eb94a9361f923d0d6c4748608b4d..6d79df25b8e7bf130a3b6fd6a9219e142558025c 100644 --- a/sasl/src/error.rs +++ b/sasl/src/error.rs @@ -1,14 +1,17 @@ +#[cfg(feature = "scram")] use openssl::error::ErrorStack; /// A wrapper enum for things that could go wrong in this crate. #[derive(Debug)] pub enum Error { + #[cfg(feature = "scram")] /// An error in OpenSSL. OpenSslErrorStack(ErrorStack), /// An error in a SASL mechanism. SaslError(String), } +#[cfg(feature = "scram")] impl From for Error { fn from(err: ErrorStack) -> Error { Error::OpenSslErrorStack(err) diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index b05f680372067c2a03adefa762d294f35c573599..af40cd3a668c2209d837f35d56225ff78a21a592 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -124,6 +124,8 @@ //! ``` extern crate base64; + +#[cfg(feature = "scram")] extern crate openssl; mod error; diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index 0d28d5e839b25604b7956307734f5910f714b81f..ede741a235d1a98ca9a793ed769f745596aa2d0e 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,8 +1,12 @@ -use common::scram::ScramProvider; use common::{Credentials, Identity}; +#[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>; } @@ -63,6 +67,7 @@ pub mod mechanisms { } } + #[cfg(feature = "scram")] mod scram { use std::marker::PhantomData; @@ -235,5 +240,6 @@ pub mod mechanisms { } pub use self::plain::Plain; + #[cfg(feature = "scram")] pub use self::scram::Scram; } From 6c117169260895b98b9865afc64d1e2a517f6d44 Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 25 Mar 2017 23:15:34 +0100 Subject: [PATCH 10/53] clean up lots of things, server-side API improved --- sasl/src/common/scram.rs | 9 + sasl/src/lib.rs | 111 ++++++++---- sasl/src/mechanisms/mod.rs | 5 + sasl/src/secret.rs | 74 ++++++++ sasl/src/server/mechanisms/mod.rs | 7 + sasl/src/server/mechanisms/plain.rs | 41 ++++- sasl/src/server/mechanisms/scram.rs | 185 ++++++++++++++++++++ sasl/src/server/mod.rs | 259 +++------------------------- 8 files changed, 419 insertions(+), 272 deletions(-) create mode 100644 sasl/src/mechanisms/mod.rs create mode 100644 sasl/src/secret.rs create mode 100644 sasl/src/server/mechanisms/mod.rs create mode 100644 sasl/src/server/mechanisms/scram.rs diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 76c90bf3f03f7f14138d9614fbd8fe9d182545c4..5862c31e2f776410e2a11a6779bd31f00e4e9a09 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -8,6 +8,8 @@ use openssl::sign::Signer; use common::Password; +use secret; + use base64; /// Generate a nonce for SCRAM authentication. @@ -19,6 +21,9 @@ pub fn generate_nonce() -> Result { /// A trait which defines the needed methods for SCRAM. pub trait ScramProvider { + /// The kind of secret this `ScramProvider` requires. + type SecretKind: secret::SecretKind; + /// The name of the hash function. fn name() -> &'static str; @@ -37,6 +42,8 @@ pub struct Sha1; impl ScramProvider for Sha1 { // TODO: look at all these unwraps + type SecretKind = secret::Pbkdf2Sha1; + fn name() -> &'static str { "SHA-1" } @@ -98,6 +105,8 @@ pub struct Sha256; impl ScramProvider for Sha256 { // TODO: look at all these unwraps + type SecretKind = secret::Pbkdf2Sha256; + fn name() -> &'static str { "SHA-256" } diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index af40cd3a668c2209d837f35d56225ff78a21a592..c454eef10273361c9ee1553369a476a8b56216b5 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)? ) ) +//! 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); -//! let expected_response = Response::Success(Identity::Username("user".to_owned()), Vec::new()); -//! assert_eq!(mech.respond(b"\0user\0pencil"), Ok(expected_response)); +//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1); //! -//! let mut mech = ServerPlain::new(MyValidator); -//! assert_eq!(mech.respond(b"\0user\0marker"), Err("authentication failure".to_owned())); +//! 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, +//! }) +//! } +//! } +//! } //! -//! let creds = Credentials::default() -//! .with_username(USERNAME) -//! .with_password(PASSWORD); +//! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha256); + //! -//! fn finish(cm: &mut CM, sm: &mut SM) -> Result +//! 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)); +//! +//! let mut mech = ServerPlain::new(MyValidator); +//! assert_eq!(mech.respond(b"\0user\0marker"), Err("authentication failed".to_owned())); //! -//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! 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); //! -//! 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); //! -//! let mut client_mech = ClientScram::::from_credentials(creds.clone()).unwrap(); -//! let mut server_mech = ServerScram::::new(MyValidator, ChannelBinding::Unsupported); +//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); //! -//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! let mut client_mech = ClientScram::::from_credentials(creds.clone()).unwrap(); +//! let mut server_mech = ServerScram::::new(MyValidator, ChannelBinding::Unsupported); +//! +//! assert_eq!(finish(&mut client_mech, &mut server_mech), Ok(Identity::Username(USERNAME.to_owned()))); +//! } //! ``` //! //! # Usage @@ -131,7 +172,9 @@ extern crate openssl; mod error; pub mod client; -pub mod common; +#[macro_use] pub mod server; +pub mod common; +pub mod secret; pub use error::Error; diff --git a/sasl/src/mechanisms/mod.rs b/sasl/src/mechanisms/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..408740a5cc4c2e3ec44a88529ec937b135f87a0c --- /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 0000000000000000000000000000000000000000..171e2f92ca00c903b616170111b81705520d4129 --- /dev/null +++ b/sasl/src/secret.rs @@ -0,0 +1,74 @@ +pub trait SecretKind { + type Value: PartialEq; +} + +pub trait Pbkdf2SecretValue { + fn salt(&self) -> &[u8]; + fn iterations(&self) -> usize; + fn digest(&self) -> &[u8]; +} + +pub struct Plain; + +#[derive(PartialEq)] +pub struct PlainValue(pub String); + +impl SecretKind for Plain { + type Value = PlainValue; +} + +pub struct Pbkdf2Sha1 { + pub salt: Vec, + pub iterations: usize, +} + +#[derive(PartialEq)] +pub struct Pbkdf2Sha1Value { + pub salt: Vec, + pub iterations: usize, + pub digest: Vec, +} + +impl SecretKind for Pbkdf2Sha1 { + type Value = Pbkdf2Sha1Value; +} + +impl Pbkdf2SecretValue for Pbkdf2Sha1Value { + fn salt(&self) -> &[u8] { + &self.salt + } + fn iterations(&self) -> usize { + self.iterations + } + fn digest(&self) -> &[u8] { + &self.digest + } +} + +pub struct Pbkdf2Sha256 { + pub salt: Vec, + pub iterations: usize, +} + +#[derive(PartialEq)] +pub struct Pbkdf2Sha256Value { + pub salt: Vec, + pub iterations: usize, + pub digest: Vec, +} + +impl SecretKind for Pbkdf2Sha256 { + type Value = Pbkdf2Sha256Value; +} + +impl Pbkdf2SecretValue for Pbkdf2Sha256Value { + fn salt(&self) -> &[u8] { + &self.salt + } + fn iterations(&self) -> usize { + self.iterations + } + fn digest(&self) -> &[u8] { + &self.digest + } +} diff --git a/sasl/src/server/mechanisms/mod.rs b/sasl/src/server/mechanisms/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..7718102564bd33cd477929d0d2ff1bfac67ee070 --- /dev/null +++ b/sasl/src/server/mechanisms/mod.rs @@ -0,0 +1,7 @@ +mod plain; +#[cfg(feature = "scram")] +mod scram; + +pub use self::plain::Plain; +#[cfg(feature = "scram")] +pub use self::scram::Scram; diff --git a/sasl/src/server/mechanisms/plain.rs b/sasl/src/server/mechanisms/plain.rs index 1594256f3af787840868af01367f0b9663f9fd82..f43f68f5ab50d9b58995eb1c8f9c1ddef585cc49 100644 --- a/sasl/src/server/mechanisms/plain.rs +++ b/sasl/src/server/mechanisms/plain.rs @@ -1,13 +1,40 @@ -use server::Mechanism; -use common::{Secret, Credentials, Password}; +use common::Identity; +use secret; +use server::{Mechanism, Response, Validator}; -pub struct Plain { - password: String, +pub struct Plain> { + validator: V, } -impl Mechanism for Plain { - fn name(&self) -> &str { "PLAIN" } +impl> Plain { + pub fn new(validator: V) -> Plain { + Plain { + validator: validator, + } + } +} + +impl> Mechanism for Plain { + fn name(&self) -> &str { + "PLAIN" + } - fn from_initial_message(validator: &V, msg: &[u8]) -> Result<(Self, String), String> { + 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 0000000000000000000000000000000000000000..d888ebc822f82215432a11d211375ac76a1aa594 --- /dev/null +++ b/sasl/src/server/mechanisms/scram.rs @@ -0,0 +1,185 @@ +use std::marker::PhantomData; + +use base64; + +use common::scram::{generate_nonce, ScramProvider}; +use common::{parse_frame, xor, ChannelBinding, Identity}; +use secret; +use secret::Pbkdf2SecretValue; +use server::{Mechanism, Provider, Response}; + +enum ScramState { + Init, + SentChallenge { + initial_client_message: Vec, + initial_server_message: Vec, + gs2_header: Vec, + server_nonce: String, + identity: Identity, + salted_password: Vec, + }, + Done, +} + +pub struct Scram +where + S: ScramProvider, + P: Provider, + ::Value: secret::Pbkdf2SecretValue, +{ + name: String, + state: ScramState, + channel_binding: ChannelBinding, + provider: P, + _marker: PhantomData, +} + +impl Scram +where + S: ScramProvider, + P: Provider, + ::Value: secret::Pbkdf2SecretValue, +{ + pub fn new(provider: P, channel_binding: ChannelBinding) -> Scram { + Scram { + name: format!("SCRAM-{}", S::name()), + state: ScramState::Init, + channel_binding: channel_binding, + provider: provider, + _marker: PhantomData, + } + } +} + +impl Mechanism for Scram +where + S: ScramProvider, + P: Provider, + ::Value: secret::Pbkdf2SecretValue, +{ + fn name(&self) -> &str { + &self.name + } + + fn respond(&mut self, payload: &[u8]) -> Result { + let next_state; + let ret; + match self.state { + ScramState::Init => { + // TODO: really ugly, mostly because parse_frame takes a &[u8] and i don't + // want to double validate utf-8 + // + // NEED TO CHANGE THIS THOUGH. IT'S AWFUL. + let mut commas = 0; + let mut idx = 0; + for &b in payload { + idx += 1; + if b == 0x2C { + commas += 1; + if commas >= 2 { + break; + } + } + } + if commas < 2 { + return Err("failed to decode message".to_owned()); + } + let gs2_header = payload[..idx].to_vec(); + let rest = payload[idx..].to_vec(); + // TODO: process gs2 header properly, not this ugly stuff + match self.channel_binding { + ChannelBinding::None | ChannelBinding::Unsupported => { + // Not supported. + if gs2_header[0] != 0x79 { + // ord("y") + return Err("channel binding not supported".to_owned()); + } + } + ref other => { + // Supported. + if gs2_header[0] == 0x79 { + // ord("y") + return Err("channel binding is supported".to_owned()); + } else if !other.supports("tls-unique") { + // TODO: grab the data + return Err("channel binding mechanism incorrect".to_owned()); + } + } + } + let frame = + parse_frame(&rest).map_err(|_| "can't decode initial message".to_owned())?; + let username = frame.get("n").ok_or_else(|| "no username".to_owned())?; + let identity = Identity::Username(username.to_owned()); + let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; + let mut server_nonce = String::new(); + server_nonce += client_nonce; + server_nonce += + &generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; + let pbkdf2 = self.provider.provide(&identity)?; + let mut buf = Vec::new(); + buf.extend(b"r="); + buf.extend(server_nonce.bytes()); + buf.extend(b",s="); + buf.extend(base64::encode(pbkdf2.salt()).bytes()); + buf.extend(b",i="); + buf.extend(pbkdf2.iterations().to_string().bytes()); + ret = Response::Proceed(buf.clone()); + next_state = ScramState::SentChallenge { + server_nonce: server_nonce, + identity: identity, + salted_password: pbkdf2.digest().to_vec(), + initial_client_message: rest, + initial_server_message: buf, + gs2_header: gs2_header, + }; + } + ScramState::SentChallenge { + ref server_nonce, + ref identity, + ref salted_password, + ref gs2_header, + ref initial_client_message, + ref initial_server_message, + } => { + let frame = parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; + let mut cb_data: Vec = Vec::new(); + cb_data.extend(gs2_header); + cb_data.extend(self.channel_binding.data()); + let mut client_final_message_bare = Vec::new(); + client_final_message_bare.extend(b"c="); + client_final_message_bare.extend(base64::encode(&cb_data).bytes()); + client_final_message_bare.extend(b",r="); + client_final_message_bare.extend(server_nonce.bytes()); + let client_key = S::hmac(b"Client Key", &salted_password); + let server_key = S::hmac(b"Server Key", &salted_password); + let stored_key = S::hash(&client_key); + let mut auth_message = Vec::new(); + auth_message.extend(initial_client_message); + auth_message.extend(b","); + auth_message.extend(initial_server_message); + auth_message.extend(b","); + auth_message.extend(client_final_message_bare.clone()); + let stored_key = S::hash(&client_key); + let client_signature = S::hmac(&auth_message, &stored_key); + let client_proof = xor(&client_key, &client_signature); + let sent_proof = frame.get("p").ok_or_else(|| "no proof".to_owned())?; + let sent_proof = + base64::decode(sent_proof).map_err(|_| "can't decode proof".to_owned())?; + if client_proof != sent_proof { + return Err("authentication failed".to_owned()); + } + let server_signature = S::hmac(&auth_message, &server_key); + let mut buf = Vec::new(); + buf.extend(b"v="); + buf.extend(base64::encode(&server_signature).bytes()); + ret = Response::Success(identity.clone(), buf); + next_state = ScramState::Done; + } + ScramState::Done => { + return Err("sasl session is already over".to_owned()); + } + } + self.state = next_state; + Ok(ret) + } +} diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index ede741a235d1a98ca9a793ed769f745596aa2d0e..6f9c4fa9e6ef9264f06b39cfddfeb996af08b0cc 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,16 +1,34 @@ -use common::{Credentials, Identity}; - -#[cfg(feature = "scram")] -use common::scram::ScramProvider; +use common::Identity; +use secret::SecretKind; + +#[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 Validator { - fn validate_credentials(&self, credentials: &Credentials) -> Result; +pub trait Provider: Validator { + fn provide(&self, identity: &Identity) -> Result; +} - #[cfg(feature = "scram")] - fn request_pbkdf2(&self) -> Result<(Vec, usize, Vec), String>; +pub trait Validator { + fn validate(&self, identity: &Identity, value: &S::Value) -> Result<(), String>; } -pub trait Mechanism { +pub trait Mechanism { fn name(&self) -> &str; fn respond(&mut self, payload: &[u8]) -> Result; } @@ -21,225 +39,4 @@ pub enum Response { Proceed(Vec), } -pub mod mechanisms { - mod plain { - use common::{ChannelBinding, Credentials, Identity, Secret}; - use server::{Mechanism, Response, Validator}; - - pub struct Plain { - validator: V, - } - - impl Plain { - pub fn new(validator: V) -> Plain { - Plain { - validator: validator, - } - } - } - - impl Mechanism for Plain { - fn name(&self) -> &str { - "PLAIN" - } - - fn respond(&mut self, payload: &[u8]) -> Result { - let mut sp = payload.split(|&b| b == 0); - sp.next(); - let username = sp - .next() - .ok_or_else(|| "no username specified".to_owned())?; - let username = - String::from_utf8(username.to_vec()).map_err(|_| "error decoding username")?; - let password = sp - .next() - .ok_or_else(|| "no password specified".to_owned())?; - let password = - String::from_utf8(password.to_vec()).map_err(|_| "error decoding password")?; - let creds = Credentials { - identity: Identity::Username(username), - secret: Secret::password_plain(password), - channel_binding: ChannelBinding::None, - }; - let ret = self.validator.validate_credentials(&creds)?; - Ok(Response::Success(ret, Vec::new())) - } - } - } - - #[cfg(feature = "scram")] - mod scram { - use std::marker::PhantomData; - - use base64; - - use common::scram::{generate_nonce, ScramProvider}; - use common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Secret}; - use server::{Mechanism, Response, Validator}; - - enum ScramState { - Init, - SentChallenge { - initial_client_message: Vec, - initial_server_message: Vec, - gs2_header: Vec, - server_nonce: String, - username: String, - salted_password: Vec, - }, - Done, - } - - pub struct Scram { - name: String, - state: ScramState, - channel_binding: ChannelBinding, - validator: V, - _marker: PhantomData, - } - - impl Scram { - pub fn new(validator: V, channel_binding: ChannelBinding) -> Scram { - Scram { - name: format!("SCRAM-{}", S::name()), - state: ScramState::Init, - channel_binding: channel_binding, - validator: validator, - _marker: PhantomData, - } - } - } - - impl Mechanism for Scram { - fn name(&self) -> &str { - &self.name - } - - fn respond(&mut self, payload: &[u8]) -> Result { - let next_state; - let ret; - match self.state { - ScramState::Init => { - // TODO: really ugly, mostly because parse_frame takes a &[u8] and i don't - // want to double validate utf-8 - // - // NEED TO CHANGE THIS THOUGH. IT'S AWFUL. - let mut commas = 0; - let mut idx = 0; - for &b in payload { - idx += 1; - if b == 0x2C { - commas += 1; - if commas >= 2 { - break; - } - } - } - if commas < 2 { - return Err("failed to decode message".to_owned()); - } - let gs2_header = payload[..idx].to_vec(); - let rest = payload[idx..].to_vec(); - // TODO: process gs2 header properly, not this ugly stuff - match self.channel_binding { - ChannelBinding::None | ChannelBinding::Unsupported => { - // Not supported. - if gs2_header[0] != 0x79 { - // ord("y") - return Err("channel binding not supported".to_owned()); - } - } - ref other => { - // Supported. - if gs2_header[0] == 0x79 { - // ord("y") - return Err("channel binding is supported".to_owned()); - } else if !other.supports("tls-unique") { - // TODO: grab the data - return Err("channel binding mechanism incorrect".to_owned()); - } - } - } - let frame = parse_frame(&rest) - .map_err(|_| "can't decode initial message".to_owned())?; - let username = frame.get("n").ok_or_else(|| "no username".to_owned())?; - let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; - let mut server_nonce = String::new(); - server_nonce += client_nonce; - server_nonce += - &generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; - let (salt, iterations, data) = self.validator.request_pbkdf2::()?; - let mut buf = Vec::new(); - buf.extend(b"r="); - buf.extend(server_nonce.bytes()); - buf.extend(b",s="); - buf.extend(base64::encode(&salt).bytes()); - buf.extend(b",i="); - buf.extend(iterations.to_string().bytes()); - ret = Response::Proceed(buf.clone()); - next_state = ScramState::SentChallenge { - server_nonce: server_nonce, - username: username.to_owned(), - salted_password: data, - initial_client_message: rest, - initial_server_message: buf, - gs2_header: gs2_header, - }; - } - ScramState::SentChallenge { - server_nonce: ref server_nonce, - username: ref username, - salted_password: ref salted_password, - gs2_header: ref gs2_header, - initial_client_message: ref initial_client_message, - initial_server_message: ref initial_server_message, - } => { - let frame = - parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; - let mut cb_data: Vec = Vec::new(); - cb_data.extend(gs2_header); - cb_data.extend(self.channel_binding.data()); - let mut client_final_message_bare = Vec::new(); - client_final_message_bare.extend(b"c="); - client_final_message_bare.extend(base64::encode(&cb_data).bytes()); - client_final_message_bare.extend(b",r="); - client_final_message_bare.extend(server_nonce.bytes()); - let client_key = S::hmac(b"Client Key", &salted_password); - let server_key = S::hmac(b"Server Key", &salted_password); - let stored_key = S::hash(&client_key); - let mut auth_message = Vec::new(); - auth_message.extend(initial_client_message); - auth_message.extend(b","); - auth_message.extend(initial_server_message); - auth_message.extend(b","); - auth_message.extend(client_final_message_bare.clone()); - let stored_key = S::hash(&client_key); - let client_signature = S::hmac(&auth_message, &stored_key); - let client_proof = xor(&client_key, &client_signature); - let sent_proof = frame.get("p").ok_or_else(|| "no proof".to_owned())?; - let sent_proof = base64::decode(sent_proof) - .map_err(|_| "can't decode proof".to_owned())?; - if client_proof != sent_proof { - return Err("authentication failed".to_owned()); - } - let server_signature = S::hmac(&auth_message, &server_key); - let mut buf = Vec::new(); - buf.extend(b"v="); - buf.extend(base64::encode(&server_signature).bytes()); - ret = Response::Success(Identity::Username(username.to_owned()), buf); - next_state = ScramState::Done; - } - ScramState::Done => { - return Err("sasl session is already over".to_owned()); - } - } - self.state = next_state; - Ok(ret) - } - } - } - - pub use self::plain::Plain; - #[cfg(feature = "scram")] - pub use self::scram::Scram; -} +pub mod mechanisms; From 35fc26f378ccabc64d83a9253ea4a7dc738e73e7 Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 25 Mar 2017 23:25:28 +0100 Subject: [PATCH 11/53] lower rust versions really don't like this --- sasl/src/server/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index 6f9c4fa9e6ef9264f06b39cfddfeb996af08b0cc..f46c91d1f070dae5a9fdfc23f5a24ead828e250a 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -3,8 +3,8 @@ use secret::SecretKind; #[macro_export] macro_rules! impl_validator_using_provider { - ( $type:ty, $secret:ty ) => { - impl $crate::server::Validator<$secret> for $type { + ( $validator:ty, $secret:ty ) => { + impl $crate::server::Validator<$secret> for $validator { fn validate( &self, identity: &$crate::common::Identity, From 97f597d89db1afef1cba155cf98153ef89152611 Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 25 Mar 2017 23:45:30 +0100 Subject: [PATCH 12/53] more API simplifications --- sasl/src/common/scram.rs | 6 ++--- sasl/src/lib.rs | 12 ++++----- sasl/src/secret.rs | 42 ++++++++--------------------- sasl/src/server/mechanisms/plain.rs | 3 +-- sasl/src/server/mechanisms/scram.rs | 14 +++++----- sasl/src/server/mod.rs | 12 ++++----- 6 files changed, 34 insertions(+), 55 deletions(-) diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 5862c31e2f776410e2a11a6779bd31f00e4e9a09..e6126b5801150a53f2e3b5a310f469879392767a 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -22,7 +22,7 @@ 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; + type Secret: secret::Secret; /// The name of the hash function. fn name() -> &'static str; @@ -42,7 +42,7 @@ pub struct Sha1; impl ScramProvider for Sha1 { // TODO: look at all these unwraps - type SecretKind = secret::Pbkdf2Sha1; + type Secret = secret::Pbkdf2Sha1; fn name() -> &'static str { "SHA-1" @@ -105,7 +105,7 @@ pub struct Sha256; impl ScramProvider for Sha256 { // TODO: look at all these unwraps - type SecretKind = secret::Pbkdf2Sha256; + type Secret = secret::Pbkdf2Sha256; fn name() -> &'static str { "SHA-256" diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index c454eef10273361c9ee1553369a476a8b56216b5..de542caf00d40aba0b5c8b12002af7b2e02c4c56 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -43,8 +43,8 @@ //! struct MyValidator; //! //! impl Validator for MyValidator { -//! fn validate(&self, identity: &Identity, value: &secret::PlainValue) -> Result<(), String> { -//! let &secret::PlainValue(ref password) = value; +//! fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), String> { +//! let &secret::Plain(ref password) = value; //! if identity != &Identity::Username(USERNAME.to_owned()) { //! Err("authentication failed".to_owned()) //! } @@ -58,7 +58,7 @@ //! } //! //! impl Provider for MyValidator { -//! fn provide(&self, identity: &Identity) -> Result { +//! fn provide(&self, identity: &Identity) -> Result { //! if identity != &Identity::Username(USERNAME.to_owned()) { //! Err("authentication failed".to_owned()) //! } @@ -67,7 +67,7 @@ //! ( &Password::Plain((PASSWORD.to_owned())) //! , &SALT[..] //! , ITERATIONS )?; -//! Ok(secret::Pbkdf2Sha1Value { +//! Ok(secret::Pbkdf2Sha1 { //! salt: SALT.to_vec(), //! iterations: ITERATIONS, //! digest: digest, @@ -79,7 +79,7 @@ //! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1); //! //! impl Provider for MyValidator { -//! fn provide(&self, identity: &Identity) -> Result { +//! fn provide(&self, identity: &Identity) -> Result { //! if identity != &Identity::Username(USERNAME.to_owned()) { //! Err("authentication failed".to_owned()) //! } @@ -88,7 +88,7 @@ //! ( &Password::Plain((PASSWORD.to_owned())) //! , &SALT[..] //! , ITERATIONS )?; -//! Ok(secret::Pbkdf2Sha256Value { +//! Ok(secret::Pbkdf2Sha256 { //! salt: SALT.to_vec(), //! iterations: ITERATIONS, //! digest: digest, diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs index 171e2f92ca00c903b616170111b81705520d4129..61654edbfd47202ce0add0dcf7f75f47f7a2dc0f 100644 --- a/sasl/src/secret.rs +++ b/sasl/src/secret.rs @@ -1,39 +1,26 @@ -pub trait SecretKind { - type Value: PartialEq; -} +pub trait Secret {} -pub trait Pbkdf2SecretValue { +pub trait Pbkdf2Secret { fn salt(&self) -> &[u8]; fn iterations(&self) -> usize; fn digest(&self) -> &[u8]; } -pub struct Plain; - -#[derive(PartialEq)] -pub struct PlainValue(pub String); +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Plain(pub String); -impl SecretKind for Plain { - type Value = PlainValue; -} +impl Secret for Plain {} +#[derive(Clone, Debug, PartialEq, Eq)] 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 Secret for Pbkdf2Sha1 {} -impl Pbkdf2SecretValue for Pbkdf2Sha1Value { +impl Pbkdf2Secret for Pbkdf2Sha1 { fn salt(&self) -> &[u8] { &self.salt } @@ -45,23 +32,16 @@ impl Pbkdf2SecretValue for Pbkdf2Sha1Value { } } +#[derive(Clone, Debug, PartialEq, Eq)] 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 Secret for Pbkdf2Sha256 {} -impl Pbkdf2SecretValue for Pbkdf2Sha256Value { +impl Pbkdf2Secret for Pbkdf2Sha256 { fn salt(&self) -> &[u8] { &self.salt } diff --git a/sasl/src/server/mechanisms/plain.rs b/sasl/src/server/mechanisms/plain.rs index f43f68f5ab50d9b58995eb1c8f9c1ddef585cc49..1deebbe5a7408667cfca74bd7de0ccedd89e5227 100644 --- a/sasl/src/server/mechanisms/plain.rs +++ b/sasl/src/server/mechanisms/plain.rs @@ -33,8 +33,7 @@ impl> Mechanism for Plain { 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))?; + self.validator.validate(&ident, &secret::Plain(password))?; Ok(Response::Success(ident, Vec::new())) } } diff --git a/sasl/src/server/mechanisms/scram.rs b/sasl/src/server/mechanisms/scram.rs index d888ebc822f82215432a11d211375ac76a1aa594..9ff46f1042c8dd46fe8b6918ef7e4a6aab498935 100644 --- a/sasl/src/server/mechanisms/scram.rs +++ b/sasl/src/server/mechanisms/scram.rs @@ -5,7 +5,7 @@ use base64; use common::scram::{generate_nonce, ScramProvider}; use common::{parse_frame, xor, ChannelBinding, Identity}; use secret; -use secret::Pbkdf2SecretValue; +use secret::Pbkdf2Secret; use server::{Mechanism, Provider, Response}; enum ScramState { @@ -24,8 +24,8 @@ enum ScramState { pub struct Scram where S: ScramProvider, - P: Provider, - ::Value: secret::Pbkdf2SecretValue, + P: Provider, + S::Secret: secret::Pbkdf2Secret, { name: String, state: ScramState, @@ -37,8 +37,8 @@ where impl Scram where S: ScramProvider, - P: Provider, - ::Value: secret::Pbkdf2SecretValue, + P: Provider, + S::Secret: secret::Pbkdf2Secret, { pub fn new(provider: P, channel_binding: ChannelBinding) -> Scram { Scram { @@ -54,8 +54,8 @@ where impl Mechanism for Scram where S: ScramProvider, - P: Provider, - ::Value: secret::Pbkdf2SecretValue, + P: Provider, + S::Secret: secret::Pbkdf2Secret, { fn name(&self) -> &str { &self.name diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index f46c91d1f070dae5a9fdfc23f5a24ead828e250a..87b4b07be1725886bb3384c74b892da91a13d585 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,5 +1,5 @@ use common::Identity; -use secret::SecretKind; +use secret::Secret; #[macro_export] macro_rules! impl_validator_using_provider { @@ -8,7 +8,7 @@ macro_rules! impl_validator_using_provider { fn validate( &self, identity: &$crate::common::Identity, - value: &<$secret as sasl::secret::SecretKind>::Value, + value: &$secret, ) -> Result<(), String> { if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value { Ok(()) @@ -20,12 +20,12 @@ macro_rules! impl_validator_using_provider { }; } -pub trait Provider: Validator { - fn provide(&self, identity: &Identity) -> Result; +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 Validator { + fn validate(&self, identity: &Identity, value: &S) -> Result<(), String>; } pub trait Mechanism { From f999429df86d48a962a8b29652798dc770dec588 Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 25 Mar 2017 23:50:58 +0100 Subject: [PATCH 13/53] derp --- sasl/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index de542caf00d40aba0b5c8b12002af7b2e02c4c56..9e1379715c77e1cf9df3e659faca72815c43f3ba 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -98,7 +98,6 @@ //! } //! //! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha256); - //! //! fn finish(cm: &mut CM, sm: &mut SM) -> Result //! where CM: ClientMechanism, From ac21b1544e8aca142e34f6a6796da09d4004d48b Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 28 Mar 2017 15:48:49 +0200 Subject: [PATCH 14/53] redundant line --- sasl/src/server/mechanisms/scram.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sasl/src/server/mechanisms/scram.rs b/sasl/src/server/mechanisms/scram.rs index 9ff46f1042c8dd46fe8b6918ef7e4a6aab498935..5f6feaec6d57b3cdc4711aa9dd59cf3ca9ed808e 100644 --- a/sasl/src/server/mechanisms/scram.rs +++ b/sasl/src/server/mechanisms/scram.rs @@ -152,7 +152,6 @@ where 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","); From d48d07a9f791100e2cf048796fde53e8f9612715 Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 28 Mar 2017 15:50:43 +0200 Subject: [PATCH 15/53] bump version, though this library should still be considered unstable, API-wise --- sasl/Cargo.toml | 2 +- sasl/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index e4b6c4866cbe511e6492a9dc020ca27c9c558996..8c0ebe2064d9662b7326b012576d832d229120ad 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.3.0" +version = "0.4.0" authors = ["lumi "] description = "A crate for SASL authentication. Currently only does the client side." homepage = "https://gitlab.com/lumi/sasl-rs" diff --git a/sasl/README.md b/sasl/README.md index 0b66d0fff538bf38e91574ec3b7eac1c93e14652..4f8eeaec6dd41346616511374f0b969aa749112f 100644 --- a/sasl/README.md +++ b/sasl/README.md @@ -4,7 +4,7 @@ sasl-rs What's this? ------------ -A crate which handles SASL authentication. +A crate which handles SASL authentication. Still unstable until 1.0.0. Can I see an example? --------------------- From 09a71b6e0ae9e0e308780f6541ee0f52ef69aa15 Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 28 Mar 2017 15:55:07 +0200 Subject: [PATCH 16/53] how did this get there? --- sasl/src/mechanisms/mod.rs | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 sasl/src/mechanisms/mod.rs diff --git a/sasl/src/mechanisms/mod.rs b/sasl/src/mechanisms/mod.rs deleted file mode 100644 index 408740a5cc4c2e3ec44a88529ec937b135f87a0c..0000000000000000000000000000000000000000 --- a/sasl/src/mechanisms/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod plain; -mod scram; - -pub use self::plain::Plain; -pub use self::scram::Scram; From b0e72d586b14822838731da13b403abcb068dabd Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 4 Apr 2017 18:04:23 +0200 Subject: [PATCH 17/53] add derive methods to Pbkdf2 secrets --- sasl/src/secret.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs index 61654edbfd47202ce0add0dcf7f75f47f7a2dc0f..54c94c8494b14e84ce30d93bb4efa230a4468232 100644 --- a/sasl/src/secret.rs +++ b/sasl/src/secret.rs @@ -18,6 +18,20 @@ pub struct Pbkdf2Sha1 { pub digest: Vec, } +impl Pbkdf2Sha1 { + #[cfg(feature = "openssl")] + pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { + use common::scram::{ScramProvider, Sha1}; + use common::Password; + let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?; + Ok(Pbkdf2Sha1 { + salt: salt.to_vec(), + iterations: iterations, + digest: digest, + }) + } +} + impl Secret for Pbkdf2Sha1 {} impl Pbkdf2Secret for Pbkdf2Sha1 { @@ -39,6 +53,20 @@ pub struct Pbkdf2Sha256 { pub digest: Vec, } +impl Pbkdf2Sha256 { + #[cfg(feature = "openssl")] + pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { + use common::scram::{ScramProvider, Sha256}; + use common::Password; + let digest = Sha256::derive(&Password::Plain(password.to_owned()), salt, iterations)?; + Ok(Pbkdf2Sha256 { + salt: salt.to_vec(), + iterations: iterations, + digest: digest, + }) + } +} + impl Secret for Pbkdf2Sha256 {} impl Pbkdf2Secret for Pbkdf2Sha256 { From a8fe78704b023e28a17f98b995e2ce880e4578d8 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 6 May 2017 13:21:58 +0100 Subject: [PATCH 18/53] remove println!() introduced in 7b52210d --- sasl/src/client/mechanisms/scram.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sasl/src/client/mechanisms/scram.rs b/sasl/src/client/mechanisms/scram.rs index 5f43a91342a0f41db2fd042d82f72dbcd72cc2d3..b19ed48c8b2f58cdec96626e4ce408177c7a0607 100644 --- a/sasl/src/client/mechanisms/scram.rs +++ b/sasl/src/client/mechanisms/scram.rs @@ -145,7 +145,6 @@ impl Mechanism for Scram { 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); From 3a7a0f225f45a622c718f8909c406dfbba5c42ae Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 6 May 2017 13:29:32 +0100 Subject: [PATCH 19/53] update base64 dependency --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 8c0ebe2064d9662b7326b012576d832d229120ad..0593f3448a4d51e78c445be58a191cddfe715f6e 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -18,7 +18,7 @@ default = ["scram"] scram = ["openssl"] [dependencies] -base64 = "0.4.0" +base64 = "0.5.0" [dependencies.openssl] version = "0.9.7" From 6d9202eba85a00aba2f78349d21f85cc53526c4c Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 6 May 2017 13:29:42 +0100 Subject: [PATCH 20/53] make a new patch release --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 0593f3448a4d51e78c445be58a191cddfe715f6e..6c704f50c1ef5efdf17cdcb8efe7e256ab80eab1 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.4.0" +version = "0.4.1" authors = ["lumi "] description = "A crate for SASL authentication. Currently only does the client side." homepage = "https://gitlab.com/lumi/sasl-rs" From 4c212d27688a4fd45a2ae0d5d444f3384e664f91 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 12 Jun 2017 22:27:29 +0100 Subject: [PATCH 21/53] update base64 and openssl dependencies --- sasl/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 6c704f50c1ef5efdf17cdcb8efe7e256ab80eab1..3416954013db15900bc7b29685b659ade54d7b72 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -18,8 +18,8 @@ default = ["scram"] scram = ["openssl"] [dependencies] -base64 = "0.5.0" +base64 = "0.6.0" [dependencies.openssl] -version = "0.9.7" +version = "0.9.13" optional = true From a7f460f900c3ad1f12284a7b9085eb7832892c23 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 27 Feb 2018 11:44:41 +0100 Subject: [PATCH 22/53] update base64 and openssl deps Signed-off-by: Marc-Antoine Perennou --- sasl/Cargo.toml | 4 ++-- sasl/src/common/scram.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 3416954013db15900bc7b29685b659ade54d7b72..62c6ec99b91555927b5b77fc0ab4327b25d3cbea 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -18,8 +18,8 @@ default = ["scram"] scram = ["openssl"] [dependencies] -base64 = "0.6.0" +base64 = "0.9.0" [dependencies.openssl] -version = "0.9.13" +version = "0.10.4" optional = true diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index e6126b5801150a53f2e3b5a310f469879392767a..6833a8994fbf52c8ed7ce487be04384f7ccf66d2 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -49,14 +49,14 @@ impl ScramProvider for Sha1 { } fn hash(data: &[u8]) -> Vec { - hash(MessageDigest::sha1(), data).unwrap() + hash(MessageDigest::sha1(), data).unwrap().to_vec() } fn hmac(data: &[u8], key: &[u8]) -> Vec { let pkey = PKey::hmac(key).unwrap(); let mut signer = Signer::new(MessageDigest::sha1(), &pkey).unwrap(); signer.update(data).unwrap(); - signer.finish().unwrap() + signer.sign_to_vec().unwrap() } fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { @@ -112,14 +112,14 @@ impl ScramProvider for Sha256 { } fn hash(data: &[u8]) -> Vec { - hash(MessageDigest::sha256(), data).unwrap() + hash(MessageDigest::sha256(), data).unwrap().to_vec() } fn hmac(data: &[u8], key: &[u8]) -> Vec { let pkey = PKey::hmac(key).unwrap(); let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); signer.update(data).unwrap(); - signer.finish().unwrap() + signer.sign_to_vec().unwrap() } fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { From a626e96dfe14b0c23c0553eae57e1d292cd5b88e Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 19 May 2018 12:42:51 +0200 Subject: [PATCH 23/53] Update dependencies further. --- sasl/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 62c6ec99b91555927b5b77fc0ab4327b25d3cbea..5b28a3c4327fbcdb0d7c4d6e667283ad94daad2e 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -18,8 +18,8 @@ default = ["scram"] scram = ["openssl"] [dependencies] -base64 = "0.9.0" +base64 = "0.9.1" [dependencies.openssl] -version = "0.10.4" +version = "0.10.7" optional = true From 28dca0c3690c27f2a44e41242dd5983c30acb0c1 Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 19 May 2018 12:49:19 +0200 Subject: [PATCH 24/53] Bump version to 0.4.2. --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 5b28a3c4327fbcdb0d7c4d6e667283ad94daad2e..46e5717b7ac5fa26fe43a5ca317a08a2c45b7ccd 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.4.1" +version = "0.4.2" authors = ["lumi "] description = "A crate for SASL authentication. Currently only does the client side." homepage = "https://gitlab.com/lumi/sasl-rs" From 0842e044d2c6505f167f762c79f350bc8b5ca90b Mon Sep 17 00:00:00 2001 From: lumi Date: Sat, 19 May 2018 13:12:24 +0200 Subject: [PATCH 25/53] Add a change log. Vitally important to the success of the mission! --- sasl/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 sasl/CHANGELOG.md diff --git a/sasl/CHANGELOG.md b/sasl/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..7df0b195672c4f76e52ebf82357f791526079524 --- /dev/null +++ b/sasl/CHANGELOG.md @@ -0,0 +1,4 @@ +Version 0.4.2, released 2018-05-19: + * Small changes + - Marc-Antoine Perennou updated the openssl and base64 dependencies to 0.10.4 and 0.9.0 respectively. + - lumi updated them further to 0.10.7 and 0.9.1 respectively. From b1708823de33d0a33e07f61c82ef9e0d18812774 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 22:44:22 +0100 Subject: [PATCH 26/53] Update base64. --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 46e5717b7ac5fa26fe43a5ca317a08a2c45b7ccd..af0f340e388ea8b3f852218997dda2244358ec53 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -18,7 +18,7 @@ default = ["scram"] scram = ["openssl"] [dependencies] -base64 = "0.9.1" +base64 = "0.10" [dependencies.openssl] version = "0.10.7" From 9e9f09a9a531bb47a559fe2a38e55544a0861a74 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 22:54:32 +0100 Subject: [PATCH 27/53] Update to Edition 2018. --- sasl/Cargo.toml | 1 + sasl/src/client/mechanisms/anonymous.rs | 4 ++-- sasl/src/client/mechanisms/plain.rs | 4 ++-- sasl/src/client/mechanisms/scram.rs | 14 +++++++------- sasl/src/client/mod.rs | 2 +- sasl/src/common/scram.rs | 4 ++-- sasl/src/lib.rs | 7 +------ sasl/src/secret.rs | 8 ++++---- sasl/src/server/mechanisms/plain.rs | 6 +++--- sasl/src/server/mechanisms/scram.rs | 10 +++++----- sasl/src/server/mod.rs | 4 ++-- 11 files changed, 30 insertions(+), 34 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index af0f340e388ea8b3f852218997dda2244358ec53..225e9f7dc28eb4dbb0b18b18caf7f256db78bfbc 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -9,6 +9,7 @@ documentation = "https://docs.rs/sasl" readme = "README.md" keywords = ["sasl", "authentication"] license = "LGPL-3.0+" +edition = "2018" [badges] gitlab = { repository = "lumi/sasl-rs" } diff --git a/sasl/src/client/mechanisms/anonymous.rs b/sasl/src/client/mechanisms/anonymous.rs index d95245a8b05bb09dbbdd78c72573f32e55892ffd..45361377669803f30b95a1cd58662cc36a7f1c66 100644 --- a/sasl/src/client/mechanisms/anonymous.rs +++ b/sasl/src/client/mechanisms/anonymous.rs @@ -1,7 +1,7 @@ //! Provides the SASL "ANONYMOUS" mechanism. -use client::Mechanism; -use common::{Credentials, Secret}; +use crate::client::Mechanism; +use crate::common::{Credentials, Secret}; /// A struct for the SASL ANONYMOUS mechanism. pub struct Anonymous; diff --git a/sasl/src/client/mechanisms/plain.rs b/sasl/src/client/mechanisms/plain.rs index 786978da1f3eaee817ece5a624d818f0f17a9672..1c5bd1a310db0acf62a5bd0b43da5d28462a0f22 100644 --- a/sasl/src/client/mechanisms/plain.rs +++ b/sasl/src/client/mechanisms/plain.rs @@ -1,7 +1,7 @@ //! Provides the SASL "PLAIN" mechanism. -use client::Mechanism; -use common::{Credentials, Identity, Password, Secret}; +use crate::client::Mechanism; +use crate::common::{Credentials, Identity, Password, Secret}; /// A struct for the SASL PLAIN mechanism. pub struct Plain { diff --git a/sasl/src/client/mechanisms/scram.rs b/sasl/src/client/mechanisms/scram.rs index b19ed48c8b2f58cdec96626e4ce408177c7a0607..abc5472ae8c7c5833322bb591c73b88156a3206d 100644 --- a/sasl/src/client/mechanisms/scram.rs +++ b/sasl/src/client/mechanisms/scram.rs @@ -2,11 +2,11 @@ use base64; -use client::Mechanism; -use common::scram::{generate_nonce, ScramProvider}; -use common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret}; +use crate::client::Mechanism; +use crate::common::scram::{generate_nonce, ScramProvider}; +use crate::common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret}; -use error::Error; +use crate::error::Error; use std::marker::PhantomData; @@ -189,9 +189,9 @@ impl Mechanism for Scram { #[cfg(test)] mod tests { - use client::mechanisms::Scram; - use client::Mechanism; - use common::scram::{Sha1, Sha256}; + use crate::client::mechanisms::Scram; + use crate::client::Mechanism; + use crate::common::scram::{Sha1, Sha256}; #[test] fn scram_sha1_works() { diff --git a/sasl/src/client/mod.rs b/sasl/src/client/mod.rs index d8655d543cea5816c55d63317dca9ffbdfb94efa..2acf9cff2d7b941f8a6100205301c99d5d08c369 100644 --- a/sasl/src/client/mod.rs +++ b/sasl/src/client/mod.rs @@ -1,4 +1,4 @@ -use common::Credentials; +use crate::common::Credentials; /// A trait which defines SASL mechanisms. pub trait Mechanism { diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 6833a8994fbf52c8ed7ce487be04384f7ccf66d2..2ccba1fcdd93787234257a1911fe91b6ee2f5677 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -6,9 +6,9 @@ use openssl::pkey::PKey; use openssl::rand::rand_bytes; use openssl::sign::Signer; -use common::Password; +use crate::common::Password; -use secret; +use crate::secret; use base64; diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 9e1379715c77e1cf9df3e659faca72815c43f3ba..2a64e7621d873abdc29ca6130e070e0b96aab961 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -163,11 +163,6 @@ //! sasl = "*" //! ``` -extern crate base64; - -#[cfg(feature = "scram")] -extern crate openssl; - mod error; pub mod client; @@ -176,4 +171,4 @@ pub mod server; pub mod common; pub mod secret; -pub use error::Error; +pub use crate::error::Error; diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs index 54c94c8494b14e84ce30d93bb4efa230a4468232..690bf36d5db639903e459874e35422f88cdc6721 100644 --- a/sasl/src/secret.rs +++ b/sasl/src/secret.rs @@ -21,8 +21,8 @@ pub struct Pbkdf2Sha1 { impl Pbkdf2Sha1 { #[cfg(feature = "openssl")] pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { - use common::scram::{ScramProvider, Sha1}; - use common::Password; + use crate::common::scram::{ScramProvider, Sha1}; + use crate::common::Password; let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?; Ok(Pbkdf2Sha1 { salt: salt.to_vec(), @@ -56,8 +56,8 @@ pub struct Pbkdf2Sha256 { impl Pbkdf2Sha256 { #[cfg(feature = "openssl")] pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { - use common::scram::{ScramProvider, Sha256}; - use common::Password; + use crate::common::scram::{ScramProvider, Sha256}; + use crate::common::Password; let digest = Sha256::derive(&Password::Plain(password.to_owned()), salt, iterations)?; Ok(Pbkdf2Sha256 { salt: salt.to_vec(), diff --git a/sasl/src/server/mechanisms/plain.rs b/sasl/src/server/mechanisms/plain.rs index 1deebbe5a7408667cfca74bd7de0ccedd89e5227..8df0e76fdab603de5091ec4220befff49ead4324 100644 --- a/sasl/src/server/mechanisms/plain.rs +++ b/sasl/src/server/mechanisms/plain.rs @@ -1,6 +1,6 @@ -use common::Identity; -use secret; -use server::{Mechanism, Response, Validator}; +use crate::common::Identity; +use crate::secret; +use crate::server::{Mechanism, Response, Validator}; pub struct Plain> { validator: V, diff --git a/sasl/src/server/mechanisms/scram.rs b/sasl/src/server/mechanisms/scram.rs index 5f6feaec6d57b3cdc4711aa9dd59cf3ca9ed808e..ad027686c0274442682bd5b90a4e763bd1af0305 100644 --- a/sasl/src/server/mechanisms/scram.rs +++ b/sasl/src/server/mechanisms/scram.rs @@ -2,11 +2,11 @@ use std::marker::PhantomData; use base64; -use common::scram::{generate_nonce, ScramProvider}; -use common::{parse_frame, xor, ChannelBinding, Identity}; -use secret; -use secret::Pbkdf2Secret; -use server::{Mechanism, Provider, Response}; +use crate::common::scram::{generate_nonce, ScramProvider}; +use crate::common::{parse_frame, xor, ChannelBinding, Identity}; +use crate::secret; +use crate::secret::Pbkdf2Secret; +use crate::server::{Mechanism, Provider, Response}; enum ScramState { Init, diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index 87b4b07be1725886bb3384c74b892da91a13d585..020e88b208d0363b80562bd4436d8367f4e5a984 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,5 +1,5 @@ -use common::Identity; -use secret::Secret; +use crate::common::Identity; +use crate::secret::Secret; #[macro_export] macro_rules! impl_validator_using_provider { From 13d63402983eb18af54174f616ec8d8f5416a150 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 23:32:39 +0100 Subject: [PATCH 28/53] Switch to RustCrypto for hashes. --- sasl/Cargo.toml | 2 ++ sasl/src/common/scram.rs | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 225e9f7dc28eb4dbb0b18b18caf7f256db78bfbc..8fc9bfc5fca4be740187607c8b5e33cefd26fd0a 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -20,6 +20,8 @@ scram = ["openssl"] [dependencies] base64 = "0.10" +sha-1 = "0.8" +sha2 = "0.8" [dependencies.openssl] version = "0.10.7" diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 2ccba1fcdd93787234257a1911fe91b6ee2f5677..ab5dd94c0563804e43a908e8b9cde7437b229cdb 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,10 +1,11 @@ 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 sha1::{Digest, Sha1 as Sha1_hash}; +use sha2::Sha256 as Sha256_hash; use crate::common::Password; @@ -49,7 +50,10 @@ impl ScramProvider for Sha1 { } fn hash(data: &[u8]) -> Vec { - hash(MessageDigest::sha1(), data).unwrap().to_vec() + let hash = Sha1_hash::digest(data); + let mut vec = Vec::with_capacity(Sha1_hash::output_size()); + vec.extend_from_slice(hash.as_slice()); + vec } fn hmac(data: &[u8], key: &[u8]) -> Vec { @@ -112,7 +116,10 @@ impl ScramProvider for Sha256 { } fn hash(data: &[u8]) -> Vec { - hash(MessageDigest::sha256(), data).unwrap().to_vec() + let hash = Sha256_hash::digest(data); + let mut vec = Vec::with_capacity(Sha256_hash::output_size()); + vec.extend_from_slice(hash.as_slice()); + vec } fn hmac(data: &[u8], key: &[u8]) -> Vec { From 392b1c66b1ffce9fa9f744de08bd61824f5a8543 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 23:40:46 +0100 Subject: [PATCH 29/53] Switch to RustCrypto for Hmac. --- sasl/Cargo.toml | 1 + sasl/src/common/scram.rs | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 8fc9bfc5fca4be740187607c8b5e33cefd26fd0a..663b1031af986a27c2235f5adf59f06c46b98fe5 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -22,6 +22,7 @@ scram = ["openssl"] base64 = "0.10" sha-1 = "0.8" sha2 = "0.8" +hmac = "0.7" [dependencies.openssl] version = "0.10.7" diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index ab5dd94c0563804e43a908e8b9cde7437b229cdb..6b88e0249f308991b2fe2dfbae347ea1333686d2 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,9 +1,8 @@ +use hmac::{Hmac, Mac}; use openssl::error::ErrorStack; use openssl::hash::MessageDigest; use openssl::pkcs5::pbkdf2_hmac; -use openssl::pkey::PKey; use openssl::rand::rand_bytes; -use openssl::sign::Signer; use sha1::{Digest, Sha1 as Sha1_hash}; use sha2::Sha256 as Sha256_hash; @@ -57,10 +56,13 @@ impl ScramProvider for Sha1 { } fn hmac(data: &[u8], key: &[u8]) -> Vec { - let pkey = PKey::hmac(key).unwrap(); - let mut signer = Signer::new(MessageDigest::sha1(), &pkey).unwrap(); - signer.update(data).unwrap(); - signer.sign_to_vec().unwrap() + type HmacSha1 = Hmac; + let mut mac = HmacSha1::new_varkey(key).unwrap(); + mac.input(data); + let result = mac.result(); + let mut vec = Vec::with_capacity(Sha1_hash::output_size()); + vec.extend_from_slice(result.code().as_slice()); + vec } fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { @@ -123,10 +125,13 @@ impl ScramProvider for Sha256 { } fn hmac(data: &[u8], key: &[u8]) -> Vec { - let pkey = PKey::hmac(key).unwrap(); - let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); - signer.update(data).unwrap(); - signer.sign_to_vec().unwrap() + type HmacSha256 = Hmac; + let mut mac = HmacSha256::new_varkey(key).unwrap(); + mac.input(data); + let result = mac.result(); + let mut vec = Vec::with_capacity(Sha256_hash::output_size()); + vec.extend_from_slice(result.code().as_slice()); + vec } fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { From 506d0b17fc2b157d91efe75e914c28d0712e861b Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 23:53:29 +0100 Subject: [PATCH 30/53] Switch to rand_os for random bytes. --- sasl/Cargo.toml | 1 + sasl/src/common/scram.rs | 13 ++++++++----- sasl/src/error.rs | 12 ++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 663b1031af986a27c2235f5adf59f06c46b98fe5..915271167c79b753e869c8221afd0a3eabc9f009 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -20,6 +20,7 @@ scram = ["openssl"] [dependencies] base64 = "0.10" +rand_os = "0.1" sha-1 = "0.8" sha2 = "0.8" hmac = "0.7" diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 6b88e0249f308991b2fe2dfbae347ea1333686d2..681524b2d1a2271773ed53203aeb8eb0f9402435 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,8 +1,10 @@ use hmac::{Hmac, Mac}; -use openssl::error::ErrorStack; use openssl::hash::MessageDigest; use openssl::pkcs5::pbkdf2_hmac; -use openssl::rand::rand_bytes; +use rand_os::{ + rand_core::{Error as RngError, RngCore}, + OsRng, +}; use sha1::{Digest, Sha1 as Sha1_hash}; use sha2::Sha256 as Sha256_hash; @@ -13,9 +15,10 @@ use crate::secret; use base64; /// Generate a nonce for SCRAM authentication. -pub fn generate_nonce() -> Result { - let mut data = vec![0; 32]; - rand_bytes(&mut data)?; +pub fn generate_nonce() -> Result { + let mut data = [0u8; 32]; + let mut rng = OsRng::new()?; + rng.fill_bytes(&mut data); Ok(base64::encode(&data)) } diff --git a/sasl/src/error.rs b/sasl/src/error.rs index 6d79df25b8e7bf130a3b6fd6a9219e142558025c..b5287073753dccf6de443bc0835961aaa24ab9f6 100644 --- a/sasl/src/error.rs +++ b/sasl/src/error.rs @@ -1,19 +1,19 @@ #[cfg(feature = "scram")] -use openssl::error::ErrorStack; +use rand_os::rand_core::Error as RngError; /// A wrapper enum for things that could go wrong in this crate. #[derive(Debug)] pub enum Error { #[cfg(feature = "scram")] - /// An error in OpenSSL. - OpenSslErrorStack(ErrorStack), + /// An error while initializing the Rng. + RngError(RngError), /// An error in a SASL mechanism. SaslError(String), } #[cfg(feature = "scram")] -impl From for Error { - fn from(err: ErrorStack) -> Error { - Error::OpenSslErrorStack(err) +impl From for Error { + fn from(err: RngError) -> Error { + Error::RngError(err) } } From 5892caa4a847c37b8f30f24b492f9e36c6ee1b26 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 23:59:31 +0100 Subject: [PATCH 31/53] Switch to RustCrypto for pbkdf2. --- sasl/Cargo.toml | 1 + sasl/src/common/scram.rs | 21 +++------------------ 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 915271167c79b753e869c8221afd0a3eabc9f009..10c12e9935bd98c3721a05db84843f791209865d 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -24,6 +24,7 @@ rand_os = "0.1" sha-1 = "0.8" sha2 = "0.8" hmac = "0.7" +pbkdf2 = { version = "0.3", default-features = false } [dependencies.openssl] version = "0.10.7" diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 681524b2d1a2271773ed53203aeb8eb0f9402435..405afafb752ae4d64c1ba525b9809fe66dba0b19 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,6 +1,5 @@ use hmac::{Hmac, Mac}; -use openssl::hash::MessageDigest; -use openssl::pkcs5::pbkdf2_hmac; +use pbkdf2::pbkdf2; use rand_os::{ rand_core::{Error as RngError, RngCore}, OsRng, @@ -72,14 +71,7 @@ impl ScramProvider for Sha1 { match *password { Password::Plain(ref plain) => { let mut result = vec![0; 20]; - pbkdf2_hmac( - plain.as_bytes(), - salt, - iterations, - MessageDigest::sha1(), - &mut result, - ) - .unwrap(); + pbkdf2::>(plain.as_bytes(), salt, iterations, &mut result); Ok(result) } Password::Pbkdf2 { @@ -141,14 +133,7 @@ impl ScramProvider for Sha256 { match *password { Password::Plain(ref plain) => { let mut result = vec![0; 32]; - pbkdf2_hmac( - plain.as_bytes(), - salt, - iterations, - MessageDigest::sha256(), - &mut result, - ) - .unwrap(); + pbkdf2::>(plain.as_bytes(), salt, iterations, &mut result); Ok(result) } Password::Pbkdf2 { From 5337a0a14983e547cb4005f68834ebf4ec4c13f8 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 18 Jan 2019 00:04:14 +0100 Subject: [PATCH 32/53] Remove the openssl dependency, fixes #4. --- sasl/Cargo.toml | 6 +----- sasl/src/secret.rs | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 10c12e9935bd98c3721a05db84843f791209865d..284b8f9633784875a75293f789cadf877a0fde23 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -16,7 +16,7 @@ gitlab = { repository = "lumi/sasl-rs" } [features] default = ["scram"] -scram = ["openssl"] +scram = [] [dependencies] base64 = "0.10" @@ -25,7 +25,3 @@ sha-1 = "0.8" sha2 = "0.8" hmac = "0.7" pbkdf2 = { version = "0.3", default-features = false } - -[dependencies.openssl] -version = "0.10.7" -optional = true diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs index 690bf36d5db639903e459874e35422f88cdc6721..31f42dd9e00123d325baee445361dc3f836d522e 100644 --- a/sasl/src/secret.rs +++ b/sasl/src/secret.rs @@ -19,7 +19,7 @@ pub struct Pbkdf2Sha1 { } impl Pbkdf2Sha1 { - #[cfg(feature = "openssl")] + #[cfg(feature = "scram")] pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { use crate::common::scram::{ScramProvider, Sha1}; use crate::common::Password; @@ -54,7 +54,7 @@ pub struct Pbkdf2Sha256 { } impl Pbkdf2Sha256 { - #[cfg(feature = "openssl")] + #[cfg(feature = "scram")] pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { use crate::common::scram::{ScramProvider, Sha256}; use crate::common::Password; From 0c426b4d17e654e061eddd777875544e0c881748 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 18 Jan 2019 00:26:41 +0100 Subject: [PATCH 33/53] Remove .unwrap() in SCRAM code. --- sasl/src/client/mechanisms/scram.rs | 8 ++++---- sasl/src/common/scram.rs | 22 +++++++++++++--------- sasl/src/server/mechanisms/scram.rs | 8 ++++---- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/sasl/src/client/mechanisms/scram.rs b/sasl/src/client/mechanisms/scram.rs index abc5472ae8c7c5833322bb591c73b88156a3206d..a3bd35c97e8d8db6e649291e90abe14389b005c0 100644 --- a/sasl/src/client/mechanisms/scram.rs +++ b/sasl/src/client/mechanisms/scram.rs @@ -137,8 +137,8 @@ impl Mechanism for Scram { client_final_message_bare.extend(b",r="); client_final_message_bare.extend(server_nonce.bytes()); 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 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(); auth_message.extend(initial_message); auth_message.push(b','); @@ -146,9 +146,9 @@ impl Mechanism for Scram { auth_message.push(b','); auth_message.extend(&client_final_message_bare); let stored_key = S::hash(&client_key); - let client_signature = S::hmac(&auth_message, &stored_key); + let client_signature = S::hmac(&auth_message, &stored_key)?; let client_proof = xor(&client_key, &client_signature); - let server_signature = S::hmac(&auth_message, &server_key); + let server_signature = S::hmac(&auth_message, &server_key)?; let mut client_final_message = Vec::new(); client_final_message.extend(&client_final_message_bare); client_final_message.extend(b",p="); diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 405afafb752ae4d64c1ba525b9809fe66dba0b19..860e441d8f42908e1eabe9581c7c52aa8b9c5357 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -33,7 +33,7 @@ pub trait ScramProvider { fn hash(data: &[u8]) -> Vec; /// A function which performs an HMAC using the hash function. - fn hmac(data: &[u8], key: &[u8]) -> Vec; + fn hmac(data: &[u8], key: &[u8]) -> Result, String>; /// A function which does PBKDF2 key derivation using the hash function. fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result, String>; @@ -43,7 +43,6 @@ pub trait ScramProvider { pub struct Sha1; impl ScramProvider for Sha1 { - // TODO: look at all these unwraps type Secret = secret::Pbkdf2Sha1; fn name() -> &'static str { @@ -57,14 +56,17 @@ impl ScramProvider for Sha1 { vec } - fn hmac(data: &[u8], key: &[u8]) -> Vec { + fn hmac(data: &[u8], key: &[u8]) -> Result, String> { type HmacSha1 = Hmac; - let mut mac = HmacSha1::new_varkey(key).unwrap(); + let mut mac = match HmacSha1::new_varkey(key) { + Ok(mac) => mac, + Err(err) => return Err(format!("{}", err)), + }; mac.input(data); let result = mac.result(); let mut vec = Vec::with_capacity(Sha1_hash::output_size()); vec.extend_from_slice(result.code().as_slice()); - vec + Ok(vec) } fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { @@ -105,7 +107,6 @@ impl ScramProvider for Sha1 { pub struct Sha256; impl ScramProvider for Sha256 { - // TODO: look at all these unwraps type Secret = secret::Pbkdf2Sha256; fn name() -> &'static str { @@ -119,14 +120,17 @@ impl ScramProvider for Sha256 { vec } - fn hmac(data: &[u8], key: &[u8]) -> Vec { + fn hmac(data: &[u8], key: &[u8]) -> Result, String> { type HmacSha256 = Hmac; - let mut mac = HmacSha256::new_varkey(key).unwrap(); + let mut mac = match HmacSha256::new_varkey(key) { + Ok(mac) => mac, + Err(err) => return Err(format!("{}", err)), + }; mac.input(data); let result = mac.result(); let mut vec = Vec::with_capacity(Sha256_hash::output_size()); vec.extend_from_slice(result.code().as_slice()); - vec + Ok(vec) } fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { diff --git a/sasl/src/server/mechanisms/scram.rs b/sasl/src/server/mechanisms/scram.rs index ad027686c0274442682bd5b90a4e763bd1af0305..a53e97ed3c88bdb0d3c0d05b1fb1a49f64f1a356 100644 --- a/sasl/src/server/mechanisms/scram.rs +++ b/sasl/src/server/mechanisms/scram.rs @@ -150,8 +150,8 @@ where 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 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(); auth_message.extend(initial_client_message); auth_message.extend(b","); @@ -159,7 +159,7 @@ where 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_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 = @@ -167,7 +167,7 @@ where if client_proof != sent_proof { return Err("authentication failed".to_owned()); } - let server_signature = S::hmac(&auth_message, &server_key); + 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()); From 4bc768c0168e543714f9264b90088ac74faba7d5 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 17 Jan 2019 23:15:06 +0100 Subject: [PATCH 34/53] Bump version to 0.4.3. --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 284b8f9633784875a75293f789cadf877a0fde23..79483d7bd62ab77998b94cbd4d4901dbd6ed5fb3 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.4.2" +version = "0.4.3" authors = ["lumi "] description = "A crate for SASL authentication. Currently only does the client side." homepage = "https://gitlab.com/lumi/sasl-rs" From 72e91043cc8ff702caec6269a0d1b5de8f06edb0 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 22 Feb 2019 03:45:39 +0100 Subject: [PATCH 35/53] Make all dependencies for SCRAM optional when it is disabled. --- sasl/Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 79483d7bd62ab77998b94cbd4d4901dbd6ed5fb3..957bcf156876226bb9bfc1e49c1a98c24cebc4f8 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -16,12 +16,12 @@ gitlab = { repository = "lumi/sasl-rs" } [features] default = ["scram"] -scram = [] +scram = ["base64", "rand_os", "sha-1", "sha2", "hmac", "pbkdf2"] [dependencies] -base64 = "0.10" -rand_os = "0.1" -sha-1 = "0.8" -sha2 = "0.8" -hmac = "0.7" -pbkdf2 = { version = "0.3", default-features = false } +base64 = { version = "0.10", optional = true } +rand_os = { version = "0.1", optional = true } +sha-1 = { version = "0.8", optional = true } +sha2 = { version = "0.8", optional = true } +hmac = { version = "0.7", optional = true } +pbkdf2 = { version = "0.3", default-features = false, optional = true } From 21e9c8e660e64bc602bf381fa3c0fe8ed0fedbfd Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 25 Feb 2020 22:32:03 +0100 Subject: [PATCH 36/53] Switch from the deprecated rand-os crate to getrandom. --- sasl/Cargo.toml | 4 ++-- sasl/src/common/scram.rs | 8 ++------ sasl/src/error.rs | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 957bcf156876226bb9bfc1e49c1a98c24cebc4f8..7bf013323c14ce44b463899c40439a9dbcdf6430 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -16,11 +16,11 @@ gitlab = { repository = "lumi/sasl-rs" } [features] default = ["scram"] -scram = ["base64", "rand_os", "sha-1", "sha2", "hmac", "pbkdf2"] +scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] [dependencies] base64 = { version = "0.10", optional = true } -rand_os = { version = "0.1", optional = true } +getrandom = { version = "0.1", optional = true } sha-1 = { version = "0.8", optional = true } sha2 = { version = "0.8", optional = true } hmac = { version = "0.7", optional = true } diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 860e441d8f42908e1eabe9581c7c52aa8b9c5357..eddc2f424a9b53b88e73bca43e6e166058884712 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,9 +1,6 @@ +use getrandom::{getrandom, Error as RngError}; use hmac::{Hmac, Mac}; use pbkdf2::pbkdf2; -use rand_os::{ - rand_core::{Error as RngError, RngCore}, - OsRng, -}; use sha1::{Digest, Sha1 as Sha1_hash}; use sha2::Sha256 as Sha256_hash; @@ -16,8 +13,7 @@ use base64; /// Generate a nonce for SCRAM authentication. pub fn generate_nonce() -> Result { let mut data = [0u8; 32]; - let mut rng = OsRng::new()?; - rng.fill_bytes(&mut data); + getrandom(&mut data)?; Ok(base64::encode(&data)) } diff --git a/sasl/src/error.rs b/sasl/src/error.rs index b5287073753dccf6de443bc0835961aaa24ab9f6..eb8859092c993f0b266268f82786879d1911ee59 100644 --- a/sasl/src/error.rs +++ b/sasl/src/error.rs @@ -1,5 +1,5 @@ #[cfg(feature = "scram")] -use rand_os::rand_core::Error as RngError; +use getrandom::Error as RngError; /// A wrapper enum for things that could go wrong in this crate. #[derive(Debug)] From 09745829f19975455d5a8551e34f1671873776b2 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 25 Feb 2020 23:31:21 +0100 Subject: [PATCH 37/53] client: Remove Result from Mechanism::initial(). --- sasl/src/client/mechanisms/plain.rs | 4 ++-- sasl/src/client/mechanisms/scram.rs | 8 ++++---- sasl/src/client/mod.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sasl/src/client/mechanisms/plain.rs b/sasl/src/client/mechanisms/plain.rs index 1c5bd1a310db0acf62a5bd0b43da5d28462a0f22..08036e608743c3737b541aa33b245eacb1570762 100644 --- a/sasl/src/client/mechanisms/plain.rs +++ b/sasl/src/client/mechanisms/plain.rs @@ -39,12 +39,12 @@ impl Mechanism for Plain { } } - fn initial(&mut self) -> Result, String> { + fn initial(&mut self) -> Vec { let mut auth = Vec::new(); auth.push(0); auth.extend(self.username.bytes()); auth.push(0); auth.extend(self.password.bytes()); - Ok(auth) + auth } } diff --git a/sasl/src/client/mechanisms/scram.rs b/sasl/src/client/mechanisms/scram.rs index a3bd35c97e8d8db6e649291e90abe14389b005c0..f3c1d30acfc84f09643d7daacef34d69946f4473 100644 --- a/sasl/src/client/mechanisms/scram.rs +++ b/sasl/src/client/mechanisms/scram.rs @@ -93,7 +93,7 @@ impl Mechanism for Scram { } } - fn initial(&mut self) -> Result, String> { + fn initial(&mut self) -> Vec { let mut gs2_header = Vec::new(); gs2_header.extend(self.channel_binding.header()); let mut bare = Vec::new(); @@ -108,7 +108,7 @@ impl Mechanism for Scram { initial_message: bare, gs2_header: gs2_header, }; - Ok(data) + data } fn response(&mut self, challenge: &[u8]) -> Result, String> { @@ -206,7 +206,7 @@ mod tests { let server_final = b"v=rmF9pqV8S7suAoZWja4dJRkFsKQ="; let mut mechanism = Scram::::new_with_nonce(username, password, client_nonce.to_owned()); - let init = mechanism.initial().unwrap(); + let init = mechanism.initial(); assert_eq!( String::from_utf8(init.clone()).unwrap(), String::from_utf8(client_init[..].to_owned()).unwrap() @@ -231,7 +231,7 @@ mod tests { let server_final = b"v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4="; let mut mechanism = Scram::::new_with_nonce(username, password, client_nonce.to_owned()); - let init = mechanism.initial().unwrap(); + let init = mechanism.initial(); assert_eq!( String::from_utf8(init.clone()).unwrap(), String::from_utf8(client_init[..].to_owned()).unwrap() diff --git a/sasl/src/client/mod.rs b/sasl/src/client/mod.rs index 2acf9cff2d7b941f8a6100205301c99d5d08c369..93463f65fd749ac23fbf18ea55b9c94262038e36 100644 --- a/sasl/src/client/mod.rs +++ b/sasl/src/client/mod.rs @@ -11,8 +11,8 @@ pub trait Mechanism { Self: Sized; /// Provides initial payload of the SASL mechanism. - fn initial(&mut self) -> Result, String> { - Ok(Vec::new()) + fn initial(&mut self) -> Vec { + Vec::new() } /// Creates a response to the SASL challenge. From 492e35b4be06b977366aa4844dd816dacb9bfddc Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 25 Feb 2020 22:32:24 +0100 Subject: [PATCH 38/53] Update base64 to 0.12. --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 7bf013323c14ce44b463899c40439a9dbcdf6430..c94be0c99b2743570d70994ce067efc2816775f8 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -19,7 +19,7 @@ default = ["scram"] scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] [dependencies] -base64 = { version = "0.10", optional = true } +base64 = { version = "0.12", optional = true } getrandom = { version = "0.1", optional = true } sha-1 = { version = "0.8", optional = true } sha2 = { version = "0.8", optional = true } From 7fd692346471d0321ab6f7bb0a685162dcbff49f Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 15 May 2020 13:48:27 +0200 Subject: [PATCH 39/53] Use error structs for errors instead of plain strings. --- sasl/src/client/mechanisms/anonymous.rs | 6 +- sasl/src/client/mechanisms/plain.rs | 8 +- sasl/src/client/mechanisms/scram.rs | 32 +++--- sasl/src/client/mod.rs | 82 ++++++++++++- sasl/src/common/scram.rs | 77 ++++++++----- sasl/src/lib.rs | 43 +++++-- sasl/src/secret.rs | 15 ++- sasl/src/server/mechanisms/plain.rs | 16 +-- sasl/src/server/mechanisms/scram.rs | 31 ++--- sasl/src/server/mod.rs | 146 +++++++++++++++++++++++- 10 files changed, 357 insertions(+), 99 deletions(-) diff --git a/sasl/src/client/mechanisms/anonymous.rs b/sasl/src/client/mechanisms/anonymous.rs index 45361377669803f30b95a1cd58662cc36a7f1c66..96b236a6b0c7829a1269f77c2e8434d4521eaa70 100644 --- a/sasl/src/client/mechanisms/anonymous.rs +++ b/sasl/src/client/mechanisms/anonymous.rs @@ -1,6 +1,6 @@ //! Provides the SASL "ANONYMOUS" mechanism. -use crate::client::Mechanism; +use crate::client::{Mechanism, MechanismError}; use crate::common::{Credentials, Secret}; /// A struct for the SASL ANONYMOUS mechanism. @@ -21,11 +21,11 @@ impl Mechanism for Anonymous { "ANONYMOUS" } - fn from_credentials(credentials: Credentials) -> Result { + fn from_credentials(credentials: Credentials) -> Result { if let Secret::None = credentials.secret { Ok(Anonymous) } else { - Err("the anonymous sasl mechanism requires no credentials".to_owned()) + Err(MechanismError::AnonymousRequiresNoCredentials) } } } diff --git a/sasl/src/client/mechanisms/plain.rs b/sasl/src/client/mechanisms/plain.rs index 08036e608743c3737b541aa33b245eacb1570762..bc08fd85b711a5b8d035b98764e00573601750b8 100644 --- a/sasl/src/client/mechanisms/plain.rs +++ b/sasl/src/client/mechanisms/plain.rs @@ -1,6 +1,6 @@ //! Provides the SASL "PLAIN" mechanism. -use crate::client::Mechanism; +use crate::client::{Mechanism, MechanismError}; use crate::common::{Credentials, Identity, Password, Secret}; /// A struct for the SASL PLAIN mechanism. @@ -27,15 +27,15 @@ impl Mechanism for Plain { "PLAIN" } - fn from_credentials(credentials: Credentials) -> Result { + fn from_credentials(credentials: Credentials) -> Result { 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()) + Err(MechanismError::PlainRequiresUsername) } } else { - Err("PLAIN requires a plaintext password".to_owned()) + Err(MechanismError::PlainRequiresPlaintextPassword) } } diff --git a/sasl/src/client/mechanisms/scram.rs b/sasl/src/client/mechanisms/scram.rs index f3c1d30acfc84f09643d7daacef34d69946f4473..10f828d8bc867a7ddcdb9b16bd6b6ae8f9adda10 100644 --- a/sasl/src/client/mechanisms/scram.rs +++ b/sasl/src/client/mechanisms/scram.rs @@ -2,7 +2,7 @@ use base64; -use crate::client::Mechanism; +use crate::client::{Mechanism, MechanismError}; use crate::common::scram::{generate_nonce, ScramProvider}; use crate::common::{parse_frame, xor, ChannelBinding, Credentials, Identity, Password, Secret}; @@ -80,16 +80,16 @@ impl Mechanism for Scram { &self.name } - fn from_credentials(credentials: Credentials) -> Result, String> { + fn from_credentials(credentials: Credentials) -> Result, MechanismError> { if let Secret::Password(password) = credentials.secret { if let Identity::Username(username) = credentials.identity { Scram::new(username, password, credentials.channel_binding) - .map_err(|_| "can't generate nonce".to_owned()) + .map_err(|_| MechanismError::CannotGenerateNonce) } else { - Err("SCRAM requires a username".to_owned()) + Err(MechanismError::ScramRequiresUsername) } } else { - Err("SCRAM requires a password".to_owned()) + Err(MechanismError::ScramRequiresPassword) } } @@ -111,7 +111,7 @@ impl Mechanism for Scram { data } - fn response(&mut self, challenge: &[u8]) -> Result, String> { + fn response(&mut self, challenge: &[u8]) -> Result, MechanismError> { let next_state; let ret; match self.state { @@ -120,13 +120,13 @@ impl Mechanism for Scram { ref gs2_header, } => { let frame = - parse_frame(challenge).map_err(|_| "can't decode challenge".to_owned())?; + parse_frame(challenge).map_err(|_| MechanismError::CannotDecodeChallenge)?; let server_nonce = frame.get("r"); let salt = frame.get("s").and_then(|v| base64::decode(v).ok()); let iterations = frame.get("i").and_then(|v| v.parse().ok()); - let server_nonce = server_nonce.ok_or_else(|| "no server nonce".to_owned())?; - let salt = salt.ok_or_else(|| "no server salt".to_owned())?; - let iterations = iterations.ok_or_else(|| "no server iterations".to_owned())?; + let server_nonce = server_nonce.ok_or_else(|| MechanismError::NoServerNonce)?; + let salt = salt.ok_or_else(|| MechanismError::NoServerSalt)?; + let iterations = iterations.ok_or_else(|| MechanismError::NoServerIterations)?; // TODO: SASLprep let mut client_final_message_bare = Vec::new(); client_final_message_bare.extend(b"c="); @@ -159,15 +159,15 @@ impl Mechanism for Scram { ret = client_final_message; } _ => { - return Err("not in the right state to receive this response".to_owned()); + return Err(MechanismError::InvalidState); } } self.state = next_state; Ok(ret) } - fn success(&mut self, data: &[u8]) -> Result<(), String> { - let frame = parse_frame(data).map_err(|_| "can't decode success response".to_owned())?; + fn success(&mut self, data: &[u8]) -> Result<(), MechanismError> { + let frame = parse_frame(data).map_err(|_| MechanismError::CannotDecodeSuccessResponse)?; match self.state { ScramState::GotServerData { ref server_signature, @@ -176,13 +176,13 @@ impl Mechanism for Scram { if sig == *server_signature { Ok(()) } else { - Err("invalid signature in success response".to_owned()) + Err(MechanismError::InvalidSignatureInSuccessResponse) } } else { - Err("no signature in success response".to_owned()) + Err(MechanismError::NoSignatureInSuccessResponse) } } - _ => Err("not in the right state to get a success response".to_owned()), + _ => Err(MechanismError::InvalidState), } } } diff --git a/sasl/src/client/mod.rs b/sasl/src/client/mod.rs index 93463f65fd749ac23fbf18ea55b9c94262038e36..621de8780c861c708ee6fa99f8a9754a56febd50 100644 --- a/sasl/src/client/mod.rs +++ b/sasl/src/client/mod.rs @@ -1,4 +1,80 @@ +use crate::common::scram::DeriveError; use crate::common::Credentials; +use hmac::crypto_mac::InvalidKeyLength; +use std::fmt; + +#[derive(Debug, PartialEq)] +pub enum MechanismError { + AnonymousRequiresNoCredentials, + + PlainRequiresUsername, + PlainRequiresPlaintextPassword, + + CannotGenerateNonce, + ScramRequiresUsername, + ScramRequiresPassword, + + CannotDecodeChallenge, + NoServerNonce, + NoServerSalt, + NoServerIterations, + DeriveError(DeriveError), + InvalidKeyLength(InvalidKeyLength), + InvalidState, + + CannotDecodeSuccessResponse, + InvalidSignatureInSuccessResponse, + NoSignatureInSuccessResponse, +} + +impl From for MechanismError { + fn from(err: DeriveError) -> MechanismError { + MechanismError::DeriveError(err) + } +} + +impl From for MechanismError { + fn from(err: InvalidKeyLength) -> MechanismError { + MechanismError::InvalidKeyLength(err) + } +} + +impl fmt::Display for MechanismError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!( + fmt, + "{}", + match self { + MechanismError::AnonymousRequiresNoCredentials => + "ANONYMOUS mechanism requires no credentials", + + MechanismError::PlainRequiresUsername => "PLAIN requires a username", + MechanismError::PlainRequiresPlaintextPassword => + "PLAIN requires a plaintext password", + + MechanismError::CannotGenerateNonce => "can't generate nonce", + MechanismError::ScramRequiresUsername => "SCRAM requires a username", + MechanismError::ScramRequiresPassword => "SCRAM requires a password", + + MechanismError::CannotDecodeChallenge => "can't decode challenge", + MechanismError::NoServerNonce => "no server nonce", + MechanismError::NoServerSalt => "no server salt", + MechanismError::NoServerIterations => "no server iterations", + MechanismError::DeriveError(err) => return write!(fmt, "derive error: {}", err), + MechanismError::InvalidKeyLength(err) => + return write!(fmt, "invalid key length: {}", err), + MechanismError::InvalidState => "not in the right state to receive this response", + + MechanismError::CannotDecodeSuccessResponse => "can't decode success response", + MechanismError::InvalidSignatureInSuccessResponse => + "invalid signature in success response", + MechanismError::NoSignatureInSuccessResponse => "no signature in success response", + } + ) + } +} + +impl std::error::Error for MechanismError {} /// A trait which defines SASL mechanisms. pub trait Mechanism { @@ -6,7 +82,7 @@ pub trait Mechanism { fn name(&self) -> &str; /// Creates this mechanism from `Credentials`. - fn from_credentials(credentials: Credentials) -> Result + fn from_credentials(credentials: Credentials) -> Result where Self: Sized; @@ -16,12 +92,12 @@ pub trait Mechanism { } /// Creates a response to the SASL challenge. - fn response(&mut self, _challenge: &[u8]) -> Result, String> { + fn response(&mut self, _challenge: &[u8]) -> Result, MechanismError> { Ok(Vec::new()) } /// Verifies the server success response, if there is one. - fn success(&mut self, _data: &[u8]) -> Result<(), String> { + fn success(&mut self, _data: &[u8]) -> Result<(), MechanismError> { Ok(()) } } diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 860e441d8f42908e1eabe9581c7c52aa8b9c5357..f65a89e9d35d41cda2c5171b1df52470e65eefef 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,4 +1,4 @@ -use hmac::{Hmac, Mac}; +use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac}; use pbkdf2::pbkdf2; use rand_os::{ rand_core::{Error as RngError, RngCore}, @@ -21,6 +21,29 @@ pub fn generate_nonce() -> Result { Ok(base64::encode(&data)) } +#[derive(Debug, PartialEq)] +pub enum DeriveError { + IncompatibleHashingMethod(String, String), + IncorrectSalt, + IncompatibleIterationCount(usize, usize), +} + +impl std::fmt::Display for DeriveError { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + DeriveError::IncompatibleHashingMethod(one, two) => { + write!(fmt, "incompatible hashing method, {} is not {}", one, two) + } + DeriveError::IncorrectSalt => write!(fmt, "incorrect salt"), + DeriveError::IncompatibleIterationCount(one, two) => { + write!(fmt, "incompatible iteration count, {} is not {}", one, two) + } + } + } +} + +impl std::error::Error for DeriveError {} + /// A trait which defines the needed methods for SCRAM. pub trait ScramProvider { /// The kind of secret this `ScramProvider` requires. @@ -33,10 +56,10 @@ pub trait ScramProvider { fn hash(data: &[u8]) -> Vec; /// A function which performs an HMAC using the hash function. - fn hmac(data: &[u8], key: &[u8]) -> Result, String>; + fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength>; /// A function which does PBKDF2 key derivation using the hash function. - fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result, String>; + fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result, DeriveError>; } /// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS @@ -56,12 +79,9 @@ impl ScramProvider for Sha1 { vec } - fn hmac(data: &[u8], key: &[u8]) -> Result, String> { + fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength> { type HmacSha1 = Hmac; - let mut mac = match HmacSha1::new_varkey(key) { - Ok(mac) => mac, - Err(err) => return Err(format!("{}", err)), - }; + let mut mac = HmacSha1::new_varkey(key)?; mac.input(data); let result = mac.result(); let mut vec = Vec::with_capacity(Sha1_hash::output_size()); @@ -69,7 +89,7 @@ impl ScramProvider for Sha1 { Ok(vec) } - fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { + fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, DeriveError> { match *password { Password::Plain(ref plain) => { let mut result = vec![0; 20]; @@ -83,17 +103,16 @@ impl ScramProvider for Sha1 { ref data, } => { if method != Self::name() { - Err(format!( - "incompatible hashing method, {} is not {}", - method, - Self::name() + Err(DeriveError::IncompatibleHashingMethod( + method.to_string(), + Self::name().to_string(), )) } else if my_salt == &salt { - Err(format!("incorrect salt")) + Err(DeriveError::IncorrectSalt) } else if my_iterations == iterations { - Err(format!( - "incompatible iteration count, {} is not {}", - my_iterations, iterations + Err(DeriveError::IncompatibleIterationCount( + my_iterations, + iterations, )) } else { Ok(data.to_vec()) @@ -120,12 +139,9 @@ impl ScramProvider for Sha256 { vec } - fn hmac(data: &[u8], key: &[u8]) -> Result, String> { + fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength> { type HmacSha256 = Hmac; - let mut mac = match HmacSha256::new_varkey(key) { - Ok(mac) => mac, - Err(err) => return Err(format!("{}", err)), - }; + let mut mac = HmacSha256::new_varkey(key)?; mac.input(data); let result = mac.result(); let mut vec = Vec::with_capacity(Sha256_hash::output_size()); @@ -133,7 +149,7 @@ impl ScramProvider for Sha256 { Ok(vec) } - fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, String> { + fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, DeriveError> { match *password { Password::Plain(ref plain) => { let mut result = vec![0; 32]; @@ -147,17 +163,16 @@ impl ScramProvider for Sha256 { ref data, } => { if method != Self::name() { - Err(format!( - "incompatible hashing method, {} is not {}", - method, - Self::name() + Err(DeriveError::IncompatibleHashingMethod( + method.to_string(), + Self::name().to_string(), )) } else if my_salt == &salt { - Err(format!("incorrect salt")) + Err(DeriveError::IncorrectSalt) } else if my_iterations == iterations { - Err(format!( - "incompatible iteration count, {} is not {}", - my_iterations, iterations + Err(DeriveError::IncompatibleIterationCount( + my_iterations, + iterations, )) } else { Ok(data.to_vec()) diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 2a64e7621d873abdc29ca6130e070e0b96aab961..9684814c43dbe1c8d7eeb7f5fe3724f945354007 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -17,7 +17,7 @@ //! //! let mut mechanism = Plain::from_credentials(creds).unwrap(); //! -//! let initial_data = mechanism.initial().unwrap(); +//! let initial_data = mechanism.initial(); //! //! assert_eq!(initial_data, b"\0user\0pencil"); //! ``` @@ -28,8 +28,9 @@ //! #[macro_use] extern crate sasl; //! //! use sasl::server::{Validator, Provider, Mechanism as ServerMechanism, Response}; +//! use sasl::server::{ValidatorError, ProviderError, MechanismError as ServerMechanismError}; //! use sasl::server::mechanisms::{Plain as ServerPlain, Scram as ServerScram}; -//! use sasl::client::Mechanism as ClientMechanism; +//! use sasl::client::{Mechanism as ClientMechanism, MechanismError as ClientMechanismError}; //! use sasl::client::mechanisms::{Plain as ClientPlain, Scram as ClientScram}; //! use sasl::common::{Identity, Credentials, Password, ChannelBinding}; //! use sasl::common::scram::{ScramProvider, Sha1, Sha256}; @@ -43,13 +44,13 @@ //! struct MyValidator; //! //! impl Validator for MyValidator { -//! fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), String> { +//! fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), ValidatorError> { //! let &secret::Plain(ref password) = value; //! if identity != &Identity::Username(USERNAME.to_owned()) { -//! Err("authentication failed".to_owned()) +//! Err(ValidatorError::AuthenticationFailed) //! } //! else if password != PASSWORD { -//! Err("authentication failed".to_owned()) +//! Err(ValidatorError::AuthenticationFailed) //! } //! else { //! Ok(()) @@ -58,9 +59,9 @@ //! } //! //! impl Provider for MyValidator { -//! fn provide(&self, identity: &Identity) -> Result { +//! fn provide(&self, identity: &Identity) -> Result { //! if identity != &Identity::Username(USERNAME.to_owned()) { -//! Err("authentication failed".to_owned()) +//! Err(ProviderError::AuthenticationFailed) //! } //! else { //! let digest = sasl::common::scram::Sha1::derive @@ -79,9 +80,9 @@ //! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha1); //! //! impl Provider for MyValidator { -//! fn provide(&self, identity: &Identity) -> Result { +//! fn provide(&self, identity: &Identity) -> Result { //! if identity != &Identity::Username(USERNAME.to_owned()) { -//! Err("authentication failed".to_owned()) +//! Err(ProviderError::AuthenticationFailed) //! } //! else { //! let digest = sasl::common::scram::Sha256::derive @@ -99,10 +100,28 @@ //! //! impl_validator_using_provider!(MyValidator, secret::Pbkdf2Sha256); //! -//! fn finish(cm: &mut CM, sm: &mut SM) -> Result +//! #[derive(Debug, PartialEq)] +//! enum MechanismError { +//! Client(ClientMechanismError), +//! Server(ServerMechanismError), +//! } +//! +//! impl From for MechanismError { +//! fn from(err: ClientMechanismError) -> MechanismError { +//! MechanismError::Client(err) +//! } +//! } +//! +//! impl From for MechanismError { +//! fn from(err: ServerMechanismError) -> MechanismError { +//! MechanismError::Server(err) +//! } +//! } +//! +//! fn finish(cm: &mut CM, sm: &mut SM) -> Result //! where CM: ClientMechanism, //! SM: ServerMechanism { -//! let init = cm.initial()?; +//! let init = cm.initial(); //! println!("C: {}", String::from_utf8_lossy(&init)); //! let mut resp = sm.respond(&init)?; //! loop { @@ -133,7 +152,7 @@ //! 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 failed".to_owned())); +//! assert_eq!(mech.respond(b"\0user\0marker"), Err(ServerMechanismError::ValidatorError(ValidatorError::AuthenticationFailed))); //! //! let creds = Credentials::default() //! .with_username(USERNAME) diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs index 31f42dd9e00123d325baee445361dc3f836d522e..05b5850b35fef0498cf12a6909fe5d8fa16fc9ef 100644 --- a/sasl/src/secret.rs +++ b/sasl/src/secret.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "scram")] +use crate::common::scram::DeriveError; + pub trait Secret {} pub trait Pbkdf2Secret { @@ -20,7 +23,11 @@ pub struct Pbkdf2Sha1 { impl Pbkdf2Sha1 { #[cfg(feature = "scram")] - pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { + pub fn derive( + password: &str, + salt: &[u8], + iterations: usize, + ) -> Result { use crate::common::scram::{ScramProvider, Sha1}; use crate::common::Password; let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?; @@ -55,7 +62,11 @@ pub struct Pbkdf2Sha256 { impl Pbkdf2Sha256 { #[cfg(feature = "scram")] - pub fn derive(password: &str, salt: &[u8], iterations: usize) -> Result { + pub fn derive( + password: &str, + salt: &[u8], + iterations: usize, + ) -> Result { use crate::common::scram::{ScramProvider, Sha256}; use crate::common::Password; let digest = Sha256::derive(&Password::Plain(password.to_owned()), salt, iterations)?; diff --git a/sasl/src/server/mechanisms/plain.rs b/sasl/src/server/mechanisms/plain.rs index 8df0e76fdab603de5091ec4220befff49ead4324..79d090f66eede2a6bc03d688da283a870a59f858 100644 --- a/sasl/src/server/mechanisms/plain.rs +++ b/sasl/src/server/mechanisms/plain.rs @@ -1,6 +1,6 @@ use crate::common::Identity; use crate::secret; -use crate::server::{Mechanism, Response, Validator}; +use crate::server::{Mechanism, MechanismError, Response, Validator}; pub struct Plain> { validator: V, @@ -19,19 +19,19 @@ impl> Mechanism for Plain { "PLAIN" } - fn respond(&mut self, payload: &[u8]) -> Result { + 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")?; + .ok_or_else(|| MechanismError::NoUsernameSpecified)?; + let username = String::from_utf8(username.to_vec()) + .map_err(|_| MechanismError::ErrorDecodingUsername)?; 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")?; + .ok_or_else(|| MechanismError::NoPasswordSpecified)?; + let password = String::from_utf8(password.to_vec()) + .map_err(|_| MechanismError::ErrorDecodingPassword)?; let ident = Identity::Username(username); self.validator.validate(&ident, &secret::Plain(password))?; Ok(Response::Success(ident, Vec::new())) diff --git a/sasl/src/server/mechanisms/scram.rs b/sasl/src/server/mechanisms/scram.rs index a53e97ed3c88bdb0d3c0d05b1fb1a49f64f1a356..41d7ac7ffa1c8597820d6d3f96df8312ebde663b 100644 --- a/sasl/src/server/mechanisms/scram.rs +++ b/sasl/src/server/mechanisms/scram.rs @@ -6,7 +6,7 @@ use crate::common::scram::{generate_nonce, ScramProvider}; use crate::common::{parse_frame, xor, ChannelBinding, Identity}; use crate::secret; use crate::secret::Pbkdf2Secret; -use crate::server::{Mechanism, Provider, Response}; +use crate::server::{Mechanism, MechanismError, Provider, Response}; enum ScramState { Init, @@ -61,7 +61,7 @@ where &self.name } - fn respond(&mut self, payload: &[u8]) -> Result { + fn respond(&mut self, payload: &[u8]) -> Result { let next_state; let ret; match self.state { @@ -82,7 +82,7 @@ where } } if commas < 2 { - return Err("failed to decode message".to_owned()); + return Err(MechanismError::FailedToDecodeMessage); } let gs2_header = payload[..idx].to_vec(); let rest = payload[idx..].to_vec(); @@ -92,29 +92,29 @@ where // Not supported. if gs2_header[0] != 0x79 { // ord("y") - return Err("channel binding not supported".to_owned()); + return Err(MechanismError::ChannelBindingNotSupported); } } ref other => { // Supported. if gs2_header[0] == 0x79 { // ord("y") - return Err("channel binding is supported".to_owned()); + return Err(MechanismError::ChannelBindingIsSupported); } else if !other.supports("tls-unique") { // TODO: grab the data - return Err("channel binding mechanism incorrect".to_owned()); + return Err(MechanismError::ChannelBindingMechanismIncorrect); } } } 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())?; + parse_frame(&rest).map_err(|_| MechanismError::CannotDecodeInitialMessage)?; + let username = frame.get("n").ok_or_else(|| MechanismError::NoUsername)?; let identity = Identity::Username(username.to_owned()); - let client_nonce = frame.get("r").ok_or_else(|| "no nonce".to_owned())?; + let client_nonce = frame.get("r").ok_or_else(|| MechanismError::NoNonce)?; let mut server_nonce = String::new(); server_nonce += client_nonce; server_nonce += - &generate_nonce().map_err(|_| "failed to generate nonce".to_owned())?; + &generate_nonce().map_err(|_| MechanismError::FailedToGenerateNonce)?; let pbkdf2 = self.provider.provide(&identity)?; let mut buf = Vec::new(); buf.extend(b"r="); @@ -141,7 +141,8 @@ where ref initial_client_message, ref initial_server_message, } => { - let frame = parse_frame(payload).map_err(|_| "can't decode response".to_owned())?; + let frame = + parse_frame(payload).map_err(|_| MechanismError::CannotDecodeResponse)?; let mut cb_data: Vec = Vec::new(); cb_data.extend(gs2_header); cb_data.extend(self.channel_binding.data()); @@ -161,11 +162,11 @@ where 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 = frame.get("p").ok_or_else(|| MechanismError::NoProof)?; let sent_proof = - base64::decode(sent_proof).map_err(|_| "can't decode proof".to_owned())?; + base64::decode(sent_proof).map_err(|_| MechanismError::CannotDecodeProof)?; if client_proof != sent_proof { - return Err("authentication failed".to_owned()); + return Err(MechanismError::AuthenticationFailed); } let server_signature = S::hmac(&auth_message, &server_key)?; let mut buf = Vec::new(); @@ -175,7 +176,7 @@ where next_state = ScramState::Done; } ScramState::Done => { - return Err("sasl session is already over".to_owned()); + return Err(MechanismError::SaslSessionAlreadyOver); } } self.state = next_state; diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index 020e88b208d0363b80562bd4436d8367f4e5a984..c144235cdf72377d954dc974384ec09bbf031330 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,5 +1,7 @@ +use crate::common::scram::DeriveError; use crate::common::Identity; use crate::secret::Secret; +use std::fmt; #[macro_export] macro_rules! impl_validator_using_provider { @@ -9,11 +11,11 @@ macro_rules! impl_validator_using_provider { &self, identity: &$crate::common::Identity, value: &$secret, - ) -> Result<(), String> { + ) -> Result<(), ValidatorError> { if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value { Ok(()) } else { - Err("authentication failure".to_owned()) + Err(ValidatorError::AuthenticationFailed) } } } @@ -21,16 +23,150 @@ macro_rules! impl_validator_using_provider { } pub trait Provider: Validator { - fn provide(&self, identity: &Identity) -> Result; + fn provide(&self, identity: &Identity) -> Result; } pub trait Validator { - fn validate(&self, identity: &Identity, value: &S) -> Result<(), String>; + fn validate(&self, identity: &Identity, value: &S) -> Result<(), ValidatorError>; +} + +#[derive(Debug, PartialEq)] +pub enum ProviderError { + AuthenticationFailed, + DeriveError(DeriveError), +} + +#[derive(Debug, PartialEq)] +pub enum ValidatorError { + AuthenticationFailed, + ProviderError(ProviderError), +} + +#[derive(Debug, PartialEq)] +pub enum MechanismError { + NoUsernameSpecified, + ErrorDecodingUsername, + NoPasswordSpecified, + ErrorDecodingPassword, + ValidatorError(ValidatorError), + + FailedToDecodeMessage, + ChannelBindingNotSupported, + ChannelBindingIsSupported, + ChannelBindingMechanismIncorrect, + CannotDecodeInitialMessage, + NoUsername, + NoNonce, + FailedToGenerateNonce, + ProviderError(ProviderError), + + CannotDecodeResponse, + InvalidKeyLength(hmac::crypto_mac::InvalidKeyLength), + NoProof, + CannotDecodeProof, + AuthenticationFailed, + SaslSessionAlreadyOver, +} + +impl From for ProviderError { + fn from(err: DeriveError) -> ProviderError { + ProviderError::DeriveError(err) + } +} + +impl From for ValidatorError { + fn from(err: ProviderError) -> ValidatorError { + ValidatorError::ProviderError(err) + } +} + +impl From for MechanismError { + fn from(err: ProviderError) -> MechanismError { + MechanismError::ProviderError(err) + } +} + +impl From for MechanismError { + fn from(err: ValidatorError) -> MechanismError { + MechanismError::ValidatorError(err) + } +} + +impl From for MechanismError { + fn from(err: hmac::crypto_mac::InvalidKeyLength) -> MechanismError { + MechanismError::InvalidKeyLength(err) + } +} + +impl fmt::Display for ProviderError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "provider error") + } +} + +impl fmt::Display for ValidatorError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "validator error") + } +} + +impl fmt::Display for MechanismError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + MechanismError::NoUsernameSpecified => write!(fmt, "no username specified"), + MechanismError::ErrorDecodingUsername => write!(fmt, "error decoding username"), + MechanismError::NoPasswordSpecified => write!(fmt, "no password specified"), + MechanismError::ErrorDecodingPassword => write!(fmt, "error decoding password"), + MechanismError::ValidatorError(err) => write!(fmt, "validator error: {}", err), + + MechanismError::FailedToDecodeMessage => write!(fmt, "failed to decode message"), + MechanismError::ChannelBindingNotSupported => { + write!(fmt, "channel binding not supported") + } + MechanismError::ChannelBindingIsSupported => { + write!(fmt, "channel binding is supported") + } + MechanismError::ChannelBindingMechanismIncorrect => { + write!(fmt, "channel binding mechanism is incorrect") + } + MechanismError::CannotDecodeInitialMessage => { + write!(fmt, "can’t decode initial message") + } + MechanismError::NoUsername => write!(fmt, "no username"), + MechanismError::NoNonce => write!(fmt, "no nonce"), + MechanismError::FailedToGenerateNonce => write!(fmt, "failed to generate nonce"), + MechanismError::ProviderError(err) => write!(fmt, "provider error: {}", err), + + MechanismError::CannotDecodeResponse => write!(fmt, "can’t decode response"), + MechanismError::InvalidKeyLength(err) => write!(fmt, "invalid key length: {}", err), + MechanismError::NoProof => write!(fmt, "no proof"), + MechanismError::CannotDecodeProof => write!(fmt, "can’t decode proof"), + MechanismError::AuthenticationFailed => write!(fmt, "authentication failed"), + MechanismError::SaslSessionAlreadyOver => write!(fmt, "SASL session already over"), + } + } +} + +impl Error for ProviderError {} + +impl Error for ValidatorError {} + +use std::error::Error; +impl Error for MechanismError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + MechanismError::ValidatorError(err) => Some(err), + MechanismError::ProviderError(err) => Some(err), + // TODO: figure out how to enable the std feature on this crate. + //MechanismError::InvalidKeyLength(err) => Some(err), + _ => None, + } + } } pub trait Mechanism { fn name(&self) -> &str; - fn respond(&mut self, payload: &[u8]) -> Result; + fn respond(&mut self, payload: &[u8]) -> Result; } #[derive(Debug, Clone, PartialEq, Eq)] From af1d3c924acba81c083a898789ed6b5fdb330e50 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 22 Jun 2020 01:19:24 +0200 Subject: [PATCH 40/53] Bump RustCrypto crates --- sasl/Cargo.toml | 8 ++++---- sasl/src/common/mod.rs | 4 ++-- sasl/src/common/scram.rs | 22 +++++++++++----------- sasl/src/lib.rs | 2 +- sasl/src/secret.rs | 18 +++++++----------- 5 files changed, 25 insertions(+), 29 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index c94be0c99b2743570d70994ce067efc2816775f8..e8101790ca39c3436258440cd643d5764243dc7d 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -21,7 +21,7 @@ scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] [dependencies] base64 = { version = "0.12", optional = true } getrandom = { version = "0.1", optional = true } -sha-1 = { version = "0.8", optional = true } -sha2 = { version = "0.8", optional = true } -hmac = { version = "0.7", optional = true } -pbkdf2 = { version = "0.3", default-features = false, optional = true } +sha-1 = { version = "0.9", optional = true } +sha2 = { version = "0.9", optional = true } +hmac = { version = "0.8", optional = true } +pbkdf2 = { version = "0.4", default-features = false, optional = true } diff --git a/sasl/src/common/mod.rs b/sasl/src/common/mod.rs index 21444f786e396a3f131671b75739b07b874bcde2..a0922f1163debd3bd030386a9bda669b9b1f21c3 100644 --- a/sasl/src/common/mod.rs +++ b/sasl/src/common/mod.rs @@ -83,7 +83,7 @@ impl Secret { pub fn password_pbkdf2>( method: S, salt: Vec, - iterations: usize, + iterations: u32, data: Vec, ) -> Secret { Secret::Password(Password::Pbkdf2 { @@ -104,7 +104,7 @@ pub enum Password { Pbkdf2 { method: String, salt: Vec, - iterations: usize, + iterations: u32, data: Vec, }, } diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 4060ce4605772a3e0e0ba661fa2981add220d7d1..6ac52f12cda326b4faa457d47e8a3d906d9ff5a0 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,5 +1,5 @@ use getrandom::{getrandom, Error as RngError}; -use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac}; +use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac, NewMac}; use pbkdf2::pbkdf2; use sha1::{Digest, Sha1 as Sha1_hash}; use sha2::Sha256 as Sha256_hash; @@ -21,7 +21,7 @@ pub fn generate_nonce() -> Result { pub enum DeriveError { IncompatibleHashingMethod(String, String), IncorrectSalt, - IncompatibleIterationCount(usize, usize), + IncompatibleIterationCount(u32, u32), } impl std::fmt::Display for DeriveError { @@ -55,7 +55,7 @@ pub trait ScramProvider { fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength>; /// A function which does PBKDF2 key derivation using the hash function. - fn derive(data: &Password, salt: &[u8], iterations: usize) -> Result, DeriveError>; + fn derive(data: &Password, salt: &[u8], iterations: u32) -> Result, DeriveError>; } /// A `ScramProvider` which provides SCRAM-SHA-1 and SCRAM-SHA-1-PLUS @@ -78,14 +78,14 @@ impl ScramProvider for Sha1 { fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength> { type HmacSha1 = Hmac; let mut mac = HmacSha1::new_varkey(key)?; - mac.input(data); - let result = mac.result(); + mac.update(data); + let result = mac.finalize(); let mut vec = Vec::with_capacity(Sha1_hash::output_size()); - vec.extend_from_slice(result.code().as_slice()); + vec.extend_from_slice(result.into_bytes().as_slice()); Ok(vec) } - fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, DeriveError> { + fn derive(password: &Password, salt: &[u8], iterations: u32) -> Result, DeriveError> { match *password { Password::Plain(ref plain) => { let mut result = vec![0; 20]; @@ -138,14 +138,14 @@ impl ScramProvider for Sha256 { fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength> { type HmacSha256 = Hmac; let mut mac = HmacSha256::new_varkey(key)?; - mac.input(data); - let result = mac.result(); + mac.update(data); + let result = mac.finalize(); let mut vec = Vec::with_capacity(Sha256_hash::output_size()); - vec.extend_from_slice(result.code().as_slice()); + vec.extend_from_slice(result.into_bytes().as_slice()); Ok(vec) } - fn derive(password: &Password, salt: &[u8], iterations: usize) -> Result, DeriveError> { + fn derive(password: &Password, salt: &[u8], iterations: u32) -> Result, DeriveError> { match *password { Password::Plain(ref plain) => { let mut result = vec![0; 32]; diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index 9684814c43dbe1c8d7eeb7f5fe3724f945354007..f99d062b5117c96b5d81442b6cf67342c9d583ab 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -39,7 +39,7 @@ //! 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; +//! const ITERATIONS: u32 = 4096; //! //! struct MyValidator; //! diff --git a/sasl/src/secret.rs b/sasl/src/secret.rs index 05b5850b35fef0498cf12a6909fe5d8fa16fc9ef..bfd5b4e54af4caf3721d518136e3b0046d82d736 100644 --- a/sasl/src/secret.rs +++ b/sasl/src/secret.rs @@ -5,7 +5,7 @@ pub trait Secret {} pub trait Pbkdf2Secret { fn salt(&self) -> &[u8]; - fn iterations(&self) -> usize; + fn iterations(&self) -> u32; fn digest(&self) -> &[u8]; } @@ -17,17 +17,13 @@ impl Secret for Plain {} #[derive(Clone, Debug, PartialEq, Eq)] pub struct Pbkdf2Sha1 { pub salt: Vec, - pub iterations: usize, + pub iterations: u32, pub digest: Vec, } impl Pbkdf2Sha1 { #[cfg(feature = "scram")] - pub fn derive( - password: &str, - salt: &[u8], - iterations: usize, - ) -> Result { + pub fn derive(password: &str, salt: &[u8], iterations: u32) -> Result { use crate::common::scram::{ScramProvider, Sha1}; use crate::common::Password; let digest = Sha1::derive(&Password::Plain(password.to_owned()), salt, iterations)?; @@ -45,7 +41,7 @@ impl Pbkdf2Secret for Pbkdf2Sha1 { fn salt(&self) -> &[u8] { &self.salt } - fn iterations(&self) -> usize { + fn iterations(&self) -> u32 { self.iterations } fn digest(&self) -> &[u8] { @@ -56,7 +52,7 @@ impl Pbkdf2Secret for Pbkdf2Sha1 { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Pbkdf2Sha256 { pub salt: Vec, - pub iterations: usize, + pub iterations: u32, pub digest: Vec, } @@ -65,7 +61,7 @@ impl Pbkdf2Sha256 { pub fn derive( password: &str, salt: &[u8], - iterations: usize, + iterations: u32, ) -> Result { use crate::common::scram::{ScramProvider, Sha256}; use crate::common::Password; @@ -84,7 +80,7 @@ impl Pbkdf2Secret for Pbkdf2Sha256 { fn salt(&self) -> &[u8] { &self.salt } - fn iterations(&self) -> usize { + fn iterations(&self) -> u32 { self.iterations } fn digest(&self) -> &[u8] { From 8d0a4230b0055fea7c6a51e3e4f6978bfa9bc198 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 26 Dec 2020 15:44:39 +0100 Subject: [PATCH 41/53] Bump dependencies again --- sasl/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index e8101790ca39c3436258440cd643d5764243dc7d..ed7619426030fdbeb8292a7797ea073bba7701be 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -19,9 +19,9 @@ default = ["scram"] scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] [dependencies] -base64 = { version = "0.12", optional = true } -getrandom = { version = "0.1", optional = true } +base64 = { version = "0.13", optional = true } +getrandom = { version = "0.2", optional = true } sha-1 = { version = "0.9", optional = true } sha2 = { version = "0.9", optional = true } -hmac = { version = "0.8", optional = true } -pbkdf2 = { version = "0.4", default-features = false, optional = true } +hmac = { version = "0.10", optional = true } +pbkdf2 = { version = "0.6", default-features = false, optional = true } From 4463f6438597a7ce0c7ca3a53577f91d4e4b8a85 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 26 Dec 2020 15:46:04 +0100 Subject: [PATCH 42/53] Bump the version --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index ed7619426030fdbeb8292a7797ea073bba7701be..7a5782b6aacef818c1d5d8d9a388e36814f69191 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sasl" -version = "0.4.3" +version = "0.5.0" authors = ["lumi "] description = "A crate for SASL authentication. Currently only does the client side." homepage = "https://gitlab.com/lumi/sasl-rs" From 90bbbd23931f8994d024d3a6a309c1a81a15e125 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 26 Dec 2020 15:46:14 +0100 Subject: [PATCH 43/53] Write a ChangeLog entry --- sasl/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sasl/CHANGELOG.md b/sasl/CHANGELOG.md index 7df0b195672c4f76e52ebf82357f791526079524..86f2bb70b3fb9374f09e7710b2a10e7785c89105 100644 --- a/sasl/CHANGELOG.md +++ b/sasl/CHANGELOG.md @@ -1,3 +1,10 @@ +Version 0.5.0, released 2020-12-26: + * Important changes + - Made all of the errors into enums, instead of strings. + * Small changes + - Replaced rand\_os with getrandom. + - Bumped all dependencies. + Version 0.4.2, released 2018-05-19: * Small changes - Marc-Antoine Perennou updated the openssl and base64 dependencies to 0.10.4 and 0.9.0 respectively. From 6c1667e17ab6352f7a84811bd5c169f46de0a46c Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 26 Dec 2020 19:11:24 +0100 Subject: [PATCH 44/53] Relicense to MPL-2.0 (from LGPL) 2020-06-07T14:29:21+0200 lumi> well i personally don't care anymore, but if someone wants it to be relicensed to MPL-2.0 i'm fine with it --- sasl/COPYING | 675 -------------------------------------------- sasl/COPYING.LESSER | 166 ----------- sasl/LICENSE | 373 ++++++++++++++++++++++++ 3 files changed, 373 insertions(+), 841 deletions(-) delete mode 100644 sasl/COPYING delete mode 100644 sasl/COPYING.LESSER create mode 100644 sasl/LICENSE diff --git a/sasl/COPYING b/sasl/COPYING deleted file mode 100644 index a737dcfed5db21fb99a8fcb812e995937d38401b..0000000000000000000000000000000000000000 --- a/sasl/COPYING +++ /dev/null @@ -1,675 +0,0 @@ - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/sasl/COPYING.LESSER b/sasl/COPYING.LESSER deleted file mode 100644 index 5f5ff16a4a0f6104fadb6a5beef527573e46b425..0000000000000000000000000000000000000000 --- a/sasl/COPYING.LESSER +++ /dev/null @@ -1,166 +0,0 @@ - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/sasl/LICENSE b/sasl/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a612ad9813b006ce81d1ee438dd784da99a54007 --- /dev/null +++ b/sasl/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. From 5316d5aa4b9f13105f23f62cf07ec8b16b598d9f Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 12 Jan 2021 18:25:07 +0100 Subject: [PATCH 45/53] Release version 0.5.0 --- sasl/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sasl/CHANGELOG.md b/sasl/CHANGELOG.md index 86f2bb70b3fb9374f09e7710b2a10e7785c89105..b51524494265ac043bf4170c943b3404d56671aa 100644 --- a/sasl/CHANGELOG.md +++ b/sasl/CHANGELOG.md @@ -1,5 +1,6 @@ -Version 0.5.0, released 2020-12-26: +Version 0.5.0, released 2021-01-12: * Important changes + - Relicensed to MPL-2.0 from LGPL-3.0-or-later. - Made all of the errors into enums, instead of strings. * Small changes - Replaced rand\_os with getrandom. From 6e22c0fcb4f493e95e6c405a13d70bc72ae30d71 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 25 Dec 2021 15:57:41 +0100 Subject: [PATCH 46/53] Bump all hash crates --- sasl/Cargo.toml | 8 ++++---- sasl/src/client/mod.rs | 8 ++++---- sasl/src/common/scram.rs | 12 ++++++------ sasl/src/server/mod.rs | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 7a5782b6aacef818c1d5d8d9a388e36814f69191..0ff2033f6066ed309698706bb3f9311c3682f79c 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -21,7 +21,7 @@ scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] [dependencies] base64 = { version = "0.13", optional = true } getrandom = { version = "0.2", optional = true } -sha-1 = { version = "0.9", optional = true } -sha2 = { version = "0.9", optional = true } -hmac = { version = "0.10", optional = true } -pbkdf2 = { version = "0.6", default-features = false, optional = true } +sha-1 = { version = "0.10", optional = true } +sha2 = { version = "0.10", optional = true } +hmac = { version = "0.12", optional = true } +pbkdf2 = { version = "0.10", default-features = false, optional = true } diff --git a/sasl/src/client/mod.rs b/sasl/src/client/mod.rs index 621de8780c861c708ee6fa99f8a9754a56febd50..8bf078f68dc6aceceb5c2897142df6c81e36e63e 100644 --- a/sasl/src/client/mod.rs +++ b/sasl/src/client/mod.rs @@ -1,6 +1,6 @@ use crate::common::scram::DeriveError; use crate::common::Credentials; -use hmac::crypto_mac::InvalidKeyLength; +use hmac::digest::InvalidLength; use std::fmt; #[derive(Debug, PartialEq)] @@ -19,7 +19,7 @@ pub enum MechanismError { NoServerSalt, NoServerIterations, DeriveError(DeriveError), - InvalidKeyLength(InvalidKeyLength), + InvalidKeyLength(InvalidLength), InvalidState, CannotDecodeSuccessResponse, @@ -33,8 +33,8 @@ impl From for MechanismError { } } -impl From for MechanismError { - fn from(err: InvalidKeyLength) -> MechanismError { +impl From for MechanismError { + fn from(err: InvalidLength) -> MechanismError { MechanismError::InvalidKeyLength(err) } } diff --git a/sasl/src/common/scram.rs b/sasl/src/common/scram.rs index 6ac52f12cda326b4faa457d47e8a3d906d9ff5a0..b39f29143420be9ee07479b3f9417311333c8726 100644 --- a/sasl/src/common/scram.rs +++ b/sasl/src/common/scram.rs @@ -1,5 +1,5 @@ use getrandom::{getrandom, Error as RngError}; -use hmac::{crypto_mac::InvalidKeyLength, Hmac, Mac, NewMac}; +use hmac::{digest::InvalidLength, Hmac, Mac}; use pbkdf2::pbkdf2; use sha1::{Digest, Sha1 as Sha1_hash}; use sha2::Sha256 as Sha256_hash; @@ -52,7 +52,7 @@ pub trait ScramProvider { fn hash(data: &[u8]) -> Vec; /// A function which performs an HMAC using the hash function. - fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength>; + fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidLength>; /// A function which does PBKDF2 key derivation using the hash function. fn derive(data: &Password, salt: &[u8], iterations: u32) -> Result, DeriveError>; @@ -75,9 +75,9 @@ impl ScramProvider for Sha1 { vec } - fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength> { + fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidLength> { type HmacSha1 = Hmac; - let mut mac = HmacSha1::new_varkey(key)?; + let mut mac = HmacSha1::new_from_slice(key)?; mac.update(data); let result = mac.finalize(); let mut vec = Vec::with_capacity(Sha1_hash::output_size()); @@ -135,9 +135,9 @@ impl ScramProvider for Sha256 { vec } - fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidKeyLength> { + fn hmac(data: &[u8], key: &[u8]) -> Result, InvalidLength> { type HmacSha256 = Hmac; - let mut mac = HmacSha256::new_varkey(key)?; + let mut mac = HmacSha256::new_from_slice(key)?; mac.update(data); let result = mac.finalize(); let mut vec = Vec::with_capacity(Sha256_hash::output_size()); diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index c144235cdf72377d954dc974384ec09bbf031330..8d1bece783d2b4ddc465352df6c4ec0bede31f9c 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -61,7 +61,7 @@ pub enum MechanismError { ProviderError(ProviderError), CannotDecodeResponse, - InvalidKeyLength(hmac::crypto_mac::InvalidKeyLength), + InvalidKeyLength(hmac::digest::InvalidLength), NoProof, CannotDecodeProof, AuthenticationFailed, @@ -92,8 +92,8 @@ impl From for MechanismError { } } -impl From for MechanismError { - fn from(err: hmac::crypto_mac::InvalidKeyLength) -> MechanismError { +impl From for MechanismError { + fn from(err: hmac::digest::InvalidLength) -> MechanismError { MechanismError::InvalidKeyLength(err) } } From 3a802eb193d0ba473cd9bdcc52bce656258c6e54 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 25 Dec 2021 16:15:08 +0100 Subject: [PATCH 47/53] Implement SASL ANONYMOUS on the server side Fixes #11. --- sasl/src/server/mechanisms/anonymous.rs | 28 +++++++++++++++++++++++++ sasl/src/server/mechanisms/mod.rs | 2 ++ sasl/src/server/mod.rs | 10 +++++++++ 3 files changed, 40 insertions(+) create mode 100644 sasl/src/server/mechanisms/anonymous.rs diff --git a/sasl/src/server/mechanisms/anonymous.rs b/sasl/src/server/mechanisms/anonymous.rs new file mode 100644 index 0000000000000000000000000000000000000000..1ee311b0c5b6640712295767c05de7885eb6a1dd --- /dev/null +++ b/sasl/src/server/mechanisms/anonymous.rs @@ -0,0 +1,28 @@ +use crate::common::Identity; +use crate::server::{Mechanism, MechanismError, Response}; +use getrandom::getrandom; + +pub struct Anonymous; + +impl Anonymous { + pub fn new() -> Anonymous { + Anonymous + } +} + +impl Mechanism for Anonymous { + fn name(&self) -> &str { + "ANONYMOUS" + } + + fn respond(&mut self, payload: &[u8]) -> Result { + if !payload.is_empty() { + return Err(MechanismError::FailedToDecodeMessage); + } + let mut rand = [0u8; 16]; + getrandom(&mut rand)?; + let username = format!("{:02x?}", rand); + let ident = Identity::Username(username); + Ok(Response::Success(ident, Vec::new())) + } +} diff --git a/sasl/src/server/mechanisms/mod.rs b/sasl/src/server/mechanisms/mod.rs index 7718102564bd33cd477929d0d2ff1bfac67ee070..9d4c9a5ef0f2f0584a6c4cb501930eb2126dff41 100644 --- a/sasl/src/server/mechanisms/mod.rs +++ b/sasl/src/server/mechanisms/mod.rs @@ -1,7 +1,9 @@ +mod anonymous; mod plain; #[cfg(feature = "scram")] mod scram; +pub use self::anonymous::Anonymous; pub use self::plain::Plain; #[cfg(feature = "scram")] pub use self::scram::Scram; diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index 8d1bece783d2b4ddc465352df6c4ec0bede31f9c..5ee233c607c7d403aa3682e4ed69150a80ff2c8c 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -62,6 +62,7 @@ pub enum MechanismError { CannotDecodeResponse, InvalidKeyLength(hmac::digest::InvalidLength), + RandomFailure(getrandom::Error), NoProof, CannotDecodeProof, AuthenticationFailed, @@ -98,6 +99,12 @@ impl From for MechanismError { } } +impl From for MechanismError { + fn from(err: getrandom::Error) -> MechanismError { + MechanismError::RandomFailure(err) + } +} + impl fmt::Display for ProviderError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "provider error") @@ -139,6 +146,9 @@ impl fmt::Display for MechanismError { MechanismError::CannotDecodeResponse => write!(fmt, "can’t decode response"), MechanismError::InvalidKeyLength(err) => write!(fmt, "invalid key length: {}", err), + MechanismError::RandomFailure(err) => { + write!(fmt, "failure to get random data: {}", err) + } MechanismError::NoProof => write!(fmt, "no proof"), MechanismError::CannotDecodeProof => write!(fmt, "can’t decode proof"), MechanismError::AuthenticationFailed => write!(fmt, "authentication failed"), From 259231bfcc2d6df384f0a2954abd90e45379caf9 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 9 Apr 2022 21:39:24 +0200 Subject: [PATCH 48/53] Bump pbkdf2 dependency to 0.11 --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index 0ff2033f6066ed309698706bb3f9311c3682f79c..f3b634938a059cd152481b7a1cf9d8c70f4ed690 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -24,4 +24,4 @@ getrandom = { version = "0.2", optional = true } sha-1 = { version = "0.10", optional = true } sha2 = { version = "0.10", optional = true } hmac = { version = "0.12", optional = true } -pbkdf2 = { version = "0.10", default-features = false, optional = true } +pbkdf2 = { version = "0.11", default-features = false, optional = true } From c9931f12a903b8ce1413fffeb9010ad0a8cdddea Mon Sep 17 00:00:00 2001 From: Gustav Palmqvist Date: Fri, 20 May 2022 07:51:51 +0200 Subject: [PATCH 49/53] Fixed not building when default-features = false --- sasl/Cargo.toml | 3 ++- sasl/src/client/mod.rs | 14 ++++++++++++-- sasl/src/server/mechanisms/anonymous.rs | 1 + sasl/src/server/mechanisms/mod.rs | 2 ++ sasl/src/server/mod.rs | 12 +++++++++++- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index f3b634938a059cd152481b7a1cf9d8c70f4ed690..c657247133dadfc690d150809a0410ace48715b1 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -15,8 +15,9 @@ edition = "2018" gitlab = { repository = "lumi/sasl-rs" } [features] -default = ["scram"] +default = ["scram", "anonymous"] scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] +anonymous = ["getrandom"] [dependencies] base64 = { version = "0.13", optional = true } diff --git a/sasl/src/client/mod.rs b/sasl/src/client/mod.rs index 8bf078f68dc6aceceb5c2897142df6c81e36e63e..8acca9ffbc200c86e19d89c60fb1fd3a2876ed74 100644 --- a/sasl/src/client/mod.rs +++ b/sasl/src/client/mod.rs @@ -1,7 +1,11 @@ -use crate::common::scram::DeriveError; +use std::fmt; + use crate::common::Credentials; + +#[cfg(feature = "scram")] +use crate::common::scram::DeriveError; +#[cfg(feature = "scram")] use hmac::digest::InvalidLength; -use std::fmt; #[derive(Debug, PartialEq)] pub enum MechanismError { @@ -18,7 +22,9 @@ pub enum MechanismError { NoServerNonce, NoServerSalt, NoServerIterations, + #[cfg(feature = "scram")] DeriveError(DeriveError), + #[cfg(feature = "scram")] InvalidKeyLength(InvalidLength), InvalidState, @@ -27,12 +33,14 @@ pub enum MechanismError { NoSignatureInSuccessResponse, } +#[cfg(feature = "scram")] impl From for MechanismError { fn from(err: DeriveError) -> MechanismError { MechanismError::DeriveError(err) } } +#[cfg(feature = "scram")] impl From for MechanismError { fn from(err: InvalidLength) -> MechanismError { MechanismError::InvalidKeyLength(err) @@ -60,7 +68,9 @@ impl fmt::Display for MechanismError { MechanismError::NoServerNonce => "no server nonce", MechanismError::NoServerSalt => "no server salt", MechanismError::NoServerIterations => "no server iterations", + #[cfg(feature = "scram")] MechanismError::DeriveError(err) => return write!(fmt, "derive error: {}", err), + #[cfg(feature = "scram")] MechanismError::InvalidKeyLength(err) => return write!(fmt, "invalid key length: {}", err), MechanismError::InvalidState => "not in the right state to receive this response", diff --git a/sasl/src/server/mechanisms/anonymous.rs b/sasl/src/server/mechanisms/anonymous.rs index 1ee311b0c5b6640712295767c05de7885eb6a1dd..5a5381e7424a96cb2d457dc38deabf3dcd9a959a 100644 --- a/sasl/src/server/mechanisms/anonymous.rs +++ b/sasl/src/server/mechanisms/anonymous.rs @@ -1,5 +1,6 @@ use crate::common::Identity; use crate::server::{Mechanism, MechanismError, Response}; + use getrandom::getrandom; pub struct Anonymous; diff --git a/sasl/src/server/mechanisms/mod.rs b/sasl/src/server/mechanisms/mod.rs index 9d4c9a5ef0f2f0584a6c4cb501930eb2126dff41..1989a580c75f1b57842a53cb1109ce5276546f9f 100644 --- a/sasl/src/server/mechanisms/mod.rs +++ b/sasl/src/server/mechanisms/mod.rs @@ -1,8 +1,10 @@ +#[cfg(feature = "anonymous")] mod anonymous; mod plain; #[cfg(feature = "scram")] mod scram; +#[cfg(feature = "anonymous")] pub use self::anonymous::Anonymous; pub use self::plain::Plain; #[cfg(feature = "scram")] diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index 5ee233c607c7d403aa3682e4ed69150a80ff2c8c..d3fcd7e90b50ff4522c4941f0443cfa61ae162c6 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -1,8 +1,10 @@ -use crate::common::scram::DeriveError; use crate::common::Identity; use crate::secret::Secret; use std::fmt; +#[cfg(feature = "scram")] +use crate::common::scram::DeriveError; + #[macro_export] macro_rules! impl_validator_using_provider { ( $validator:ty, $secret:ty ) => { @@ -33,6 +35,7 @@ pub trait Validator { #[derive(Debug, PartialEq)] pub enum ProviderError { AuthenticationFailed, + #[cfg(feature = "scram")] DeriveError(DeriveError), } @@ -61,7 +64,9 @@ pub enum MechanismError { ProviderError(ProviderError), CannotDecodeResponse, + #[cfg(feature = "scram")] InvalidKeyLength(hmac::digest::InvalidLength), + #[cfg(any(feature = "scram", feature = "anonymous"))] RandomFailure(getrandom::Error), NoProof, CannotDecodeProof, @@ -69,6 +74,7 @@ pub enum MechanismError { SaslSessionAlreadyOver, } +#[cfg(feature = "scram")] impl From for ProviderError { fn from(err: DeriveError) -> ProviderError { ProviderError::DeriveError(err) @@ -93,12 +99,14 @@ impl From for MechanismError { } } +#[cfg(feature = "scram")] impl From for MechanismError { fn from(err: hmac::digest::InvalidLength) -> MechanismError { MechanismError::InvalidKeyLength(err) } } +#[cfg(any(feature = "scram", feature = "anonymous"))] impl From for MechanismError { fn from(err: getrandom::Error) -> MechanismError { MechanismError::RandomFailure(err) @@ -145,7 +153,9 @@ impl fmt::Display for MechanismError { MechanismError::ProviderError(err) => write!(fmt, "provider error: {}", err), MechanismError::CannotDecodeResponse => write!(fmt, "can’t decode response"), + #[cfg(feature = "scram")] MechanismError::InvalidKeyLength(err) => write!(fmt, "invalid key length: {}", err), + #[cfg(any(feature = "scram", feature = "anonymous"))] MechanismError::RandomFailure(err) => { write!(fmt, "failure to get random data: {}", err) } From 14dddf4aaa0abe33bdef0194fb727a57797e7762 Mon Sep 17 00:00:00 2001 From: Gustav Palmqvist Date: Fri, 20 May 2022 18:24:36 +0200 Subject: [PATCH 50/53] Check build without default features in ci-cd. --- sasl/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/src/lib.rs b/sasl/src/lib.rs index f99d062b5117c96b5d81442b6cf67342c9d583ab..f85571a78b629138c2b9a6d0b82b35831a39cdd0 100644 --- a/sasl/src/lib.rs +++ b/sasl/src/lib.rs @@ -24,7 +24,7 @@ //! //! ## More complex usage //! -//! ```rust +//! ```rust,ignore //! #[macro_use] extern crate sasl; //! //! use sasl::server::{Validator, Provider, Mechanism as ServerMechanism, Response}; From be42dac7927f7449d6c337ea6563176be6cdfca5 Mon Sep 17 00:00:00 2001 From: Gustav Palmqvist Date: Thu, 19 May 2022 00:19:24 +0200 Subject: [PATCH 51/53] Update crate information --- sasl/Cargo.toml | 8 ++++---- sasl/README.md | 17 ++++------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index c657247133dadfc690d150809a0410ace48715b1..c4af61389b0dd94e882a04ddb2e67019f11b0e55 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -3,16 +3,16 @@ name = "sasl" version = "0.5.0" authors = ["lumi "] description = "A crate for SASL authentication. Currently only does the client side." -homepage = "https://gitlab.com/lumi/sasl-rs" -repository = "https://gitlab.com/lumi/sasl-rs" +homepage = "https://gitlab.com/xmpp-rs/sasl-rs" +repository = "https://gitlab.com/xmpp-rs/sasl-rs" documentation = "https://docs.rs/sasl" readme = "README.md" keywords = ["sasl", "authentication"] -license = "LGPL-3.0+" +license = "MPL-2.0" edition = "2018" [badges] -gitlab = { repository = "lumi/sasl-rs" } +gitlab = { repository = "xmpp-rs/sasl-rs" } [features] default = ["scram", "anonymous"] diff --git a/sasl/README.md b/sasl/README.md index 4f8eeaec6dd41346616511374f0b969aa749112f..5972c5915932aacc3e975bf3fabc8464d57bb965 100644 --- a/sasl/README.md +++ b/sasl/README.md @@ -14,22 +14,13 @@ Look at the documentation [here](https://docs.rs/sasl). What license is it under? ------------------------- -LGPLv3 or later. See `COPYING` and `COPYING.LESSER`. +MPL-2.0. See `LICENSE`. License yadda yadda. -------------------- Copyright 2017, sasl-rs contributors. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. From 372e37b31fde36352635179c6d7cc902a9113243 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 21 Dec 2022 10:29:23 +0100 Subject: [PATCH 52/53] Bump base64 dependency to 0.20 --- sasl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sasl/Cargo.toml b/sasl/Cargo.toml index c4af61389b0dd94e882a04ddb2e67019f11b0e55..098fe144f22a54ae2078c7a0d601b929cd6a29d9 100644 --- a/sasl/Cargo.toml +++ b/sasl/Cargo.toml @@ -20,7 +20,7 @@ scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"] anonymous = ["getrandom"] [dependencies] -base64 = { version = "0.13", optional = true } +base64 = { version = "0.20", optional = true } getrandom = { version = "0.2", optional = true } sha-1 = { version = "0.10", optional = true } sha2 = { version = "0.10", optional = true } From fd26d04635133d0e928114b896c7cccfbfebfe43 Mon Sep 17 00:00:00 2001 From: Raman Hafiyatulin Date: Sun, 30 Jul 2023 17:17:26 +0300 Subject: [PATCH 53/53] macro `impl_validator_using_provider!`: use `$crate::server::ValidatorError` instead of ValidatorError --- sasl/src/server/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sasl/src/server/mod.rs b/sasl/src/server/mod.rs index d3fcd7e90b50ff4522c4941f0443cfa61ae162c6..897b000d88eb990676e8b532d97d96184432e79b 100644 --- a/sasl/src/server/mod.rs +++ b/sasl/src/server/mod.rs @@ -13,11 +13,11 @@ macro_rules! impl_validator_using_provider { &self, identity: &$crate::common::Identity, value: &$secret, - ) -> Result<(), ValidatorError> { + ) -> Result<(), $crate::server::ValidatorError> { if &(self as &$crate::server::Provider<$secret>).provide(identity)? == value { Ok(()) } else { - Err(ValidatorError::AuthenticationFailed) + Err($crate::server::ValidatorError::AuthenticationFailed) } } }