tlsn_core/transcript/
tls.rs1use 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#[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 #[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 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 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 if let Some(record) = sent_iter.next_back() {
113 match record.typ {
114 ContentType::ApplicationData => {}
115 ContentType::Alert => {
116 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 if let Some(record) = recv_iter.next_back() {
146 match record.typ {
147 ContentType::ApplicationData => {}
148 ContentType::Alert => {
149 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 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 pub fn time(&self) -> u64 {
209 self.time
210 }
211
212 pub fn version(&self) -> &TlsVersion {
214 &self.version
215 }
216
217 pub fn server_cert_chain(&self) -> Option<&[Certificate]> {
219 self.server_cert_chain.as_deref()
220 }
221
222 pub fn server_signature(&self) -> Option<&ServerSignature> {
224 self.server_signature.as_ref()
225 }
226
227 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 pub fn handshake_data(&self) -> &HandshakeData {
239 &self.handshake_data
240 }
241
242 pub fn sent(&self) -> &[Record] {
244 &self.sent
245 }
246
247 pub fn recv(&self) -> &[Record] {
249 &self.recv
250 }
251
252 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#[derive(Clone)]
295pub struct Record {
296 pub seq: u64,
298 pub typ: ContentType,
300 pub plaintext: Option<Vec<u8>>,
302 pub explicit_nonce: Vec<u8>,
304 pub ciphertext: Vec<u8>,
306 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}