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