rust_rcs_core/security/authentication/
challenge.rs

1// Copyright 2023 宋昊文
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::ffi::log::platform_log;
16use crate::internet::parameter::ParameterParser;
17use crate::internet::syntax;
18
19use super::auth_param::AuthParamParser;
20
21const LOG_TAG: &str = "auth";
22
23const CHARSET_TOKEN_68: [u8; 68] = [
24    b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
25    b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'A', b'B', b'C', b'D', b'E', b'F',
26    b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V',
27    b'W', b'X', b'Y', b'Z', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'-', b'.',
28    b'_', b'~', b'+', b'/',
29];
30
31pub struct Challenge<'a> {
32    pub auth_scheme: &'a [u8],
33
34    params: &'a [u8],
35}
36
37impl<'a> Challenge<'a> {
38    pub fn get_params(&self) -> ParameterParser<'a> {
39        ParameterParser::new(self.params, b',', true)
40    }
41}
42
43enum State<'a> {
44    ExpectingScheme,
45    ExpectingTokenOrParam(&'a [u8], usize),
46    ExpectingParamOrScheme(&'a [u8], usize),
47}
48
49/// Parse Http/Sip WWW-Authenticate response Challenge per RFC 7235
50///
51/// # Examples
52///
53/// ```
54/// use rust_rcs_core::security::authentication::challenge::ChallengeParser;
55///
56/// let a = b"Newauth realm=\"apps\", type=1,\n                       title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"";
57/// let mut parser = ChallengeParser::new(a);
58///
59/// let b = parser.next().unwrap();
60/// assert_eq!(b.auth_scheme, b"Newauth");
61///
62/// let mut p = b.get_params();
63/// let c = p.next().unwrap();
64/// assert_eq!(c.name, b"realm");
65/// assert_eq!(c.value.unwrap(), b"\"apps\"");
66/// let c = p.next().unwrap();
67/// assert_eq!(c.name, b"type");
68/// assert_eq!(c.value.unwrap(), b"1");
69/// let c = p.next().unwrap();
70/// assert_eq!(c.name, b"title");
71/// assert_eq!(c.value.unwrap(), b"\"Login to \\\"apps\\\"\"");
72///
73/// let b = parser.next().unwrap();
74/// assert_eq!(b.auth_scheme, b"Basic");
75///
76/// let mut p = b.get_params();
77/// let c = p.next().unwrap();
78/// assert_eq!(c.name, b"realm");
79/// assert_eq!(c.value.unwrap(), b"\"simple\"");
80///
81/// let a = b"Digest realm=\"example.com\", qop=\"auth\", algorithm=SHA-256, nonce=\"ce696741c46032ba0470d841551f3a8be8e0cc3e2591353bd6bc822436cc8615b56706342a90a3e13c2fd94debdc839000a8b50acb64612f998d93ce628605c1\"";
82/// let mut parser = ChallengeParser::new(a);
83///
84/// let b = parser.next().unwrap();
85/// assert_eq!(b.auth_scheme, b"Digest");
86///
87/// let mut p = b.get_params();
88/// let c = p.next().unwrap();
89/// assert_eq!(c.name, b"realm");
90/// assert_eq!(c.value.unwrap(), b"\"example.com\"");
91///
92/// let c = p.next().unwrap();
93/// assert_eq!(c.name, b"qop");
94/// assert_eq!(c.value.unwrap(), b"\"auth\"");
95///
96/// let c = p.next().unwrap();
97/// assert_eq!(c.name, b"algorithm");
98/// assert_eq!(c.value.unwrap(), b"SHA-256");
99///
100/// let c = p.next().unwrap();
101/// assert_eq!(c.name, b"nonce");
102/// assert_eq!(c.value.unwrap(), b"\"ce696741c46032ba0470d841551f3a8be8e0cc3e2591353bd6bc822436cc8615b56706342a90a3e13c2fd94debdc839000a8b50acb64612f998d93ce628605c1\"");
103///
104/// let c = p.next().is_none();
105/// assert_eq!(c, true);
106///
107/// let b = parser.next().is_none();
108/// assert_eq!(b, true);
109/// ```
110pub struct ChallengeParser<'a> {
111    state: State<'a>,
112    pub s: &'a [u8],
113    pub p: usize,
114}
115
116impl<'a> ChallengeParser<'a> {
117    pub fn new(s: &'a [u8]) -> ChallengeParser<'a> {
118        ChallengeParser {
119            state: State::ExpectingScheme,
120            s,
121            p: 0,
122        }
123    }
124}
125
126impl<'a> Iterator for ChallengeParser<'a> {
127    type Item = Challenge<'a>;
128    fn next(&mut self) -> Option<Challenge<'a>> {
129        if self.p < self.s.len() {
130            loop {
131                match self.state {
132                    State::ExpectingScheme => {
133                        let (scheme, advance) = if let Some(idx) =
134                            syntax::index_with_token_escaping(&self.s[self.p..], b' ')
135                        {
136                            (&self.s[self.p..self.p + idx], idx)
137                        } else {
138                            (&self.s[self.p..], self.s.len() - self.p)
139                        };
140
141                        let scheme = syntax::trim(scheme);
142                        if scheme.len() == 0 {
143                            self.p = self.p + advance;
144                        } else {
145                            if self.p + advance + 1 < self.s.len() {
146                                self.p = self.p
147                                    + advance
148                                    + syntax::index_skipping_ows_and_obs_fold(
149                                        &self.s[self.p + advance + 1..],
150                                    )
151                                    + 1;
152
153                                self.state = State::ExpectingTokenOrParam(scheme, self.p);
154                            } else {
155                                break;
156                            }
157                        }
158                    }
159
160                    State::ExpectingTokenOrParam(scheme, start_of_params) => {
161                        let (chunk, advance) = if let Some(idx) =
162                            syntax::index_with_token_escaping(&self.s[self.p..], b' ')
163                        {
164                            (&self.s[self.p..self.p + idx], idx)
165                        } else {
166                            (&self.s[self.p..], self.s.len() - self.p)
167                        };
168
169                        let chunk = syntax::trim(chunk);
170                        let mut is_token = true;
171                        for c in chunk {
172                            if !CHARSET_TOKEN_68.contains(c) {
173                                is_token = false;
174                                break;
175                            }
176                        }
177
178                        if is_token {
179                            self.state = State::ExpectingScheme;
180
181                            self.p = self.p
182                                + advance
183                                + syntax::index_skipping_ows_and_obs_fold(
184                                    &self.s[self.p + advance + 1..],
185                                )
186                                + 1;
187
188                            return Some(Challenge {
189                                auth_scheme: scheme,
190                                params: &self.s[start_of_params..self.p + advance],
191                            });
192                        } else {
193                            if let Some((_, advance)) =
194                                self.s[self.p..self.p + advance].try_auth_param()
195                            {
196                                if self.p + advance + 1 < self.s.len() {
197                                    self.p = self.p
198                                        + advance
199                                        + syntax::index_skipping_ows_and_obs_fold(
200                                            &self.s[self.p + advance + 1..],
201                                        )
202                                        + 1;
203                                } else {
204                                    self.p += advance;
205                                }
206                                self.state = State::ExpectingParamOrScheme(scheme, start_of_params);
207                            } else {
208                                platform_log(
209                                    LOG_TAG,
210                                    "neither token68 nor auth-param detected in challenge string",
211                                );
212                                break;
213                            }
214                        }
215                    }
216
217                    State::ExpectingParamOrScheme(scheme, start_of_params) => {
218                        let (chunk, advance) = if let Some(idx) =
219                            syntax::index_with_token_escaping(&self.s[self.p..], b' ')
220                        {
221                            (&self.s[self.p..self.p + idx], idx)
222                        } else {
223                            (&self.s[self.p..], self.s.len() - self.p)
224                        };
225
226                        if let Some((_, advance)) =
227                            self.s[self.p..self.p + advance].try_auth_param()
228                        {
229                            if self.p + advance + 1 < self.s.len() {
230                                self.p = self.p
231                                    + advance
232                                    + syntax::index_skipping_ows_and_obs_fold(
233                                        &self.s[self.p + advance + 1..],
234                                    )
235                                    + 1;
236                            } else {
237                                self.p += advance;
238                            }
239                        } else {
240                            let chunk = syntax::trim(chunk);
241
242                            let challenge = Challenge {
243                                auth_scheme: scheme,
244                                params: &self.s[start_of_params..self.p],
245                            };
246
247                            if self.p + advance + 1 < self.s.len() {
248                                self.p = self.p
249                                    + advance
250                                    + syntax::index_skipping_ows_and_obs_fold(
251                                        &self.s[self.p + advance + 1..],
252                                    )
253                                    + 1;
254
255                                self.state = State::ExpectingTokenOrParam(chunk, self.p);
256
257                                return Some(challenge);
258                            } else {
259                                self.p = self.p + advance;
260
261                                self.state = State::ExpectingTokenOrParam(chunk, self.p);
262
263                                return Some(challenge);
264                            }
265                        }
266                    }
267                }
268            }
269        }
270
271        None
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::ChallengeParser;
278
279    #[test]
280    fn test_decode() {
281        let a = b"Newauth realm=\"apps\", type=1,\n                       title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"";
282        let mut parser = ChallengeParser::new(a);
283
284        let b = parser.next().unwrap();
285        assert_eq!(b.auth_scheme, b"Newauth");
286
287        let mut p = b.get_params();
288        let c = p.next().unwrap();
289        assert_eq!(c.name, b"realm");
290        assert_eq!(c.value.unwrap(), b"\"apps\"");
291        let c = p.next().unwrap();
292        assert_eq!(c.name, b"type");
293        assert_eq!(c.value.unwrap(), b"1");
294        let c = p.next().unwrap();
295        assert_eq!(c.name, b"title");
296        assert_eq!(c.value.unwrap(), b"\"Login to \\\"apps\\\"\"");
297
298        let b = parser.next().unwrap();
299        assert_eq!(b.auth_scheme, b"Basic");
300
301        let mut p = b.get_params();
302        let c = p.next().unwrap();
303        assert_eq!(c.name, b"realm");
304        assert_eq!(c.value.unwrap(), b"\"simple\"");
305
306        let a = b"Digest realm=\"example.com\", qop=\"auth\", algorithm=SHA-256, nonce=\"ce696741c46032ba0470d841551f3a8be8e0cc3e2591353bd6bc822436cc8615b56706342a90a3e13c2fd94debdc839000a8b50acb64612f998d93ce628605c1\"";
307        let mut parser = ChallengeParser::new(a);
308
309        let b = parser.next().unwrap();
310        assert_eq!(b.auth_scheme, b"Digest");
311
312        let mut p = b.get_params();
313        let c = p.next().unwrap();
314        assert_eq!(c.name, b"realm");
315        assert_eq!(c.value.unwrap(), b"\"example.com\"");
316
317        let c = p.next().unwrap();
318        assert_eq!(c.name, b"qop");
319        assert_eq!(c.value.unwrap(), b"\"auth\"");
320
321        let c = p.next().unwrap();
322        assert_eq!(c.name, b"algorithm");
323        assert_eq!(c.value.unwrap(), b"SHA-256");
324
325        let c = p.next().unwrap();
326        assert_eq!(c.name, b"nonce");
327        assert_eq!(c.value.unwrap(), b"\"ce696741c46032ba0470d841551f3a8be8e0cc3e2591353bd6bc822436cc8615b56706342a90a3e13c2fd94debdc839000a8b50acb64612f998d93ce628605c1\"");
328
329        let c = p.next().is_none();
330        assert_eq!(c, true);
331
332        let b = parser.next().is_none();
333        assert_eq!(b, true);
334    }
335}