rust_rcs_core/security/
mod.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
15pub mod aka;
16pub mod authentication;
17pub mod gba;
18
19use std::sync::{Arc, Mutex};
20
21use cached::{Cached, TimedSizedCache};
22
23use crate::internet::{syntax, Header};
24use crate::util::rand;
25
26use self::authentication::authentication_info::AsAuthenticationInfo;
27use self::{
28    authentication::digest::{DigestAnswerParams, DigestChallenge, DigestCredentials},
29    gba::{get_gba_realm, GbaContext},
30};
31
32pub struct CachedDigestParameter {
33    pub algorithm: Vec<u8>,
34    pub realm: Vec<u8>,
35    pub uri: Vec<u8>,
36    pub qop: Option<Vec<u8>>,
37    pub next_nonce: Vec<u8>,
38}
39
40pub struct SecurityContext {
41    cache: Arc<Mutex<TimedSizedCache<String, CachedDigestParameter>>>,
42}
43
44impl SecurityContext {
45    pub fn new() -> SecurityContext {
46        SecurityContext {
47            cache: Arc::new(Mutex::new(
48                TimedSizedCache::with_size_and_lifespan_and_refresh(32, 60 * 60, true),
49            )),
50        }
51    }
52
53    pub fn preload_auth(
54        &self,
55        gba_context: &Arc<GbaContext>,
56        host: &str,
57        cipher_id: Option<(u8, u8)>,
58        method: &[u8],
59        body_digest: Option<&[u8]>,
60    ) -> Option<DigestAnswerParams> {
61        if let Some(param) = self.cache.lock().unwrap().cache_get(host) {
62            if let Some(_) = get_gba_realm(&param.realm) {
63                if let Some(bootstrapped_context) = gba_context.try_get_bootstrapped_context() {
64                    if let Ok(credential) =
65                        bootstrapped_context.get_credential(host.as_bytes(), cipher_id)
66                    {
67                        let cnonce = rand::create_raw_alpha_numeric_string(16);
68                        let nc = bootstrapped_context.increase_and_get_use_count();
69
70                        let digest_answer = DigestAnswerParams {
71                            realm: syntax::unquote(&param.realm).to_vec(),
72                            algorithm: Some(param.algorithm.to_vec()),
73
74                            username: credential.username,
75                            uri: syntax::unquote(&param.uri).to_vec(),
76
77                            challenge: Some(DigestChallenge {
78                                realm: param.realm.to_vec(),
79                                nonce: param.next_nonce.to_vec(),
80                                algorithm: param.algorithm.to_vec(),
81                                domain: None,
82                                opaque: None,
83                                qop: param.qop.clone(),
84                            }),
85
86                            credentials: Some(DigestCredentials {
87                                password: credential.password,
88                                client_data: Some(method.to_vec()),
89                                client_nonce: Some((cnonce, nc)),
90                                entity_digest: match body_digest {
91                                    Some(body_digest) => Some(body_digest.to_vec()),
92                                    None => None,
93                                },
94                                extra_params: Vec::new(),
95                            }),
96                        };
97
98                        return Some(digest_answer);
99                    }
100                }
101            }
102        }
103
104        None
105    }
106
107    pub fn update_auth_info(
108        &self,
109        authentication_info_header: &Header,
110        host: &str,
111        uri: &[u8],
112        challenge: &DigestChallenge,
113        have_entity: bool,
114    ) {
115        let authentication_info = authentication_info_header
116            .get_value()
117            .as_authentication_info();
118        if let Some(next_nonce) = authentication_info.next_nonce {
119            self.cache.lock().unwrap().cache_set(
120                String::from(host),
121                CachedDigestParameter {
122                    algorithm: challenge.algorithm.to_vec(),
123                    realm: challenge.realm.to_vec(),
124                    uri: uri.to_vec(),
125                    qop: match challenge.as_challenge_param().preferred_qop(have_entity) {
126                        Some(qop) => Some(qop.to_vec()),
127                        None => None,
128                    },
129                    next_nonce: next_nonce.to_vec(),
130                },
131            );
132        }
133    }
134}