ctclient_async/
lib.rs

1//! Certificate Transparency Log client suitable for monitoring, quick
2//! SCT validation, gossiping, etc.
3//!
4//! The source code of this project contains some best-effort explanation
5//! comments for others trying to implement such a client. As of 2019,
6//! the documentation that exists out there are (in my opinion) pretty lacking,
7//! and I had some bad time trying to implement this.
8//!
9//! All `pub_key` are in DER format, which is the format returned (in base64)
10//! by google's trusted log list. `signature`s are *Digitally-signed structs*, and
11//! `raw_signature`s are ASN1-encoded signatures.
12//!
13//! Best effort are made to catch misbehavior by CT logs or invalid certificates. It is up
14//! to the user of this library to decide what to do when logs don't behave corrctly.
15//!
16//! This project is not intended to be a beginner friendly tutorial on how a
17//! CT log works. To learn more about CT, you can read [my blog article](https://blog.maowtm.org/ct/en.html)
18//! or [the RFC](https://tools.ietf.org/html/rfc6962).
19//!
20//! API calls are currently all blocking. If anyone is interested in rewriting them in Futures, PR is welcome.
21
22// todo: gossiping
23
24#[macro_use(lazy_static)]
25extern crate lazy_static;
26
27use std::{fmt, io, path};
28
29use futures::StreamExt;
30use futures::pin_mut;
31use log::{info, warn};
32use openssl::pkey::PKey;
33use openssl::x509::X509;
34
35use internal::new_http_client;
36pub use sct::{SctEntry, SignedCertificateTimestamp};
37pub use sth::SignedTreeHead;
38
39use crate::internal::openssl_ffi::{x509_clone, x509_make_a_looks_like_issued_by_b};
40use crate::internal::{
41    Leaf, check_consistency_proof, check_inclusion_proof, fetch_inclusion_proof,
42};
43
44mod sct;
45mod sth;
46
47pub mod certutils;
48pub mod google_log_list;
49pub mod internal;
50pub mod jsons;
51pub mod utils;
52
53#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
54compile_error!("You must enable either the `native-tls` or `rustls-tls` feature.");
55
56#[cfg(all(feature = "native-tls", feature = "rustls-tls"))]
57compile_error!("You must enable only one of the `native-tls` and `rustls-tls` features, not both.");
58
59/// Errors that this library could produce.
60#[derive(Debug)]
61pub enum Error {
62    /// Something strange happened.
63    Unknown(String),
64
65    /// You provided something bad.
66    InvalidArgument(String),
67
68    /// File IO error
69    FileIO(path::PathBuf, io::Error),
70
71    /// Network IO error
72    NetIO(reqwest::Error),
73
74    /// The CT server provided us with invalid signature.
75    InvalidSignature(String),
76
77    /// The CT server responded with something other than 200.
78    InvalidResponseStatus(reqwest::StatusCode),
79
80    /// Server responded with something bad (e.g. malformed JSON)
81    MalformedResponseBody(String),
82
83    /// Server returned an invalid consistency proof.
84    InvalidConsistencyProof {
85        prev_size: u64,
86        new_size: u64,
87        desc: String,
88    },
89
90    /// ConsistencyProofPart::verify failed
91    CannotVerifyTreeData(String),
92
93    /// Something's wrong with the certificate.
94    BadCertificate(String),
95
96    /// Server returned an invalid inclusion proof.
97    InvalidInclusionProof {
98        tree_size: u64,
99        leaf_index: u64,
100        desc: String,
101    },
102
103    /// A malformed SCT is given.
104    BadSct(String),
105
106    /// We asked for a certain entry expecting it to be there, but the server gave us nothing.
107    ExpectedEntry(u64),
108}
109
110/// Either a fetched and checked [`SignedTreeHead`], or a [`SignedTreeHead`] that has a valid signature
111/// but did not pass some internal checks, or just an [`Error`].
112#[derive(Debug)]
113pub enum SthResult {
114    /// Got the new tree head.
115    Ok(SignedTreeHead),
116
117    /// Something went wrong and no tree head was received.
118    Err(Error),
119
120    /// Something went wrong, but the server returned a valid signed tree head.
121    /// The underlying error is wrapped inside. You may wish to log this.
122    ErrWithSth(Error, SignedTreeHead),
123}
124
125impl SthResult {
126    /// Return a signed tree head, if there is one received.
127    ///
128    /// This can return a `Some` even when there is error, if for example, the server returned a valid signed
129    /// tree head but failed to provide a consistency proof. You may wish to log this.
130    pub fn tree_head(&self) -> Option<&SignedTreeHead> {
131        match self {
132            SthResult::Ok(sth) => Some(sth),
133            SthResult::Err(_) => None,
134            SthResult::ErrWithSth(_, sth) => Some(sth),
135        }
136    }
137
138    pub fn is_ok(&self) -> bool {
139        matches!(self, SthResult::Ok(_))
140    }
141
142    pub fn is_err(&self) -> bool {
143        !self.is_ok()
144    }
145
146    /// Return the [`SignedTreeHead`], if this is a Ok. Otherwise panic.
147    pub fn unwrap(self) -> SignedTreeHead {
148        match self {
149            SthResult::Ok(sth) => sth,
150            _ => {
151                panic!(
152                    "unwrap called on SthResult with error: {}",
153                    self.unwrap_err()
154                )
155            }
156        }
157    }
158
159    /// Return the [`Error`], if this is an `Err` or `ErrWithSth`. Otherwise panic.
160    pub fn unwrap_err(self) -> Error {
161        match self {
162            SthResult::ErrWithSth(e, _) => e,
163            SthResult::Err(e) => e,
164            _ => panic!("unwrap_err called on SthResult that is ok."),
165        }
166    }
167
168    /// Return the [`SignedTreeHead`], if this is a `Ok` or `ErrWithSth`. Otherwise panic.
169    pub fn unwrap_tree_head(self) -> SignedTreeHead {
170        match self {
171            SthResult::Ok(sth) => sth,
172            SthResult::ErrWithSth(_, sth) => sth,
173            SthResult::Err(e) => panic!("unwrap_tree_head called on SthResult with error: {}", e),
174        }
175    }
176}
177
178impl fmt::Display for Error {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        match self {
181            Error::Unknown(desc) => write!(f, "{}", desc),
182            Error::InvalidArgument(desc) => write!(f, "Invalid argument: {}", desc),
183            Error::FileIO(path, e) => write!(f, "{}: {}", path.to_string_lossy(), &e),
184            Error::NetIO(e) => write!(f, "Network IO error: {}", &e),
185            Error::InvalidSignature(desc) => write!(f, "Invalid signature received: {}", &desc),
186            Error::InvalidResponseStatus(response_code) => write!(
187                f,
188                "Server responded with {} {}",
189                response_code.as_u16(),
190                response_code.as_str()
191            ),
192            Error::MalformedResponseBody(desc) => {
193                write!(f, "Unable to parse server response: {}", &desc)
194            }
195            Error::InvalidConsistencyProof {
196                prev_size,
197                new_size,
198                desc,
199            } => write!(
200                f,
201                "Server provided an invalid consistency proof from {} to {}: {}",
202                prev_size, new_size, &desc
203            ),
204            Error::CannotVerifyTreeData(desc) => write!(
205                f,
206                "The certificates returned by the server is inconsistent with the previously provided consistency proof: {}",
207                &desc
208            ),
209            Error::BadCertificate(desc) => write!(
210                f,
211                "The certificate returned by the server has a problem: {}",
212                &desc
213            ),
214            Error::InvalidInclusionProof {
215                tree_size,
216                leaf_index,
217                desc,
218            } => write!(
219                f,
220                "Server provided an invalid inclusion proof of {} in tree with size {}: {}",
221                leaf_index, tree_size, desc
222            ),
223            Error::BadSct(desc) => write!(f, "The SCT received is invalid: {}", desc),
224            Error::ExpectedEntry(leaf_index) => write!(
225                f,
226                "The server did not return the leaf with index {}, even though we believe it should be there.",
227                leaf_index
228            ),
229        }
230    }
231}
232
233/// A stateful CT monitor.
234///
235/// One instance of this struct only concerns with one particular log. To monitor multiple
236/// logs, you can create multiple such instances and run them on different threads.
237///
238/// It remembers a last checked tree root, so that it only checks the newly added
239/// certificates in the log each time you call [`update`](Self::update).
240pub struct CTClient {
241    base_url: reqwest::Url,
242    pub_key: PKey<openssl::pkey::Public>,
243    http_client: reqwest::Client,
244    latest_size: u64,
245    latest_tree_hash: [u8; 32],
246}
247
248impl fmt::Debug for CTClient {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(
251            f,
252            "CT log {}: current root = {}, size = {}",
253            self.base_url,
254            utils::u8_to_hex(&self.latest_tree_hash[..]),
255            self.latest_size
256        )
257    }
258}
259
260impl CTClient {
261    /// Construct a new `CTClient` instance, and fetch the latest tree root.
262    ///
263    /// Previous certificates in this log will not be checked.
264    ///
265    /// # Errors
266    ///
267    /// * If `base_url` does not ends with `/`.
268    ///
269    /// # Example
270    ///
271    /// ```
272    /// use ctclient_async::CTClient;
273    /// use base64::decode;
274    /// # tokio_test::block_on(async {
275    /// // URL and public key copy-pasted from https://www.gstatic.com/ct/log_list/v3/all_logs_list.json .
276    /// let public_key = decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGoAaFRkZI3m0+qB5jo3VwdzCtZaSfpTgw34UfAoNLUaonRuxQWUMX5jEWhd5gVtKFEHsr6ldDqsSGXHNQ++7lw==").unwrap();
277    /// let client = CTClient::new_from_latest_th("https://ct.cloudflare.com/logs/nimbus2025/", &public_key).await.unwrap();
278    /// # });
279    /// ```
280    pub async fn new_from_latest_th(base_url: &str, pub_key: &[u8]) -> Result<Self, Error> {
281        if !base_url.ends_with('/') {
282            return Err(Error::InvalidArgument("baseUrl must end with /".to_owned()));
283        }
284        let base_url = reqwest::Url::parse(base_url)
285            .map_err(|e| Error::InvalidArgument(format!("Unable to parse url: {}", &e)))?;
286        let http_client = new_http_client()?;
287        let evp_pkey = PKey::public_key_from_der(pub_key)
288            .map_err(|e| Error::InvalidArgument(format!("Error parsing public key: {}", &e)))?;
289        let sth = internal::check_tree_head(&http_client, &base_url, &evp_pkey).await?;
290        Ok(CTClient {
291            base_url,
292            pub_key: evp_pkey,
293            http_client,
294            latest_size: sth.tree_size,
295            latest_tree_hash: sth.root_hash,
296        })
297    }
298
299    /// Construct a new `CTClient` that will check all certificates included after
300    /// the given tree state.
301    ///
302    /// Previous certificates in this log before the provided tree hash will not be checked.
303    ///
304    /// # Example
305    ///
306    /// ```
307    /// use ctclient_async::{CTClient, utils};
308    /// use base64::decode;
309    /// // URL and public key copy-pasted from https://www.gstatic.com/ct/log_list/v3/all_logs_list.json .
310    /// let public_key = decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01EAhx4o0zPQrXTcYjgCt4MVFsT0Pwjzb1RwrM0lhWDlxAYPP6/gyMCXNkOn/7KFsjL7rwk78tHMpY8rXn8AYg==").unwrap();
311    /// use std::convert::TryInto;
312    /// // Tree captured on 2020-05-12 15:34:11 UTC
313    /// let th: [u8; 32] = (&utils::hex_to_u8("63875e88a3e37dc5b6cdbe213fe1df490d40193e4777f79467958ee157de70d6")[..]).try_into().unwrap();
314    /// let client = CTClient::new_from_perv_tree_hash("https://ct.cloudflare.com/logs/nimbus2020/", &public_key, th, 299304276).unwrap();
315    /// ```
316    pub fn new_from_perv_tree_hash(
317        base_url: &str,
318        pub_key: &[u8],
319        tree_hash: [u8; 32],
320        tree_size: u64,
321    ) -> Result<Self, Error> {
322        if !base_url.ends_with('/') {
323            return Err(Error::InvalidArgument("baseUrl must end with /".to_owned()));
324        }
325        let base_url = reqwest::Url::parse(base_url)
326            .map_err(|e| Error::InvalidArgument(format!("Unable to parse url: {}", &e)))?;
327        let http_client = new_http_client()?;
328        let evp_pkey = PKey::public_key_from_der(pub_key)
329            .map_err(|e| Error::InvalidArgument(format!("Error parsing public key: {}", &e)))?;
330        Ok(CTClient {
331            base_url,
332            pub_key: evp_pkey,
333            http_client,
334            latest_size: tree_size,
335            latest_tree_hash: tree_hash,
336        })
337    }
338
339    /// Get the last checked tree head. Returns `(tree_size, root_hash)`.
340    pub fn get_checked_tree_head(&self) -> (u64, [u8; 32]) {
341        (self.latest_size, self.latest_tree_hash)
342    }
343
344    /// Get the underlying http client used to call CT APIs.
345    pub fn get_reqwest_client(&self) -> &reqwest::Client {
346        &self.http_client
347    }
348
349    /// Get the base_url of the log currently being monitored by this client.
350    ///
351    /// This is the url that was passed to the constructor.
352    pub fn get_base_url(&self) -> &reqwest::Url {
353        &self.base_url
354    }
355
356    /// Calls `self.update()` with `None` as `cert_handler`.
357    pub async fn light_update(&mut self) -> SthResult {
358        self.update(None::<fn(&[X509])>).await
359    }
360
361    /// Fetch the latest tree root, check all the new certificates if `cert_handler` is a Some, and update our
362    /// internal "last checked tree root".
363    ///
364    /// This function should never panic, no matter what the server does to us.
365    ///
366    /// Return the latest [`SignedTreeHead`] (STH) returned by the server, even if
367    /// it is the same as last time, or if it rolled back (new tree_size < current tree_size).
368    ///
369    /// To log the behavior of CT logs, store the returned tree head and signature in some kind
370    /// of database (even when error). This can be used to prove a misconduct (such as a non-extending-only tree)
371    /// in the future.
372    ///
373    /// Will only update the stored latest tree head if an [`Ok`](SthResult::Ok) is returned.
374    pub async fn update<H>(&mut self, mut cert_handler: Option<H>) -> SthResult
375    where
376        H: FnMut(&[X509]),
377    {
378        let mut delaycheck = std::time::Instant::now();
379        let sth = match internal::check_tree_head(&self.http_client, &self.base_url, &self.pub_key)
380            .await
381        {
382            Ok(s) => s,
383            Err(e) => return SthResult::Err(e),
384        };
385        let new_tree_size = sth.tree_size;
386        let new_tree_root = sth.root_hash;
387        use std::cmp::Ordering;
388        match new_tree_size.cmp(&self.latest_size) {
389            Ordering::Equal => {
390                if new_tree_root == self.latest_tree_hash {
391                    info!("{} remained the same.", self.base_url.as_str());
392                    SthResult::Ok(sth)
393                } else {
394                    SthResult::ErrWithSth(
395                        Error::InvalidConsistencyProof {
396                            prev_size: self.latest_size,
397                            new_size: new_tree_size,
398                            desc: format!(
399                                "Server forked! {} and {} both correspond to tree_size {}",
400                                &utils::u8_to_hex(&self.latest_tree_hash),
401                                &utils::u8_to_hex(&new_tree_root),
402                                new_tree_size
403                            ),
404                        },
405                        sth,
406                    )
407                }
408            }
409            Ordering::Less => {
410                // Make sure server isn't doing trick with us.
411                match internal::check_consistency_proof(
412                    &self.http_client,
413                    &self.base_url,
414                    new_tree_size,
415                    self.latest_size,
416                    &new_tree_root,
417                    &self.latest_tree_hash,
418                )
419                .await
420                {
421                    Ok(_) => {
422                        warn!(
423                            "{} rolled back? {} -> {}",
424                            self.base_url.as_str(),
425                            self.latest_size,
426                            new_tree_size
427                        );
428                        SthResult::Ok(sth)
429                    }
430                    Err(e) => SthResult::ErrWithSth(
431                        Error::InvalidConsistencyProof {
432                            prev_size: new_tree_size,
433                            new_size: self.latest_size,
434                            desc: format!(
435                                "Server rolled back, and can't provide a consistency proof from the rolled back tree to the original tree: {}",
436                                e
437                            ),
438                        },
439                        sth,
440                    ),
441                }
442            }
443            Ordering::Greater => {
444                let consistency_proof_parts = match internal::check_consistency_proof(
445                    &self.http_client,
446                    &self.base_url,
447                    self.latest_size,
448                    new_tree_size,
449                    &self.latest_tree_hash,
450                    &new_tree_root,
451                )
452                .await
453                {
454                    Ok(k) => k,
455                    Err(e) => return SthResult::ErrWithSth(e, sth),
456                };
457
458                if cert_handler.is_some() {
459                    let i_start = self.latest_size;
460                    let leafs = internal::get_entries(
461                        &self.http_client,
462                        &self.base_url,
463                        i_start..new_tree_size,
464                        500,
465                    );
466                    // `get_entries` returns a stream backed by an async block which is !Unpin.
467                    // Pin it on the stack so we can `.next().await` without requiring `Unpin`.
468                    pin_mut!(leafs);
469                    let mut leaf_hashes: Vec<[u8; 32]> =
470                        Vec::with_capacity((new_tree_size - i_start) as usize);
471                    for i in i_start..new_tree_size {
472                        match leafs.next().await {
473                            Some(Ok(leaf)) => {
474                                leaf_hashes.push(leaf.hash);
475                                if let Err(e) = self.check_leaf(&leaf, &mut cert_handler) {
476                                    return SthResult::ErrWithSth(e, sth);
477                                }
478                            }
479                            Some(Err(e)) => {
480                                return SthResult::ErrWithSth(
481                                    if let Error::MalformedResponseBody(inner_e) = e {
482                                        Error::MalformedResponseBody(format!(
483                                            "While parsing leaf #{}: {}",
484                                            i, &inner_e
485                                        ))
486                                    } else {
487                                        e
488                                    },
489                                    sth,
490                                );
491                            }
492                            None => {
493                                return SthResult::ErrWithSth(Error::ExpectedEntry(i), sth);
494                            }
495                        }
496                        if delaycheck.elapsed() > std::time::Duration::from_secs(1) {
497                            info!(
498                                "{}: Catching up: {} / {} ({}%)",
499                                self.base_url.as_str(),
500                                i,
501                                new_tree_size,
502                                ((i - i_start) * 1000 / (new_tree_size - i_start)) as f32 / 10f32
503                            );
504                            delaycheck = std::time::Instant::now();
505                        }
506                    }
507                    assert_eq!(leaf_hashes.len(), (new_tree_size - i_start) as usize);
508                    for proof_part in consistency_proof_parts.into_iter() {
509                        assert!(proof_part.subtree.0 >= i_start);
510                        assert!(proof_part.subtree.1 <= new_tree_size);
511                        if let Err(e) = proof_part.verify(
512                            &leaf_hashes[(proof_part.subtree.0 - i_start) as usize
513                                ..(proof_part.subtree.1 - i_start) as usize],
514                        ) {
515                            return SthResult::ErrWithSth(Error::CannotVerifyTreeData(e), sth);
516                        }
517                    }
518                    info!(
519                        "{} updated to {} {} (read {} leaves)",
520                        self.base_url.as_str(),
521                        new_tree_size,
522                        &utils::u8_to_hex(&new_tree_root),
523                        new_tree_size - i_start
524                    );
525                } else {
526                    info!(
527                        "{} light updated to {} {}",
528                        self.base_url.as_str(),
529                        new_tree_size,
530                        &utils::u8_to_hex(&new_tree_root)
531                    );
532                }
533
534                self.latest_size = new_tree_size;
535                self.latest_tree_hash = new_tree_root;
536                SthResult::Ok(sth)
537            }
538        }
539    }
540
541    /// Called by [`Self::update`](crate::CTClient::update) for each leaf received
542    /// to check the certificates. Usually no need to call yourself.
543    pub fn check_leaf<H>(
544        &self,
545        leaf: &internal::Leaf,
546        cert_handler: &mut Option<H>,
547    ) -> Result<(), Error>
548    where
549        H: FnMut(&[X509]),
550    {
551        let chain: Vec<_> = leaf
552            .x509_chain
553            .iter()
554            .map(|der| openssl::x509::X509::from_der(&der[..]))
555            .collect();
556        for rs in chain.iter() {
557            if let Err(e) = rs {
558                return Err(Error::BadCertificate(format!(
559                    "While decoding certificate: {}",
560                    e
561                )));
562            }
563        }
564        let chain: Vec<X509> = chain.into_iter().map(|x| x.unwrap()).collect();
565        if chain.len() <= 1 {
566            return Err(Error::BadCertificate("Empty certificate chain?".to_owned()));
567        }
568        for part in chain.windows(2) {
569            let ca = &part[1];
570            let target = &part[0];
571            let ca_pkey = ca.public_key().map_err(|e| {
572                Error::BadCertificate(format!("Can't get public key from ca: {}", e))
573            })?;
574            let verify_success = target
575                .verify(&ca_pkey)
576                .map_err(|e| Error::Unknown(format!("{}", e)))?;
577            if !verify_success {
578                return Err(Error::BadCertificate(
579                    "Invalid certificate chain.".to_owned(),
580                ));
581            }
582        }
583        if let Some(tbs) = &leaf.tbs_cert {
584            use internal::openssl_ffi::{x509_remove_poison, x509_to_tbs};
585            let cert = chain[0].as_ref();
586            let mut cert_clone = x509_clone(&cert)
587                .map_err(|e| Error::Unknown(format!("Duplicating certificate: {}", e)))?;
588            x509_remove_poison(&mut cert_clone)
589                .map_err(|e| Error::Unknown(format!("While removing poison: {}", e)))?;
590            let expected_tbs = x509_to_tbs(&cert_clone)
591                .map_err(|e| Error::Unknown(format!("x509_to_tbs errored: {}", e)))?;
592            if tbs != &expected_tbs {
593                // Maybe the precert is signed with an intermediate precert signing CA. The TBS will nevertheless contain the
594                // "true" CA as the issuer name.
595                // In that case, chain[1] is the precert signing CA, and chain[2] is the "true" signing CA.
596                let mut tbs_correct = false;
597                if chain.len() > 2 {
598                    x509_make_a_looks_like_issued_by_b(&mut cert_clone, &chain[2]).map_err(
599                        |e| {
600                            Error::Unknown(format!(
601                                "x509_make_a_looks_like_issued_by_b failed: {}",
602                                e
603                            ))
604                        },
605                    )?;
606                    let new_expected_tbs = x509_to_tbs(&cert_clone)
607                        .map_err(|e| Error::Unknown(format!("x509_to_tbs errored: {}", e)))?;
608                    if tbs == &new_expected_tbs {
609                        tbs_correct = true;
610                    }
611                }
612                if !tbs_correct {
613                    return Err(Error::BadCertificate(
614                        "TBS does not match pre-cert.".to_owned(),
615                    ));
616                }
617            }
618        }
619
620        if let Some(handler) = cert_handler {
621            handler(&chain);
622        }
623        Ok(())
624    }
625
626    /// Given a [`SignedCertificateTimestamp`], check that the CT log monitored by this client can provide
627    /// an inclusion proof that backs the sct, and return the leaf index.
628    ///
629    /// Does not check the signature on the sct, and also does not check that the maximum merge delay has passed.
630    pub async fn check_inclusion_proof_for_sct(
631        &self,
632        sct: &SignedCertificateTimestamp,
633    ) -> Result<u64, Error> {
634        let th = self.get_checked_tree_head();
635        check_inclusion_proof(
636            self.get_reqwest_client(),
637            &self.base_url,
638            th.0,
639            &th.1,
640            &sct.derive_leaf_hash(),
641        )
642        .await
643    }
644
645    pub async fn first_leaf_after(&self, timestamp: u64) -> Result<Option<(u64, Leaf)>, Error> {
646        let mut low = 0u64;
647        let mut high = self.latest_size;
648        let mut last_leaf: Option<(u64, Leaf)> = None;
649        while low < high {
650            let mid = (low + high - 1) / 2;
651            let entries_iter =
652                internal::get_entries(&self.http_client, &self.base_url, mid..mid + 1, 1);
653            // Pin the async-stream-backed iterator so it can be polled across await points.
654            pin_mut!(entries_iter);
655            match entries_iter.next().await {
656                None => return Err(Error::ExpectedEntry(mid)),
657                Some(Err(e)) => return Err(e),
658                Some(Ok(got_entry)) => {
659                    let got_timestamp = got_entry.timestamp;
660                    use std::cmp::Ordering::*;
661                    match got_timestamp.cmp(&timestamp) {
662                        Equal => return Ok(Some((mid, got_entry))),
663                        Less => {
664                            low = mid + 1;
665                        }
666                        Greater => {
667                            last_leaf = Some((mid, got_entry));
668                            high = mid;
669                        }
670                    }
671                }
672            }
673        }
674        if low > self.latest_size {
675            Ok(None)
676        } else {
677            Ok(Some(last_leaf.unwrap()))
678        }
679    }
680
681    pub async fn first_tree_head_after(
682        &self,
683        timestamp: u64,
684    ) -> Result<Option<(u64, [u8; 32])>, Error> {
685        let fla = self.first_leaf_after(timestamp).await?;
686        if fla.is_none() {
687            return Ok(None);
688        }
689        let fla = fla.unwrap();
690        let tsize = fla.0 + 1;
691        let inclusion_res =
692            fetch_inclusion_proof(&self.http_client, &self.base_url, tsize, &fla.1.hash).await?;
693        if inclusion_res.leaf_index != fla.0 {
694            return Err(Error::Unknown(
695                "inclusion result.leaf_index != expected".to_owned(),
696            ));
697        }
698        Ok(Some((tsize, inclusion_res.calculated_tree_hash)))
699    }
700
701    pub async fn rollback_to_timestamp(&mut self, timestamp: u64) -> Result<(), Error> {
702        let res = self.first_tree_head_after(timestamp).await?;
703        if res.is_none() {
704            return Ok(());
705        }
706        let (tsize, thash) = res.unwrap();
707        if tsize < self.latest_size {
708            check_consistency_proof(
709                &self.http_client,
710                &self.base_url,
711                tsize,
712                self.latest_size,
713                &thash,
714                &self.latest_tree_hash,
715            )
716            .await?;
717            self.latest_size = tsize;
718            self.latest_tree_hash = thash;
719            info!(
720                "{}: Rolled back to {} {}",
721                self.base_url.as_str(),
722                tsize,
723                utils::u8_to_hex(&thash)
724            );
725        }
726        Ok(())
727    }
728
729    /// Serialize the state of this client into bytes
730    pub fn as_bytes(&self) -> Result<Vec<u8>, Error> {
731        // Scheme: (All integers are in big-endian, fixed array don't specify length)
732        // [Version: u8] [base_url in UTF-8] 0x00 [tree_size: u64] [tree_hash: [u8; 32]] [len of pub_key: u32] [pub_key: [u8]: DER public key for this log] [sha256 of everything seen before: [u8; 32]]
733        let mut v = Vec::new();
734        v.push(0u8); // Version = development
735        let url_bytes = self.base_url.as_str().as_bytes();
736        assert!(!url_bytes.contains(&0u8));
737        v.extend_from_slice(url_bytes);
738        v.push(0u8);
739        v.extend_from_slice(&u64::to_be_bytes(self.latest_size));
740        assert_eq!(self.latest_tree_hash.len(), 32);
741        v.extend_from_slice(&self.latest_tree_hash);
742        let pub_key = self
743            .pub_key
744            .public_key_to_der()
745            .map_err(|e| Error::Unknown(format!("While encoding public key: {}", &e)))?;
746        assert!(pub_key.len() < u32::MAX as usize);
747        v.extend_from_slice(&u32::to_be_bytes(pub_key.len() as u32));
748        v.extend_from_slice(&pub_key);
749        v.extend_from_slice(&utils::sha256(&v));
750        Ok(v)
751    }
752
753    /// Parse a byte string returned by [`Self::as_bytes`](CTClient::as_bytes).
754    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
755        use std::convert::TryInto;
756        fn e_inval() -> Result<CTClient, Error> {
757            Err(Error::InvalidArgument("The bytes are invalid.".to_owned()))
758        }
759        let mut input = bytes;
760        if input.is_empty() {
761            return e_inval();
762        }
763        let version = input[0];
764        input = &input[1..];
765        if version != 0 {
766            return Err(Error::InvalidArgument(
767                "The bytes are encoded by a ctclient of higher version.".to_owned(),
768            ));
769        }
770        let base_url_len = match input.iter().position(|x| *x == 0) {
771            Some(k) => k,
772            None => return e_inval(),
773        };
774        let base_url = std::str::from_utf8(&input[..base_url_len])
775            .map_err(|e| Error::InvalidArgument(format!("Invalid UTF-8 in base_url: {}", &e)))?;
776        input = &input[base_url_len + 1..];
777        if input.len() < 8 {
778            return e_inval();
779        }
780        let tree_size = u64::from_be_bytes(input[..8].try_into().unwrap());
781        input = &input[8..];
782        if input.len() < 32 {
783            return e_inval();
784        }
785        let tree_hash: [u8; 32] = input[..32].try_into().unwrap();
786        input = &input[32..];
787        if input.len() < 4 {
788            return e_inval();
789        }
790        let len_pub_key = u32::from_be_bytes(input[..4].try_into().unwrap());
791        input = &input[4..];
792        if input.len() < len_pub_key as usize {
793            return e_inval();
794        }
795        let pub_key = &input[..len_pub_key as usize];
796        input = &input[len_pub_key as usize..];
797        if input.len() < 32 {
798            return e_inval();
799        }
800        let checksum: [u8; 32] = input[..32].try_into().unwrap();
801        input = &input[32..];
802        if !input.is_empty() {
803            return e_inval();
804        }
805        let expect_checksum = utils::sha256(&bytes[..bytes.len() - 32]);
806        #[cfg(not(fuzzing))]
807        {
808            if checksum != expect_checksum {
809                return e_inval();
810            }
811        }
812        let pub_key = openssl::pkey::PKey::<openssl::pkey::Public>::public_key_from_der(pub_key)
813            .map_err(|e| Error::InvalidArgument(format!("Can't parse public key: {}", &e)))?;
814        Ok(CTClient {
815            base_url: reqwest::Url::parse(base_url)
816                .map_err(|e| Error::InvalidArgument(format!("Unable to parse base_url: {}", &e)))?,
817            pub_key,
818            http_client: new_http_client()?,
819            latest_size: tree_size,
820            latest_tree_hash: tree_hash,
821        })
822    }
823}
824
825#[cfg(test)]
826mod tests {
827    use super::*;
828
829    #[tokio::test]
830    async fn as_bytes_test() {
831        let c = CTClient::new_from_latest_th("https://ct.googleapis.com/logs/argon2019/", &utils::hex_to_u8("3059301306072a8648ce3d020106082a8648ce3d030107034200042373109be1f35ef6986b6995961078ce49dbb404fc712c5a92606825c04a1aa1b0612d1b8714a9baf00133591d0530e94215e755d72af8b4a2ba45c946918756")).await.unwrap();
832        let mut bytes = c.as_bytes().unwrap();
833        println!("bytes: {}", &base64::encode(&bytes));
834        let mut c_clone = CTClient::from_bytes(&bytes).unwrap();
835        assert_eq!(c.latest_size, c_clone.latest_size);
836        assert_eq!(c.latest_tree_hash, c_clone.latest_tree_hash);
837        assert_eq!(c.base_url, c_clone.base_url);
838        c_clone.light_update().await.unwrap(); // test public key
839        let len = bytes.len();
840        bytes[len - 1] ^= 1;
841        CTClient::from_bytes(&bytes).expect_err("");
842    }
843}
844
845#[cfg(test)]
846mod long_tests;