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