rust_rcs_core/security/authentication/
digest.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
15extern crate data_encoding;
16extern crate md5;
17extern crate ring;
18
19use data_encoding::HEXLOWER;
20
21use md5::{Digest, Md5};
22
23use ring::digest;
24use ring::digest::SHA256;
25
26use crate::ffi::log::platform_log;
27use crate::internet::syntax;
28
29use super::challenge::Challenge;
30
31const LOG_TAG: &str = "digest";
32
33pub struct DigestChallengeParams<'a> {
34    pub realm: &'a [u8],
35    pub nonce: &'a [u8],
36    pub algorithm: &'a [u8],
37
38    pub domain: Option<&'a [u8]>,
39    pub opaque: Option<&'a [u8]>,
40    pub qop: Option<&'a [u8]>,
41}
42
43impl<'a> DigestChallengeParams<'a> {
44    pub fn from_challenge(challenge: Challenge<'a>) -> Option<DigestChallengeParams<'a>> {
45        let mut realm: Option<&'a [u8]> = None;
46        let mut nonce: Option<&'a [u8]> = None;
47        let mut algorithm: Option<&'a [u8]> = None;
48
49        let mut domain: Option<&'a [u8]> = None;
50        let mut opaque: Option<&'a [u8]> = None;
51        let mut qop: Option<&'a [u8]> = None; // MANDATORY since rfc7616
52
53        for param in challenge.get_params() {
54            match param.name {
55                b"realm" => {
56                    realm = match param.value {
57                        Some(v) => Some(syntax::unquote(v)),
58                        None => None,
59                    };
60                }
61
62                b"nonce" => {
63                    nonce = match param.value {
64                        Some(v) => Some(syntax::unquote(v)),
65                        None => None,
66                    };
67                }
68
69                b"algorithm" => {
70                    algorithm = param.value;
71                }
72
73                b"domain" => {
74                    domain = match param.value {
75                        Some(v) => Some(syntax::unquote(v)),
76                        None => None,
77                    };
78                }
79
80                b"opaque" => {
81                    opaque = match param.value {
82                        Some(v) => Some(syntax::unquote(v)),
83                        None => None,
84                    };
85                }
86
87                b"qop" => {
88                    qop = match param.value {
89                        Some(v) => Some(syntax::unquote(v)),
90                        None => None,
91                    };
92                }
93
94                _ => {}
95            }
96        }
97
98        if let (Some(realm), Some(nonce)) = (realm, nonce) {
99            if let Some(algorithm) = algorithm {
100                Some(DigestChallengeParams {
101                    realm,
102                    nonce,
103                    algorithm,
104
105                    domain,
106                    opaque,
107                    qop,
108                })
109            } else {
110                Some(DigestChallengeParams {
111                    realm,
112                    nonce,
113                    algorithm: b"MD5",
114
115                    domain,
116                    opaque,
117                    qop,
118                })
119            }
120        } else {
121            None
122        }
123    }
124
125    pub fn preferred_qop(&self, have_entity: bool) -> Option<&'a [u8]> {
126        if let Some(qop) = self.qop {
127            let mut auth: Option<&[u8]> = None;
128            let mut auth_int: Option<&[u8]> = None;
129            let mut iter = qop.split(|c| *c == b',');
130            while let Some(qop) = iter.next() {
131                if qop == b"auth" {
132                    auth.replace(qop);
133                } else if qop == b"auth-int" {
134                    auth_int.replace(qop);
135                }
136            }
137
138            if let Some(auth_int) = auth_int {
139                if have_entity {
140                    return Some(auth_int);
141                }
142            }
143
144            if let Some(auth) = auth {
145                return Some(auth);
146            }
147
148            if let Some(auth_int) = auth_int {
149                return Some(auth_int);
150            }
151        }
152
153        None
154    }
155
156    fn create_response(
157        &self,
158        username: &[u8],
159        uri: &[u8],
160        credentials: &DigestCredentials,
161        hash_algorithm: &[u8],
162    ) -> Result<(String, Option<&[u8]>), ErrorKind> {
163        let mut hash_function = HashFunction::new(hash_algorithm)?;
164
165        hash_function.update(username);
166        hash_function.update(b":");
167        hash_function.update(self.realm);
168        hash_function.update(b":");
169        hash_function.update(&credentials.password);
170
171        let a1 = hash_function.finish();
172
173        platform_log(LOG_TAG, format!("on intermediate result a1: {}", &a1));
174
175        let mut hash_function = HashFunction::new(hash_algorithm)?;
176
177        if let Some(method) = &credentials.client_data {
178            hash_function.update(method);
179        }
180
181        hash_function.update(b":");
182        hash_function.update(uri);
183
184        let qop;
185
186        if let Some(entity_digest) = &credentials.entity_digest {
187            qop = self.preferred_qop(true);
188            if let Some(b"auth-int") = qop {
189                hash_function.update(b":");
190                hash_function.update(entity_digest);
191            }
192        } else {
193            qop = self.preferred_qop(false);
194            if let Some(b"auth-int") = qop {
195                hash_function.update(b":");
196                let empty = empty_digest(hash_algorithm)?;
197                hash_function.update(empty.as_bytes());
198            }
199        }
200
201        let a2 = hash_function.finish();
202
203        platform_log(LOG_TAG, format!("on intermediate result a2: {}", &a2));
204
205        let mut hash_function = HashFunction::new(hash_algorithm)?;
206
207        hash_function.update(a1.as_bytes());
208        hash_function.update(b":");
209        hash_function.update(self.nonce);
210        hash_function.update(b":");
211
212        match qop {
213            Some(qop) => {
214                if qop == b"auth" || qop == b"auth-int" {
215                    if let Some((cnonce, nc)) = &credentials.client_nonce {
216                        hash_function.update(format!("{:08x}", nc).as_bytes());
217                        hash_function.update(b":");
218                        hash_function.update(cnonce);
219                        hash_function.update(b":");
220                        hash_function.update(qop);
221                        hash_function.update(b":");
222                    } else {
223                        return Err(ErrorKind::MissingInput);
224                    }
225                }
226            }
227
228            _ => {}
229        }
230
231        hash_function.update(a2.as_bytes());
232
233        Ok((hash_function.finish(), qop))
234    }
235
236    pub fn to_challenge(&self) -> DigestChallenge {
237        DigestChallenge {
238            realm: self.realm.to_vec(),
239            nonce: self.nonce.to_vec(),
240            algorithm: self.algorithm.to_vec(),
241
242            domain: match self.domain {
243                Some(domain) => Some(domain.to_vec()),
244                None => None,
245            },
246
247            opaque: match self.opaque {
248                Some(opaque) => Some(opaque.to_vec()),
249                None => None,
250            },
251
252            qop: match self.qop {
253                Some(qop) => Some(qop.to_vec()),
254                None => None,
255            },
256        }
257    }
258}
259
260pub struct DigestChallenge {
261    pub realm: Vec<u8>,
262    pub nonce: Vec<u8>,
263    pub algorithm: Vec<u8>,
264
265    pub domain: Option<Vec<u8>>,
266    pub opaque: Option<Vec<u8>>,
267    pub qop: Option<Vec<u8>>,
268}
269
270impl DigestChallenge {
271    pub fn as_challenge_param(&self) -> DigestChallengeParams {
272        DigestChallengeParams {
273            realm: &self.realm,
274            nonce: &self.nonce,
275            algorithm: &self.algorithm,
276
277            domain: match &self.domain {
278                Some(domain) => Some(&domain),
279                None => None,
280            },
281
282            opaque: match &self.opaque {
283                Some(opaque) => Some(&opaque),
284                None => None,
285            },
286
287            qop: match &self.qop {
288                Some(qop) => Some(&qop),
289                None => None,
290            },
291        }
292    }
293}
294
295pub struct DigestCredentials {
296    pub password: Vec<u8>,
297
298    pub client_data: Option<Vec<u8>>,
299    pub client_nonce: Option<(Vec<u8>, u32)>,
300    pub entity_digest: Option<Vec<u8>>,
301
302    pub extra_params: Vec<(Vec<u8>, Vec<u8>)>,
303}
304
305// to-do: use referenced values
306pub struct DigestAnswerParams {
307    pub realm: Vec<u8>,
308    pub algorithm: Option<Vec<u8>>,
309
310    pub username: Vec<u8>,
311    pub uri: Vec<u8>,
312
313    pub challenge: Option<DigestChallenge>,
314
315    pub credentials: Option<DigestCredentials>,
316}
317
318impl DigestAnswerParams {
319    pub fn make_authorization_header(
320        &self,
321        hash_algorithm: Option<&[u8]>,
322        with_empty_nonce: bool,
323        with_empty_response: bool,
324    ) -> Result<Vec<u8>, ErrorKind> {
325        platform_log(LOG_TAG, format!("make authorization header with algorithm={} username={} password={} method={} realm={} uri={} qop={} nonce={} cnonce={} nc={}", if let Some(hash_algorithm) = &hash_algorithm{
326            String::from_utf8_lossy(hash_algorithm)
327        } else {
328            std::borrow::Cow::Borrowed("")
329        }, String::from_utf8_lossy(&self.username),
330        if let Some(credentials) = &self.credentials {
331            HEXLOWER.encode(&credentials.password)
332        } else {
333            String::from("")
334        },
335        if let Some(credentials) = &self.credentials {
336            if let Some(client_data) = &credentials.client_data {
337                String::from_utf8_lossy(client_data)
338            } else {
339                std::borrow::Cow::Borrowed("")
340            }
341        } else {
342            std::borrow::Cow::Borrowed("")
343        },
344        if let Some(challenge) = &self.challenge {
345            String::from_utf8_lossy(&challenge.realm)
346        } else {
347            String::from_utf8_lossy(&self.realm)
348        },
349        String::from_utf8_lossy(&self.uri),
350        if let Some(challenge) = &self.challenge {
351            let mut have_entity = false;
352            if let Some(credentials) = &self.credentials {
353                if let Some(_) = credentials.entity_digest {
354                    have_entity = true;
355                }
356            }
357            if let Some(qop) = challenge.as_challenge_param().preferred_qop(have_entity) {
358                String::from_utf8_lossy(qop)
359            } else {
360                std::borrow::Cow::Borrowed("")
361            }
362        } else {
363            std::borrow::Cow::Borrowed("")
364        },
365        if let Some(challenge) = &self.challenge {
366            String::from_utf8_lossy(&challenge.nonce)
367        } else {
368            std::borrow::Cow::Borrowed("")
369        },
370        if let Some(credentials) = &self.credentials {
371            if let Some((client_nonce, _)) = &credentials.client_nonce {
372                String::from_utf8_lossy(client_nonce)
373            } else {
374                std::borrow::Cow::Borrowed("")
375            }
376        } else {
377            std::borrow::Cow::Borrowed("")
378        },
379        if let Some(credentials) = &self.credentials {
380            if let Some((_, nc)) = &credentials.client_nonce {
381                format!("{}", nc)
382            } else {
383                String::from("")
384            }
385        } else {
386            String::from("")
387        },
388    ));
389
390        let mut v = Vec::new();
391
392        v.extend_from_slice(b"Digest ");
393        v.extend_from_slice(b"username=\"");
394        v.extend_from_slice(&self.username);
395        v.extend_from_slice(b"\"");
396        if let Some(challenge) = &self.challenge {
397            v.extend_from_slice(b",realm=\"");
398            v.extend_from_slice(&challenge.realm);
399            v.extend_from_slice(b"\"");
400        } else {
401            v.extend_from_slice(b",realm=\"");
402            v.extend_from_slice(&self.realm);
403            v.extend_from_slice(b"\"");
404        }
405        v.extend_from_slice(b",uri=\"");
406        v.extend_from_slice(&self.uri);
407        v.extend_from_slice(b"\"");
408
409        if let Some(algorithm) = &self.algorithm {
410            v.extend_from_slice(b",algorithm=");
411            v.extend_from_slice(&algorithm);
412        }
413
414        if let Some(challenge) = &self.challenge {
415            if let Some(domain) = &challenge.domain {
416                v.extend_from_slice(b",domain=\"");
417                v.extend_from_slice(&domain);
418                v.extend_from_slice(b"\"");
419            }
420
421            let mut have_entity = false;
422            if let Some(credentials) = &self.credentials {
423                if let Some(_) = credentials.entity_digest {
424                    have_entity = true;
425                }
426            }
427
428            if let Some(qop) = challenge.as_challenge_param().preferred_qop(have_entity) {
429                v.extend_from_slice(b",qop=");
430                v.extend_from_slice(qop);
431            }
432
433            v.extend_from_slice(b",nonce=\"");
434            v.extend_from_slice(&challenge.nonce);
435            v.extend_from_slice(b"\"");
436        } else {
437            if with_empty_nonce {
438                v.extend_from_slice(b",nonce=\"");
439                v.extend_from_slice(b"");
440                v.extend_from_slice(b"\"");
441            }
442        }
443
444        let mut response_appended = false;
445
446        if let Some(credentials) = &self.credentials {
447            if let Some((cnonce, nc)) = &credentials.client_nonce {
448                v.extend_from_slice(b",cnonce=\"");
449                v.extend_from_slice(&cnonce);
450                v.extend_from_slice(b"\",nc=");
451                v.extend_from_slice(format!("{:08x}", nc).as_bytes());
452            }
453
454            if let (Some(challenge), Some(algorithm)) = (&self.challenge, hash_algorithm) {
455                if let Ok((response, qop)) = challenge.as_challenge_param().create_response(
456                    &self.username,
457                    &self.uri,
458                    &credentials,
459                    algorithm,
460                ) {
461                    v.extend_from_slice(b",response=\"");
462                    v.extend_from_slice(response.as_bytes());
463                    v.extend_from_slice(b"\"");
464                    response_appended = true;
465                }
466            }
467        }
468
469        if !response_appended && with_empty_response {
470            v.extend_from_slice(b",response=\"");
471            v.extend_from_slice(b"");
472            v.extend_from_slice(b"\"");
473        }
474
475        if let Some(credentials) = &self.credentials {
476            for (extra_param_name, extra_param_value) in &credentials.extra_params {
477                v.extend_from_slice(b",");
478                v.extend_from_slice(extra_param_name);
479                v.extend_from_slice(b"=\"");
480                v.extend_from_slice(extra_param_value);
481                v.extend_from_slice(b"\"");
482            }
483        }
484
485        if let Some(challenge) = &self.challenge {
486            if let Some(opaque) = &challenge.opaque {
487                v.extend_from_slice(b",opaque=\"");
488                v.extend_from_slice(&opaque);
489                v.extend_from_slice(b"\"");
490            }
491        }
492
493        Ok(v)
494    }
495}
496
497pub enum ErrorKind {
498    MissingInput,
499    NoSuchAlgorithm,
500}
501
502enum HashFunction {
503    MD5(Md5),
504    SHA256(digest::Context),
505}
506
507impl HashFunction {
508    fn new(algorithm: &[u8]) -> Result<HashFunction, ErrorKind> {
509        if algorithm.eq_ignore_ascii_case(b"md5") {
510            Ok(HashFunction::MD5(Md5::new()))
511        } else if algorithm.eq_ignore_ascii_case(b"sha256")
512            || algorithm.eq_ignore_ascii_case(b"sha-256")
513        {
514            Ok(HashFunction::SHA256(digest::Context::new(&SHA256)))
515        } else {
516            Err(ErrorKind::NoSuchAlgorithm)
517        }
518    }
519
520    fn update(&mut self, data: &[u8]) {
521        match self {
522            HashFunction::MD5(md5) => md5.update(data),
523
524            HashFunction::SHA256(context) => context.update(data),
525        }
526    }
527
528    fn finish(self) -> String {
529        match self {
530            HashFunction::MD5(md5) => HEXLOWER.encode(&md5.finalize()),
531
532            HashFunction::SHA256(context) => HEXLOWER.encode(context.finish().as_ref()),
533        }
534    }
535}
536
537pub fn empty_digest(algorithm: &[u8]) -> Result<String, ErrorKind> {
538    match algorithm {
539        b"md5" => {
540            let md5 = Md5::new();
541            Ok(HEXLOWER.encode(&md5.finalize()))
542        }
543
544        b"sha256" | b"sha-256" => {
545            let context = digest::Context::new(&SHA256);
546            Ok(HEXLOWER.encode(context.finish().as_ref()))
547        }
548
549        _ => Err(ErrorKind::NoSuchAlgorithm),
550    }
551}