iroh_db_security/
key.rs

1use std::fmt;
2
3use iroh_db_core::AuthorId;
4use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
5
6use crate::SecurityError;
7
8/// A random symmetric key for one domain epoch.
9#[derive(Clone, Zeroize, ZeroizeOnDrop)]
10pub struct DomainKey([u8; 32]);
11
12impl DomainKey {
13    /// Generates a key from the operating system's cryptographic random source.
14    pub fn generate() -> Result<Self, SecurityError> {
15        let mut bytes = [0_u8; 32];
16        getrandom::fill(&mut bytes).map_err(|_| SecurityError::RandomnessUnavailable)?;
17        Ok(Self(bytes))
18    }
19
20    /// Imports exact key bytes from a trusted key provider.
21    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
22        Self(bytes)
23    }
24
25    pub(crate) const fn as_bytes(&self) -> &[u8; 32] {
26        &self.0
27    }
28
29    /// Derives a domain-separated object key without exposing the epoch key.
30    pub fn derive_subkey(
31        &self,
32        salt: &[u8],
33        context: &[u8],
34    ) -> Result<Zeroizing<[u8; 32]>, SecurityError> {
35        use hkdf::Hkdf;
36        use sha2::Sha256;
37
38        let hkdf = Hkdf::<Sha256>::new(Some(salt), &self.0);
39        let mut output = Zeroizing::new([0_u8; 32]);
40        hkdf.expand(context, &mut *output)
41            .map_err(|_| SecurityError::KeyDerivationFailed)?;
42        Ok(output)
43    }
44}
45
46impl fmt::Debug for DomainKey {
47    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48        formatter.write_str("DomainKey([redacted])")
49    }
50}
51
52/// The iroh endpoint signing identity used by the database protocol.
53#[derive(Clone)]
54pub struct IrohSigner {
55    secret_key: iroh_base::SecretKey,
56}
57
58impl IrohSigner {
59    /// Generates a new iroh endpoint identity.
60    pub fn generate() -> Self {
61        Self {
62            secret_key: iroh_base::SecretKey::generate(),
63        }
64    }
65
66    /// Imports deterministic endpoint secret bytes from a trusted key provider.
67    pub fn from_secret_bytes(bytes: [u8; 32]) -> Self {
68        Self {
69            secret_key: iroh_base::SecretKey::from_bytes(&bytes),
70        }
71    }
72
73    /// Wraps the same iroh secret key used by an endpoint.
74    pub const fn new(secret_key: iroh_base::SecretKey) -> Self {
75        Self { secret_key }
76    }
77
78    /// Returns the authenticated iroh endpoint identity.
79    pub fn author(&self) -> AuthorId {
80        AuthorId::from_bytes(*self.secret_key.public().as_bytes())
81    }
82
83    /// Signs canonical commit bytes under the commit-specific context.
84    pub fn sign(&self, canonical_commit_bytes: &[u8]) -> [u8; 64] {
85        self.secret_key
86            .sign(&commit_signature_message(canonical_commit_bytes))
87            .to_bytes()
88    }
89
90    /// Signs canonical snapshot bytes under a snapshot-specific context.
91    pub fn sign_snapshot(&self, canonical_snapshot_bytes: &[u8]) -> [u8; 64] {
92        self.secret_key
93            .sign(&snapshot_signature_message(canonical_snapshot_bytes))
94            .to_bytes()
95    }
96
97    /// Signs a canonical backup catalog under a backup-specific context.
98    pub fn sign_backup_catalog(&self, canonical_catalog_bytes: &[u8]) -> [u8; 64] {
99        self.secret_key
100            .sign(&backup_signature_message(canonical_catalog_bytes))
101            .to_bytes()
102    }
103
104    /// Signs a bounded gossip hint under a gossip-specific protocol context.
105    pub fn sign_gossip(&self, canonical_hint_bytes: &[u8]) -> [u8; 64] {
106        self.secret_key
107            .sign(&gossip_signature_message(canonical_hint_bytes))
108            .to_bytes()
109    }
110
111    /// Signs canonical domain-authority bytes under a capability-specific context.
112    pub fn sign_capability(&self, canonical_authority_bytes: &[u8]) -> [u8; 64] {
113        self.secret_key
114            .sign(&capability_signature_message(canonical_authority_bytes))
115            .to_bytes()
116    }
117
118    /// Signs canonical genesis bytes under a descriptor-specific context.
119    pub fn sign_domain_descriptor(&self, canonical_descriptor_bytes: &[u8]) -> [u8; 64] {
120        self.secret_key
121            .sign(&domain_descriptor_signature_message(
122                canonical_descriptor_bytes,
123            ))
124            .to_bytes()
125    }
126
127    /// Signs a targeted invitation under an invitation-specific context.
128    pub fn sign_invitation(&self, canonical_invitation_bytes: &[u8]) -> [u8; 64] {
129        self.secret_key
130            .sign(&invitation_signature_message(canonical_invitation_bytes))
131            .to_bytes()
132    }
133
134    /// Signs a history-compacting authority checkpoint.
135    pub fn sign_authority_checkpoint(&self, canonical_bytes: &[u8]) -> [u8; 64] {
136        self.secret_key
137            .sign(&authority_checkpoint_signature_message(canonical_bytes))
138            .to_bytes()
139    }
140
141    /// Signs a domain control transition under a control-specific context.
142    pub fn sign_control(&self, canonical_control_bytes: &[u8]) -> [u8; 64] {
143        self.secret_key
144            .sign(&control_signature_message(canonical_control_bytes))
145            .to_bytes()
146    }
147
148    pub(crate) fn secret_bytes(&self) -> [u8; 32] {
149        self.secret_key.to_bytes()
150    }
151}
152
153impl fmt::Debug for IrohSigner {
154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155        formatter.write_str("IrohSigner([redacted])")
156    }
157}
158
159pub(crate) fn commit_signature_message(canonical_commit_bytes: &[u8]) -> Vec<u8> {
160    let mut message = Vec::with_capacity(27 + canonical_commit_bytes.len());
161    message.extend_from_slice(b"iroh-db/commit-signature/v1");
162    message.extend_from_slice(canonical_commit_bytes);
163    message
164}
165
166pub(crate) fn snapshot_signature_message(canonical_snapshot_bytes: &[u8]) -> Vec<u8> {
167    let mut message = Vec::with_capacity(29 + canonical_snapshot_bytes.len());
168    message.extend_from_slice(b"iroh-db/snapshot-signature/v1");
169    message.extend_from_slice(canonical_snapshot_bytes);
170    message
171}
172
173pub(crate) fn backup_signature_message(canonical_catalog_bytes: &[u8]) -> Vec<u8> {
174    let mut message = Vec::with_capacity(27 + canonical_catalog_bytes.len());
175    message.extend_from_slice(b"iroh-db/backup-signature/v1");
176    message.extend_from_slice(canonical_catalog_bytes);
177    message
178}
179
180pub(crate) fn gossip_signature_message(canonical_hint_bytes: &[u8]) -> Vec<u8> {
181    let mut message = Vec::with_capacity(27 + canonical_hint_bytes.len());
182    message.extend_from_slice(b"iroh-db/gossip-signature/v1");
183    message.extend_from_slice(canonical_hint_bytes);
184    message
185}
186
187pub(crate) fn capability_signature_message(canonical_authority_bytes: &[u8]) -> Vec<u8> {
188    let mut message = Vec::with_capacity(31 + canonical_authority_bytes.len());
189    message.extend_from_slice(b"iroh-db/capability-signature/v1");
190    message.extend_from_slice(canonical_authority_bytes);
191    message
192}
193
194pub(crate) fn domain_descriptor_signature_message(canonical_bytes: &[u8]) -> Vec<u8> {
195    let mut message = Vec::with_capacity(38 + canonical_bytes.len());
196    message.extend_from_slice(b"iroh-db/domain-descriptor-signature/v1");
197    message.extend_from_slice(canonical_bytes);
198    message
199}
200
201pub(crate) fn invitation_signature_message(canonical_bytes: &[u8]) -> Vec<u8> {
202    let mut message = Vec::with_capacity(31 + canonical_bytes.len());
203    message.extend_from_slice(b"iroh-db/invitation-signature/v1");
204    message.extend_from_slice(canonical_bytes);
205    message
206}
207
208pub(crate) fn authority_checkpoint_signature_message(canonical_bytes: &[u8]) -> Vec<u8> {
209    let mut message = Vec::with_capacity(41 + canonical_bytes.len());
210    message.extend_from_slice(b"iroh-db/authority-checkpoint-signature/v1");
211    message.extend_from_slice(canonical_bytes);
212    message
213}
214
215pub(crate) fn control_signature_message(canonical_bytes: &[u8]) -> Vec<u8> {
216    let mut message = Vec::with_capacity(28 + canonical_bytes.len());
217    message.extend_from_slice(b"iroh-db/control-signature/v1");
218    message.extend_from_slice(canonical_bytes);
219    message
220}