• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, PinsVerifyInfo>,
48 }
49 
50 #[derive(Debug, PartialEq, Clone)]
51 pub(crate) struct PinsVerifyInfo {
52     strategy: PinsVerifyStrategy,
53     digest: String,
54 }
55 
56 #[derive(Debug, PartialEq, Clone)]
57 enum PinsVerifyStrategy {
58     RootCertificate,
59     LeafCertificate,
60 }
61 
62 impl PinsVerifyInfo {
new(strategy: PinsVerifyStrategy, digest: &str) -> Self63     fn new(strategy: PinsVerifyStrategy, digest: &str) -> Self {
64         Self {
65             strategy,
66             digest: digest.to_string(),
67         }
68     }
69 
is_root(&self) -> bool70     pub fn is_root(&self) -> bool {
71         matches!(self.strategy, PinsVerifyStrategy::RootCertificate)
72     }
73 
get_digest(&self) -> &str74     pub fn get_digest(&self) -> &str {
75         &self.digest
76     }
77 }
78 
79 /// A builder which is used to construct `PubKeyPins`.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use ylong_http_client::PubKeyPinsBuilder;
85 ///
86 /// let builder = PubKeyPinsBuilder::new();
87 /// ```
88 pub struct PubKeyPinsBuilder {
89     pub_keys: Result<HashMap<String, PinsVerifyInfo>, HttpClientError>,
90 }
91 
92 impl PubKeyPinsBuilder {
93     /// Creates a new `PubKeyPinsBuilder`.
94     ///
95     /// # Examples
96     ///
97     /// ```
98     /// use ylong_http_client::PubKeyPinsBuilder;
99     ///
100     /// let builder = PubKeyPinsBuilder::new();
101     /// ```
new() -> Self102     pub fn new() -> Self {
103         Self {
104             pub_keys: Ok(HashMap::new()),
105         }
106     }
107 
108     /// Sets a tuple of (server, public key digest) for `PubKeyPins`, using
109     /// the server certificate pinning strategy.
110     ///
111     /// # Examples
112     ///
113     /// ```
114     /// use ylong_http_client::PubKeyPinsBuilder;
115     ///
116     /// let pins = PubKeyPinsBuilder::new()
117     ///     .add(
118     ///         "https://example.com",
119     ///         "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=",
120     ///     )
121     ///     .build()
122     ///     .unwrap();
123     /// ```
add(mut self, uri: &str, digest: &str) -> Self124     pub fn add(mut self, uri: &str, digest: &str) -> Self {
125         self.pub_keys = self.pub_keys.and_then(move |mut keys| {
126             let auth = parse_uri(uri)?;
127             let info = PinsVerifyInfo::new(PinsVerifyStrategy::LeafCertificate, digest);
128             let _ = keys.insert(auth, info);
129             Ok(keys)
130         });
131         self
132     }
133 
134     /// Sets a tuple of (server, public key digest) for `PubKeyPins`, using
135     /// the root certificate pinning strategy.
136     /// <div class="warning">
137     /// Ensure that the server returns the complete certificate chain, including
138     /// the root certificate; otherwise, the client's public key pinning
139     /// validation will fail and return an error. </div>
140     ///
141     /// # Examples
142     ///
143     /// ```
144     /// use ylong_http_client::PubKeyPinsBuilder;
145     ///
146     /// let pins = PubKeyPinsBuilder::new()
147     ///     .add_with_root_strategy(
148     ///         "https://example.com",
149     ///         "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=",
150     ///     )
151     ///     .build()
152     ///     .unwrap();
153     /// ```
add_with_root_strategy(mut self, uri: &str, digest: &str) -> Self154     pub fn add_with_root_strategy(mut self, uri: &str, digest: &str) -> Self {
155         self.pub_keys = self.pub_keys.and_then(move |mut keys| {
156             let auth = parse_uri(uri)?;
157             let info = PinsVerifyInfo::new(PinsVerifyStrategy::RootCertificate, digest);
158             let _ = keys.insert(auth, info);
159             Ok(keys)
160         });
161         self
162     }
163 
164     /// Builds a `PubKeyPins`.
165     ///
166     /// # Examples
167     ///
168     /// ```
169     /// use ylong_http_client::PubKeyPinsBuilder;
170     ///
171     /// let pins = PubKeyPinsBuilder::new()
172     ///     .add(
173     ///         "https://example.com",
174     ///         "sha256//VHQAbNl67nmkZJNESeYKvTxb5bTmd1maWnMKG/tjcAY=",
175     ///     )
176     ///     .build()
177     ///     .unwrap();
178     /// ```
build(self) -> Result<PubKeyPins, HttpClientError>179     pub fn build(self) -> Result<PubKeyPins, HttpClientError> {
180         Ok(PubKeyPins {
181             pub_keys: self.pub_keys?,
182         })
183     }
184 }
185 
186 impl PubKeyPins {
187     /// Creates a new builder for  `PubKeyPins`.
188     ///
189     /// # Examples
190     ///
191     /// ```
192     /// use ylong_http_client::PubKeyPins;
193     ///
194     /// let builder = PubKeyPins::builder();
195     /// ```
builder() -> PubKeyPinsBuilder196     pub fn builder() -> PubKeyPinsBuilder {
197         PubKeyPinsBuilder::new()
198     }
199 
200     /// Get the Public Key Pinning for a domain
get_pin(&self, domain: &str) -> Option<PinsVerifyInfo>201     pub(crate) fn get_pin(&self, domain: &str) -> Option<PinsVerifyInfo> {
202         self.pub_keys.get(&String::from(domain)).cloned()
203     }
204 }
205 
206 /// The Default implement of `PubKeyPinsBuilder`.
207 impl Default for PubKeyPinsBuilder {
208     /// Creates a new builder for  `PubKeyPins`.
209     ///
210     /// # Examples
211     ///
212     /// ```
213     /// use ylong_http_client::PubKeyPinsBuilder;
214     ///
215     /// let builder = PubKeyPinsBuilder::default();
216     /// ```
default() -> Self217     fn default() -> Self {
218         Self::new()
219     }
220 }
221 
parse_uri(uri: &str) -> Result<String, HttpClientError>222 fn parse_uri(uri: &str) -> Result<String, HttpClientError> {
223     let parsed = Uri::try_from(uri).map_err(|e| HttpClientError::from_error(Build, e))?;
224     let auth = match (parsed.host(), parsed.port()) {
225         (None, _) => {
226             return err_from_msg!(Build, "uri has no host");
227         }
228         (Some(host), Some(port)) => {
229             format!("{}:{}", host.as_str(), port.as_str())
230         }
231         (Some(host), None) => {
232             format!("{}:443", host.as_str())
233         }
234     };
235     Ok(auth)
236 }
237 
238 // TODO The SSLError thrown here is meaningless and has no information.
sha256_digest( pub_key: &[u8], len: c_int, digest: &mut [u8], ) -> Result<(), SslError>239 pub(crate) unsafe fn sha256_digest(
240     pub_key: &[u8],
241     len: c_int,
242     digest: &mut [u8],
243 ) -> Result<(), SslError> {
244     let md_ctx = EVP_MD_CTX_new();
245     if md_ctx.is_null() {
246         return Err(SslError {
247             code: SslErrorCode::SSL,
248             internal: Some(InternalError::Ssl(ErrorStack::get())),
249         });
250     }
251     let init = EVP_DigestInit(md_ctx, EVP_sha256());
252     if init == 0 {
253         EVP_MD_CTX_free(md_ctx);
254         return Err(SslError {
255             code: SslErrorCode::SSL,
256             internal: Some(InternalError::Ssl(ErrorStack::get())),
257         });
258     }
259     EVP_DigestUpdate(md_ctx, pub_key.as_ptr(), len);
260 
261     let start = 0;
262     EVP_DigestFinal_ex(md_ctx, digest.as_mut_ptr(), &start);
263 
264     EVP_MD_CTX_free(md_ctx);
265 
266     Ok(())
267 }
268 
269 #[cfg(test)]
270 mod ut_verify_pinning {
271     use std::collections::HashMap;
272 
273     use libc::c_int;
274 
275     use super::{PinsVerifyInfo, PinsVerifyStrategy};
276     use crate::util::c_openssl::verify::sha256_digest;
277     use crate::{PubKeyPins, PubKeyPinsBuilder};
278 
279     /// UT test cases for `PubKeyPins::clone`.
280     ///
281     /// # Brief
282     /// 1. Creates a `PubKeyPins`.
283     /// 2. Calls `PubKeyPins::clone` .
284     /// 3. Checks if the assert result is correct.
285     #[test]
ut_pubkey_pins_clone()286     fn ut_pubkey_pins_clone() {
287         let mut map = HashMap::new();
288         let _value = map.insert(
289             "ylong_http.com:443".to_string(),
290             PinsVerifyInfo::new(
291                 PinsVerifyStrategy::LeafCertificate,
292                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
293             ),
294         );
295         let pins = PubKeyPins { pub_keys: map };
296         let pins_clone = pins.clone();
297         assert_eq!(pins.pub_keys, pins_clone.pub_keys);
298 
299         let pins_info = PinsVerifyInfo::new(
300             PinsVerifyStrategy::RootCertificate,
301             "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
302         );
303         let pins_info_clone = pins_info.clone();
304         assert_eq!(pins_info, pins_info_clone);
305     }
306 
307     /// UT test cases for `PubKeyPinsBuilder::add`.
308     ///
309     /// # Brief
310     /// 1. Creates a `PubKeyPinsBuilder`.
311     /// 2. Calls `PubKeyPins::add` .
312     /// 3. Checks if the assert result is correct.
313     #[test]
ut_pubkey_pins_builder_add()314     fn ut_pubkey_pins_builder_add() {
315         let pins = PubKeyPins::builder()
316             .add(
317                 "/data/storage",
318                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
319             )
320             .build()
321             .err();
322         assert_eq!(
323             format!("{:?}", pins.unwrap()),
324             "HttpClientError { ErrorKind: Build, Cause: uri has no host }"
325         );
326         let pins = PubKeyPinsBuilder::default()
327             .add(
328                 "https://ylong_http.com",
329                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
330             )
331             .build()
332             .unwrap();
333         assert_eq!(
334             pins.get_pin("ylong_http.com:443"),
335             Some(PinsVerifyInfo::new(
336                 PinsVerifyStrategy::LeafCertificate,
337                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno="
338             ))
339         );
340         let pins = PubKeyPinsBuilder::default()
341             .add_with_root_strategy(
342                 "https://ylong_http.com",
343                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=",
344             )
345             .build()
346             .unwrap();
347         assert_eq!(
348             pins.get_pin("ylong_http.com:443"),
349             Some(PinsVerifyInfo::new(
350                 PinsVerifyStrategy::RootCertificate,
351                 "sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno="
352             ))
353         );
354     }
355 
356     /// UT test cases for `sha256_digest.
357     ///
358     /// # Brief
359     /// 1. Calls `sha256_digest` .
360     /// 2. Checks if the assert result is correct.
361     #[test]
ut_pubkey_sha256_digest()362     fn ut_pubkey_sha256_digest() {
363         let pubkey =
364             bytes_from_hex("d0e8b8f11c98f369016eb2ed3c541e1f01382f9d5b3104c9ffd06b6175a46271")
365                 .unwrap();
366 
367         let key_words = Vec::from("Hello, SHA-256!");
368 
369         let mut hash = [0u8; 32];
370         assert!(unsafe {
371             sha256_digest(key_words.as_slice(), key_words.len() as c_int, &mut hash)
372         }
373         .is_ok());
374 
375         assert_eq!(hash.as_slice(), pubkey.as_slice());
376     }
377 
bytes_from_hex(str: &str) -> Option<Vec<u8>>378     fn bytes_from_hex(str: &str) -> Option<Vec<u8>> {
379         if str.len() % 2 != 0 {
380             return None;
381         }
382         let mut vec = Vec::new();
383         let mut remained = str;
384         while !remained.is_empty() {
385             let (left, right) = remained.split_at(2);
386             match u8::from_str_radix(left, 16) {
387                 Ok(num) => vec.push(num),
388                 Err(_) => return None,
389             }
390             remained = right;
391         }
392         Some(vec)
393     }
394 }
395