dimpl 0.6.2

DTLS 1.2/1.3 implementation (Sans‑IO, Sync)
Documentation
//! Low-level DTLS message parsing and serialization types.
//!
//! This module exposes enums and helpers used by the public API for negotiating
//! cipher suites and signature algorithms, as well as parsing wire formats.
//! Only the public items are documented here; the rest are internal helpers.

mod certificate;
mod certificate_request;
mod certificate_verify;
mod client_hello;
mod client_key_exchange;
mod digitally_signed;
mod extension;
mod extensions;
mod finished;
mod handshake;
mod hello_verify;
mod id;
mod named_group;
mod record;
mod server_hello;
mod server_key_exchange;
mod wrapped;

use arrayvec::ArrayVec;
pub use certificate::Certificate;
pub use certificate_request::CertificateRequest;
pub use certificate_verify::CertificateVerify;
pub use client_hello::ClientHello;
pub use client_key_exchange::{ClientKeyExchange, ClientPskKeys, ExchangeKeys};
pub use digitally_signed::DigitallySigned;
pub use extension::{Extension, ExtensionType};
pub use extensions::ec_point_formats::ECPointFormatsExtension;
pub use extensions::signature_algorithms::SignatureAlgorithmsExtension;
pub use extensions::supported_groups::SupportedGroupsExtension;
pub use extensions::use_srtp::{SrtpProfileId, SrtpProfileVec, UseSrtpExtension};
pub use finished::Finished;
pub use handshake::{Body, Handshake, Header, MessageType};
pub use hello_verify::HelloVerifyRequest;
pub use id::{Cookie, SessionId};
pub use named_group::CurveType;
pub use record::DTLSRecord;

// Re-export shared types for backwards compatibility
pub use crate::types::{
    CompressionMethod, ContentType, HashAlgorithm, NamedGroup, NamedGroupVec, ProtocolVersion,
    Random, Sequence, SignatureAlgorithm,
};
pub use server_hello::ServerHello;
pub use server_key_exchange::{PskParams, ServerKeyExchange, ServerKeyExchangeParams};
pub use wrapped::{Asn1Cert, DistinguishedName};

use nom::IResult;
use nom::number::complete::{be_u8, be_u16};

pub type CipherSuiteVec = ArrayVec<Dtls12CipherSuite, { Dtls12CipherSuite::supported().len() }>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
/// Supported TLS 1.2 cipher suites for DTLS.
pub enum Dtls12CipherSuite {
    // ECDHE with AES-GCM
    /// ECDHE with ECDSA authentication, AES-256-GCM, SHA-384
    ECDHE_ECDSA_AES256_GCM_SHA384, // 0xC02C
    /// ECDHE with ECDSA authentication, AES-128-GCM, SHA-256
    ECDHE_ECDSA_AES128_GCM_SHA256, // 0xC02B
    /// ECDHE with ECDSA authentication, ChaCha20-Poly1305, SHA-256
    ECDHE_ECDSA_CHACHA20_POLY1305_SHA256, // 0xCCA9

    // PSK cipher suites (no certificate authentication)
    /// PSK with AES-128-CCM-8 (8-byte tag), SHA-256
    PSK_AES128_CCM_8, // 0xC0A8

    /// Unknown or unsupported cipher suite by its IANA value
    Unknown(u16),
}

impl Default for Dtls12CipherSuite {
    fn default() -> Self {
        Self::Unknown(0)
    }
}

impl Dtls12CipherSuite {
    /// Convert the 16-bit IANA value to a `Dtls12CipherSuite`.
    pub fn from_u16(value: u16) -> Self {
        match value {
            // ECDHE with AES-GCM
            0xC02C => Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384,
            0xC02B => Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256,
            0xCCA9 => Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256,

            // PSK
            0xC0A8 => Dtls12CipherSuite::PSK_AES128_CCM_8,

            _ => Dtls12CipherSuite::Unknown(value),
        }
    }

    /// Return the 16-bit IANA value for this cipher suite.
    pub fn as_u16(&self) -> u16 {
        match self {
            // ECDHE with AES-GCM
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384 => 0xC02C,
            Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256 => 0xC02B,
            Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256 => 0xCCA9,

            Dtls12CipherSuite::PSK_AES128_CCM_8 => 0xC0A8,

            Dtls12CipherSuite::Unknown(value) => *value,
        }
    }

    /// Parse a `Dtls12CipherSuite` from network byte order.
    pub fn parse(input: &[u8]) -> IResult<&[u8], Dtls12CipherSuite> {
        let (input, value) = be_u16(input)?;
        Ok((input, Dtls12CipherSuite::from_u16(value)))
    }

    /// Length in bytes of verify_data for Finished MACs.
    pub fn verify_data_length(&self) -> usize {
        match self {
            // AES-GCM suites
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384
            | Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256
            | Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256
            | Dtls12CipherSuite::PSK_AES128_CCM_8 => 12,

            Dtls12CipherSuite::Unknown(_) => 12, // Default length for unknown cipher suites
        }
    }

    /// The key exchange algorithm family for this cipher suite.
    pub fn as_key_exchange_algorithm(&self) -> KeyExchangeAlgorithm {
        match self {
            // All ECDHE ciphers
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384
            | Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256
            | Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256 => {
                KeyExchangeAlgorithm::EECDH
            }

            Dtls12CipherSuite::PSK_AES128_CCM_8 => KeyExchangeAlgorithm::PSK,

            Dtls12CipherSuite::Unknown(_) => KeyExchangeAlgorithm::Unknown,
        }
    }

    /// Whether this cipher suite uses ECC-based key exchange.
    pub fn has_ecc(&self) -> bool {
        matches!(
            self,
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384
                | Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256
                | Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256
        )
    }

    /// Whether this cipher suite uses PSK (Pre-Shared Key) key exchange.
    pub fn is_psk(&self) -> bool {
        matches!(self, Dtls12CipherSuite::PSK_AES128_CCM_8)
    }

    /// All supported cipher suites in server preference order.
    pub const fn all() -> &'static [Dtls12CipherSuite; 4] {
        &[
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384,
            Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256,
            Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256,
            Dtls12CipherSuite::PSK_AES128_CCM_8,
        ]
    }

    /// Cipher suites compatible with a given certificate's signature algorithm.
    pub fn compatible_with_certificate(
        cert_type: SignatureAlgorithm,
    ) -> &'static [Dtls12CipherSuite; 3] {
        match cert_type {
            SignatureAlgorithm::ECDSA => &[
                Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384,
                Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256,
                Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256,
            ],
            _ => panic!("Need either RSA or ECDSA certificate"),
        }
    }

    fn need_encrypt_then_mac(&self) -> bool {
        // We do not support and ciphers such as:
        // ECDHE-RSA-AES128-SHA
        // ECDHE-RSA-AES256-SHA
        // DHE-RSA-AES128-SHA256
        false
    }

    /// The hash algorithm used by this cipher suite.
    pub fn hash_algorithm(&self) -> HashAlgorithm {
        match self {
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384 => HashAlgorithm::SHA384,
            Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256
            | Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256
            | Dtls12CipherSuite::PSK_AES128_CCM_8 => HashAlgorithm::SHA256,
            Dtls12CipherSuite::Unknown(_) => HashAlgorithm::Unknown(0),
        }
    }

    /// The signature algorithm associated with the suite's key exchange.
    ///
    /// Returns `None` for PSK cipher suites (no signature authentication).
    pub fn signature_algorithm(&self) -> Option<SignatureAlgorithm> {
        match self {
            Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384
            | Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256
            | Dtls12CipherSuite::ECDHE_ECDSA_CHACHA20_POLY1305_SHA256 => {
                Some(SignatureAlgorithm::ECDSA)
            }
            Dtls12CipherSuite::PSK_AES128_CCM_8 => None,
            Dtls12CipherSuite::Unknown(_) => Some(SignatureAlgorithm::Unknown(0)),
        }
    }

    /// Returns true if this cipher suite is supported by this implementation.
    pub fn is_supported(&self) -> bool {
        Self::supported().contains(self)
    }

    /// Supported DTLS 1.2 cipher suites in server preference order.
    pub const fn supported() -> &'static [Dtls12CipherSuite; 4] {
        Self::all()
    }
}

pub type CompressionMethodVec =
    ArrayVec<CompressionMethod, { CompressionMethod::supported().len() }>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum KeyExchangeAlgorithm {
    EECDH,
    PSK,
    Unknown,
}

pub type CertificateTypeVec =
    ArrayVec<ClientCertificateType, { ClientCertificateType::supported().len() }>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub enum ClientCertificateType {
    RSA_SIGN,
    DSS_SIGN,
    RSA_FIXED_DH,
    DSS_FIXED_DH,
    RSA_EPHEMERAL_DH,
    DSS_EPHEMERAL_DH,
    FORTEZZA_DMS,
    ECDSA_SIGN,
    Unknown(u8),
}

impl Default for ClientCertificateType {
    fn default() -> Self {
        Self::Unknown(0)
    }
}

impl ClientCertificateType {
    pub fn from_u8(value: u8) -> Self {
        match value {
            1 => ClientCertificateType::RSA_SIGN,
            2 => ClientCertificateType::DSS_SIGN,
            3 => ClientCertificateType::RSA_FIXED_DH,
            4 => ClientCertificateType::DSS_FIXED_DH,
            5 => ClientCertificateType::RSA_EPHEMERAL_DH,
            6 => ClientCertificateType::DSS_EPHEMERAL_DH,
            20 => ClientCertificateType::FORTEZZA_DMS,
            64 => ClientCertificateType::ECDSA_SIGN,
            _ => ClientCertificateType::Unknown(value),
        }
    }

    /// Returns true if this certificate type is supported by this implementation.
    /// Currently only ECDSA_SIGN is supported.
    pub fn is_supported(&self) -> bool {
        Self::supported().contains(self)
    }

    /// Supported client certificate types.
    pub const fn supported() -> &'static [ClientCertificateType; 1] {
        &[ClientCertificateType::ECDSA_SIGN]
    }

    pub fn as_u8(&self) -> u8 {
        match self {
            ClientCertificateType::RSA_SIGN => 1,
            ClientCertificateType::DSS_SIGN => 2,
            ClientCertificateType::RSA_FIXED_DH => 3,
            ClientCertificateType::DSS_FIXED_DH => 4,
            ClientCertificateType::RSA_EPHEMERAL_DH => 5,
            ClientCertificateType::DSS_EPHEMERAL_DH => 6,
            ClientCertificateType::FORTEZZA_DMS => 20,
            ClientCertificateType::ECDSA_SIGN => 64,
            ClientCertificateType::Unknown(value) => *value,
        }
    }

    pub fn parse(input: &[u8]) -> IResult<&[u8], ClientCertificateType> {
        let (input, value) = be_u8(input)?;
        Ok((input, ClientCertificateType::from_u8(value)))
    }
}

// SignatureAlgorithm and HashAlgorithm are now in crate::types

pub type SignatureAndHashAlgorithmVec =
    ArrayVec<SignatureAndHashAlgorithm, { SignatureAndHashAlgorithm::supported().len() }>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SignatureAndHashAlgorithm {
    pub hash: HashAlgorithm,
    pub signature: SignatureAlgorithm,
}

impl SignatureAndHashAlgorithm {
    pub const fn new(hash: HashAlgorithm, signature: SignatureAlgorithm) -> Self {
        SignatureAndHashAlgorithm { hash, signature }
    }

    pub fn from_u16(value: u16) -> Self {
        let hash = HashAlgorithm::from_u8((value >> 8) as u8);
        let signature = SignatureAlgorithm::from_u8(value as u8);
        SignatureAndHashAlgorithm { hash, signature }
    }

    pub fn as_u16(&self) -> u16 {
        ((self.hash.as_u8() as u16) << 8) | (self.signature.as_u8() as u16)
    }

    pub fn parse(input: &[u8]) -> IResult<&[u8], SignatureAndHashAlgorithm> {
        let (input, value) = be_u16(input)?;
        Ok((input, SignatureAndHashAlgorithm::from_u16(value)))
    }

    /// All recognized signature+hash combinations (same as `supported()`).
    #[allow(dead_code)]
    pub const fn all() -> &'static [SignatureAndHashAlgorithm; 4] {
        Self::supported()
    }

    /// Supported signature+hash combinations.
    pub const fn supported() -> &'static [SignatureAndHashAlgorithm; 4] {
        const SUPPORTED: &[SignatureAndHashAlgorithm; 4] = &[
            SignatureAndHashAlgorithm::new(HashAlgorithm::SHA256, SignatureAlgorithm::ECDSA),
            SignatureAndHashAlgorithm::new(HashAlgorithm::SHA384, SignatureAlgorithm::ECDSA),
            SignatureAndHashAlgorithm::new(HashAlgorithm::SHA256, SignatureAlgorithm::RSA),
            SignatureAndHashAlgorithm::new(HashAlgorithm::SHA384, SignatureAlgorithm::RSA),
        ];

        SUPPORTED
    }

    /// Returns true if this signature+hash combination is supported.
    pub fn is_supported(&self) -> bool {
        Self::supported().contains(self)
    }
}