Skip to main content

tlsn_core/transcript/
tls.rs

1//! TLS transcript.
2
3use crate::{
4    connection::{CertBinding, ServerSignature, TlsVersion},
5    transcript::{Direction, Transcript, tls::builder::SfHashInput},
6    webpki::CertificateDer,
7};
8
9use sha2::{Digest, Sha256};
10
11mod builder;
12pub use builder::TlsTranscriptBuilder;
13
14/// A transcript of TLS records sent and received by the prover.
15///
16/// # Invariants
17///
18/// * First record of `TlsTranscript::sent` or `TlsTranscript::recv` is the
19///   finished record.
20/// * Records are ordered but records which are not of type
21///   [`ContentType::ApplicationData`] can be missing.
22/// * Handshake related fields may be absent.
23/// * Plaintext of records may be absent.
24#[derive(Debug, Clone)]
25pub struct TlsTranscript {
26    pub(crate) time: u64,
27    pub(crate) version: TlsVersion,
28    pub(crate) server_signature: Option<ServerSignature>,
29    pub(crate) server_cert_chain: Option<Vec<CertificateDer>>,
30    pub(crate) certificate_binding: CertBinding,
31    pub(crate) sent: Vec<Record>,
32    pub(crate) recv: Vec<Record>,
33    pub(crate) cf_hash: Option<[u8; 32]>,
34    pub(crate) session_hash: Option<[u8; 32]>,
35    pub(crate) sf_hash: Option<SfHashInput>,
36}
37
38impl TlsTranscript {
39    /// Returns a builder for [`TlsTranscript`].
40    pub fn builder<'a>() -> TlsTranscriptBuilder<'a> {
41        TlsTranscriptBuilder::default()
42    }
43
44    /// Returns the start time of the connection.
45    pub fn time(&self) -> u64 {
46        self.time
47    }
48
49    /// Returns the TLS protocol version.
50    pub fn version(&self) -> TlsVersion {
51        self.version
52    }
53
54    /// Returns the signature of the server.
55    pub fn server_signature(&self) -> Option<&ServerSignature> {
56        self.server_signature.as_ref()
57    }
58
59    /// Returns the certificate chain.
60    pub fn server_cert_chain(&self) -> Option<&[CertificateDer]> {
61        self.server_cert_chain.as_deref()
62    }
63
64    /// Returns the certificate binding.
65    pub fn certificate_binding(&self) -> &CertBinding {
66        &self.certificate_binding
67    }
68
69    /// Returns the sent records.
70    pub fn sent(&self) -> &[Record] {
71        &self.sent
72    }
73
74    /// Returns the received records.
75    pub fn recv(&self) -> &[Record] {
76        &self.recv
77    }
78
79    /// Returns the client finished record.
80    pub fn client_finished(&self) -> &Record {
81        self.sent()
82            .first()
83            .expect("client finished record should be present")
84    }
85
86    /// Returns the client finished verify data.
87    pub fn cf_vd(&self) -> Option<&[u8]> {
88        let cf = self.client_finished();
89
90        // Strips off the handshake message header.
91        cf.plaintext.as_ref().and_then(|plain| plain.get(4..))
92    }
93
94    /// Returns the server finished record.
95    pub fn server_finished(&self) -> &Record {
96        self.recv()
97            .first()
98            .expect("server finished record should be present")
99    }
100
101    /// Returns the server finished verify data.
102    pub fn sf_vd(&self) -> Option<&[u8]> {
103        let sf = self.server_finished();
104
105        // Strips off the handshake message header.
106        sf.plaintext.as_ref().and_then(|plain| plain.get(4..))
107    }
108
109    /// Returns the client finished hash.
110    pub fn cf_hash(&self) -> Option<[u8; 32]> {
111        self.cf_hash.as_ref().copied()
112    }
113
114    /// Returns the session hash.
115    ///
116    /// The session hash is the SHA-256 digest over the handshake messages
117    /// from ClientHello up to and including ClientKeyExchange (RFC 7627).
118    /// It is used to derive the extended master secret.
119    pub fn session_hash(&self) -> Option<[u8; 32]> {
120        self.session_hash.as_ref().copied()
121    }
122
123    /// Returns the server finished hash given the client finished verify
124    /// data.
125    pub fn sf_hash(&self, cf_vd: &[u8; 12]) -> Option<[u8; 32]> {
126        let sf_hash = self.sf_hash.as_ref()?;
127        let SfHashInput {
128            sent_hs_bytes,
129            recv_hs_bytes,
130            sent_ch_end,
131            recv_shd_end,
132        } = sf_hash;
133
134        let mut hasher = Sha256::new();
135        hasher.update(&sent_hs_bytes[..*sent_ch_end]);
136        hasher.update(&recv_hs_bytes[..*recv_shd_end]);
137        hasher.update(&sent_hs_bytes[*sent_ch_end..]);
138
139        // Append the reconstructed Client Finished handshake message.
140        hasher.update([0x14, 0x00, 0x00, 0x0c]);
141        hasher.update(cf_vd);
142        hasher.update(&recv_hs_bytes[*recv_shd_end..]);
143
144        let sf_hash = hasher.finalize().into();
145        Some(sf_hash)
146    }
147
148    /// Returns the application data transcript.
149    pub fn to_transcript(&self) -> Result<Transcript, TlsTranscriptError> {
150        let mut sent = Vec::new();
151        let mut recv = Vec::new();
152
153        for record in self
154            .sent
155            .iter()
156            .filter(|record| record.typ == ContentType::ApplicationData)
157        {
158            let plaintext = record
159                .plaintext
160                .as_ref()
161                .ok_or(ErrorRepr::Incomplete {
162                    direction: Direction::Sent,
163                    seq: record.seq,
164                })?
165                .clone();
166            sent.extend_from_slice(&plaintext);
167        }
168
169        for record in self
170            .recv
171            .iter()
172            .filter(|record| record.typ == ContentType::ApplicationData)
173        {
174            let plaintext = record
175                .plaintext
176                .as_ref()
177                .ok_or(ErrorRepr::Incomplete {
178                    direction: Direction::Received,
179                    seq: record.seq,
180                })?
181                .clone();
182            recv.extend_from_slice(&plaintext);
183        }
184
185        Ok(Transcript::new(sent, recv))
186    }
187}
188
189/// A TLS record.
190#[derive(Clone)]
191pub struct Record {
192    /// Sequence number.
193    pub seq: u64,
194    /// Content type.
195    pub typ: ContentType,
196    /// Plaintext.
197    pub plaintext: Option<Vec<u8>>,
198    /// Explicit nonce.
199    pub explicit_nonce: Vec<u8>,
200    /// Ciphertext.
201    pub ciphertext: Vec<u8>,
202    /// Tag.
203    pub tag: Option<Vec<u8>>,
204}
205
206opaque_debug::implement!(Record);
207
208/// TLS record content type.
209#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
210pub enum ContentType {
211    /// Change cipher spec protocol.
212    ChangeCipherSpec,
213    /// Alert protocol.
214    Alert,
215    /// Handshake protocol.
216    Handshake,
217    /// Application data protocol.
218    ApplicationData,
219    /// Heartbeat protocol.
220    Heartbeat,
221    /// Unknown protocol.
222    Unknown(u8),
223}
224
225impl From<ContentType> for tls_core::msgs::enums::ContentType {
226    fn from(content_type: ContentType) -> Self {
227        match content_type {
228            ContentType::ChangeCipherSpec => tls_core::msgs::enums::ContentType::ChangeCipherSpec,
229            ContentType::Alert => tls_core::msgs::enums::ContentType::Alert,
230            ContentType::Handshake => tls_core::msgs::enums::ContentType::Handshake,
231            ContentType::ApplicationData => tls_core::msgs::enums::ContentType::ApplicationData,
232            ContentType::Heartbeat => tls_core::msgs::enums::ContentType::Heartbeat,
233            ContentType::Unknown(id) => tls_core::msgs::enums::ContentType::Unknown(id),
234        }
235    }
236}
237
238impl From<tls_core::msgs::enums::ContentType> for ContentType {
239    fn from(content_type: tls_core::msgs::enums::ContentType) -> Self {
240        match content_type {
241            tls_core::msgs::enums::ContentType::ChangeCipherSpec => ContentType::ChangeCipherSpec,
242            tls_core::msgs::enums::ContentType::Alert => ContentType::Alert,
243            tls_core::msgs::enums::ContentType::Handshake => ContentType::Handshake,
244            tls_core::msgs::enums::ContentType::ApplicationData => ContentType::ApplicationData,
245            tls_core::msgs::enums::ContentType::Heartbeat => ContentType::Heartbeat,
246            tls_core::msgs::enums::ContentType::Unknown(id) => ContentType::Unknown(id),
247        }
248    }
249}
250
251/// Error type.
252#[derive(Debug, thiserror::Error)]
253#[error("TLS transcript error: {0}")]
254pub struct TlsTranscriptError(#[from] ErrorRepr);
255
256impl TlsTranscriptError {
257    fn parse(msg: impl Into<String>) -> Self {
258        Self(ErrorRepr::Parse(msg.into()))
259    }
260
261    fn missing(field: &'static str) -> Self {
262        Self(ErrorRepr::Missing(field))
263    }
264
265    fn validation(msg: impl Into<String>) -> Self {
266        Self(ErrorRepr::Validation(msg.into()))
267    }
268}
269
270#[derive(Debug, thiserror::Error)]
271pub(crate) enum ErrorRepr {
272    #[error("parse error: {0}")]
273    Parse(String),
274    #[error("missing field: {0}")]
275    Missing(&'static str),
276    #[error("incomplete transcript ({direction}): seq {seq}")]
277    Incomplete { direction: Direction, seq: u64 },
278    #[error("validation error: {0}")]
279    Validation(String),
280}