dkls23/proto/scheme.rs
1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! Cryptographic Scheme Implementation for Secure Communication
5//!
6//! This module provides the core cryptographic primitives and protocols
7//! for secure communication between parties in the DKLS23 protocol.
8//! It implements an encryption scheme that combines:
9//! - Authenticated Encryption with Associated Data (AEAD)
10//! - Key exchange using X25519
11//! - Secure key derivation
12//!
13//! # Security Properties
14//!
15//! - Forward secrecy through ephemeral key exchange
16//! - Message authentication and integrity
17//! - Nonce reuse prevention
18//! - Secure key derivation using SHA-256
19//!
20//! # Implementation Details
21//!
22//! The module provides:
23//! - `EncryptionScheme` trait defining the interface for secure communication
24//! - `AeadX25519` implementation using ChaCha20-Poly1305 for AEAD
25//! - Secure nonce generation and management
26//! - Key pair management for multiple parties
27
28#![allow(unused_imports, dead_code, unused_variables)]
29
30use std::marker::PhantomData;
31
32use aead::{
33 consts::{U10, U32},
34 generic_array::{typenum::Unsigned, GenericArray},
35 AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser, Nonce, Tag,
36};
37use chacha20::hchacha;
38use rand_core::CryptoRngCore;
39use sha2::{Digest, Sha256};
40use x25519_dalek::{PublicKey, ReusableSecret};
41use zeroize::Zeroizing;
42
43use crate::pairs::Pairs;
44
45/// Error indicating invalid public key format or operation
46#[derive(Debug)]
47pub struct PublicKeyError;
48
49/// Error indicating encryption or decryption failure
50#[derive(Debug)]
51pub struct EncryptionError;
52
53/// Type alias for a shared key used in encryption
54type SharedKey = Zeroizing<GenericArray<u8, U32>>;
55
56/// Represents an encryption scheme interface for in-place AEAD and
57/// managing key exchange.
58///
59/// A type implementing EncryptionScheme encapsulates two
60/// functionalities:
61///
62/// - An AEAD algorithm such as ChaCha20 or AES-GCM and its
63/// implementation details, such as nonce generation.
64///
65/// - The derivation of encryption keys for one or more pairs of parties.
66/// The concrete type of public/private key pair and key exchange is
67/// an implementation detail.
68///
69/// # Security Considerations
70///
71/// Implementations must ensure:
72/// - Forward secrecy through proper key exchange
73/// - Nonce uniqueness for each encryption
74/// - Secure key derivation
75/// - Proper authentication of messages
76pub trait EncryptionScheme: Send {
77 /// Return external representation of own public key
78 fn public_key(&self) -> &[u8];
79
80 /// Sets or updates the public key for a specified receiver index.
81 ///
82 /// This function is used to associate a public key with a designated
83 /// receiver identified by an index. It facilitates secure communication
84 /// setup by ensuring that messages can be encrypted such that only the
85 /// receiver with the corresponding private key can decrypt them.
86 ///
87 /// # Parameters
88 ///
89 /// - `receiver_index`: An integer value that uniquely identifies the
90 /// receiver within the system. This index is used to specify which
91 /// receiver the public key is associated with.
92 ///
93 /// - `public_key`: A byte slice representing the public key of the receiver.
94 /// This key is used in cryptographic operations to ensure that only
95 /// the intended receiver can decrypt messages encrypted with this key.
96 ///
97 /// # Errors
98 ///
99 /// - `PublicKeyError`: An error is returned if the public key cannot be
100 /// set due to invalid formatting, an invalid receiver index, or any
101 /// other issue encountered in the operation.
102 fn receiver_public_key(
103 &mut self,
104 receiver_index: usize,
105 public_key: &[u8],
106 ) -> Result<(), PublicKeyError>;
107
108 /// Encrypts the provided data buffer using associated data and
109 /// manages an associated tail segment.
110 ///
111 /// # Parameters
112 ///
113 /// - `associated_data`: A byte slice containing additional
114 /// authenticated data (AAD) that will be used to ensure the
115 /// integrity and authenticity of the encrypted data. This data is
116 /// not encrypted but is included in the integrity check.
117 ///
118 /// - `buffer`: A mutable byte slice containing the plaintext data
119 /// that will be encrypted in place. Upon successful encryption,
120 /// this buffer will contain the ciphertext data.
121 ///
122 /// - `tail`: A mutable byte slice representing the trailing
123 /// segment of the data buffer that may be used to store for
124 /// trailing portion of the data that should be considered during
125 /// decryption.
126 ///
127 /// - `receive`: An index or identifier associated with the
128 /// receiver of the message. This may be used for deriving
129 /// encryption keys.
130 ///
131 /// # Errors
132 ///
133 /// `EncryptionError` if issues arise such as missing keys,
134 /// incorrect buffer lengths, or any other problems during the
135 /// encryption process.
136 fn encrypt(
137 &mut self,
138 associated_data: &[u8],
139 buffer: &mut [u8],
140 tail: &mut [u8],
141 receive: usize,
142 ) -> Result<(), EncryptionError>;
143
144 /// Decrypts the provided data buffer using associated data and a
145 /// tail segment.
146 ///
147 /// # Parameters
148 ///
149 /// - `associated_data`: A byte slice containing additional
150 /// authenticated data (AAD) that will be used along with the
151 /// buffer to ensure the integrity and authenticity of the
152 /// decryption process.
153 ///
154 /// - `buffer`: A mutable byte slice holding the encrypted data
155 /// that will be decrypted in place. Upon successful decryption,
156 /// this buffer will contain the plaintext data.
157 ///
158 /// - `tail`: A byte slice representing the trailing portion of
159 /// the data that should be considered during decryption. This is
160 /// typically used nonce and/or authentication tag.
161 ///
162 /// - `sender`: An index or identifier representing the sender of
163 /// the message. This might be used to retrieve or derive
164 /// encryption keys.
165 ///
166 /// # Errors
167 ///
168 /// - `EncryptionError`: This function may return an
169 /// `EncryptionError` in several situations such as when the
170 /// decryption key is not found, when the input data is tampered
171 /// with or if the cryptographic verification of the AAD fails.
172 fn decrypt(
173 &self,
174 associated_data: &[u8],
175 buffer: &mut [u8],
176 tail: &[u8],
177 sender: usize,
178 ) -> Result<(), EncryptionError>;
179
180 /// Return size of trailing segment. See method `encrypt()` and `decrypt()`.
181 fn overhead(&self) -> usize;
182}
183
184/// Counter to create a unique nonce for encryption operations.
185///
186/// This struct maintains a counter that is used to generate unique nonces
187/// for each encryption operation. The counter is incremented for each
188/// encryption to ensure nonce uniqueness.
189///
190/// # Security Considerations
191///
192/// - The counter is 32-bit and will panic on overflow
193/// - Nonces are derived from the counter value
194/// - Each encryption operation must use a unique nonce
195#[derive(Default)]
196pub struct NonceCounter(u32);
197
198impl NonceCounter {
199 /// Creates a new counter initialized to 0.
200 pub fn new() -> Self {
201 Self(0)
202 }
203
204 /// Increments the counter and returns a new nonce.
205 ///
206 /// # Type Parameters
207 ///
208 /// * `S`: The AEAD scheme type that determines the nonce size
209 ///
210 /// # Returns
211 ///
212 /// A new nonce derived from the incremented counter value
213 ///
214 /// # Panics
215 ///
216 /// Panics if the counter overflows (exceeds u32::MAX)
217 pub fn next_nonce<S: AeadCore>(&mut self) -> Nonce<S> {
218 self.0 = self.0.checked_add(1).expect("nonce overflow");
219
220 let mut nonce = Nonce::<S>::default();
221 nonce[..4].copy_from_slice(&self.0.to_le_bytes());
222
223 nonce
224 }
225}
226
227/// Implementation of EncryptionScheme using X25519 for key exchange
228/// and any AEAD scheme for encryption.
229///
230/// This struct combines:
231/// - X25519 for key exchange
232/// - A configurable AEAD scheme for encryption
233/// - Secure nonce generation
234/// - Key pair management for multiple parties
235///
236/// # Type Parameters
237///
238/// * `S`: The AEAD scheme to use for encryption (e.g., ChaCha20Poly1305)
239pub struct AeadX25519<S> {
240 secret: ReusableSecret,
241 public_key: PublicKey,
242 counter: NonceCounter,
243 pk: Pairs<(SharedKey, PublicKey), usize>,
244 marker: PhantomData<S>,
245}
246
247impl<S> AeadX25519<S> {
248 /// Generate a new [`AeadX25519`] with the supplied RNG.
249 ///
250 /// # Arguments
251 ///
252 /// * `rng`: A cryptographically secure random number generator
253 ///
254 /// # Returns
255 ///
256 /// A new `AeadX25519` instance with:
257 /// - A randomly generated X25519 key pair
258 /// - An initialized nonce counter
259 /// - An empty key pair store
260 pub fn new(rng: &mut impl CryptoRngCore) -> Self {
261 let secret = ReusableSecret::random_from_rng(rng);
262 let public_key = PublicKey::from(&secret);
263
264 Self {
265 secret,
266 public_key,
267 counter: NonceCounter::new(),
268 pk: Pairs::new(),
269 marker: PhantomData,
270 }
271 }
272
273 /// Create a new [`AeadX25519`] from a provided `ReusableSecret`.
274 ///
275 /// # Arguments
276 ///
277 /// * `secret`: A pre-existing X25519 secret key
278 ///
279 /// # Returns
280 ///
281 /// A new `AeadX25519` instance with:
282 /// - The provided secret key and its corresponding public key
283 /// - An initialized nonce counter
284 /// - An empty key pair store
285 pub fn from_secret(secret: ReusableSecret) -> Self {
286 let public_key = PublicKey::from(&secret);
287
288 Self {
289 secret,
290 public_key,
291 counter: NonceCounter::new(),
292 pk: Pairs::new(),
293 marker: PhantomData,
294 }
295 }
296}
297
298impl<S> EncryptionScheme for AeadX25519<S>
299where
300 S: AeadInPlace + KeyInit + Send,
301{
302 fn overhead(&self) -> usize {
303 S::TagSize::USIZE + S::NonceSize::USIZE
304 }
305
306 fn encrypt(
307 &mut self,
308 associated_data: &[u8],
309 buffer: &mut [u8],
310 tail: &mut [u8],
311 receive: usize,
312 ) -> Result<(), EncryptionError> {
313 if tail.len() != self.overhead() {
314 return Err(EncryptionError);
315 }
316
317 let (key, public_key) =
318 self.pk.find_pair_or_err(receive, EncryptionError)?;
319
320 let key = Zeroizing::new(
321 Sha256::new_with_prefix(public_key)
322 .chain_update(key)
323 .finalize(),
324 );
325
326 let key = Key::<S>::from_slice(key.as_slice());
327
328 let nonce = self.counter.next_nonce::<S>();
329 let tag = S::new(key)
330 .encrypt_in_place_detached(&nonce, associated_data, buffer)
331 .map_err(|_| EncryptionError)?;
332
333 tail[..S::TagSize::USIZE].copy_from_slice(&tag);
334 tail[S::TagSize::USIZE..].copy_from_slice(&nonce);
335
336 Ok(())
337 }
338
339 fn decrypt(
340 &self,
341 associated_data: &[u8],
342 buffer: &mut [u8],
343 tail: &[u8],
344 sender: usize,
345 ) -> Result<(), EncryptionError> {
346 if tail.len() != self.overhead() {
347 return Err(EncryptionError);
348 }
349
350 let (key, public_key) =
351 self.pk.find_pair_or_err(sender, EncryptionError)?;
352
353 let key = Zeroizing::new(
354 Sha256::new_with_prefix(self.public_key)
355 .chain_update(key)
356 .finalize(),
357 );
358
359 let key = Key::<S>::from_slice(key.as_slice());
360
361 let nonce = Nonce::<S>::from_slice(&tail[S::TagSize::USIZE..]);
362 let tag = Tag::<S>::from_slice(&tail[..S::TagSize::USIZE]);
363
364 S::new(key)
365 .decrypt_in_place_detached(nonce, associated_data, buffer, tag)
366 .map_err(|_| EncryptionError)?;
367
368 Ok(())
369 }
370
371 fn public_key(&self) -> &[u8] {
372 self.public_key.as_bytes()
373 }
374
375 fn receiver_public_key(
376 &mut self,
377 receiver_index: usize,
378 pk: &[u8],
379 ) -> Result<(), PublicKeyError> {
380 let pk: [u8; 32] = pk.try_into().map_err(|_| PublicKeyError)?;
381 let pk = PublicKey::from(pk);
382
383 let shared_secret = self.secret.diffie_hellman(&pk);
384
385 if !shared_secret.was_contributory() {
386 return Err(PublicKeyError);
387 }
388
389 let shared_key = Zeroizing::new(hchacha::<U10>(
390 GenericArray::from_slice(shared_secret.as_bytes()),
391 &GenericArray::default(),
392 ));
393
394 self.pk.push(receiver_index, (shared_key, pk));
395
396 Ok(())
397 }
398}