mod.rs

  1use std::collections::HashMap;
  2use std::string::FromUtf8Error;
  3
  4#[cfg(feature = "scram")]
  5#[cfg_attr(docsrs, doc(cfg(feature = "scram")))]
  6pub mod scram;
  7
  8#[derive(Clone, Debug, PartialEq, Eq)]
  9pub enum Identity {
 10    None,
 11    Username(String),
 12}
 13
 14impl From<String> for Identity {
 15    fn from(s: String) -> Identity {
 16        Identity::Username(s)
 17    }
 18}
 19
 20impl<'a> From<&'a str> for Identity {
 21    fn from(s: &'a str) -> Identity {
 22        Identity::Username(s.to_owned())
 23    }
 24}
 25
 26/// A struct containing SASL credentials.
 27#[derive(Clone, Debug)]
 28pub struct Credentials {
 29    /// The requested identity.
 30    pub identity: Identity,
 31    /// The secret used to authenticate.
 32    pub secret: Secret,
 33    /// Channel binding data, for *-PLUS mechanisms.
 34    pub channel_binding: ChannelBinding,
 35}
 36
 37impl Default for Credentials {
 38    fn default() -> Credentials {
 39        Credentials {
 40            identity: Identity::None,
 41            secret: Secret::None,
 42            channel_binding: ChannelBinding::Unsupported,
 43        }
 44    }
 45}
 46
 47impl Credentials {
 48    /// Creates a new Credentials with the specified username.
 49    pub fn with_username<N: Into<String>>(mut self, username: N) -> Credentials {
 50        self.identity = Identity::Username(username.into());
 51        self
 52    }
 53
 54    /// Creates a new Credentials with the specified plaintext password.
 55    pub fn with_password<P: Into<String>>(mut self, password: P) -> Credentials {
 56        self.secret = Secret::password_plain(password);
 57        self
 58    }
 59
 60    /// Creates a new Credentials with the specified channel binding.
 61    pub fn with_channel_binding(mut self, channel_binding: ChannelBinding) -> Credentials {
 62        self.channel_binding = channel_binding;
 63        self
 64    }
 65}
 66
 67/// Represents a SASL secret, like a password.
 68#[derive(Clone, Debug, PartialEq, Eq)]
 69pub enum Secret {
 70    /// No extra data needed.
 71    None,
 72    /// Password required.
 73    Password(Password),
 74}
 75
 76impl Secret {
 77    pub fn password_plain<S: Into<String>>(password: S) -> Secret {
 78        Secret::Password(Password::Plain(password.into()))
 79    }
 80
 81    pub fn password_pbkdf2<S: Into<String>>(
 82        method: S,
 83        salt: Vec<u8>,
 84        iterations: u32,
 85        data: Vec<u8>,
 86    ) -> Secret {
 87        Secret::Password(Password::Pbkdf2 {
 88            method: method.into(),
 89            salt,
 90            iterations,
 91            data,
 92        })
 93    }
 94}
 95
 96/// Represents a password.
 97#[derive(Clone, Debug, PartialEq, Eq)]
 98pub enum Password {
 99    /// A plaintext password.
100    Plain(String),
101    /// A password digest derived using PBKDF2.
102    Pbkdf2 {
103        method: String,
104        salt: Vec<u8>,
105        iterations: u32,
106        data: Vec<u8>,
107    },
108}
109
110impl From<String> for Password {
111    fn from(s: String) -> Password {
112        Password::Plain(s)
113    }
114}
115
116impl<'a> From<&'a str> for Password {
117    fn from(s: &'a str) -> Password {
118        Password::Plain(s.to_owned())
119    }
120}
121
122#[cfg(test)]
123#[test]
124fn xor_works() {
125    assert_eq!(
126        xor(
127            &[135, 94, 53, 134, 73, 233, 140, 221, 150, 12, 96, 111, 54, 66, 11, 76],
128            &[163, 9, 122, 180, 107, 44, 22, 252, 248, 134, 112, 82, 84, 122, 56, 209]
129        ),
130        &[36, 87, 79, 50, 34, 197, 154, 33, 110, 138, 16, 61, 98, 56, 51, 157]
131    );
132}
133
134#[doc(hidden)]
135pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
136    assert_eq!(a.len(), b.len());
137    let mut ret = Vec::with_capacity(a.len());
138    for (a, b) in a.iter().zip(b) {
139        ret.push(a ^ b);
140    }
141    ret
142}
143
144#[doc(hidden)]
145pub fn parse_frame(frame: &[u8]) -> Result<HashMap<String, String>, FromUtf8Error> {
146    let inner = String::from_utf8(frame.to_owned())?;
147    let mut ret = HashMap::new();
148    for s in inner.split(',') {
149        let mut tmp = s.splitn(2, '=');
150        let key = tmp.next();
151        let val = tmp.next();
152        if let (Some(k), Some(v)) = (key, val) {
153            ret.insert(k.to_owned(), v.to_owned());
154        }
155    }
156    Ok(ret)
157}
158
159/// Channel binding configuration.
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub enum ChannelBinding {
162    /// No channel binding data.
163    None,
164    /// Advertise that the client does not think the server supports channel binding.
165    Unsupported,
166    /// p=tls-unique channel binding data (for TLSĀ 1.2).
167    TlsUnique(Vec<u8>),
168    /// p=tls-exporter channel binding data (for TLSĀ 1.3).
169    TlsExporter(Vec<u8>),
170}
171
172impl ChannelBinding {
173    /// Return the gs2 header for this channel binding mechanism.
174    pub fn header(&self) -> &[u8] {
175        match *self {
176            ChannelBinding::None => b"n,,",
177            ChannelBinding::Unsupported => b"y,,",
178            ChannelBinding::TlsUnique(_) => b"p=tls-unique,,",
179            ChannelBinding::TlsExporter(_) => b"p=tls-exporter,,",
180        }
181    }
182
183    /// Return the channel binding data for this channel binding mechanism.
184    pub fn data(&self) -> &[u8] {
185        match *self {
186            ChannelBinding::None => &[],
187            ChannelBinding::Unsupported => &[],
188            ChannelBinding::TlsUnique(ref data) => data,
189            ChannelBinding::TlsExporter(ref data) => data,
190        }
191    }
192
193    /// Checks whether this channel binding mechanism is supported.
194    pub fn supports(&self, mechanism: &str) -> bool {
195        match *self {
196            ChannelBinding::None => false,
197            ChannelBinding::Unsupported => false,
198            ChannelBinding::TlsUnique(_) => mechanism == "tls-unique",
199            ChannelBinding::TlsExporter(_) => mechanism == "tls-exporter",
200        }
201    }
202}