1#[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#[derive(Debug)]
61pub enum Error {
62 Unknown(String),
64
65 InvalidArgument(String),
67
68 FileIO(path::PathBuf, io::Error),
70
71 NetIO(reqwest::Error),
73
74 InvalidSignature(String),
76
77 InvalidResponseStatus(reqwest::StatusCode),
79
80 MalformedResponseBody(String),
82
83 InvalidConsistencyProof {
85 prev_size: u64,
86 new_size: u64,
87 desc: String,
88 },
89
90 CannotVerifyTreeData(String),
92
93 BadCertificate(String),
95
96 InvalidInclusionProof {
98 tree_size: u64,
99 leaf_index: u64,
100 desc: String,
101 },
102
103 BadSct(String),
105
106 ExpectedEntry(u64),
108}
109
110#[derive(Debug)]
113pub enum SthResult {
114 Ok(SignedTreeHead),
116
117 Err(Error),
119
120 ErrWithSth(Error, SignedTreeHead),
123}
124
125impl SthResult {
126 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 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 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 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
233pub 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 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 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 pub fn get_checked_tree_head(&self) -> (u64, [u8; 32]) {
341 (self.latest_size, self.latest_tree_hash)
342 }
343
344 pub fn get_reqwest_client(&self) -> &reqwest::Client {
346 &self.http_client
347 }
348
349 pub fn get_base_url(&self) -> &reqwest::Url {
353 &self.base_url
354 }
355
356 pub async fn light_update(&mut self) -> SthResult {
358 self.update(None::<fn(&[X509])>).await
359 }
360
361 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 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 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 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 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 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_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(×tamp) {
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 pub fn as_bytes(&self) -> Result<Vec<u8>, Error> {
731 let mut v = Vec::new();
734 v.push(0u8); 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 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(); 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;