Skip to main content

hide_format/
lib.rs

1use thiserror::Error;
2
3mod codec;
4pub use codec::{
5    Metadata, ProtectedHeader, RecipientStanza, SIGNATURE_LEN, SignatureStanza, VERIFYING_KEY_LEN,
6    decode_header, encode_header,
7};
8
9pub const MAGIC: [u8; 8] = *b"HIDE\r\n\x1a\n";
10pub const PREAMBLE_LEN: usize = 16;
11pub const MAX_HEADER_LEN: usize = 1024 * 1024;
12pub const CHUNK_LEN: usize = 65_536;
13pub const SUITE: u16 = 1;
14pub const MAX_RECIPIENTS: usize = 64;
15pub const MAX_SIGNATURES: usize = 8;
16pub const MAX_METADATA_LEN: usize = 262_144;
17pub const TAG_LEN: usize = 16;
18
19#[derive(Debug, Error, PartialEq, Eq)]
20pub enum FormatError {
21    #[error("malformed HIDE header")]
22    MalformedHeader,
23    #[error("noncanonical CBOR encoding")]
24    NonCanonical,
25    #[error("unsupported cryptographic suite")]
26    UnsupportedSuite,
27    #[error("invalid file metadata")]
28    InvalidMetadata,
29    #[error("invalid HIDE container")]
30    InvalidContainer,
31    #[error("unsupported HIDE version")]
32    UnsupportedVersion,
33    #[error("unsupported HIDE object kind or flags")]
34    UnsupportedFeature,
35    #[error("header length is outside protocol limits")]
36    HeaderTooLarge,
37}
38
39impl From<minicbor::decode::Error> for FormatError {
40    fn from(_: minicbor::decode::Error) -> Self {
41        Self::MalformedHeader
42    }
43}
44
45impl From<minicbor::encode::Error<core::convert::Infallible>> for FormatError {
46    fn from(_: minicbor::encode::Error<core::convert::Infallible>) -> Self {
47        Self::MalformedHeader
48    }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct Preamble {
53    header_len: u32,
54    signed: bool,
55}
56
57impl Preamble {
58    pub fn new(header_len: usize) -> Result<Self, FormatError> {
59        Self::with_signed(header_len, false)
60    }
61
62    /// Signed containers advertise minor 2, so a v0.1 reader refuses them rather
63    /// than opening them with the signature silently ignored.
64    pub fn with_signed(header_len: usize, signed: bool) -> Result<Self, FormatError> {
65        if header_len == 0 || header_len > MAX_HEADER_LEN {
66            return Err(FormatError::HeaderTooLarge);
67        }
68        Ok(Self {
69            header_len: header_len as u32,
70            signed,
71        })
72    }
73
74    pub fn header_len(self) -> usize {
75        self.header_len as usize
76    }
77
78    pub fn is_signed(self) -> bool {
79        self.signed
80    }
81
82    pub fn encode(self) -> [u8; PREAMBLE_LEN] {
83        let mut bytes = [0; PREAMBLE_LEN];
84        bytes[..8].copy_from_slice(&MAGIC);
85        bytes[9] = if self.signed { 2 } else { 1 };
86        bytes[10] = 1;
87        bytes[12..].copy_from_slice(&self.header_len.to_be_bytes());
88        bytes
89    }
90
91    pub fn decode(bytes: &[u8]) -> Result<Self, FormatError> {
92        if bytes.len() != PREAMBLE_LEN || bytes[..8] != MAGIC {
93            return Err(FormatError::InvalidContainer);
94        }
95        if bytes[8] != 0 || !matches!(bytes[9], 1 | 2) {
96            return Err(FormatError::UnsupportedVersion);
97        }
98        if bytes[10] != 1 || bytes[11] != 0 {
99            return Err(FormatError::UnsupportedFeature);
100        }
101        let length = u32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
102        Self::with_signed(length as usize, bytes[9] == 2)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn preamble_has_exact_wire_encoding() -> Result<(), FormatError> {
112        let preamble = Preamble::new(0x1234)?;
113        assert_eq!(
114            preamble.encode(),
115            [
116                0x48, 0x49, 0x44, 0x45, 13, 10, 26, 10, 0, 1, 1, 0, 0, 0, 0x12, 0x34
117            ]
118        );
119        assert_eq!(Preamble::decode(&preamble.encode())?, preamble);
120        Ok(())
121    }
122
123    #[test]
124    fn rejects_every_truncated_preamble() -> Result<(), FormatError> {
125        let bytes = Preamble::new(128)?.encode();
126        for length in 0..PREAMBLE_LEN {
127            assert_eq!(
128                Preamble::decode(&bytes[..length]),
129                Err(FormatError::InvalidContainer)
130            );
131        }
132        Ok(())
133    }
134
135    #[test]
136    fn rejects_unknown_version_kind_flags_and_bad_magic() -> Result<(), FormatError> {
137        let original = Preamble::new(128)?.encode();
138        for offset in 0..12 {
139            let mut bytes = original;
140            bytes[offset] ^= 0x80;
141            assert!(Preamble::decode(&bytes).is_err(), "offset {offset}");
142        }
143        Ok(())
144    }
145
146    #[test]
147    fn rejects_hostile_length_before_allocation() -> Result<(), FormatError> {
148        assert!(Preamble::new(MAX_HEADER_LEN).is_ok());
149        assert_eq!(Preamble::new(0), Err(FormatError::HeaderTooLarge));
150        assert_eq!(
151            Preamble::new(MAX_HEADER_LEN + 1),
152            Err(FormatError::HeaderTooLarge)
153        );
154        let mut bytes = Preamble::new(128)?.encode();
155        bytes[12..].copy_from_slice(&u32::MAX.to_be_bytes());
156        assert_eq!(Preamble::decode(&bytes), Err(FormatError::HeaderTooLarge));
157        Ok(())
158    }
159}