1use alloc::vec::Vec;
13
14use aes_gcm::Aes256Gcm;
15use aes_gcm::aead::{Aead, KeyInit, Payload};
16use chacha20poly1305::ChaCha20Poly1305 as ChaChaCipher;
17use x25519_dalek::{PublicKey, StaticSecret};
18use zeroize::Zeroizing;
19
20use crate::alg::ContentAlgorithm;
21use crate::claims::Claims;
22use crate::codec::{self, ClaimsExpectation, NONCE_LEN};
23use crate::error::{BuildError, DecodeError, OpenError};
24use crate::kdf::{self, KdfParties};
25use crate::keys::X25519RecipientPublic;
26use crate::traits::{OpenRequest, Recipient};
27use crate::types::{ContentType, CoseBytes, ExternalAad, KeyId};
28
29#[derive(Debug, Clone)]
31pub struct EncryptParams<'a> {
32 pub content_type: ContentType,
34 pub plaintext: &'a [u8],
36 pub recipient: X25519RecipientPublic,
38 pub content_algorithm: ContentAlgorithm,
40 pub external_aad: ExternalAad,
42 pub kdf_parties: KdfParties,
44}
45
46#[cfg(feature = "fixtures")]
49#[derive(Debug)]
50pub struct SealParts {
51 pub ephemeral_private: Zeroizing<[u8; 32]>,
53 pub nonce: [u8; NONCE_LEN],
55}
56
57pub fn random_array<const N: usize>() -> Result<Zeroizing<[u8; N]>, BuildError> {
59 let mut buf = Zeroizing::new([0u8; N]);
60 getrandom::fill(buf.as_mut_slice()).map_err(|_| BuildError::Rng)?;
61 Ok(buf)
62}
63
64pub fn aead_seal(
66 alg: ContentAlgorithm,
67 key: &Zeroizing<[u8; 32]>,
68 nonce: &[u8; NONCE_LEN],
69 plaintext: &[u8],
70 aad: &[u8],
71) -> Result<Vec<u8>, BuildError> {
72 let payload = Payload {
73 msg: plaintext,
74 aad,
75 };
76 match alg {
77 ContentAlgorithm::A256Gcm => Aes256Gcm::new_from_slice(key.as_slice())
78 .map_err(|_| BuildError::SealFailed)?
79 .encrypt(aes_gcm::Nonce::from_slice(nonce), payload)
80 .map_err(|_| BuildError::SealFailed),
81 ContentAlgorithm::ChaCha20Poly1305 => ChaChaCipher::new_from_slice(key.as_slice())
82 .map_err(|_| BuildError::SealFailed)?
83 .encrypt(chacha20poly1305::Nonce::from_slice(nonce), payload)
84 .map_err(|_| BuildError::SealFailed),
85 }
86}
87
88pub fn aead_open(
91 alg: ContentAlgorithm,
92 key: &Zeroizing<[u8; 32]>,
93 nonce: &[u8; NONCE_LEN],
94 ciphertext: &[u8],
95 aad: &[u8],
96) -> Result<Zeroizing<Vec<u8>>, OpenError> {
97 let payload = Payload {
98 msg: ciphertext,
99 aad,
100 };
101 let plaintext = match alg {
102 ContentAlgorithm::A256Gcm => Aes256Gcm::new_from_slice(key.as_slice())
103 .map_err(|_| OpenError::OpenFailed)?
104 .decrypt(aes_gcm::Nonce::from_slice(nonce), payload)
105 .map_err(|_| OpenError::OpenFailed)?,
106 ContentAlgorithm::ChaCha20Poly1305 => ChaChaCipher::new_from_slice(key.as_slice())
107 .map_err(|_| OpenError::OpenFailed)?
108 .decrypt(chacha20poly1305::Nonce::from_slice(nonce), payload)
109 .map_err(|_| OpenError::OpenFailed)?,
110 };
111 Ok(Zeroizing::new(plaintext))
112}
113
114pub struct EncryptCore<'a> {
117 pub content_algorithm: ContentAlgorithm,
119 pub content_type: &'a ContentType,
121 pub claims: Option<&'a Claims>,
123 pub plaintext: &'a [u8],
125 pub recipient: &'a X25519RecipientPublic,
127 pub external_aad: &'a ExternalAad,
129 pub kdf_parties: &'a KdfParties,
131}
132
133pub fn build_encrypt_core(
136 core: &EncryptCore<'_>,
137 ephemeral_private: &Zeroizing<[u8; 32]>,
138 nonce: [u8; NONCE_LEN],
139) -> Result<Vec<u8>, BuildError> {
140 let ephemeral_secret = StaticSecret::from(**ephemeral_private);
141 let ephemeral_pub = PublicKey::from(&ephemeral_secret).to_bytes();
142
143 let recipient_pub = PublicKey::from(core.recipient.public);
144 let shared_secret = ephemeral_secret.diffie_hellman(&recipient_pub);
145 if !shared_secret.was_contributory() {
148 return Err(BuildError::SealFailed);
149 }
150 let shared = Zeroizing::new(shared_secret.to_bytes());
151
152 let recipient_protected = codec::encode_recipient_protected(core.kdf_parties)
153 .map_err(|codec::CodecError| BuildError::Codec)?;
154 let info = codec::kdf_context(
155 core.content_algorithm,
156 core.kdf_parties,
157 &recipient_protected,
158 )
159 .map_err(|codec::CodecError| BuildError::Codec)?;
160 let cek = kdf::derive_cek(&shared, &info).map_err(|kdf::KdfFailed| BuildError::SealFailed)?;
161
162 let protected =
163 codec::encode_encrypt_protected(core.content_algorithm, core.content_type, core.claims)
164 .map_err(|codec::CodecError| BuildError::Codec)?;
165 let aad = codec::enc_structure(&protected, core.external_aad.as_bytes())
166 .map_err(|codec::CodecError| BuildError::Codec)?;
167 let ciphertext = aead_seal(core.content_algorithm, &cek, &nonce, core.plaintext, &aad)?;
168
169 codec::assemble_encrypt(&codec::EncryptAssembly {
170 protected: &protected,
171 iv: &nonce,
172 ciphertext: &ciphertext,
173 recipient_protected: &recipient_protected,
174 recipient_kid: &core.recipient.key_id,
175 ephemeral_x: &ephemeral_pub,
176 })
177 .map_err(|codec::CodecError| BuildError::Codec)
178}
179
180impl EncryptParams<'_> {
181 const fn core(&self) -> EncryptCore<'_> {
182 EncryptCore {
183 content_algorithm: self.content_algorithm,
184 content_type: &self.content_type,
185 claims: None,
186 plaintext: self.plaintext,
187 recipient: &self.recipient,
188 external_aad: &self.external_aad,
189 kdf_parties: &self.kdf_parties,
190 }
191 }
192}
193
194pub fn build_encrypted(params: &EncryptParams<'_>) -> Result<CoseBytes, BuildError> {
206 let ephemeral = random_array::<32>()?;
207 let nonce = random_array::<NONCE_LEN>()?;
208 build_encrypt_core(¶ms.core(), &ephemeral, *nonce).map(CoseBytes::new)
209}
210
211#[cfg(feature = "fixtures")]
217pub fn build_encrypted_with_parts(
218 params: &EncryptParams<'_>,
219 parts: &SealParts,
220) -> Result<CoseBytes, BuildError> {
221 build_encrypt_core(¶ms.core(), &parts.ephemeral_private, parts.nonce).map(CoseBytes::new)
222}
223
224#[derive(Debug, Clone)]
226pub struct EncryptedMessage {
227 pub content_type: ContentType,
229 pub recipient_key_id: KeyId,
231 pub content_algorithm: ContentAlgorithm,
233 pub parties: KdfParties,
235 bytes: Vec<u8>,
238}
239
240pub fn decode_encrypted(bytes: &[u8]) -> Result<EncryptedMessage, DecodeError> {
246 let decoded = codec::decode_encrypt_strict(bytes, ClaimsExpectation::Forbidden)?;
247 Ok(EncryptedMessage {
248 content_type: decoded.content_type,
249 recipient_key_id: decoded.recipient_kid,
250 content_algorithm: decoded.content_algorithm,
251 parties: decoded.parties,
252 bytes: bytes.to_vec(),
253 })
254}
255
256#[derive(Debug)]
258pub struct Opened {
259 pub plaintext: Zeroizing<Vec<u8>>,
261 pub content_type: ContentType,
263}
264
265impl EncryptedMessage {
266 pub async fn open<R: Recipient>(
275 &self,
276 recipient: &R,
277 aad: &ExternalAad,
278 expected_parties: Option<&KdfParties>,
279 ) -> Result<Opened, OpenError> {
280 if recipient.key_id() != &self.recipient_key_id {
281 return Err(OpenError::RecipientKeyMismatch);
282 }
283 if let Some(expected) = expected_parties
284 && *expected != self.parties
285 {
286 return Err(OpenError::PartyMismatch);
287 }
288 let request = OpenRequest {
289 cose_encrypt: &self.bytes,
290 external_aad: aad,
291 expected_parties,
292 };
293 let plaintext = recipient.open(&request).await?;
294 Ok(Opened {
295 plaintext,
296 content_type: self.content_type.clone(),
297 })
298 }
299}