jkipsec 0.1.0

Userspace IKEv2/IPsec VPN responder for terminating iOS VPN tunnels and exposing the inner IP traffic. Pairs with jktcp for a fully userspace TCP/IP stack.
Documentation
//! Generic IKEv2 payload framing (RFC 7296 §3.2) plus a registry of payload
//! type codes. Type-specific parsing lives in `payload_types.rs` (later). For
//! now we just walk the chain and hold raw byte slices so the higher layer
//! can dispatch.

#![allow(missing_docs)]

use super::ParseError;

/// Length of the generic payload header in bytes.
pub const PAYLOAD_HEADER_LEN: usize = 4;

/// Payload type registry (RFC 7296 §3.2 + §3.16/§3.17 for EAP/encrypted, and
/// RFC 7383 for fragmentation).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PayloadKind {
    /// Sentinel - terminates the payload chain.
    None,
    SecurityAssociation,      // 33  SA
    KeyExchange,              // 34  KE
    IdentificationInitiator,  // 35  IDi
    IdentificationResponder,  // 36  IDr
    Certificate,              // 37  CERT
    CertificateRequest,       // 38  CERTREQ
    Authentication,           // 39  AUTH
    Nonce,                    // 40  Ni / Nr
    Notify,                   // 41  N
    Delete,                   // 42  D
    VendorId,                 // 43  V
    TrafficSelectorInitiator, // 44 TSi
    TrafficSelectorResponder, // 45 TSr
    Encrypted,                // 46  SK   (encrypted+authenticated)
    Configuration,            // 47  CP
    EapPayload,               // 48  EAP
    EncryptedFragment,        // 53  SKF  (RFC 7383)
    Other(u8),
}

impl PayloadKind {
    pub fn from_u8(v: u8) -> Self {
        match v {
            0 => Self::None,
            33 => Self::SecurityAssociation,
            34 => Self::KeyExchange,
            35 => Self::IdentificationInitiator,
            36 => Self::IdentificationResponder,
            37 => Self::Certificate,
            38 => Self::CertificateRequest,
            39 => Self::Authentication,
            40 => Self::Nonce,
            41 => Self::Notify,
            42 => Self::Delete,
            43 => Self::VendorId,
            44 => Self::TrafficSelectorInitiator,
            45 => Self::TrafficSelectorResponder,
            46 => Self::Encrypted,
            47 => Self::Configuration,
            48 => Self::EapPayload,
            53 => Self::EncryptedFragment,
            other => Self::Other(other),
        }
    }

    pub fn as_u8(self) -> u8 {
        match self {
            Self::None => 0,
            Self::SecurityAssociation => 33,
            Self::KeyExchange => 34,
            Self::IdentificationInitiator => 35,
            Self::IdentificationResponder => 36,
            Self::Certificate => 37,
            Self::CertificateRequest => 38,
            Self::Authentication => 39,
            Self::Nonce => 40,
            Self::Notify => 41,
            Self::Delete => 42,
            Self::VendorId => 43,
            Self::TrafficSelectorInitiator => 44,
            Self::TrafficSelectorResponder => 45,
            Self::Encrypted => 46,
            Self::Configuration => 47,
            Self::EapPayload => 48,
            Self::EncryptedFragment => 53,
            Self::Other(v) => v,
        }
    }
}

/// Generic payload header: `next | critical+reserved | length`.
#[derive(Debug, Clone, Copy)]
pub struct PayloadHeader {
    pub next_payload: PayloadKind,
    pub critical: bool,
    /// Length of the payload *including* this 4-byte header.
    pub length: u16,
}

impl PayloadHeader {
    pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
        if bytes.len() < PAYLOAD_HEADER_LEN {
            return Err(ParseError::Truncated {
                what: "payload header",
                need: PAYLOAD_HEADER_LEN,
                got: bytes.len(),
            });
        }
        Ok(Self {
            next_payload: PayloadKind::from_u8(bytes[0]),
            critical: bytes[1] & 0b1000_0000 != 0,
            length: u16::from_be_bytes([bytes[2], bytes[3]]),
        })
    }
}

/// One unparsed payload: the generic header plus the raw body bytes
/// (the bytes *after* the 4-byte payload header, length = `header.length - 4`).
///
/// `kind` is the type *of this payload*, derived from the `next_payload` field
/// of the *previous* header (or the IKE header's `next_payload` for the first).
#[derive(Debug, Clone)]
pub struct RawPayload<'a> {
    pub kind: PayloadKind,
    pub header: PayloadHeader,
    pub body: &'a [u8],
}

/// A typed payload: populated lazily by the type-specific parsers in later
/// modules. For now this enum holds only the raw form so the parser compiles
/// without the rest of the tree.
#[derive(Debug, Clone)]
pub enum Payload<'a> {
    Raw(RawPayload<'a>),
}

/// Walk a payload chain starting at `bytes`, where `first_kind` is the kind of
/// the first payload (taken from the IKE header's `next_payload`).
///
/// Returns the chain in order. The chain ends when a payload's `next_payload`
/// is `None`, or when the input is exhausted.
pub fn walk_chain<'a>(
    first_kind: PayloadKind,
    mut bytes: &'a [u8],
) -> Result<Vec<RawPayload<'a>>, ParseError> {
    let mut out = Vec::new();
    let mut kind = first_kind;

    while kind != PayloadKind::None {
        let header = PayloadHeader::parse(bytes)?;
        let total = header.length as usize;
        if total < PAYLOAD_HEADER_LEN {
            return Err(ParseError::BadLength {
                what: "payload",
                value: total,
            });
        }
        if bytes.len() < total {
            return Err(ParseError::Truncated {
                what: "payload body",
                need: total,
                got: bytes.len(),
            });
        }
        let body = &bytes[PAYLOAD_HEADER_LEN..total];
        out.push(RawPayload { kind, header, body });
        kind = header.next_payload;
        bytes = &bytes[total..];

        // Encrypted (SK) payload terminates the unencrypted chain. Anything
        // after it lives inside the ciphertext, not in the outer chain.
        if matches!(
            out.last().unwrap().kind,
            PayloadKind::Encrypted | PayloadKind::EncryptedFragment
        ) {
            break;
        }
    }

    Ok(out)
}