Skip to content

Commit 53cd909

Browse files
committed
returned serde back with a compile-time warning about using serde
1 parent 26fac20 commit 53cd909

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

Cargo.lock

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chacha20/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ rand_core-compatible RNGs based on those ciphers.
2222
cfg-if = "1"
2323
cipher = { version = "0.5.0-rc.3", optional = true, features = ["stream-wrapper"] }
2424
rand_core = { version = "0.10.0-rc-3", optional = true, default-features = false }
25+
serde = { version = "1.0", features = ["derive"], optional = true }
2526

2627
# `zeroize` is an explicit dependency because this crate may be used without the `cipher` crate
2728
zeroize = { version = "1.8.1", optional = true, default-features = false }
@@ -34,11 +35,13 @@ cipher = { version = "0.5.0-rc.3", features = ["dev"] }
3435
hex-literal = "1"
3536
proptest = "1"
3637
rand_chacha = "0.9"
38+
serde_json = "1.0.120"
3739

3840
[features]
3941
default = ["cipher"]
4042
legacy = ["cipher"]
4143
rng = ["rand_core"]
44+
serde = ["dep:serde"]
4245
xchacha = ["cipher"]
4346

4447
[package.metadata.docs.rs]

chacha20/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn main() {
2+
if cfg!(feature = "serde") {
3+
println!("cargo:warning=`serde` feature is enabled. Serializing CSPRNG states can leave unzeroizable copies of the seed in memory.");
4+
}
5+
}

chacha20/src/rng.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ use rand_core::{
1616
#[cfg(feature = "zeroize")]
1717
use zeroize::{Zeroize, ZeroizeOnDrop};
1818

19+
#[cfg(feature = "serde")]
20+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
21+
1922
use crate::{
2023
ChaChaCore, R8, R12, R20, Rounds, backends,
2124
variants::{Legacy, Variant},
@@ -29,6 +32,7 @@ pub(crate) const BLOCK_WORDS: u8 = 16;
2932
/// The seed for ChaCha20. Implements ZeroizeOnDrop when the
3033
/// zeroize feature is enabled.
3134
#[derive(PartialEq, Eq, Default, Clone)]
35+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3236
pub struct Seed([u8; 32]);
3337

3438
impl AsRef<[u8; 32]> for Seed {
@@ -471,11 +475,34 @@ macro_rules! impl_chacha_rng {
471475
}
472476
}
473477

478+
#[cfg(feature = "serde")]
479+
impl Serialize for $ChaChaXRng {
480+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
481+
where
482+
S: Serializer,
483+
{
484+
$abst::$ChaChaXRng::from(self).serialize(s)
485+
}
486+
}
487+
#[cfg(feature = "serde")]
488+
impl<'de> Deserialize<'de> for $ChaChaXRng {
489+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
490+
where
491+
D: Deserializer<'de>,
492+
{
493+
$abst::$ChaChaXRng::deserialize(d).map(|x| Self::from(&x))
494+
}
495+
}
496+
474497
mod $abst {
498+
#[cfg(feature = "serde")]
499+
use serde::{Deserialize, Serialize};
500+
475501
// The abstract state of a ChaCha stream, independent of implementation choices. The
476502
// comparison and serialization of this object is considered a semver-covered part of
477503
// the API.
478504
#[derive(Debug, PartialEq, Eq)]
505+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
479506
pub(crate) struct $ChaChaXRng {
480507
seed: crate::rng::Seed,
481508
stream: u64,
@@ -540,6 +567,53 @@ pub(crate) mod tests {
540567
26, 27, 28, 29, 30, 31, 32,
541568
];
542569

570+
#[cfg(feature = "serde")]
571+
#[test]
572+
fn test_chacha_serde_roundtrip() {
573+
let seed = [
574+
1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0,
575+
0, 0, 0, 2, 92,
576+
];
577+
let mut rng1 = ChaCha20Rng::from_seed(seed);
578+
let mut rng2 = ChaCha12Rng::from_seed(seed);
579+
let mut rng3 = ChaCha8Rng::from_seed(seed);
580+
581+
let encoded1 = serde_json::to_string(&rng1).unwrap();
582+
let encoded2 = serde_json::to_string(&rng2).unwrap();
583+
let encoded3 = serde_json::to_string(&rng3).unwrap();
584+
585+
let mut decoded1: ChaCha20Rng = serde_json::from_str(&encoded1).unwrap();
586+
let mut decoded2: ChaCha12Rng = serde_json::from_str(&encoded2).unwrap();
587+
let mut decoded3: ChaCha8Rng = serde_json::from_str(&encoded3).unwrap();
588+
589+
assert_eq!(rng1, decoded1);
590+
assert_eq!(rng2, decoded2);
591+
assert_eq!(rng3, decoded3);
592+
593+
assert_eq!(rng1.next_u32(), decoded1.next_u32());
594+
assert_eq!(rng2.next_u32(), decoded2.next_u32());
595+
assert_eq!(rng3.next_u32(), decoded3.next_u32());
596+
}
597+
598+
// This test validates that:
599+
// 1. a hard-coded serialization demonstrating the format at time of initial release can still
600+
// be deserialized to a ChaChaRng
601+
// 2. re-serializing the resultant object produces exactly the original string
602+
//
603+
// Condition 2 is stronger than necessary: an equivalent serialization (e.g. with field order
604+
// permuted, or whitespace differences) would also be admissible, but would fail this test.
605+
// However testing for equivalence of serialized data is difficult, and there shouldn't be any
606+
// reason we need to violate the stronger-than-needed condition, e.g. by changing the field
607+
// definition order.
608+
#[cfg(feature = "serde")]
609+
#[test]
610+
fn test_chacha_serde_format_stability() {
611+
let j = r#"{"seed":[4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8],"stream":27182818284,"word_pos":314159265359}"#;
612+
let r: ChaChaRng = serde_json::from_str(j).unwrap();
613+
let j1 = serde_json::to_string(&r).unwrap();
614+
assert_eq!(j, j1);
615+
}
616+
543617
#[test]
544618
fn test_rng_output() {
545619
let mut rng = ChaCha20Rng::from_seed(KEY);

0 commit comments

Comments
 (0)