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 channel 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,
89 iterations,
90 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.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 if let (Some(k), Some(v)) = (key, val) {
152 ret.insert(k.to_owned(), v.to_owned());
153 }
154 }
155 Ok(ret)
156}
157
158/// Channel binding configuration.
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub enum ChannelBinding {
161 /// No channel binding data.
162 None,
163 /// Advertise that the client does not think the server supports channel binding.
164 Unsupported,
165 /// p=tls-unique channel binding data (for TLSĀ 1.2).
166 TlsUnique(Vec<u8>),
167 /// p=tls-exporter channel binding data (for TLSĀ 1.3).
168 TlsExporter(Vec<u8>),
169}
170
171impl ChannelBinding {
172 /// Return the gs2 header for this channel binding mechanism.
173 pub fn header(&self) -> &[u8] {
174 match *self {
175 ChannelBinding::None => b"n,,",
176 ChannelBinding::Unsupported => b"y,,",
177 ChannelBinding::TlsUnique(_) => b"p=tls-unique,,",
178 ChannelBinding::TlsExporter(_) => b"p=tls-exporter,,",
179 }
180 }
181
182 /// Return the channel binding data for this channel binding mechanism.
183 pub fn data(&self) -> &[u8] {
184 match *self {
185 ChannelBinding::None => &[],
186 ChannelBinding::Unsupported => &[],
187 ChannelBinding::TlsUnique(ref data) => data,
188 ChannelBinding::TlsExporter(ref data) => data,
189 }
190 }
191
192 /// Checks whether this channel binding mechanism is supported.
193 pub fn supports(&self, mechanism: &str) -> bool {
194 match *self {
195 ChannelBinding::None => false,
196 ChannelBinding::Unsupported => false,
197 ChannelBinding::TlsUnique(_) => mechanism == "tls-unique",
198 ChannelBinding::TlsExporter(_) => mechanism == "tls-exporter",
199 }
200 }
201}