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