1use blake3::Hasher;
2use enigma_aead;
3use enigma_node_types::{PublicIdentity, UserId};
4use rand::rngs::OsRng;
5use rand::RngCore;
6use serde::{Deserialize, Serialize};
7use serde_with::{base64::Base64, hex::Hex, serde_as};
8use x25519_dalek::{PublicKey, StaticSecret};
9
10use crate::config::{EnvelopeConfig, EnvelopeKeyConfig};
11use crate::error::{RegistryError, RegistryResult};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct EnvelopeKey {
15 pub kid: [u8; 8],
16 pub private: [u8; 32],
17 pub public: [u8; 32],
18 pub active: bool,
19 pub not_after: Option<u64>,
20}
21
22impl EnvelopeKey {
23 pub fn from_config(cfg: &EnvelopeKeyConfig) -> RegistryResult<Self> {
24 let kid_bytes = hex::decode(&cfg.kid_hex)
25 .map_err(|_| RegistryError::Config("invalid kid_hex".to_string()))?;
26 let mut kid = [0u8; 8];
27 kid.copy_from_slice(&kid_bytes);
28 let priv_bytes = hex::decode(&cfg.x25519_private_key_hex)
29 .map_err(|_| RegistryError::Config("invalid x25519_private_key_hex".to_string()))?;
30 let mut private = [0u8; 32];
31 private.copy_from_slice(&priv_bytes);
32 let secret = StaticSecret::from(private);
33 let public_key = PublicKey::from(&secret);
34 let mut public = [0u8; 32];
35 public.copy_from_slice(public_key.as_bytes());
36 Ok(EnvelopeKey {
37 kid,
38 private,
39 public,
40 active: cfg.active,
41 not_after: cfg.not_after_epoch_ms,
42 })
43 }
44
45 pub fn is_expired(&self, now_ms: u64) -> bool {
46 match self.not_after {
47 Some(limit) => now_ms > limit,
48 None => false,
49 }
50 }
51}
52
53#[derive(Clone, Debug)]
54pub struct EnvelopeKeySet {
55 pub keys: Vec<EnvelopeKey>,
56}
57
58impl EnvelopeKeySet {
59 pub fn from_config(cfg: &EnvelopeConfig) -> RegistryResult<Self> {
60 let mut keys = Vec::new();
61 for entry in &cfg.keys {
62 keys.push(EnvelopeKey::from_config(entry)?);
63 }
64 Ok(EnvelopeKeySet { keys })
65 }
66
67 pub fn active_key(&self, now_ms: u64) -> Option<EnvelopeKey> {
68 self.keys
69 .iter()
70 .find(|k| k.active && !k.is_expired(now_ms))
71 .cloned()
72 }
73
74 pub fn find_by_kid(&self, kid: &[u8; 8], now_ms: u64) -> Option<EnvelopeKey> {
75 self.keys
76 .iter()
77 .find(|k| &k.kid == kid && !k.is_expired(now_ms))
78 .cloned()
79 }
80
81 pub fn public_keys(&self) -> Vec<EnvelopePublicKey> {
82 self.keys
83 .iter()
84 .map(|k| EnvelopePublicKey {
85 kid_hex: hex::encode(k.kid),
86 x25519_public_key_hex: hex::encode(k.public),
87 active: k.active,
88 not_after_epoch_ms: k.not_after,
89 })
90 .collect()
91 }
92}
93
94#[serde_as]
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
96#[serde(deny_unknown_fields)]
97pub struct IdentityEnvelope {
98 #[serde_as(as = "Hex")]
99 pub kid: [u8; 8],
100 #[serde_as(as = "Hex")]
101 pub sender_pubkey: [u8; 32],
102 #[serde_as(as = "Hex")]
103 pub nonce: [u8; 24],
104 #[serde_as(as = "Base64")]
105 pub ciphertext: Vec<u8>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109#[serde(deny_unknown_fields)]
110pub struct EnvelopePublicKey {
111 pub kid_hex: String,
112 pub x25519_public_key_hex: String,
113 pub active: bool,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub not_after_epoch_ms: Option<u64>,
116}
117
118#[derive(Clone)]
119pub struct EnvelopeCrypto {
120 pepper: [u8; 32],
121}
122
123impl EnvelopeCrypto {
124 pub fn new(pepper: [u8; 32]) -> Self {
125 EnvelopeCrypto { pepper }
126 }
127
128 pub fn pepper_bytes(&self) -> [u8; 32] {
129 self.pepper
130 }
131
132 pub fn decrypt_identity(
133 &self,
134 envelope: &IdentityEnvelope,
135 key: &EnvelopeKey,
136 handle: &UserId,
137 now_ms: u64,
138 ) -> RegistryResult<PublicIdentity> {
139 if key.is_expired(now_ms) {
140 return Err(RegistryError::InvalidInput(
141 "expired envelope key".to_string(),
142 ));
143 }
144 if &envelope.kid != &key.kid {
145 return Err(RegistryError::InvalidInput(
146 "unknown envelope key".to_string(),
147 ));
148 }
149 let shared = derive_shared_secret(&key.private, &envelope.sender_pubkey)?;
150 let aead_key = derive_aead_key(self.pepper, handle.as_bytes(), &shared);
151 let plaintext = enigma_aead::open(
152 aead_key,
153 envelope.nonce,
154 &envelope.ciphertext,
155 handle.as_bytes(),
156 )
157 .map_err(|_| RegistryError::InvalidInput("unable to decrypt envelope".to_string()))?;
158 let identity: PublicIdentity = serde_json::from_slice(&plaintext)?;
159 Ok(identity)
160 }
161
162 pub fn encrypt_identity_for_peer(
163 &self,
164 key: &EnvelopeKey,
165 handle: &UserId,
166 identity: &PublicIdentity,
167 peer_pubkey: [u8; 32],
168 nonce: Option<[u8; 24]>,
169 now_ms: u64,
170 ) -> RegistryResult<IdentityEnvelope> {
171 if key.is_expired(now_ms) {
172 return Err(RegistryError::InvalidInput(
173 "expired envelope key".to_string(),
174 ));
175 }
176 let shared = derive_shared_secret(&key.private, &peer_pubkey)?;
177 let aead_key = derive_aead_key(self.pepper, handle.as_bytes(), &shared);
178 let mut selected_nonce = [0u8; 24];
179 if let Some(nonce_bytes) = nonce {
180 selected_nonce = nonce_bytes;
181 } else {
182 OsRng.fill_bytes(&mut selected_nonce);
183 }
184 let plaintext = serde_json::to_vec(identity)?;
185 let ciphertext = enigma_aead::seal(aead_key, selected_nonce, &plaintext, handle.as_bytes())
186 .map_err(|_| RegistryError::Internal)?;
187 Ok(IdentityEnvelope {
188 kid: key.kid,
189 sender_pubkey: key.public,
190 nonce: selected_nonce,
191 ciphertext,
192 })
193 }
194
195 pub fn encrypt_with_sender(
196 &self,
197 kid: [u8; 8],
198 sender_secret: [u8; 32],
199 recipient_pubkey: [u8; 32],
200 handle: &UserId,
201 identity: &PublicIdentity,
202 nonce: [u8; 24],
203 ) -> RegistryResult<IdentityEnvelope> {
204 let sender_secret = StaticSecret::from(sender_secret);
205 let sender_pub = PublicKey::from(&sender_secret);
206 let shared = sender_secret.diffie_hellman(&PublicKey::from(recipient_pubkey));
207 let aead_key = derive_aead_key(self.pepper, handle.as_bytes(), shared.as_bytes());
208 let plaintext = serde_json::to_vec(identity)?;
209 let ciphertext = enigma_aead::seal(aead_key, nonce, &plaintext, handle.as_bytes())
210 .map_err(|_| RegistryError::Internal)?;
211 let mut sender_pub_bytes = [0u8; 32];
212 sender_pub_bytes.copy_from_slice(sender_pub.as_bytes());
213 Ok(IdentityEnvelope {
214 kid,
215 sender_pubkey: sender_pub_bytes,
216 nonce,
217 ciphertext,
218 })
219 }
220}
221
222fn derive_shared_secret(private: &[u8; 32], peer_pubkey: &[u8; 32]) -> RegistryResult<[u8; 32]> {
223 let secret = StaticSecret::from(*private);
224 let peer = PublicKey::from(*peer_pubkey);
225 let shared = secret.diffie_hellman(&peer);
226 let mut out = [0u8; 32];
227 out.copy_from_slice(shared.as_bytes());
228 Ok(out)
229}
230
231fn derive_aead_key(pepper: [u8; 32], handle: &[u8; 32], shared: &[u8]) -> [u8; 32] {
232 let mut hasher = Hasher::new();
233 hasher.update(b"enigma:registry:envelope:v1");
234 hasher.update(&pepper);
235 hasher.update(handle);
236 hasher.update(shared);
237 let digest = hasher.finalize();
238 let mut key = [0u8; 32];
239 key.copy_from_slice(digest.as_bytes());
240 key
241}