pyrus_crypto/message/
builder.rs

1use chacha20poly1305::{
2    aead::{AeadCore, KeyInit},
3    XChaCha20Poly1305, XNonce,
4};
5use ed25519_dalek::{Signature, SigningKey};
6
7use indexmap::IndexMap;
8
9use rand_core::{OsRng, RngCore};
10
11use zeroize::Zeroizing;
12
13use std::io::Read;
14use std::sync::Arc;
15
16use crate::cert::Cert;
17use crate::crypto;
18use crate::error::{CryptoError, Result};
19use crate::Fingerprint;
20
21use super::header::*;
22use super::Message;
23
24/// A struct used to construct a message.
25///
26/// It implements a Rust twist on the state pattern by returning intermediate 
27/// types: `SignedMessage`, `SymEncryptedMessage` and `AsmEncryptedMessage`. 
28/// They all implement the [`MessageFinal`] trait with the return types being:
29/// * [`Result<Message>`] for `SymEncryptedMessage` and 
30/// `AsmEncryptedMessage`,
31/// * [`Message`] for `SignedMessage`.
32///
33/// In addition the `SignedMessage` type implements the following methods:
34/// * [`encrypt_for`](crate::message::builder::MessageBuilder::encrypt_for),
35/// * [`encrypt_with`](crate::message::builder::MessageBuilder::encrypt_with),
36/// which have the same API and functionality as their counterparts defined in 
37/// [`MessageBuilder`].
38///
39/// # Example
40/// A typical message build method call chain:
41/// ```rust
42/// # use std::error::Error;
43/// # use pyrus_crypto::message::Message;
44/// # use pyrus_crypto::message::MessageFinal;
45/// # use pyrus_crypto::cert::Cert;
46/// # use std::sync::Arc;
47/// # fn main() -> Result<(), Box<dyn Error>> {
48/// # let larry: Arc<Cert> = Arc::new(Cert::new("Larry <larry@gentoo.org>"));
49/// # let agentcow = Arc::new(Cert::new("agentcow"));
50/// # let agenthorse = Arc::new(Cert::new("agenthorse"));
51/// # let friends = vec![agentcow.clone(), agenthorse.clone()];
52/// # let plaintext = b"Let's meet up in the evening";
53/// let msg = Message::new(&larry)? // start building a message (issuer: larry)
54///     .write(&plaintext[..])? // add plaintext content to the message
55///     .sign() // sign the message (issuer: larry)
56///     .encrypt_for(&friends)? // asymmetrically encrypt the message (for friends)
57///     .finalize()?; // finalize the message (returns Result<Message>)
58/// # Ok(())
59/// # }
60/// ```
61pub struct MessageBuilder<'a> {
62    pub(super) issuer: &'a Cert,
63    pub(super) payload: Vec<u8>,
64}
65
66impl<'a> MessageBuilder<'a> {
67    /// Adds plaintext content to the message. `content` must implement the 
68    /// [`Read`] trait.
69    ///
70    /// # Errors
71    /// * [`CryptoError::Unknown`] when a reading error or other error related 
72    /// to the reader object occured.
73    pub fn write<R: Read>(mut self, mut content: R) -> Result<Self> {
74        content
75            .read_to_end(&mut self.payload)
76            .map_err(|_| CryptoError::Unknown)?;
77        Ok(self)
78    }
79
80    /// Signs the message.
81    pub fn sign(self) -> SignedMessage<'a> {
82        let key = SigningKey::from_bytes(self.issuer.signing_secret().unwrap());
83        let signature = crypto::sign_message(&key, &self.payload);
84        SignedMessage {
85            issuer: self.issuer,
86            signature,
87            payload: self.payload,
88        }
89    }
90    
91    /// Symmetrically encrypt the message using a `passphrase`.
92    pub fn encrypt_with(self, passphrase: impl AsRef<[u8]>) -> Result<SymEncryptedMessage<'a>> {
93        let salt = {
94            let mut s = [0; 32];
95            OsRng.fill_bytes(&mut s);
96            s
97        };
98        let key = crypto::derive_key(passphrase.as_ref(), &salt[..])?;
99        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
100
101        Ok(SymEncryptedMessage {
102            issuer: self.issuer,
103            signature: None,
104            salt,
105            nonce,
106            key,
107            payload: self.payload,
108        })
109    }
110
111    /// Asymmetrically encrypts the message for every recipient in `recipients`.
112    pub fn encrypt_for(self, recipients: &'a [Arc<Cert>]) -> Result<AsmEncryptedMessage<'a>> {
113        let key: Zeroizing<[u8; 32]> =
114            Zeroizing::new(XChaCha20Poly1305::generate_key(&mut OsRng).into());
115        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
116
117        let mut rec_list = IndexMap::new();
118        for recipient in recipients {
119            let shared_secret = Zeroizing::new(
120                self.issuer
121                    .encryption_secret()
122                    .unwrap()
123                    .diffie_hellman(recipient.encryption_public()),
124            );
125            let encrypted_key = crypto::encrypt_key(&key[..], &nonce, &shared_secret)?;
126            rec_list.insert(*recipient.fingerprint(), encrypted_key);
127        }
128        Ok(AsmEncryptedMessage {
129            issuer: self.issuer,
130            signature: None,
131            recipients: rec_list,
132            nonce,
133            key,
134            payload: self.payload,
135        })
136    }
137}
138
139pub struct SignedMessage<'a> {
140    issuer: &'a Cert,
141    signature: Signature,
142    payload: Vec<u8>,
143}
144
145impl<'a> SignedMessage<'a> {
146    pub fn encrypt_with(self, passphrase: impl AsRef<[u8]>) -> Result<SymEncryptedMessage<'a>> {
147        let salt = {
148            let mut s = [0; 32];
149            OsRng.fill_bytes(&mut s);
150            s
151        };
152        let key = crypto::derive_key(passphrase.as_ref(), &salt[..])?;
153        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
154
155        Ok(SymEncryptedMessage {
156            issuer: self.issuer,
157            signature: Some(self.signature),
158            salt,
159            nonce,
160            key,
161            payload: self.payload,
162        })
163    }
164
165    pub fn encrypt_for(self, recipients: &'a [Arc<Cert>]) -> Result<AsmEncryptedMessage<'a>> {
166        let key: Zeroizing<[u8; 32]> =
167            Zeroizing::new(XChaCha20Poly1305::generate_key(&mut OsRng).into());
168        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
169
170        let mut rec_list = IndexMap::new();
171        for recipient in recipients {
172            let shared_secret = Zeroizing::new(
173                self.issuer
174                    .encryption_secret()
175                    .unwrap()
176                    .diffie_hellman(recipient.encryption_public()),
177            );
178            let encrypted_key = crypto::encrypt_key(&key[..], &nonce, &shared_secret)?;
179            rec_list.insert(*recipient.fingerprint(), encrypted_key);
180        }
181        Ok(AsmEncryptedMessage {
182            issuer: self.issuer,
183            signature: Some(self.signature),
184            recipients: rec_list,
185            nonce,
186            key,
187            payload: self.payload,
188        })
189    }
190}
191
192pub struct SymEncryptedMessage<'a> {
193    issuer: &'a Cert,
194    signature: Option<Signature>,
195    salt: [u8; 32],
196    nonce: XNonce,
197    key: Zeroizing<[u8; 32]>,
198    payload: Vec<u8>,
199}
200
201pub struct AsmEncryptedMessage<'a> {
202    issuer: &'a Cert,
203    signature: Option<Signature>,
204    recipients: IndexMap<Fingerprint, Vec<u8>>,
205    nonce: XNonce,
206    key: Zeroizing<[u8; 32]>,
207    payload: Vec<u8>,
208}
209
210/// The trait used to indicate an intermediate type may finalize a message. 
211///
212/// More about those intermediate types may be read [here](MessageBuilder).
213/// The following hidden types implement this trait:
214/// * `SignedMessage`,
215/// * `SymEncryptedMessage`,
216/// * `AsmEncryptedMessage`.
217pub trait MessageFinal {
218    /// The return type of the finalize method.
219    ///
220    /// Either [`Message`] or [`Result<Message>`]
221    type R;
222
223    /// Finalaze the message and return `R`.
224    fn finalize(self) -> Self::R;
225}
226
227impl MessageFinal for SignedMessage<'_> {
228    type R = Message;
229
230    fn finalize(self) -> Self::R {
231        let msg_type = MessageType::Signed {
232            signature: self.signature,
233        };
234        let header = Header {
235            msg_type,
236            issuer: *self.issuer.fingerprint(),
237        };
238        Message {
239            header,
240            payload: self.payload,
241        }
242    }
243}
244
245impl MessageFinal for SymEncryptedMessage<'_> {
246    type R = Result<Message>;
247
248    fn finalize(self) -> Self::R {
249        let msg_type = match self.signature {
250            Some(s) => MessageType::SignedEncryptedSym {
251                signature: s,
252                salt: self.salt,
253                nonce: self.nonce.into(),
254            },
255            None => MessageType::EncryptedSym {
256                salt: self.salt,
257                nonce: self.nonce.into(),
258            },
259        };
260        let header = Header {
261            msg_type,
262            issuer: *self.issuer.fingerprint(),
263        };
264
265        let aad = header.as_bytes()?;
266        let payload = crypto::encrypt_symmetric(&self.key, &self.nonce, &self.payload, &aad)?;
267        Ok(Message { header, payload })
268    }
269}
270
271impl MessageFinal for AsmEncryptedMessage<'_> {
272    type R = Result<Message>;
273
274    fn finalize(self) -> Self::R {
275        let msg_type = match self.signature {
276            Some(s) => MessageType::SignedEncryptedAsm {
277                signature: s,
278                keys: self.recipients,
279                nonce: self.nonce.into(),
280            },
281            None => MessageType::EncryptedAsm {
282                keys: self.recipients,
283                nonce: self.nonce.into(),
284            },
285        };
286        let header = Header {
287            msg_type,
288            issuer: *self.issuer.fingerprint(),
289        };
290
291        let aad = header.as_bytes()?;
292        let payload = crypto::encrypt_symmetric(&self.key, &self.nonce, &self.payload, &aad)?;
293        Ok(Message { header, payload })
294    }
295}