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#[derive(Debug)]
55pub enum Error {
56 Unknown(String),
58
59 InvalidArgument(String),
61
62 FileIO(path::PathBuf, io::Error),
64
65 NetIO(reqwest::Error),
67
68 InvalidSignature(String),
70
71 InvalidResponseStatus(reqwest::StatusCode),
73
74 MalformedResponseBody(String),
76
77 InvalidConsistencyProof {
79 prev_size: u64,
80 new_size: u64,
81 desc: String,
82 },
83
84 CannotVerifyTreeData(String),
86
87 BadCertificate(String),
89
90 InvalidInclusionProof {
92 tree_size: u64,
93 leaf_index: u64,
94 desc: String,
95 },
96
97 BadSct(String),
99
100 ExpectedEntry(u64),
102}
103
104#[derive(Debug)]
107pub enum SthResult {
108 Ok(SignedTreeHead),
110
111 Err(Error),
113
114 ErrWithSth(Error, SignedTreeHead),
117}
118
119impl SthResult {
120 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 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 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 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
227pub 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 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 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 pub fn get_checked_tree_head(&self) -> (u64, [u8; 32]) {
333 (self.latest_size, self.latest_tree_hash)
334 }
335
336 pub fn get_reqwest_client(&self) -> &reqwest::Client {
338 &self.http_client
339 }
340
341 pub fn get_base_url(&self) -> &reqwest::Url {
345 &self.base_url
346 }
347
348 pub async fn light_update(&mut self) -> SthResult {
350 self.update(None::<fn(&[X509])>).await
351 }
352
353 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 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 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 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 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 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_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(×tamp) {
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 pub fn as_bytes(&self) -> Result<Vec<u8>, Error> {
723 let mut v = Vec::new();
726 v.push(0u8); 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 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(); 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;