1use base64::engine::general_purpose::STANDARD as B64;
34use base64::Engine as _;
35use sha2::{Digest, Sha256};
36use zeroize::Zeroizing;
37
38pub fn file_id(bytes: &[u8]) -> String {
42 Sha256::digest(bytes)
43 .iter()
44 .map(|b| format!("{b:02x}"))
45 .collect()
46}
47
48pub const HEADER: &str = "gunnar-crypt keyring v1";
50pub const KEY_LEN: usize = 64;
52pub const ID_KEY_LEN: usize = 32;
54
55#[derive(Clone, PartialEq, Eq)]
57pub struct IdKey(Zeroizing<[u8; ID_KEY_LEN]>);
58
59impl IdKey {
60 pub fn from_bytes(bytes: [u8; ID_KEY_LEN]) -> IdKey {
62 IdKey(Zeroizing::new(bytes))
63 }
64
65 #[cfg(feature = "keygen")]
67 pub fn generate() -> IdKey {
68 let mut bytes = Zeroizing::new([0u8; ID_KEY_LEN]);
69 getrandom::getrandom(&mut bytes[..]).expect("the OS random source is available");
70 IdKey(bytes)
71 }
72
73 pub fn bytes(&self) -> &[u8; ID_KEY_LEN] {
75 &self.0
76 }
77}
78
79impl std::fmt::Debug for IdKey {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(f, "IdKey(…)")
82 }
83}
84
85#[derive(Clone)]
87pub struct DataKey {
88 epoch: u32,
89 bytes: Zeroizing<[u8; KEY_LEN]>,
90}
91
92impl DataKey {
93 pub fn from_bytes(epoch: u32, bytes: [u8; KEY_LEN]) -> DataKey {
95 DataKey {
96 epoch,
97 bytes: Zeroizing::new(bytes),
98 }
99 }
100
101 #[cfg(feature = "keygen")]
103 pub fn generate(epoch: u32) -> DataKey {
104 let mut bytes = Zeroizing::new([0u8; KEY_LEN]);
105 getrandom::getrandom(&mut bytes[..]).expect("the OS random source is available");
106 DataKey { epoch, bytes }
107 }
108
109 pub fn epoch(&self) -> u32 {
111 self.epoch
112 }
113
114 pub fn bytes(&self) -> &[u8; KEY_LEN] {
116 &self.bytes
117 }
118
119 pub fn id(&self) -> String {
122 let mut h = Sha256::new();
123 h.update(b"gunnar-crypt key id v1\0");
124 h.update(&self.bytes[..]);
125 let d = h.finalize();
126 d[..8].iter().map(|b| format!("{b:02x}")).collect()
127 }
128}
129
130impl std::fmt::Debug for DataKey {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(f, "DataKey {{ epoch: {}, id: {} }}", self.epoch, self.id())
133 }
134}
135
136impl PartialEq for DataKey {
137 fn eq(&self, other: &Self) -> bool {
138 self.epoch == other.epoch && self.bytes[..] == other.bytes[..]
141 }
142}
143impl Eq for DataKey {}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147pub enum KeyringError {
148 NotAKeyring,
150 BadLine(usize),
152 BadEpochs,
154 Empty,
156 BadIdKey,
158}
159
160impl std::fmt::Display for KeyringError {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 KeyringError::NotAKeyring => write!(f, "not a gunnar-crypt key ring"),
164 KeyringError::BadLine(n) => write!(f, "key ring line {n} is malformed"),
165 KeyringError::BadEpochs => {
166 write!(f, "key ring epochs must run 1, 2, 3, … without gaps")
167 }
168 KeyringError::Empty => write!(f, "the key ring holds no key"),
169 KeyringError::BadIdKey => {
170 write!(f, "the key ring's id-key line is missing or malformed")
171 }
172 }
173 }
174}
175impl std::error::Error for KeyringError {}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
179pub enum ContinuityError {
180 Dropped(u32),
182 Replaced(u32),
186 IdKeyChanged,
188}
189
190impl std::fmt::Display for ContinuityError {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 match self {
193 ContinuityError::Dropped(e) => write!(
194 f,
195 "the new key ring drops epoch {e}, which this clone holds — refused \
196 (a key ring only ever grows; this one may have been substituted)"
197 ),
198 ContinuityError::Replaced(e) => write!(
199 f,
200 "the new key ring has a different key for epoch {e} than this clone \
201 holds — refused (a substituted key ring would let its maker read \
202 everything written after it)"
203 ),
204 ContinuityError::IdKeyChanged => write!(
205 f,
206 "the new key ring has a different id key — refused (the id key is made \
207 once and never changes; this ring may have been substituted)"
208 ),
209 }
210 }
211}
212impl std::error::Error for ContinuityError {}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct Keyring {
217 id_key: IdKey,
218 keys: Vec<DataKey>,
219}
220
221impl Keyring {
222 pub fn new(id_key: IdKey, first: DataKey) -> Keyring {
224 assert_eq!(first.epoch, 1, "a key ring starts at epoch 1");
225 Keyring {
226 id_key,
227 keys: vec![first],
228 }
229 }
230
231 #[cfg(feature = "keygen")]
233 pub fn generate() -> Keyring {
234 Keyring::new(IdKey::generate(), DataKey::generate(1))
235 }
236
237 pub fn id_key(&self) -> &IdKey {
239 &self.id_key
240 }
241
242 pub fn truncated(&self, epoch: u32) -> Keyring {
245 Keyring {
246 id_key: self.id_key.clone(),
247 keys: self.keys[..(epoch as usize).min(self.keys.len()).max(1)].to_vec(),
248 }
249 }
250
251 #[cfg(feature = "keygen")]
253 pub fn rotate(&mut self) -> u32 {
254 let next = self.newest().epoch + 1;
255 self.keys.push(DataKey::generate(next));
256 next
257 }
258
259 pub fn get(&self, epoch: u32) -> Option<&DataKey> {
261 if epoch == 0 {
262 return None;
263 }
264 self.keys.get(epoch as usize - 1)
265 }
266
267 pub fn newest(&self) -> &DataKey {
269 self.keys.last().expect("a key ring is never empty")
270 }
271
272 pub fn keys(&self) -> &[DataKey] {
274 &self.keys
275 }
276
277 pub fn to_text(&self) -> Zeroizing<String> {
279 let mut s = String::with_capacity(32 + self.keys.len() * 100);
280 s.push_str(HEADER);
281 s.push('\n');
282 s.push_str(&format!("id-key {}\n", B64.encode(&self.id_key.0[..])));
283 for k in &self.keys {
284 s.push_str(&format!("epoch {} {}\n", k.epoch, B64.encode(&k.bytes[..])));
285 }
286 Zeroizing::new(s)
287 }
288
289 pub fn parse(text: &str) -> Result<Keyring, KeyringError> {
291 let mut lines = text.lines();
292 if lines.next().map(str::trim_end) != Some(HEADER) {
293 return Err(KeyringError::NotAKeyring);
294 }
295 let mut keys = Vec::new();
296 let mut id_key = None;
297 for (i, line) in lines.enumerate() {
298 let line = line.trim();
299 if line.is_empty() || line.starts_with('#') {
300 continue;
301 }
302 if let Some(b) = line.strip_prefix("id-key ") {
303 if id_key.is_some() {
304 return Err(KeyringError::BadIdKey);
305 }
306 let raw = Zeroizing::new(B64.decode(b.trim()).map_err(|_| KeyringError::BadIdKey)?);
307 let bytes: [u8; ID_KEY_LEN] =
308 raw[..].try_into().map_err(|_| KeyringError::BadIdKey)?;
309 id_key = Some(IdKey::from_bytes(bytes));
310 continue;
311 }
312 let mut parts = line.split_ascii_whitespace();
313 let (Some("epoch"), Some(n), Some(b), None) =
314 (parts.next(), parts.next(), parts.next(), parts.next())
315 else {
316 return Err(KeyringError::BadLine(i + 2));
317 };
318 let epoch: u32 = n.parse().map_err(|_| KeyringError::BadLine(i + 2))?;
319 let raw = Zeroizing::new(B64.decode(b).map_err(|_| KeyringError::BadLine(i + 2))?);
320 let bytes: [u8; KEY_LEN] = raw[..]
321 .try_into()
322 .map_err(|_| KeyringError::BadLine(i + 2))?;
323 if epoch as usize != keys.len() + 1 {
324 return Err(KeyringError::BadEpochs);
325 }
326 keys.push(DataKey::from_bytes(epoch, bytes));
327 }
328 if keys.is_empty() {
329 return Err(KeyringError::Empty);
330 }
331 let id_key = id_key.ok_or(KeyringError::BadIdKey)?;
332 Ok(Keyring { id_key, keys })
333 }
334
335 pub fn check_continuity(&self, newer: &Keyring) -> Result<(), ContinuityError> {
339 if newer.id_key != self.id_key {
340 return Err(ContinuityError::IdKeyChanged);
341 }
342 for k in &self.keys {
343 match newer.get(k.epoch) {
344 None => return Err(ContinuityError::Dropped(k.epoch)),
345 Some(n) if n != k => return Err(ContinuityError::Replaced(k.epoch)),
346 Some(_) => {}
347 }
348 }
349 Ok(())
350 }
351}