1use std::io::{self, BufRead, Read};
2
3use byteorder::WriteBytesExt;
4use bytes::Bytes;
5use rand::{CryptoRng, Rng};
6
7use crate::{
8 crypto::{
9 aead::{AeadAlgorithm, ChunkSize, StreamEncryptor},
10 sym::SymmetricKeyAlgorithm,
11 },
12 errors::{ensure_eq, format_err, InvalidInputSnafu, Result},
13 packet::{GnupgAeadDataConfig, PacketHeader, PacketTrait, SymEncryptedProtectedDataConfig},
14 parsing_reader::BufReadParsing,
15 ser::Serialize,
16 types::Tag,
17};
18
19#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
21pub enum ProtectedDataConfig {
22 Seipd(SymEncryptedProtectedDataConfig),
23 GnupgAead(GnupgAeadDataConfig),
24}
25
26#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
29pub struct SymEncryptedProtectedData {
30 packet_header: PacketHeader,
31 config: Config,
32 #[debug("{}", hex::encode(data))]
33 data: Bytes,
34}
35
36#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
37pub enum Config {
38 V1,
39 V2 {
40 sym_alg: SymmetricKeyAlgorithm,
41 aead: AeadAlgorithm,
42 chunk_size: ChunkSize,
43 #[debug("{}", hex::encode(salt))]
44 salt: [u8; 32],
45 },
46}
47
48impl Config {
49 pub fn try_from_reader<R: BufRead>(mut data: R) -> Result<Self> {
50 let version = data.read_u8()?;
51 match version {
52 0x01 => Ok(Self::V1),
53 0x02 => {
54 let sym_alg = data.read_u8().map(SymmetricKeyAlgorithm::from)?;
55 let aead = data.read_u8().map(AeadAlgorithm::from)?;
56 let chunk_size = data
57 .read_u8()?
58 .try_into()
59 .map_err(|_| InvalidInputSnafu.build())?;
60 let salt = data.read_array::<32>()?;
61
62 Ok(Self::V2 {
63 sym_alg,
64 aead,
65 chunk_size,
66 salt,
67 })
68 }
69 _ => Err(format_err!(
70 "unknown SymEncryptedProtectedData version {}",
71 version
72 )),
73 }
74 }
75}
76
77impl SymEncryptedProtectedData {
78 pub fn try_from_reader<B: BufRead>(packet_header: PacketHeader, mut data: B) -> Result<Self> {
80 ensure_eq!(
81 packet_header.tag(),
82 Tag::SymEncryptedProtectedData,
83 "invalid tag"
84 );
85
86 let config = Config::try_from_reader(&mut data)?;
87 let data = data.rest()?;
88
89 Ok(SymEncryptedProtectedData {
90 packet_header,
91 config,
92 data: data.freeze(),
93 })
94 }
95
96 pub fn encrypt_seipdv1<R: CryptoRng + Rng>(
98 rng: R,
99 alg: SymmetricKeyAlgorithm,
100 key: &[u8],
101 plaintext: &[u8],
102 ) -> Result<Self> {
103 let data: Bytes = alg.encrypt_protected(rng, key, plaintext)?.into();
104 let config = Config::V1;
105 let len = config.write_len() + data.len();
106 let packet_header =
107 PacketHeader::new_fixed(Tag::SymEncryptedProtectedData, len.try_into()?);
108
109 Ok(SymEncryptedProtectedData {
110 packet_header,
111 config,
112 data,
113 })
114 }
115
116 pub fn encrypt_seipdv2<R: CryptoRng + Rng>(
118 mut rng: R,
119 sym_alg: SymmetricKeyAlgorithm,
120 aead: AeadAlgorithm,
121 chunk_size: ChunkSize,
122 session_key: &[u8],
123 mut plaintext: &[u8],
124 ) -> Result<Self> {
125 let mut salt = [0u8; 32];
127 rng.fill(&mut salt[..]);
128
129 let mut encryptor = crate::crypto::aead::StreamEncryptor::new(
130 sym_alg,
131 aead,
132 chunk_size,
133 session_key,
134 &salt,
135 &mut plaintext,
136 )?;
137
138 let mut out = Vec::new();
139 encryptor.read_to_end(&mut out)?;
140
141 let config = Config::V2 {
142 sym_alg,
143 aead,
144 chunk_size,
145 salt,
146 };
147 let data: Bytes = out.into();
148 let len = config.write_len() + data.len();
149 let packet_header =
150 PacketHeader::new_fixed(Tag::SymEncryptedProtectedData, len.try_into()?);
151
152 Ok(SymEncryptedProtectedData {
153 packet_header,
154 config,
155 data,
156 })
157 }
158
159 pub fn encrypt_seipdv2_stream<R: io::Read>(
161 sym_alg: SymmetricKeyAlgorithm,
162 aead: AeadAlgorithm,
163 chunk_size: ChunkSize,
164 session_key: &[u8],
165 salt: [u8; 32],
166 source: R,
167 ) -> Result<StreamEncryptor<R>> {
168 let encryptor =
169 StreamEncryptor::new(sym_alg, aead, chunk_size, session_key, &salt, source)?;
170
171 Ok(encryptor)
172 }
173
174 pub fn data(&self) -> &Bytes {
175 &self.data
176 }
177
178 pub fn version(&self) -> usize {
179 match self.config {
180 Config::V1 => 1,
181 Config::V2 { .. } => 2,
182 }
183 }
184
185 pub fn config(&self) -> &Config {
187 &self.config
188 }
189
190 pub fn decrypt(
192 &self,
193 session_key: &[u8],
194 sym_alg: Option<SymmetricKeyAlgorithm>,
195 ) -> Result<Vec<u8>> {
196 match &self.config {
197 Config::V1 => {
198 let sym_alg = sym_alg.expect("v1");
199 let mut decryptor = StreamDecryptor::v1(sym_alg, session_key, &self.data[..])?;
200 let mut out = Vec::new();
201 decryptor.read_to_end(&mut out)?;
202 Ok(out)
203 }
204 Config::V2 {
205 sym_alg,
206 aead,
207 chunk_size,
208 salt,
209 } => {
210 ensure_eq!(
211 session_key.len(),
212 sym_alg.key_size(),
213 "Unexpected session key length for {:?}",
214 sym_alg
215 );
216
217 let mut decryptor = StreamDecryptor::v2(
218 *sym_alg,
219 *aead,
220 *chunk_size,
221 salt,
222 session_key,
223 &self.data[..],
224 )?;
225 let mut out = Vec::new();
226 decryptor.read_to_end(&mut out)?;
227 Ok(out)
228 }
229 }
230 }
231}
232
233impl Serialize for Config {
234 fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
235 match self {
236 Config::V1 => {
237 writer.write_u8(0x01)?;
238 }
239 Config::V2 {
240 sym_alg,
241 aead,
242 chunk_size,
243 salt,
244 } => {
245 writer.write_u8(0x02)?;
246 writer.write_u8((*sym_alg).into())?;
247 writer.write_u8((*aead).into())?;
248 writer.write_u8((*chunk_size).into())?;
249 writer.write_all(salt)?;
250 }
251 }
252 Ok(())
253 }
254
255 fn write_len(&self) -> usize {
256 match self {
257 Config::V1 => 1,
258 Config::V2 { salt, .. } => {
259 let mut sum = 1 + 1 + 1 + 1;
260 sum += salt.len();
261 sum
262 }
263 }
264 }
265}
266impl Serialize for SymEncryptedProtectedData {
267 fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
268 self.config.to_writer(writer)?;
269 writer.write_all(&self.data)?;
270 Ok(())
271 }
272
273 fn write_len(&self) -> usize {
274 let mut sum = self.config.write_len();
275 sum += self.data.len();
276 sum
277 }
278}
279
280impl PacketTrait for SymEncryptedProtectedData {
281 fn packet_header(&self) -> &PacketHeader {
282 &self.packet_header
283 }
284}
285
286#[derive(Debug)]
287#[allow(clippy::large_enum_variant)]
288pub enum StreamDecryptor<R: BufRead> {
289 V1(crate::crypto::sym::StreamDecryptor<R>),
290 GnupgAead(crate::crypto::aead::StreamDecryptor<R>),
291 V2(crate::crypto::aead::StreamDecryptor<R>),
292}
293
294impl<R: BufRead> BufRead for StreamDecryptor<R> {
295 fn fill_buf(&mut self) -> io::Result<&[u8]> {
296 match self {
297 Self::V1(r) => r.fill_buf(),
298 Self::GnupgAead(r) => r.fill_buf(),
299 Self::V2(r) => r.fill_buf(),
300 }
301 }
302
303 fn consume(&mut self, amt: usize) {
304 match self {
305 Self::V1(r) => r.consume(amt),
306 Self::GnupgAead(r) => r.consume(amt),
307 Self::V2(r) => r.consume(amt),
308 }
309 }
310}
311
312impl<R: BufRead> Read for StreamDecryptor<R> {
313 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
314 match self {
315 Self::V1(r) => r.read(buf),
316 Self::GnupgAead(r) => r.read(buf),
317 Self::V2(r) => r.read(buf),
318 }
319 }
320}
321
322impl<R: BufRead> StreamDecryptor<R> {
323 pub fn v1(sym_alg: SymmetricKeyAlgorithm, key: &[u8], source: R) -> Result<Self> {
324 let decryptor = sym_alg.stream_decryptor_protected(key, source)?;
325 Ok(Self::V1(decryptor))
326 }
327
328 pub fn gnupg_aead(
329 sym_alg: SymmetricKeyAlgorithm,
330 aead: AeadAlgorithm,
331 chunk_size: ChunkSize,
332 key: &[u8],
333 iv: &[u8],
334 source: R,
335 ) -> Result<Self> {
336 let decryptor = crate::crypto::aead::StreamDecryptor::new_gnupg(
337 sym_alg, aead, chunk_size, key, iv, source,
338 )?;
339 Ok(Self::GnupgAead(decryptor))
340 }
341
342 pub fn v2(
343 sym_alg: SymmetricKeyAlgorithm,
344 aead: AeadAlgorithm,
345 chunk_size: ChunkSize,
346 salt: &[u8; 32],
347 key: &[u8],
348 source: R,
349 ) -> Result<Self> {
350 let decryptor = crate::crypto::aead::StreamDecryptor::new_rfc9580(
351 sym_alg, aead, chunk_size, salt, key, source,
352 )?;
353 Ok(Self::V2(decryptor))
354 }
355
356 pub fn into_inner(self) -> R {
357 match self {
358 Self::V1(r) => r.into_inner(),
359 Self::GnupgAead(r) => r.into_inner(),
360 Self::V2(r) => r.into_inner(),
361 }
362 }
363
364 pub fn get_ref(&self) -> &R {
365 match self {
366 Self::V1(r) => r.get_ref(),
367 Self::GnupgAead(r) => r.get_ref(),
368 Self::V2(r) => r.get_ref(),
369 }
370 }
371
372 pub fn get_mut(&mut self) -> &mut R {
373 match self {
374 Self::V1(r) => r.get_mut(),
375 Self::GnupgAead(r) => r.get_mut(),
376 Self::V2(r) => r.get_mut(),
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 #![allow(clippy::unwrap_used)]
384
385 use proptest::{collection::vec, prelude::*};
386 use rand::{RngCore, SeedableRng};
387 use rand_chacha::ChaCha8Rng;
388
389 use super::*;
390 use crate::{
391 packet::sym_encrypted_protected_data::Config,
392 types::{PacketHeaderVersion, PacketLength},
393 };
394
395 #[test]
396 fn test_aead_message_sizes() {
397 let mut rng = ChaCha8Rng::from_seed([0u8; 32]);
400
401 const SYM_ALG: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
402
403 let mut session_key = [0; 16];
404 rng.fill_bytes(&mut session_key);
405
406 for size in 0..=512 {
409 let mut message = vec![0; size];
410 rng.fill_bytes(&mut message);
411
412 for aead in [AeadAlgorithm::Ocb, AeadAlgorithm::Eax, AeadAlgorithm::Gcm] {
413 println!("{size} bytes: {aead:?}");
414 let enc = SymEncryptedProtectedData::encrypt_seipdv2(
415 &mut rng,
416 SYM_ALG,
417 aead,
418 ChunkSize::C64B,
419 &session_key,
420 &message,
421 )
422 .expect("encrypt");
423
424 let dec = enc.decrypt(&session_key, Some(SYM_ALG)).expect("decrypt");
425 assert_eq!(message, dec);
426
427 let mut buffer = Vec::new();
429 enc.to_writer(&mut buffer).unwrap();
430 assert_eq!(buffer.len(), enc.write_len());
431
432 let back =
433 SymEncryptedProtectedData::try_from_reader(enc.packet_header, &mut &buffer[..])
434 .unwrap();
435 assert_eq!(enc, back);
436 }
437 }
438 }
439
440 impl Arbitrary for Config {
441 type Parameters = ();
442 type Strategy = BoxedStrategy<Self>;
443
444 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
445 prop_oneof![
446 Just(Config::V1),
447 any::<(SymmetricKeyAlgorithm, AeadAlgorithm, ChunkSize)>()
448 .prop_flat_map(move |(sym_alg, aead, chunk_size)| {
449 (
450 Just(sym_alg),
451 Just(aead),
452 Just(chunk_size),
453 vec(0u8..=255u8, 32),
454 )
455 })
456 .prop_map(move |(sym_alg, aead, chunk_size, salt)| Config::V2 {
457 sym_alg,
458 aead,
459 chunk_size,
460 salt: salt.try_into().unwrap(),
461 })
462 ]
463 .boxed()
464 }
465 }
466
467 impl Arbitrary for SymEncryptedProtectedData {
468 type Parameters = ();
469 type Strategy = BoxedStrategy<Self>;
470
471 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
472 any::<Config>()
473 .prop_flat_map(move |config| (Just(config), vec(0u8..=255u8, 0..=2048)))
474 .prop_map(move |(config, data)| {
475 let len = 1u32; let packet_header = PacketHeader::from_parts(
477 PacketHeaderVersion::New,
478 Tag::SymEncryptedProtectedData,
479 PacketLength::Fixed(len),
480 )
481 .unwrap();
482 SymEncryptedProtectedData {
483 config,
484 packet_header,
485 data: data.into(),
486 }
487 })
488 .boxed()
489 }
490 }
491
492 proptest! {
493 #[test]
494 fn write_len(data: SymEncryptedProtectedData) {
495 let mut buf = Vec::new();
496 data.to_writer(&mut buf).unwrap();
497 prop_assert_eq!(buf.len(), data.write_len());
498 }
499
500
501 #[test]
502 fn packet_roundtrip(data: SymEncryptedProtectedData) {
503 let mut buf = Vec::new();
504 data.to_writer(&mut buf).unwrap();
505 let new_data = SymEncryptedProtectedData::try_from_reader(data.packet_header, &mut &buf[..]).unwrap();
506 prop_assert_eq!(data, new_data);
507 }
508 }
509}