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