# tls.rs - source
Source of the Rust file `crates/core/src/transcript/tls.rs`.

Skip to main content

#
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}