1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19pub enum ContentType {
20 ChangeCipherSpec,
22 Alert,
24 Handshake,
26 ApplicationData,
28 Heartbeat,
30 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#[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 #[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 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 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 if let Some(record) = sent_iter.next_back() {
156 match record.typ {
157 ContentType::ApplicationData => {}
158 ContentType::Alert => {
159 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 if let Some(record) = recv_iter.next_back() {
189 match record.typ {
190 ContentType::ApplicationData => {}
191 ContentType::Alert => {
192 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 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 pub fn time(&self) -> u64 {
252 self.time
253 }
254
255 pub fn version(&self) -> &TlsVersion {
257 &self.version
258 }
259
260 pub fn server_cert_chain(&self) -> Option<&[CertificateDer]> {
262 self.server_cert_chain.as_deref()
263 }
264
265 pub fn server_signature(&self) -> Option<&ServerSignature> {
267 self.server_signature.as_ref()
268 }
269
270 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 pub fn certificate_binding(&self) -> &CertBinding {
282 &self.certificate_binding
283 }
284
285 pub fn sent(&self) -> &[Record] {
287 &self.sent
288 }
289
290 pub fn recv(&self) -> &[Record] {
292 &self.recv
293 }
294
295 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#[derive(Clone)]
338pub struct Record {
339 pub seq: u64,
341 pub typ: ContentType,
343 pub plaintext: Option<Vec<u8>>,
345 pub explicit_nonce: Vec<u8>,
347 pub ciphertext: Vec<u8>,
349 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}