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