tlsn_core/transcript/
tls.rs

1//! TLS transcript.
2
3use crate::{
4    connection::{
5        CertBinding, CertBindingV1_2, ServerEphemKey, ServerSignature, TlsVersion, VerifyData,
6    },
7    transcript::{Direction, Transcript},
8    webpki::CertificateDer,
9};
10use tls_core::msgs::{
11    alert::AlertMessagePayload,
12    codec::{Codec, Reader},
13    enums::{AlertDescription, ProtocolVersion},
14    handshake::{HandshakeMessagePayload, HandshakePayload},
15};
16
17/// TLS record content type.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19pub enum ContentType {
20    /// Change cipher spec protocol.
21    ChangeCipherSpec,
22    /// Alert protocol.
23    Alert,
24    /// Handshake protocol.
25    Handshake,
26    /// Application data protocol.
27    ApplicationData,
28    /// Heartbeat protocol.
29    Heartbeat,
30    /// Unknown protocol.
31    Unknown(u8),
32}
33
34impl From<ContentType> for tls_core::msgs::enums::ContentType {
35    fn from(content_type: ContentType) -> Self {
36        match content_type {
37            ContentType::ChangeCipherSpec => tls_core::msgs::enums::ContentType::ChangeCipherSpec,
38            ContentType::Alert => tls_core::msgs::enums::ContentType::Alert,
39            ContentType::Handshake => tls_core::msgs::enums::ContentType::Handshake,
40            ContentType::ApplicationData => tls_core::msgs::enums::ContentType::ApplicationData,
41            ContentType::Heartbeat => tls_core::msgs::enums::ContentType::Heartbeat,
42            ContentType::Unknown(id) => tls_core::msgs::enums::ContentType::Unknown(id),
43        }
44    }
45}
46
47impl From<tls_core::msgs::enums::ContentType> for ContentType {
48    fn from(content_type: tls_core::msgs::enums::ContentType) -> Self {
49        match content_type {
50            tls_core::msgs::enums::ContentType::ChangeCipherSpec => ContentType::ChangeCipherSpec,
51            tls_core::msgs::enums::ContentType::Alert => ContentType::Alert,
52            tls_core::msgs::enums::ContentType::Handshake => ContentType::Handshake,
53            tls_core::msgs::enums::ContentType::ApplicationData => ContentType::ApplicationData,
54            tls_core::msgs::enums::ContentType::Heartbeat => ContentType::Heartbeat,
55            tls_core::msgs::enums::ContentType::Unknown(id) => ContentType::Unknown(id),
56        }
57    }
58}
59
60/// A transcript of TLS records sent and received by the prover.
61#[derive(Debug, Clone)]
62pub struct TlsTranscript {
63    time: u64,
64    version: TlsVersion,
65    server_cert_chain: Option<Vec<CertificateDer>>,
66    server_signature: Option<ServerSignature>,
67    certificate_binding: CertBinding,
68    sent: Vec<Record>,
69    recv: Vec<Record>,
70}
71
72impl TlsTranscript {
73    /// Creates a new TLS transcript.
74    #[allow(clippy::too_many_arguments)]
75    pub fn new(
76        time: u64,
77        version: TlsVersion,
78        server_cert_chain: Option<Vec<CertificateDer>>,
79        server_signature: Option<ServerSignature>,
80        certificate_binding: CertBinding,
81        verify_data: VerifyData,
82        sent: Vec<Record>,
83        recv: Vec<Record>,
84    ) -> Result<Self, TlsTranscriptError> {
85        let mut sent_iter = sent.iter();
86        let mut recv_iter = recv.iter();
87
88        // Make sure the client finished verify data message was sent first.
89        if let Some(record) = sent_iter.next() {
90            let payload = record
91                .plaintext
92                .as_ref()
93                .ok_or(TlsTranscriptError::validation(
94                    "client finished message was hidden from the follower",
95                ))?;
96
97            let mut reader = Reader::init(payload);
98            let payload =
99                HandshakeMessagePayload::read_version(&mut reader, ProtocolVersion::TLSv1_2)
100                    .ok_or(TlsTranscriptError::validation(
101                        "first record sent was not a handshake message",
102                    ))?;
103
104            let HandshakePayload::Finished(vd) = payload.payload else {
105                return Err(TlsTranscriptError::validation(
106                    "first record sent was not a client finished message",
107                ));
108            };
109
110            if vd.0 != verify_data.client_finished {
111                return Err(TlsTranscriptError::validation(
112                    "inconsistent client finished verify data",
113                ));
114            }
115        } else {
116            return Err(TlsTranscriptError::validation(
117                "client finished was not sent",
118            ));
119        }
120
121        // Make sure the server finished verify data message was received first.
122        if let Some(record) = recv_iter.next() {
123            let payload = record
124                .plaintext
125                .as_ref()
126                .ok_or(TlsTranscriptError::validation(
127                    "server finished message was hidden from the follower",
128                ))?;
129
130            let mut reader = Reader::init(payload);
131            let payload =
132                HandshakeMessagePayload::read_version(&mut reader, ProtocolVersion::TLSv1_2)
133                    .ok_or(TlsTranscriptError::validation(
134                        "first record received was not a handshake message",
135                    ))?;
136
137            let HandshakePayload::Finished(vd) = payload.payload else {
138                return Err(TlsTranscriptError::validation(
139                    "first record received was not a server finished message",
140                ));
141            };
142
143            if vd.0 != verify_data.server_finished {
144                return Err(TlsTranscriptError::validation(
145                    "inconsistent server finished verify data",
146                ));
147            }
148        } else {
149            return Err(TlsTranscriptError::validation(
150                "server finished was not received",
151            ));
152        }
153
154        // Verify last record sent was either application data or close notify.
155        if let Some(record) = sent_iter.next_back() {
156            match record.typ {
157                ContentType::ApplicationData => {}
158                ContentType::Alert => {
159                    // Ensure the alert is a close notify.
160                    let payload =
161                        record
162                            .plaintext
163                            .as_ref()
164                            .ok_or(TlsTranscriptError::validation(
165                                "alert content was hidden from the follower",
166                            ))?;
167
168                    let mut reader = Reader::init(payload);
169                    let payload = AlertMessagePayload::read(&mut reader).ok_or(
170                        TlsTranscriptError::validation("alert message was malformed"),
171                    )?;
172
173                    let AlertDescription::CloseNotify = payload.description else {
174                        return Err(TlsTranscriptError::validation(
175                            "sent alert that is not close notify",
176                        ));
177                    };
178                }
179                typ => {
180                    return Err(TlsTranscriptError::validation(format!(
181                        "sent unexpected record content type: {typ:?}"
182                    )))
183                }
184            }
185        }
186
187        // Verify last record received was either application data or close notify.
188        if let Some(record) = recv_iter.next_back() {
189            match record.typ {
190                ContentType::ApplicationData => {}
191                ContentType::Alert => {
192                    // Ensure the alert is a close notify.
193                    let payload =
194                        record
195                            .plaintext
196                            .as_ref()
197                            .ok_or(TlsTranscriptError::validation(
198                                "alert content was hidden from the follower",
199                            ))?;
200
201                    let mut reader = Reader::init(payload);
202                    let payload = AlertMessagePayload::read(&mut reader).ok_or(
203                        TlsTranscriptError::validation("alert message was malformed"),
204                    )?;
205
206                    let AlertDescription::CloseNotify = payload.description else {
207                        return Err(TlsTranscriptError::validation(
208                            "received alert that is not close notify",
209                        ));
210                    };
211                }
212                typ => {
213                    return Err(TlsTranscriptError::validation(format!(
214                        "received unexpected record content type: {typ:?}"
215                    )))
216                }
217            }
218        }
219
220        // Ensure all other records were application data.
221        for record in sent_iter {
222            if record.typ != ContentType::ApplicationData {
223                return Err(TlsTranscriptError::validation(format!(
224                    "sent unexpected record content type: {:?}",
225                    record.typ
226                )));
227            }
228        }
229
230        for record in recv_iter {
231            if record.typ != ContentType::ApplicationData {
232                return Err(TlsTranscriptError::validation(format!(
233                    "received unexpected record content type: {:?}",
234                    record.typ
235                )));
236            }
237        }
238
239        Ok(Self {
240            time,
241            version,
242            server_cert_chain,
243            server_signature,
244            certificate_binding,
245            sent,
246            recv,
247        })
248    }
249
250    /// Returns the start time of the connection.
251    pub fn time(&self) -> u64 {
252        self.time
253    }
254
255    /// Returns the TLS protocol version.
256    pub fn version(&self) -> &TlsVersion {
257        &self.version
258    }
259
260    /// Returns the server certificate chain.
261    pub fn server_cert_chain(&self) -> Option<&[CertificateDer]> {
262        self.server_cert_chain.as_deref()
263    }
264
265    /// Returns the server signature.
266    pub fn server_signature(&self) -> Option<&ServerSignature> {
267        self.server_signature.as_ref()
268    }
269
270    /// Returns the server ephemeral key used in the TLS handshake.
271    pub fn server_ephemeral_key(&self) -> &ServerEphemKey {
272        match &self.certificate_binding {
273            CertBinding::V1_2(CertBindingV1_2 {
274                server_ephemeral_key,
275                ..
276            }) => server_ephemeral_key,
277        }
278    }
279
280    /// Returns the certificate binding data.
281    pub fn certificate_binding(&self) -> &CertBinding {
282        &self.certificate_binding
283    }
284
285    /// Returns the sent records.
286    pub fn sent(&self) -> &[Record] {
287        &self.sent
288    }
289
290    /// Returns the received records.
291    pub fn recv(&self) -> &[Record] {
292        &self.recv
293    }
294
295    /// Returns the application data transcript.
296    pub fn to_transcript(&self) -> Result<Transcript, TlsTranscriptError> {
297        let mut sent = Vec::new();
298        let mut recv = Vec::new();
299
300        for record in self
301            .sent
302            .iter()
303            .filter(|record| record.typ == ContentType::ApplicationData)
304        {
305            let plaintext = record
306                .plaintext
307                .as_ref()
308                .ok_or(ErrorRepr::Incomplete {
309                    direction: Direction::Sent,
310                    seq: record.seq,
311                })?
312                .clone();
313            sent.extend_from_slice(&plaintext);
314        }
315
316        for record in self
317            .recv
318            .iter()
319            .filter(|record| record.typ == ContentType::ApplicationData)
320        {
321            let plaintext = record
322                .plaintext
323                .as_ref()
324                .ok_or(ErrorRepr::Incomplete {
325                    direction: Direction::Received,
326                    seq: record.seq,
327                })?
328                .clone();
329            recv.extend_from_slice(&plaintext);
330        }
331
332        Ok(Transcript::new(sent, recv))
333    }
334}
335
336/// A TLS record.
337#[derive(Clone)]
338pub struct Record {
339    /// Sequence number.
340    pub seq: u64,
341    /// Content type.
342    pub typ: ContentType,
343    /// Plaintext.
344    pub plaintext: Option<Vec<u8>>,
345    /// Explicit nonce.
346    pub explicit_nonce: Vec<u8>,
347    /// Ciphertext.
348    pub ciphertext: Vec<u8>,
349    /// Tag.
350    pub tag: Option<Vec<u8>>,
351}
352
353opaque_debug::implement!(Record);
354
355#[derive(Debug, thiserror::Error)]
356#[error("TLS transcript error: {0}")]
357pub struct TlsTranscriptError(#[from] ErrorRepr);
358
359impl TlsTranscriptError {
360    fn validation(msg: impl Into<String>) -> Self {
361        Self(ErrorRepr::Validation(msg.into()))
362    }
363}
364
365#[derive(Debug, thiserror::Error)]
366enum ErrorRepr {
367    #[error("validation error: {0}")]
368    Validation(String),
369    #[error("incomplete transcript ({direction}): seq {seq}")]
370    Incomplete { direction: Direction, seq: u64 },
371}