tlsn_core/transcript/
tls.rs

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