Skip to main content

typhoon/certificate/
utils.rs

1//! Shared constants, header I/O helpers, and error types for certificate files.
2
3use std::io::{self, Read, Write};
4use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
5
6use classic_mceliece_rust::{CRYPTO_PUBLICKEYBYTES, CRYPTO_SECRETKEYBYTES};
7
8use crate::bytes::FixedByteBuffer;
9
10// ── Stable format constants ──────────────────────────────────────────────────
11
12/// Classic McEliece 348864 public key size in bytes.
13pub const EPK_BYTES: usize = 261_120;
14/// Classic McEliece 348864 secret key size in bytes.
15pub const ESK_BYTES: usize = 6_492;
16/// Ed25519 key size in bytes (signing key, verifying key, or obfuscation key).
17pub const ED25519_BYTES: usize = 32;
18/// X25519 key size in bytes (public or static secret).
19pub const X25519_BYTES: usize = 32;
20
21// Compile-time guards: catch upstream constant drift before it silently corrupts files.
22const _: () = assert!(EPK_BYTES == CRYPTO_PUBLICKEYBYTES, "EPK_BYTES must match CRYPTO_PUBLICKEYBYTES");
23const _: () = assert!(ESK_BYTES == CRYPTO_SECRETKEYBYTES, "ESK_BYTES must match CRYPTO_SECRETKEYBYTES");
24
25// ── File header constants ────────────────────────────────────────────────────
26
27pub(crate) const MAGIC: &[u8; 7] = b"TYPHOON";
28#[cfg(feature = "server")]
29pub(crate) const TYPE_SERVER: u8 = b'S';
30pub(crate) const TYPE_CLIENT: u8 = b'C';
31pub(crate) const FORMAT_VERSION: u8 = 1;
32
33#[cfg(any(feature = "fast_software", feature = "fast_hardware"))]
34pub(crate) const MODE_BYTE: u8 = b'F';
35#[cfg(any(feature = "full_software", feature = "full_hardware"))]
36pub(crate) const MODE_BYTE: u8 = b'U';
37
38// ── CertificateError ─────────────────────────────────────────────────────────
39
40/// Error type for certificate file operations.
41#[derive(Debug, thiserror::Error)]
42pub enum CertificateError {
43    #[error("I/O error: {0}")]
44    Io(#[from] io::Error),
45    #[error("invalid file: bad magic bytes")]
46    InvalidMagic,
47    #[error("invalid file type: expected '{expected}', got '{got}'")]
48    InvalidType {
49        expected: char,
50        got: char,
51    },
52    #[error("mode mismatch: file was written for a different crypto mode")]
53    ModeMismatch,
54    #[error("unsupported format version: {0}")]
55    UnsupportedVersion(u8),
56    #[error("invalid key data in file")]
57    InvalidKeyData,
58    #[error("certificate contains no server addresses")]
59    NoAddresses,
60}
61
62// ── Header I/O ────────────────────────────────────────────────────────────────
63
64pub(crate) fn write_header(w: &mut impl Write, record_type: u8) -> Result<(), io::Error> {
65    w.write_all(MAGIC)?;
66    w.write_all(&[record_type, MODE_BYTE, FORMAT_VERSION])
67}
68
69pub(crate) fn read_header(r: &mut impl Read, expected_type: u8) -> Result<(), CertificateError> {
70    let mut magic = [0u8; 7];
71    r.read_exact(&mut magic)?;
72    if &magic != MAGIC {
73        return Err(CertificateError::InvalidMagic);
74    }
75    let mut header = [0u8; 3];
76    r.read_exact(&mut header)?;
77    let [record_type, mode, version] = header;
78    if record_type != expected_type {
79        return Err(CertificateError::InvalidType {
80            expected: expected_type as char,
81            got: record_type as char,
82        });
83    }
84    if mode != MODE_BYTE {
85        return Err(CertificateError::ModeMismatch);
86    }
87    if version != FORMAT_VERSION {
88        return Err(CertificateError::UnsupportedVersion(version));
89    }
90    Ok(())
91}
92
93// ── Address I/O ───────────────────────────────────────────────────────────────
94
95pub(crate) fn write_addresses(w: &mut impl Write, addrs: &[SocketAddr]) -> Result<(), io::Error> {
96    w.write_all(&(addrs.len() as u16).to_be_bytes())?;
97    for addr in addrs {
98        match addr.ip() {
99            IpAddr::V4(ip) => {
100                w.write_all(&[4u8])?;
101                w.write_all(&ip.octets())?;
102            }
103            IpAddr::V6(ip) => {
104                w.write_all(&[6u8])?;
105                w.write_all(&ip.octets())?;
106            }
107        }
108        w.write_all(&addr.port().to_be_bytes())?;
109    }
110    Ok(())
111}
112
113pub(crate) fn read_addresses(r: &mut impl Read) -> Result<Vec<SocketAddr>, CertificateError> {
114    let mut count_bytes = [0u8; 2];
115    r.read_exact(&mut count_bytes)?;
116    let count = u16::from_be_bytes(count_bytes) as usize;
117    let mut addrs = Vec::with_capacity(count);
118    for _ in 0..count {
119        let mut family = [0u8; 1];
120        r.read_exact(&mut family)?;
121        let ip = match family[0] {
122            4 => {
123                let mut octets = [0u8; 4];
124                r.read_exact(&mut octets)?;
125                IpAddr::V4(Ipv4Addr::from(octets))
126            }
127            6 => {
128                let mut octets = [0u8; 16];
129                r.read_exact(&mut octets)?;
130                IpAddr::V6(Ipv6Addr::from(octets))
131            }
132            _ => return Err(CertificateError::InvalidKeyData),
133        };
134        let mut port_bytes = [0u8; 2];
135        r.read_exact(&mut port_bytes)?;
136        addrs.push(SocketAddr::new(ip, u16::from_be_bytes(port_bytes)));
137    }
138    Ok(addrs)
139}
140
141// ── ObfuscationBufferContainer ────────────────────────────────────────────────
142
143/// Trait for types containing obfuscation key material.
144pub(crate) trait ObfuscationBufferContainer {
145    /// Get obfuscation buffer (OBFS in fast mode, OPK bytes in full mode).
146    fn obfuscation_buffer(&self) -> FixedByteBuffer<ED25519_BYTES>;
147}