Skip to main content

luct_core/v1/
tree.rs

1use crate::{
2    Version,
3    store::Hashable,
4    utils::codec::{CodecError, Decode, Encode},
5    v1::{LogEntry, extension::CtExtensions},
6};
7use sha2::{Digest, Sha256};
8use std::io::{Cursor, Read, Write};
9
10/// See RFC 6962 3.4
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct MerkleTreeLeaf {
13    pub(crate) version: Version,
14    pub(crate) leaf: Leaf,
15}
16
17impl Hashable for MerkleTreeLeaf {
18    fn hash(&self) -> [u8; 32] {
19        let mut bytes = Cursor::new(vec![]);
20        bytes.write_all(&[0]).unwrap();
21        self.encode(&mut bytes).unwrap();
22
23        Sha256::digest(bytes.into_inner()).into()
24    }
25}
26
27impl Encode for MerkleTreeLeaf {
28    fn encode(&self, mut writer: impl Write) -> Result<(), CodecError> {
29        self.version.encode(&mut writer)?;
30        self.leaf.encode(&mut writer)?;
31        Ok(())
32    }
33}
34
35impl Decode for MerkleTreeLeaf {
36    fn decode(mut reader: impl Read) -> Result<Self, CodecError> {
37        Ok(Self {
38            version: Version::decode(&mut reader)?,
39            leaf: Leaf::decode(&mut reader)?,
40        })
41    }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub(crate) enum Leaf {
46    TimestampedEntry(TimestampedEntry),
47}
48
49impl Encode for Leaf {
50    fn encode(&self, mut writer: impl Write) -> Result<(), CodecError> {
51        match self {
52            Leaf::TimestampedEntry(entry) => {
53                writer.write_all(&[0])?;
54                entry.encode(&mut writer)?;
55            }
56        };
57        Ok(())
58    }
59}
60
61impl Decode for Leaf {
62    fn decode(mut reader: impl Read) -> Result<Self, CodecError> {
63        let mut buf = vec![0u8];
64        reader.read_exact(&mut buf)?;
65
66        match buf[0] {
67            0 => Ok(Leaf::TimestampedEntry(TimestampedEntry::decode(
68                &mut reader,
69            )?)),
70            val => Err(CodecError::UnknownVariant("MerkleLeafType", val as u64)),
71        }
72    }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub(crate) struct TimestampedEntry {
77    pub(crate) timestamp: u64,
78    pub(crate) log_entry: LogEntry,
79    pub(crate) extensions: CtExtensions,
80}
81
82impl Encode for TimestampedEntry {
83    fn encode(&self, mut writer: impl Write) -> Result<(), CodecError> {
84        self.timestamp.encode(&mut writer)?;
85        self.log_entry.encode(&mut writer)?;
86        self.extensions.encode(&mut writer)?;
87        Ok(())
88    }
89}
90
91impl Decode for TimestampedEntry {
92    fn decode(mut reader: impl Read) -> Result<Self, CodecError> {
93        Ok(Self {
94            timestamp: u64::decode(&mut reader)?,
95            log_entry: LogEntry::decode(&mut reader)?,
96            extensions: CtExtensions::decode(&mut reader)?,
97        })
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::{
105        CertificateChain, tests::CERT_CHAIN_GOOGLE_COM, v1::responses::GetEntriesResponse,
106    };
107
108    const GOOGLE_GET_ENTRY: &str = include_str!("../../../testdata/google-entry.json");
109
110    #[test]
111    fn parse_get_entry_response() {
112        let response: GetEntriesResponse = serde_json::from_str(GOOGLE_GET_ENTRY).unwrap();
113        assert_eq!(response.entries.len(), 1);
114
115        // Test round trip
116        let reencoded = serde_json::to_string(&response).unwrap();
117        let response2: GetEntriesResponse = serde_json::from_str(&reencoded).unwrap();
118        assert_eq!(response, response2);
119    }
120
121    #[test]
122    fn generate_precert_comparison() {
123        let response: GetEntriesResponse = serde_json::from_str(GOOGLE_GET_ENTRY).unwrap();
124        let leaf = response.entries[0].leaf_input.0.0.clone();
125
126        let Leaf::TimestampedEntry(entry) = leaf.leaf;
127        let log_entry1 = entry.log_entry;
128
129        let cert2 = CertificateChain::from_pem_chain(CERT_CHAIN_GOOGLE_COM).unwrap();
130        cert2.verify_chain().unwrap();
131        let log_entry2 = cert2.as_log_entry_v1(true).unwrap();
132
133        assert_eq!(log_entry1, log_entry2);
134    }
135
136    #[test]
137    fn test_leaf_creation() {
138        let response: GetEntriesResponse = serde_json::from_str(GOOGLE_GET_ENTRY).unwrap();
139        let leaf1 = response.entries[0].leaf_input.0.0.clone();
140
141        let cert2 = CertificateChain::from_pem_chain(CERT_CHAIN_GOOGLE_COM).unwrap();
142        cert2.verify_chain().unwrap();
143        let sct2 = cert2.cert().extract_scts_v1().unwrap();
144        let leaf2 = cert2.as_leaf_v1(&sct2[0], true).unwrap();
145
146        assert_eq!(leaf1, leaf2)
147    }
148}