# tag.rs - source
Source of the Rust file `crates/tlsn/src/tag.rs`.

Skip to main content

#
tlsn/
tag.rs

    1//! TLS record tag verification.
    2
    3use crate::ghash::ghash;
    4
    5use cipher::{Cipher, aes::Aes128};
    6use mpz_core::bitvec::BitVec;
    7use mpz_memory_core::{
    8    DecodeFutureTyped,
    9    binary::{Binary, U8},
    10};
    11use mpz_vm_core::{Vm, prelude::*};
    12use tls_client::ProtocolVersion;
    13use tls_core::cipher::make_tls12_aad;
    14use tlsn_core::{connection::TlsVersion, transcript::Record};
    15
    16/// Proves the verification of tags of the given `records`,
    17/// returning a proof.
    18///
    19/// # Arguments
    20///
    21/// * `vm` - Virtual machine.
    22/// * `key_iv` - Cipher key and IV.
    23/// * `mac_key` - MAC key.
    24/// * `tls_version` - TLS protocol version.
    25/// * `records` - Records for which the verification is to be proven.
    26pub(crate) fn verify_tags(
    27    vm: &mut dyn Vm<Binary>,
    28    key_iv: (Array<U8, 16>, Array<U8, 4>),
    29    mac_key: Array<U8, 16>,
    30    tls_version: TlsVersion,
    31    records: Vec<Record>,
    32) -> Result<TagProof, TagProofError> {
    33    let mut aes = Aes128::default();
    34    aes.set_key(key_iv.0);
    35    aes.set_iv(key_iv.1);
    36
    37    // Compute j0 blocks.
    38    let j0s = records
    39        .iter()
    40        .map(|rec| {
    41            let block = aes.alloc_ctr_block(vm).map_err(TagProofError::vm)?;
    42
    43            let explicit_nonce: [u8; 8] =
    44                rec.explicit_nonce
    45                    .clone()
    46                    .try_into()
    47                    .map_err(|explicit_nonce: Vec<_>| ErrorRepr::ExplicitNonceLength {
    48                        expected: 8,
    49                        actual: explicit_nonce.len(),
    50                    })?;
    51
    52            vm.assign(block.explicit_nonce, explicit_nonce)
    53                .map_err(TagProofError::vm)?;
    54            vm.commit(block.explicit_nonce).map_err(TagProofError::vm)?;
    55
    56            // j0's counter is set to 1.
    57            vm.assign(block.counter, 1u32.to_be_bytes())
    58                .map_err(TagProofError::vm)?;
    59            vm.commit(block.counter).map_err(TagProofError::vm)?;
    60
    61            let j0 = vm.decode(block.output).map_err(TagProofError::vm)?;
    62
    63            Ok(j0)
    64        })
    65        .collect::<Result<Vec<_>, TagProofError>>()?;
    66
    67    let mac_key = vm.decode(mac_key).map_err(TagProofError::vm)?;
    68
    69    Ok(TagProof {
    70        tls_version,
    71        j0s,
    72        records,
    73        mac_key,
    74    })
    75}
    76
    77/// Proof of tag verification.
    78#[derive(Debug)]
    79#[must_use]
    80pub(crate) struct TagProof {
    81    tls_version: TlsVersion,
    82    /// The j0 block for each record.
    83    j0s: Vec<DecodeFutureTyped<BitVec, [u8; 16]>>,
    84    records: Vec<Record>,
    85    /// The MAC key for tag computation.
    86    mac_key: DecodeFutureTyped<BitVec, [u8; 16]>,
    87}
    88
    89impl TagProof {
    90    /// Verifies the proof.
    91    pub(crate) fn verify(self) -> Result<(), TagProofError> {
    92        let Self {
    93            tls_version,
    94            j0s,
    95            mut mac_key,
    96            records,
    97        } = self;
    98
    99        let mac_key = mac_key
    100            .try_recv()
    101            .map_err(TagProofError::vm)?
    102            .ok_or_else(|| ErrorRepr::NotDecoded)?;
    103
    104        let vers = match tls_version {
    105            TlsVersion::V1_2 => ProtocolVersion::TLSv1_2,
    106            TlsVersion::V1_3 => ProtocolVersion::TLSv1_3,
    107        };
    108
    109        for (mut j0, rec) in j0s.into_iter().zip(records) {
    110            let j0 = j0
    111                .try_recv()
    112                .map_err(TagProofError::vm)?
    113                .ok_or_else(|| ErrorRepr::NotDecoded)?;
    114
    115            let aad = make_tls12_aad(rec.seq, rec.typ.into(), vers, rec.ciphertext.len());
    116
    117            let ghash_tag = ghash(aad.as_ref(), &rec.ciphertext, &mac_key);
    118
    119            let record_tag = match rec.tag.as_ref() {
    120                Some(tag) => tag,
    121                None => {
    122                    // This will never happen, since we only call this method
    123                    // for proofs where the records' tags are known.
    124                    return Err(ErrorRepr::UnknownTag.into());
    125                }
    126            };
    127
    128            if *record_tag
    129                != ghash_tag
    130                    .into_iter()
    131                    .zip(j0)
    132                    .map(|(a, b)| a ^ b)
    133                    .collect::<Vec<_>>()
    134            {
    135                return Err(ErrorRepr::InvalidTag.into());
    136            }
    137        }
    138
    139        Ok(())
    140    }
    141}
    142
    143/// Error for [`J0Proof`].
    144#[derive(Debug, thiserror::Error)]
    145#[error(transparent)]
    146pub(crate) struct TagProofError(#[from] ErrorRepr);
    147
    148impl TagProofError {
    149    fn vm<E>(err: E) -> Self
    150    where
    151        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
    152    {
    153        Self(ErrorRepr::Vm(err.into()))
    154    }
    155}
    156
    157#[derive(Debug, thiserror::Error)]
    158#[error("j0 proof error: {0}")]
    159enum ErrorRepr {
    160    #[error("value was not decoded")]
    161    NotDecoded,
    162    #[error("VM error: {0}")]
    163    Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
    164    #[error("tag does not match expected")]
    165    InvalidTag,
    166    #[error("tag is not known")]
    167    UnknownTag,
    168    #[error("invalid explicit nonce length: expected {expected}, got {actual}")]
    169    ExplicitNonceLength { expected: usize, actual: usize },
    170}