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 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}