ctclient_async/internal/
mod.rs1use std::convert::TryInto;
6
7use log::{debug, trace};
8use openssl::pkey::PKey;
9
10use crate::{Error, SignedTreeHead, jsons, utils};
11pub use consistency::*;
12pub use digitally_signed_struct::*;
13pub use getentries::*;
14pub use inclusion::*;
15pub use leaf::*;
16
17mod consistency;
18mod digitally_signed_struct;
19mod getentries;
20mod inclusion;
21mod leaf;
22pub mod openssl_ffi;
23
24pub fn new_http_client() -> Result<reqwest::Client, Error> {
29 use std::time;
30 let mut def_headers = reqwest::header::HeaderMap::new();
31 def_headers.insert(
32 "User-Agent",
33 reqwest::header::HeaderValue::from_static("rust-ctclient"),
34 );
35 match reqwest::Client::builder()
36 .connect_timeout(time::Duration::from_secs(5))
37 .gzip(true)
38 .default_headers(def_headers)
39 .redirect(reqwest::redirect::Policy::none())
40 .build()
41 {
42 Ok(r) => Ok(r),
43 Err(e) => Err(Error::Unknown(format!("{}", &e))),
44 }
45}
46
47pub async fn get_json<J: serde::de::DeserializeOwned>(
49 client: &reqwest::Client,
50 base_url: &reqwest::Url,
51 path: &str,
52) -> Result<J, Error> {
53 let url = base_url.join(path).unwrap();
54 let url_str = url.as_str().to_owned();
55 let response = client.get(url).send().await.map_err(Error::NetIO)?;
56 if response.status().as_u16() != 200 {
57 debug!("GET {} -> {}", &url_str, response.status());
58 return Err(Error::InvalidResponseStatus(response.status()));
59 }
60 let response = response.text().await.map_err(Error::NetIO)?;
61 if response.len() > 150 {
62 debug!("GET {} -> {:?}...", &url_str, &response[..150]);
63 } else {
64 trace!("GET {} -> {:?}", &url_str, &response);
65 }
66 let json = serde_json::from_str(&response).map_err(|e| {
67 Error::MalformedResponseBody(format!(
68 "Unable to decode JSON: {} (response is {:?})",
69 &e, &response
70 ))
71 })?;
72 Ok(json)
73}
74
75pub async fn check_tree_head(
89 client: &reqwest::Client,
90 base_url: &reqwest::Url,
91 pub_key: &PKey<openssl::pkey::Public>,
92) -> Result<SignedTreeHead, Error> {
93 let response: jsons::STH = get_json(client, base_url, "ct/v1/get-sth").await?;
94 let root_hash = base64::decode(&response.sha256_root_hash).map_err(|e| {
95 Error::MalformedResponseBody(format!(
96 "base64 decode failure on root sha256: {} (trying to decode {:?})",
97 &e, &response.sha256_root_hash
98 ))
99 })?;
100 if root_hash.len() != 32 {
101 return Err(Error::MalformedResponseBody(format!(
102 "Invalid server response: sha256_root_hash should have length of 32. Server response is {:?}",
103 &response
104 )));
105 }
106 let dss = base64::decode(&response.tree_head_signature).map_err(|e| {
107 Error::MalformedResponseBody(format!(
108 "base64 decode failure on signature: {} (trying to decode {:?})",
109 &e, &response.tree_head_signature
110 ))
111 })?;
112 let sth = SignedTreeHead {
113 tree_size: response.tree_size,
114 timestamp: response.timestamp,
115 root_hash: root_hash[..].try_into().unwrap(),
116 signature: dss,
117 };
118 sth.verify(pub_key)?;
119 trace!(
120 "{} tree head now on {} {}",
121 base_url.as_str(),
122 sth.tree_size,
123 &utils::u8_to_hex(&sth.root_hash)
124 );
125 Ok(sth)
126}