1use chacha20poly1305::{
2 KeyInit as _, XChaCha20Poly1305, XNonce,
3 aead::{Aead as _, Payload as AeadPayload},
4};
5use relay_core::prelude::Payload;
6use serde::{Deserialize, Serialize};
7use serde_with::{base64::Base64, serde_as};
8use x25519_dalek::{PublicKey, StaticSecret};
9
10use relay_crypto::{
11 alg::{AlgorithmError, EncryptionAlgorithm, EncryptionContext},
12 hex,
13 hkdf::Hkdf,
14 kid::kid_from_key,
15 rand_core::{CryptoRng, RngCore},
16 record::{Key, PubRecord},
17 sha2::Sha256,
18};
19
20#[serde_as]
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Meta {
23 #[serde_as(as = "Base64")]
24 pub sender_ephemeral: Vec<u8>,
25 #[serde_as(as = "Base64")]
26 pub nonce: Vec<u8>,
27}
28
29#[derive(Debug, Clone)]
30pub struct Context {
31 key: [u8; 32],
32 nonce: [u8; 24],
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Secrets {
37 #[serde(with = "hex::serde")]
38 pub secret: [u8; 32],
39}
40
41pub struct X25519XChaCha20Poly1305;
42
43impl EncryptionAlgorithm for X25519XChaCha20Poly1305 {
44 type Meta = Meta;
45 type Secrets = Secrets;
46 type Context = Context;
47
48 fn alg() -> &'static str {
49 "x25519-hkdf-sha256-xchacha20poly1305"
50 }
51
52 fn generate(
53 &self,
54 mut rng: impl RngCore + CryptoRng,
55 ) -> Result<(Key, Self::Secrets), AlgorithmError> {
56 let secret = StaticSecret::random_from_rng(&mut rng);
57 let public = PublicKey::from(&secret);
58 let kid = kid_from_key(public.as_bytes().to_vec());
59
60 Ok((
61 Key {
62 alg: Self::alg().to_string(),
63 kid,
64 data: public.as_bytes().to_vec(),
65 },
66 Secrets {
67 secret: secret.to_bytes(),
68 },
69 ))
70 }
71
72 fn encrypt_inner(
73 &self,
74 mut rng: impl RngCore + CryptoRng,
75 recipient: &PubRecord,
76 payload: &[u8],
77 aad: &[u8],
78 ) -> Result<(Self::Meta, Vec<u8>), AlgorithmError> {
79 let recipient_public_key: &[u8; 32] = recipient
80 .encryption
81 .data
82 .as_slice()
83 .try_into()
84 .map_err(|_| {
85 AlgorithmError::InvalidMeta("recipient public key must be 32 bytes".into())
86 })?;
87 let recipient = PublicKey::from(*recipient_public_key);
88
89 let ephemeral = x25519_dalek::EphemeralSecret::random_from_rng(&mut rng);
90 let ephemeral_pub = PublicKey::from(&ephemeral);
91
92 let shared = ephemeral.diffie_hellman(&recipient);
93
94 let hkdf = Hkdf::<Sha256>::new(None, shared.as_bytes());
95 let mut key = [0u8; 32];
96 hkdf.expand(&Self::info(), &mut key)
97 .map_err(|e| AlgorithmError::KeyDerivation(e.to_string()))?;
98
99 let aead = XChaCha20Poly1305::new(&key.into());
100
101 let mut nonce = [0u8; 24];
102 rng.fill_bytes(&mut nonce);
103
104 let ciphertext = aead
105 .encrypt(
106 XNonce::from_slice(&nonce),
107 AeadPayload { msg: payload, aad },
108 )
109 .map_err(|e| AlgorithmError::Encrypt(e.to_string()))?;
110
111 Ok((
112 Meta {
113 sender_ephemeral: ephemeral_pub.as_bytes().to_vec(),
114 nonce: nonce.to_vec(),
115 },
116 ciphertext,
117 ))
118 }
119
120 fn open_inner(
121 &self,
122 meta: &Self::Meta,
123 secrets: &Self::Secrets,
124 ) -> Result<Self::Context, AlgorithmError> {
125 let ephemeral: &[u8; 32] = meta
126 .sender_ephemeral
127 .as_slice()
128 .try_into()
129 .map_err(|_| AlgorithmError::Ephemeral)?;
130 let sender = PublicKey::from(*ephemeral);
131
132 let secret_key = StaticSecret::from(secrets.secret);
133 let shared = secret_key.diffie_hellman(&sender);
134
135 let hkdf = Hkdf::<Sha256>::new(None, shared.as_bytes());
136 let mut key = [0u8; 32];
137 hkdf.expand(&Self::info(), &mut key)
138 .map_err(|e| AlgorithmError::KeyDerivation(e.to_string()))?;
139
140 let nonce: [u8; 24] = meta
141 .nonce
142 .as_slice()
143 .try_into()
144 .map_err(|_| AlgorithmError::Nonce)?;
145
146 Ok(Context { key, nonce })
147 }
148}
149
150impl EncryptionContext for Context {
151 fn decrypt<T>(&self, payload: &Payload<T>, aad: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
152 let aead = XChaCha20Poly1305::new(&self.key.into());
153
154 aead.decrypt(
155 XNonce::from_slice(&self.nonce),
156 AeadPayload {
157 msg: &payload.ciphertext,
158 aad,
159 },
160 )
161 .map_err(|e| AlgorithmError::Decrypt(e.to_string()))
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use relay_core::chrono::Utc;
169 use relay_crypto::rng::os_rng_hkdf;
170
171 #[test]
172 fn encrypt_decrypt() {
173 let mut rng = os_rng_hkdf(None, b"test").unwrap();
174 let (key, secrets) = X25519XChaCha20Poly1305
175 .generate(&mut rng)
176 .expect("Generation failed");
177 let record = PubRecord {
178 id: "recipient".to_string(),
179 created_at: Utc::now(),
180 expires_at: None,
181 encryption: key,
182 signing: Key {
183 alg: String::new(),
184 kid: String::new(),
185 data: vec![],
186 },
187 };
188
189 let message = b"Hello, world!";
190 let aad = b"Additional authenticated data";
191
192 let payload: Payload<()> = X25519XChaCha20Poly1305
193 .encrypt(rng, &record, message, aad)
194 .expect("Encryption failed");
195
196 let context = X25519XChaCha20Poly1305
197 .open(&payload.info, &serde_json::to_vec(&secrets).unwrap())
198 .unwrap();
199
200 let decrypted_message = context.decrypt(&payload, aad).unwrap();
201 assert_eq!(decrypted_message, message);
202 }
203}