1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 //     http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 use std::collections::HashMap;
15 
16 use libc::c_int;
17 use ylong_http::request::uri::Uri;
18 
19 use crate::util::c_openssl::error::ErrorStack;
20 use crate::util::c_openssl::ffi::x509::{
21     EVP_DigestFinal_ex, EVP_DigestInit, EVP_DigestUpdate, EVP_MD_CTX_free, EVP_MD_CTX_new,
22     EVP_sha256,
23 };
24 use crate::util::c_openssl::ssl::{InternalError, SslError, SslErrorCode};
25 use crate::ErrorKind::Build;
26 use crate::HttpClientError;
27 
28 /// A structure that serves Certificate and Public Key Pinning.
29 /// The map key is server authority(host:port), value is Base64(sha256(Server's
30 /// Public Key)).
31 ///
32 /// # Examples
33 ///
34 /// ```
35 /// use ylong_http_client::PubKeyPins;
36 ///
37 /// let pins = PubKeyPins::builder()
38 ///     .add(
39 ///         "https://example.com",
40 ///         "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=",
41 ///     )
42 ///     .build()
43 ///     .unwrap();
44 /// ```
45 #[derive(Clone)]
46 pub struct PubKeyPins {
47     pub(crate) pub_keys: HashMap<String, String>,
48 }
49 
50 /// A builder which is used to construct `PubKeyPins`.
51 ///
52 /// # Examples
53 ///
54 /// ```
55 /// use ylong_http_client::PubKeyPinsBuilder;
56 ///
57 /// let builder = PubKeyPinsBuilder::new();
58 /// ```
59 pub struct PubKeyPinsBuilder {
60     pub_keys: Result<HashMap<String, String>, HttpClientError>,
61 }
62 
63 impl PubKeyPinsBuilder {
64     /// Creates a new `PubKeyPinsBuilder`.
65     ///
66     /// # Examples
67     ///
68     /// ```
69     /// use ylong_http_client::PubKeyPinsBuilder;
70     ///
71     /// let builder = PubKeyPinsBuilder::new();
72     /// ```
new() -> Self73     pub fn new() -> Self {
74         Self {
75             pub_keys: Ok(HashMap::new()),
76         }
77     }
78 
79     /// Sets a tuple of (server, public key digest) for `PubKeyPins`.
80     ///
81     /// # Examples
82     ///
83     /// ```
84     /// use ylong_http_client::PubKeyPinsBuilder;
85     ///
86     /// let pins = PubKeyPinsBuilder::new()
87     ///     .add(
88     ///         "https://example.com",
89     ///         "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=",
90     ///     )
91     ///     .build()
92     ///     .unwrap();
93     /// ```
add(mut self, uri: &str, digest: &str) -> Self94     pub fn add(mut self, uri: &str, digest: &str) -> Self {
95         self.pub_keys = self.pub_keys.and_then(move |mut keys| {
96             let parsed = Uri::try_from(uri).map_err(|e| HttpClientError::from_error(Build, e))?;
97             let auth = match (parsed.host(), parsed.port()) {
98                 (None, _) => {
99                     return err_from_msg!(Build, "uri has no host");
100                 }
101                 (Some(host), Some(port)) => {
102                     format!("{}:{}", host.as_str(), port.as_str())
103                 }
104                 (Some(host), None) => {
105                     format!("{}:443", host.as_str())
106                 }
107             };
108             let pub_key = String::from(digest);
109             let _ = keys.insert(auth, pub_key);
110             Ok(keys)
111         });
112         self
113     }
114 
115     /// Builds a `PubKeyPins`.
116     ///
117     /// # Examples
118     ///
119     /// ```
120     /// use ylong_http_client::PubKeyPinsBuilder;
121     ///
122     /// let pins = PubKeyPinsBuilder::new()
123     ///     .add(
124     ///         "https://example.com",
125     ///         "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=",
126     ///     )
127     ///     .build()
128     ///     .unwrap();
129     /// ```
build(self) -> Result<PubKeyPins, HttpClientError>130     pub fn build(self) -> Result<PubKeyPins, HttpClientError> {
131         Ok(PubKeyPins {
132             pub_keys: self.pub_keys?,
133         })
134     }
135 }
136 
137 impl PubKeyPins {
138     /// Creates a new builder for  `PubKeyPins`.
139     ///
140     /// # Examples
141     ///
142     /// ```
143     /// use ylong_http_client::PubKeyPins;
144     ///
145     /// let builder = PubKeyPins::builder();
146     /// ```
builder() -> PubKeyPinsBuilder147     pub fn builder() -> PubKeyPinsBuilder {
148         PubKeyPinsBuilder::new()
149     }
get_pin(&self, domain: &str) -> Option<String>150     pub(crate) fn get_pin(&self, domain: &str) -> Option<String> {
151         self.pub_keys.get(&String::from(domain)).cloned()
152     }
153 }
154 
155 /// The Default implement of `PubKeyPinsBuilder`.
156 impl Default for PubKeyPinsBuilder {
157     /// Creates a new builder for  `PubKeyPins`.
158     ///
159     /// # Examples
160     ///
161     /// ```
162     /// use ylong_http_client::PubKeyPinsBuilder;
163     ///
164     /// let builder = PubKeyPinsBuilder::default();
165     /// ```
default() -> Self166     fn default() -> Self {
167         Self::new()
168     }
169 }
170 
171 // TODO The SSLError thrown here is meaningless and has no information.
sha256_digest( pub_key: &[u8], len: c_int, digest: &mut [u8], ) -> Result<(), SslError>172 pub(crate) unsafe fn sha256_digest(
173     pub_key: &[u8],
174     len: c_int,
175     digest: &mut [u8],
176 ) -> Result<(), SslError> {
177     let md_ctx = EVP_MD_CTX_new();
178     if md_ctx.is_null() {
179         return Err(SslError {
180             code: SslErrorCode::SSL,
181             internal: Some(InternalError::Ssl(ErrorStack::get())),
182         });
183     }
184     let init = EVP_DigestInit(md_ctx, EVP_sha256());
185     if init == 0 {
186         EVP_MD_CTX_free(md_ctx);
187         return Err(SslError {
188             code: SslErrorCode::SSL,
189             internal: Some(InternalError::Ssl(ErrorStack::get())),
190         });
191     }
192     EVP_DigestUpdate(md_ctx, pub_key.as_ptr(), len);
193 
194     let start = 0;
195     EVP_DigestFinal_ex(md_ctx, digest.as_mut_ptr(), &start);
196 
197     EVP_MD_CTX_free(md_ctx);
198 
199     Ok(())
200 }
201 
202 #[cfg(test)]
203 mod ut_verify_pinning {
204     use std::collections::HashMap;
205 
206     use libc::c_int;
207 
208     use crate::util::c_openssl::verify::sha256_digest;
209     use crate::{PubKeyPins, PubKeyPinsBuilder};
210 
211     /// UT test cases for `PubKeyPins::clone`.
212     ///
213     /// # Brief
214     /// 1. Creates a `PubKeyPins`.
215     /// 2. Calls `PubKeyPins::clone` .
216     /// 3. Checks if the assert result is correct.
217     #[test]
ut_pubkey_pins_clone()218     fn ut_pubkey_pins_clone() {
219         let mut map = HashMap::new();
220         let _value = map.insert(
221             "ylong_http.com:443".to_string(),
222             "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=".to_string(),
223         );
224         let pins = PubKeyPins { pub_keys: map };
225         let pins_clone = pins.clone();
226         assert_eq!(pins.pub_keys, pins_clone.pub_keys);
227     }
228 
229     /// UT test cases for `PubKeyPinsBuilder::add`.
230     ///
231     /// # Brief
232     /// 1. Creates a `PubKeyPinsBuilder`.
233     /// 2. Calls `PubKeyPins::add` .
234     /// 3. Checks if the assert result is correct.
235     #[test]
ut_pubkey_pins_builder_add()236     fn ut_pubkey_pins_builder_add() {
237         let pins = PubKeyPins::builder()
238             .add(
239                 "/data/storage",
240                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
241             )
242             .build()
243             .err();
244         assert_eq!(
245             format!("{:?}", pins.unwrap()),
246             "HttpClientError { ErrorKind: Build, Cause: uri has no host }"
247         );
248         let pins = PubKeyPinsBuilder::default()
249             .add(
250                 "https://ylong_http.com",
251                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
252             )
253             .build()
254             .unwrap();
255         assert_eq!(
256             pins.get_pin("ylong_http.com:443"),
257             Some("sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=".to_string())
258         );
259     }
260 
261     /// UT test cases for `sha256_digest.
262     ///
263     /// # Brief
264     /// 1. Calls `sha256_digest` .
265     /// 2. Checks if the assert result is correct.
266     #[test]
ut_pubkey_sha256_digest()267     fn ut_pubkey_sha256_digest() {
268         let pubkey =
269             bytes_from_hex("d0e8b8f11c98f369016eb2ed3c541e1f01382f9d5b3104c9ffd06b6175a46271")
270                 .unwrap();
271 
272         let key_words = Vec::from("Hello, SHA-256!");
273 
274         let mut hash = [0u8; 32];
275         assert!(unsafe {
276             sha256_digest(key_words.as_slice(), key_words.len() as c_int, &mut hash)
277         }
278         .is_ok());
279 
280         assert_eq!(hash.as_slice(), pubkey.as_slice());
281     }
282 
bytes_from_hex(str: &str) -> Option<Vec<u8>>283     fn bytes_from_hex(str: &str) -> Option<Vec<u8>> {
284         if str.len() % 2 != 0 {
285             return None;
286         }
287         let mut vec = Vec::new();
288         let mut remained = str;
289         while !remained.is_empty() {
290             let (left, right) = remained.split_at(2);
291             match u8::from_str_radix(left, 16) {
292                 Ok(num) => vec.push(num),
293                 Err(_) => return None,
294             }
295             remained = right;
296         }
297         Some(vec)
298     }
299 }
300