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