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}