ctclient_async/internal/
mod.rs

1//! Things that are only useful if you are doing your own API calling.
2//!
3//! Note that the RFC calls inclusion proof "audit proof".
4
5use 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
24/// Construct a new [`reqwest::Client`] to be used with the
25/// functions in this module. You don't necessary need to use this.
26///
27/// The client constructed will not store cookie or follow redirect.
28pub 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
47/// Perform a GET request and parse the result as a JSON.
48pub 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
75/// Check, verify and return the latest tree head from the CT log at
76/// `base_url`.
77///
78/// This function is only useful to those who want to do some custom CT API
79/// calling. [`CTClient`](crate::CTClient) will automatically update its cache of
80/// tree root. If you use `CTClient`, call
81/// [`CTClient::get_checked_tree_head`](crate::CTClient::get_checked_tree_head)
82/// instead.
83///
84/// # Params
85///
86/// * `client`: A [`reqwest::Client`](reqwest::Client) instance. See
87/// [`CTClient::get_reqwest_client`](crate::CTClient::get_reqwest_client)
88pub 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}