Skip to main content

opentdf_protocol/nanotdf/
header.rs

1//! NanoTDF Header Structures
2//!
3//! The header contains all metadata needed to decrypt the NanoTDF payload.
4
5use crate::binary::{BinaryRead, BinaryWrite, read_u8, write_u8};
6use crate::nanotdf::{MAGIC_NUMBER_AND_VERSION, Policy, resource_locator::ResourceLocator};
7use std::io::{self, Read, Write};
8
9/// Magic number and version (3 bytes)
10/// "L1L" in base64 = 0x4C314C
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct MagicNumberAndVersion {
13    pub bytes: [u8; 3],
14}
15
16impl Default for MagicNumberAndVersion {
17    fn default() -> Self {
18        Self {
19            bytes: MAGIC_NUMBER_AND_VERSION,
20        }
21    }
22}
23
24impl MagicNumberAndVersion {
25    /// Create a new magic number and version
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    /// Validate that this matches the expected magic number and version
31    pub fn validate(&self) -> io::Result<()> {
32        if self.bytes != MAGIC_NUMBER_AND_VERSION {
33            return Err(io::Error::new(
34                io::ErrorKind::InvalidData,
35                format!(
36                    "Invalid magic number and version: {:02X}{:02X}{:02X} (expected 4C314C)",
37                    self.bytes[0], self.bytes[1], self.bytes[2]
38                ),
39            ));
40        }
41        Ok(())
42    }
43}
44
45impl BinaryRead for MagicNumberAndVersion {
46    fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
47        let mut bytes = [0u8; 3];
48        reader.read_exact(&mut bytes)?;
49        let magic = Self { bytes };
50        magic.validate()?;
51        Ok(magic)
52    }
53}
54
55impl BinaryWrite for MagicNumberAndVersion {
56    fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
57        writer.write_all(&self.bytes)
58    }
59
60    fn serialized_size(&self) -> usize {
61        3
62    }
63}
64
65/// Elliptic Curve parameters for key agreement and signatures
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[repr(u8)]
68pub enum EccMode {
69    /// secp256r1 (P-256) - NIST curve
70    Secp256r1 = 0x00,
71    /// secp384r1 (P-384) - NIST curve
72    Secp384r1 = 0x01,
73    /// secp521r1 (P-521) - NIST curve
74    Secp521r1 = 0x02,
75    /// secp256k1 - Bitcoin curve
76    Secp256k1 = 0x03,
77}
78
79impl EccMode {
80    /// Parse from 3-bit value
81    pub fn from_bits(bits: u8) -> io::Result<Self> {
82        match bits & 0x07 {
83            0x00 => Ok(EccMode::Secp256r1),
84            0x01 => Ok(EccMode::Secp384r1),
85            0x02 => Ok(EccMode::Secp521r1),
86            0x03 => Ok(EccMode::Secp256k1),
87            _ => Err(io::Error::new(
88                io::ErrorKind::InvalidData,
89                format!("Invalid ECC mode: {}", bits),
90            )),
91        }
92    }
93
94    /// Convert to 3-bit value
95    pub fn to_bits(self) -> u8 {
96        self as u8
97    }
98
99    /// Get the size of compressed public keys for this curve
100    pub fn public_key_size(self) -> usize {
101        match self {
102            EccMode::Secp256r1 => 33, // 1 byte prefix + 32 bytes
103            EccMode::Secp384r1 => 49, // 1 byte prefix + 48 bytes
104            EccMode::Secp521r1 => 67, // 1 byte prefix + 66 bytes
105            EccMode::Secp256k1 => 33, // 1 byte prefix + 32 bytes
106        }
107    }
108
109    /// Get the size of ECDSA signatures for this curve
110    pub fn signature_size(self) -> usize {
111        match self {
112            EccMode::Secp256r1 => 64,  // r (32 bytes) + s (32 bytes)
113            EccMode::Secp384r1 => 96,  // r (48 bytes) + s (48 bytes)
114            EccMode::Secp521r1 => 132, // r (66 bytes) + s (66 bytes)
115            EccMode::Secp256k1 => 64,  // r (32 bytes) + s (32 bytes)
116        }
117    }
118}
119
120/// ECC and Binding Mode (1 byte bitfield)
121///
122/// ```text
123/// ┌─────────────┬──────────┬──────────────────┐
124/// │USE_ECDSA(1b)│UNUSED(4b)│ECC Params Enum(3b)│
125/// └─────────────┴──────────┴──────────────────┘
126/// ```
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct EccAndBindingMode {
129    /// Use ECDSA for policy binding (true) or GMAC (false)
130    pub use_ecdsa_binding: bool,
131    /// ECC parameters to use for ephemeral key
132    pub ecc_mode: EccMode,
133}
134
135impl EccAndBindingMode {
136    /// Create a new ECC and binding mode
137    pub fn new(use_ecdsa_binding: bool, ecc_mode: EccMode) -> Self {
138        Self {
139            use_ecdsa_binding,
140            ecc_mode,
141        }
142    }
143
144    /// Parse from byte
145    pub fn from_byte(byte: u8) -> io::Result<Self> {
146        let use_ecdsa_binding = (byte & 0x80) != 0;
147        let ecc_mode = EccMode::from_bits(byte & 0x07)?;
148        Ok(Self {
149            use_ecdsa_binding,
150            ecc_mode,
151        })
152    }
153
154    /// Convert to byte
155    pub fn to_byte(self) -> u8 {
156        let ecdsa_bit = if self.use_ecdsa_binding { 0x80 } else { 0x00 };
157        ecdsa_bit | self.ecc_mode.to_bits()
158    }
159}
160
161impl BinaryRead for EccAndBindingMode {
162    fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
163        let byte = read_u8(reader)?;
164        Self::from_byte(byte)
165    }
166}
167
168impl BinaryWrite for EccAndBindingMode {
169    fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
170        write_u8(writer, self.to_byte())
171    }
172
173    fn serialized_size(&self) -> usize {
174        1
175    }
176}
177
178/// Symmetric cipher for payload encryption
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180#[repr(u8)]
181pub enum SymmetricCipher {
182    /// AES-256-GCM with 64-bit authentication tag
183    Aes256Gcm64 = 0x00,
184    /// AES-256-GCM with 96-bit authentication tag
185    Aes256Gcm96 = 0x01,
186    /// AES-256-GCM with 104-bit authentication tag
187    Aes256Gcm104 = 0x02,
188    /// AES-256-GCM with 112-bit authentication tag
189    Aes256Gcm112 = 0x03,
190    /// AES-256-GCM with 120-bit authentication tag
191    Aes256Gcm120 = 0x04,
192    /// AES-256-GCM with 128-bit authentication tag
193    Aes256Gcm128 = 0x05,
194}
195
196impl SymmetricCipher {
197    /// Parse from 4-bit value
198    pub fn from_bits(bits: u8) -> io::Result<Self> {
199        match bits & 0x0F {
200            0x00 => Ok(SymmetricCipher::Aes256Gcm64),
201            0x01 => Ok(SymmetricCipher::Aes256Gcm96),
202            0x02 => Ok(SymmetricCipher::Aes256Gcm104),
203            0x03 => Ok(SymmetricCipher::Aes256Gcm112),
204            0x04 => Ok(SymmetricCipher::Aes256Gcm120),
205            0x05 => Ok(SymmetricCipher::Aes256Gcm128),
206            _ => Err(io::Error::new(
207                io::ErrorKind::InvalidData,
208                format!("Invalid symmetric cipher: {}", bits),
209            )),
210        }
211    }
212
213    /// Convert to 4-bit value
214    pub fn to_bits(self) -> u8 {
215        self as u8
216    }
217
218    /// Get the size of the authentication tag in bytes
219    pub fn tag_size(self) -> usize {
220        match self {
221            SymmetricCipher::Aes256Gcm64 => 8,
222            SymmetricCipher::Aes256Gcm96 => 12,
223            SymmetricCipher::Aes256Gcm104 => 13,
224            SymmetricCipher::Aes256Gcm112 => 14,
225            SymmetricCipher::Aes256Gcm120 => 15,
226            SymmetricCipher::Aes256Gcm128 => 16,
227        }
228    }
229}
230
231/// Payload signature mode
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct PayloadSignatureMode {
234    /// Whether a signature is present
235    pub has_signature: bool,
236    /// ECC mode for signature (if present)
237    pub signature_ecc_mode: Option<EccMode>,
238}
239
240impl PayloadSignatureMode {
241    /// Create mode without signature
242    pub fn none() -> Self {
243        Self {
244            has_signature: false,
245            signature_ecc_mode: None,
246        }
247    }
248
249    /// Create mode with signature
250    pub fn with_signature(ecc_mode: EccMode) -> Self {
251        Self {
252            has_signature: true,
253            signature_ecc_mode: Some(ecc_mode),
254        }
255    }
256
257    /// Get the signature size if present
258    pub fn signature_size(&self) -> usize {
259        if let Some(ecc_mode) = self.signature_ecc_mode {
260            ecc_mode.public_key_size() + ecc_mode.signature_size()
261        } else {
262            0
263        }
264    }
265}
266
267/// Symmetric and Payload Config (1 byte bitfield)
268///
269/// ```text
270/// ┌──────────────┬────────────────┬─────────────────────┐
271/// │HAS_SIG(1b)   │Sig ECC Mode(3b)│Symmetric Cipher(4b) │
272/// └──────────────┴────────────────┴─────────────────────┘
273/// ```
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub struct SymmetricAndPayloadConfig {
276    pub signature_mode: PayloadSignatureMode,
277    pub symmetric_cipher: SymmetricCipher,
278}
279
280impl SymmetricAndPayloadConfig {
281    /// Create a new config
282    pub fn new(signature_mode: PayloadSignatureMode, symmetric_cipher: SymmetricCipher) -> Self {
283        Self {
284            signature_mode,
285            symmetric_cipher,
286        }
287    }
288
289    /// Parse from byte
290    pub fn from_byte(byte: u8) -> io::Result<Self> {
291        let has_signature = (byte & 0x80) != 0;
292        let signature_ecc_mode = if has_signature {
293            Some(EccMode::from_bits((byte >> 4) & 0x07)?)
294        } else {
295            None
296        };
297        let symmetric_cipher = SymmetricCipher::from_bits(byte & 0x0F)?;
298
299        Ok(Self {
300            signature_mode: PayloadSignatureMode {
301                has_signature,
302                signature_ecc_mode,
303            },
304            symmetric_cipher,
305        })
306    }
307
308    /// Convert to byte
309    pub fn to_byte(self) -> u8 {
310        let sig_bit = if self.signature_mode.has_signature {
311            0x80
312        } else {
313            0x00
314        };
315        let sig_ecc = if let Some(ecc_mode) = self.signature_mode.signature_ecc_mode {
316            ecc_mode.to_bits() << 4
317        } else {
318            0x00
319        };
320        sig_bit | sig_ecc | self.symmetric_cipher.to_bits()
321    }
322}
323
324impl BinaryRead for SymmetricAndPayloadConfig {
325    fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
326        let byte = read_u8(reader)?;
327        Self::from_byte(byte)
328    }
329}
330
331impl BinaryWrite for SymmetricAndPayloadConfig {
332    fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
333        write_u8(writer, self.to_byte())
334    }
335
336    fn serialized_size(&self) -> usize {
337        1
338    }
339}
340
341/// NanoTDF Header
342///
343/// Contains all metadata needed to decrypt the payload:
344/// - Magic number and version
345/// - KAS location
346/// - Encryption parameters
347/// - Policy
348/// - Ephemeral public key
349#[derive(Debug, Clone, PartialEq, Eq)]
350pub struct Header {
351    pub magic_number_and_version: MagicNumberAndVersion,
352    pub kas: ResourceLocator,
353    pub ecc_and_binding_mode: EccAndBindingMode,
354    pub symmetric_and_payload_config: SymmetricAndPayloadConfig,
355    pub policy: Policy,
356    pub ephemeral_public_key: Vec<u8>,
357}
358
359impl Header {
360    /// Create a new header
361    pub fn new(
362        kas: ResourceLocator,
363        ecc_and_binding_mode: EccAndBindingMode,
364        symmetric_and_payload_config: SymmetricAndPayloadConfig,
365        policy: Policy,
366        ephemeral_public_key: Vec<u8>,
367    ) -> io::Result<Self> {
368        // Validate ephemeral public key size
369        let expected_size = ecc_and_binding_mode.ecc_mode.public_key_size();
370        if ephemeral_public_key.len() != expected_size {
371            return Err(io::Error::new(
372                io::ErrorKind::InvalidInput,
373                format!(
374                    "Invalid ephemeral public key size: {} (expected {})",
375                    ephemeral_public_key.len(),
376                    expected_size
377                ),
378            ));
379        }
380
381        Ok(Self {
382            magic_number_and_version: MagicNumberAndVersion::new(),
383            kas,
384            ecc_and_binding_mode,
385            symmetric_and_payload_config,
386            policy,
387            ephemeral_public_key,
388        })
389    }
390}
391
392impl BinaryRead for Header {
393    fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
394        let magic_number_and_version = MagicNumberAndVersion::read_from(reader)?;
395        let kas = ResourceLocator::read_from(reader)?;
396        let ecc_and_binding_mode = EccAndBindingMode::read_from(reader)?;
397        let symmetric_and_payload_config = SymmetricAndPayloadConfig::read_from(reader)?;
398        let policy = Policy::read_from(reader)?;
399
400        // Read ephemeral public key (size depends on ECC mode)
401        let key_size = ecc_and_binding_mode.ecc_mode.public_key_size();
402        let mut ephemeral_public_key = vec![0u8; key_size];
403        reader.read_exact(&mut ephemeral_public_key)?;
404
405        Ok(Self {
406            magic_number_and_version,
407            kas,
408            ecc_and_binding_mode,
409            symmetric_and_payload_config,
410            policy,
411            ephemeral_public_key,
412        })
413    }
414}
415
416impl BinaryWrite for Header {
417    fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
418        self.magic_number_and_version.write_to(writer)?;
419        self.kas.write_to(writer)?;
420        self.ecc_and_binding_mode.write_to(writer)?;
421        self.symmetric_and_payload_config.write_to(writer)?;
422        self.policy.write_to(writer)?;
423        writer.write_all(&self.ephemeral_public_key)?;
424        Ok(())
425    }
426
427    fn serialized_size(&self) -> usize {
428        self.magic_number_and_version.serialized_size()
429            + self.kas.serialized_size()
430            + self.ecc_and_binding_mode.serialized_size()
431            + self.symmetric_and_payload_config.serialized_size()
432            + self.policy.serialized_size()
433            + self.ephemeral_public_key.len()
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440
441    #[test]
442    fn test_magic_number() {
443        assert_eq!(MAGIC_NUMBER_AND_VERSION, [0x4C, 0x31, 0x4C]);
444        let magic = MagicNumberAndVersion::new();
445        assert_eq!(magic.bytes, [0x4C, 0x31, 0x4C]);
446        assert!(magic.validate().is_ok());
447    }
448
449    #[test]
450    fn test_ecc_mode_sizes() {
451        assert_eq!(EccMode::Secp256r1.public_key_size(), 33);
452        assert_eq!(EccMode::Secp384r1.public_key_size(), 49);
453        assert_eq!(EccMode::Secp521r1.public_key_size(), 67);
454        assert_eq!(EccMode::Secp256k1.public_key_size(), 33);
455
456        assert_eq!(EccMode::Secp256r1.signature_size(), 64);
457        assert_eq!(EccMode::Secp384r1.signature_size(), 96);
458        assert_eq!(EccMode::Secp521r1.signature_size(), 132);
459    }
460
461    #[test]
462    fn test_ecc_and_binding_mode() {
463        let mode = EccAndBindingMode::new(true, EccMode::Secp256r1);
464        let byte = mode.to_byte();
465        assert_eq!(byte, 0x80); // 1000 0000
466
467        let parsed = EccAndBindingMode::from_byte(byte).unwrap();
468        assert_eq!(parsed, mode);
469    }
470
471    #[test]
472    fn test_symmetric_cipher_tag_sizes() {
473        assert_eq!(SymmetricCipher::Aes256Gcm64.tag_size(), 8);
474        assert_eq!(SymmetricCipher::Aes256Gcm96.tag_size(), 12);
475        assert_eq!(SymmetricCipher::Aes256Gcm128.tag_size(), 16);
476    }
477
478    #[test]
479    fn test_symmetric_and_payload_config() {
480        let config = SymmetricAndPayloadConfig::new(
481            PayloadSignatureMode::with_signature(EccMode::Secp256r1),
482            SymmetricCipher::Aes256Gcm128,
483        );
484        let byte = config.to_byte();
485        // Has signature (1) + ECC mode 0 (000) + Cipher 5 (0101)
486        // 1000 0101 = 0x85
487        assert_eq!(byte, 0x85);
488
489        let parsed = SymmetricAndPayloadConfig::from_byte(byte).unwrap();
490        assert_eq!(parsed.signature_mode.has_signature, true);
491        assert_eq!(
492            parsed.signature_mode.signature_ecc_mode,
493            Some(EccMode::Secp256r1)
494        );
495        assert_eq!(parsed.symmetric_cipher, SymmetricCipher::Aes256Gcm128);
496    }
497}