1use std::collections::BTreeMap;
2use std::fmt;
3
4use hkdf::Hkdf;
5use sha2::Sha256;
6use zeroize::Zeroizing;
7
8use crate::{Error, Result};
9
10pub type KeyId = u32;
11
12pub struct SecretKey(pub(crate) Zeroizing<[u8; 32]>);
14
15impl SecretKey {
16 pub fn generate() -> Result<Self> {
17 let mut bytes = Zeroizing::new([0; 32]);
18 getrandom::fill(bytes.as_mut()).map_err(|_| Error::Randomness)?;
19 Ok(Self(bytes))
20 }
21
22 pub fn from_bytes(bytes: [u8; 32]) -> Self {
25 Self(Zeroizing::new(bytes))
26 }
27}
28
29impl fmt::Debug for SecretKey {
30 fn fmt(
31 &self,
32 f: &mut fmt::Formatter<'_>,
33 ) -> fmt::Result {
34 f.write_str("SecretKey([REDACTED])")
35 }
36}
37
38pub struct Keyring {
41 pub(crate) active: KeyId,
42 pub(crate) keys: BTreeMap<KeyId, SecretKey>,
43}
44
45impl Keyring {
46 pub fn new(
47 active: KeyId,
48 keys: impl IntoIterator<Item = (KeyId, SecretKey)>,
49 ) -> Result<Self> {
50 let mut retained = BTreeMap::new();
51 for (id, key) in keys {
52 if retained.insert(id, key).is_some() {
53 return Err(Error::DuplicateKey);
54 }
55 }
56 if !retained.contains_key(&active) {
57 return Err(Error::MissingActiveKey);
58 }
59 Ok(Self { active, keys: retained })
60 }
61}
62
63pub(crate) fn derive(
64 key: &SecretKey,
65 context: &[u8],
66) -> Zeroizing<[u8; 32]> {
67 let mut result = Zeroizing::new([0; 32]);
68 Hkdf::<Sha256>::new(Some(b"orbit-auth/v1/hkdf-sha256"), key.0.as_ref())
69 .expand(context, result.as_mut())
70 .expect("32 bytes is a valid HKDF-SHA256 output length");
71 result
72}
73
74pub(crate) fn context(parts: &[&[u8]]) -> Vec<u8> {
76 let mut bytes = Vec::new();
77 for part in parts {
78 bytes.extend_from_slice(&(part.len() as u64).to_be_bytes());
79 bytes.extend_from_slice(part);
80 }
81 bytes
82}