enigma_node_registry/
envelope.rs

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}