From d6a1b4f1f089228c8402364629fb765fb4fac6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Tue, 16 Jul 2024 02:00:01 +0200 Subject: [PATCH] Fragment record association verification with a `file_tag` --- cddl/kdf_usage.cddl | 2 +- cddl/registry.cddl | 1 + cddl/segment.cddl | 4 +- src/error.rs | 2 + src/record/key.rs | 18 +++++++ src/record/mod.rs | 19 ++++++-- src/record/segment.rs | 86 ++++++++++++++++++++++++++------- src/registry.rs | 22 +++++++++ tests/cddl.rs | 28 ++++++----- tests/test.proptest-regressions | 1 + tests/util.rs | 35 +++++++++++++- 11 files changed, 181 insertions(+), 37 deletions(-) diff --git a/cddl/kdf_usage.cddl b/cddl/kdf_usage.cddl index ba5e632..dd333f7 100644 --- a/cddl/kdf_usage.cddl +++ b/cddl/kdf_usage.cddl @@ -9,7 +9,7 @@ RRR_KdfUsage_SuccessionNonce = ( RRR_KdfUsage_Fragment = ( fragment: { - usage: "encryption_key" / "file_name", + usage: "encryption_key" / "file_name" / "file_tag", parameters: { record_nonce: uint, segment_index: uint, diff --git a/cddl/registry.cddl b/cddl/registry.cddl index 8ecf42d..e9778d9 100644 --- a/cddl/registry.cddl +++ b/cddl/registry.cddl @@ -12,6 +12,7 @@ RRR_Registry = { kdf: { algorithm: RRR_KdfAlgorithm, file_name_length_in_bytes: uint .ge 1, + file_tag_length_in_bytes: uint .ge 16, succession_nonce_length_in_bytes: uint .ge 16, root_predecessor_nonce: bstr, ; .size (16..1000), ; The upper bound is arbitrary TODO: not supported by cddl-rs }, diff --git a/cddl/segment.cddl b/cddl/segment.cddl index 3ec029b..2584c58 100644 --- a/cddl/segment.cddl +++ b/cddl/segment.cddl @@ -15,10 +15,12 @@ RRR_SegmentMetadata = { } RRR_SegmentMetadataRegistered = ( + RRR_SegmentMetadataRegistered_file_tag ^=> bstr, ; .size (32..10000), ? RRR_SegmentMetadataRegistered_last ^=> bool, ) -RRR_SegmentMetadataRegistered_last = 1 ; last -- This is the last segment of the record. +RRR_SegmentMetadataRegistered_file_tag = 1 ; file_tag -- Used to verify fragment's association to a record +RRR_SegmentMetadataRegistered_last = 2 ; last -- This is the last segment of the record. ; ; CBOR Object Signing and Encryption (COSE) diff --git a/src/error.rs b/src/error.rs index 235d936..64ba64a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,8 @@ pub enum Error { CollisionResolutionFailed, #[error(transparent)] InvalidParameter(#[from] InvalidParameterError), + #[error("File tag mismatch")] + FileTagMismatch, } impl From> for Error { diff --git a/src/record/key.rs b/src/record/key.rs index a7fc9b0..342f8e3 100644 --- a/src/record/key.rs +++ b/src/record/key.rs @@ -13,6 +13,7 @@ use std::fmt::Debug; use std::ops::{Deref, DerefMut}; use zeroize::{Zeroize, ZeroizeOnDrop}; +use super::segment::FragmentFileTagBytes; use super::{RecordName, SuccessionNonce}; #[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] @@ -127,6 +128,23 @@ impl HashedRecordKey { Ok(FragmentFileNameBytes(BytesOrHexString(okm))) } + pub async fn derive_fragment_file_tag( + &self, + kdf_params: &RegistryConfigKdf, + fragment_parameters: &KdfUsageFragmentParameters, + ) -> Result { + let mut okm = + vec![0_u8; kdf_params.get_file_tag_length_in_bytes() as usize].into_boxed_slice(); + let usage = KdfUsage::Fragment { + usage: KdfUsageFragmentUsage::FileTag, + parameters: fragment_parameters.clone(), + }; + + self.derive_key(&usage, kdf_params, &mut okm).await?; + + Ok(FragmentFileTagBytes(BytesOrHexString(okm))) + } + pub async fn derive_fragment_encryption_key( &self, kdf_params: &RegistryConfigKdf, diff --git a/src/record/mod.rs b/src/record/mod.rs index 74f4273..ea7daa2 100644 --- a/src/record/mod.rs +++ b/src/record/mod.rs @@ -209,19 +209,26 @@ impl Record { }; let fragment_file_name = fragment_key.derive_file_name(®istry.config.kdf).await?; + let fragment_file_tag = fragment_key.derive_file_tag(®istry.config.kdf).await?; let fragment_path = registry.get_fragment_path(&fragment_file_name); let segment_result: Result = try { let fragment_file = File::open(&fragment_path).await?; let fragment_file_guard = fragment_file.lock_read().await?; - - Segment::read_fragment( + let segment = Segment::read_fragment( ®istry.config.verifying_keys, ®istry.config.kdf, fragment_file_guard, &fragment_key, ) - .await? + .await?; + let found_file_tag = segment.metadata.get_file_tag()?; + + if found_file_tag != fragment_file_tag { + Err(Error::FileTagMismatch)?; + } + + segment }; trace!( @@ -273,7 +280,7 @@ impl Record { if let Error::Io(error) = error { error.kind() != io::ErrorKind::NotFound } else { - true + !matches!(error, &Error::FileTagMismatch) } }); @@ -366,7 +373,7 @@ impl Record { ); if collides_with_existing_fragment { - // TODO: Check whether the colliding file is actually encrypted with the same key? + // TODO: Check whether the colliding file has the same fragment file tag? continue 'next_nonce; } @@ -392,6 +399,8 @@ impl Record { let segment = Segment { metadata: { let mut metadata = SegmentMetadata::default(); + metadata + .insert_file_tag(fragment_key.derive_file_tag(®istry.config.kdf).await?); if segment_index == segment_sizes.len() - 1 { metadata.insert_last(); } diff --git a/src/record/segment.rs b/src/record/segment.rs index 8214fcf..e1914d1 100644 --- a/src/record/segment.rs +++ b/src/record/segment.rs @@ -16,9 +16,9 @@ use coset::{ }; use derive_more::{Deref, DerefMut}; use itertools::Itertools; -use proptest::arbitrary::{any, Arbitrary}; +use proptest::arbitrary::any; +use proptest::collection::vec; use proptest::prop_compose; -use proptest::strategy::{BoxedStrategy, Strategy}; use proptest_derive::Arbitrary; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -73,6 +73,15 @@ impl FragmentKey { .await } + pub async fn derive_file_tag( + &self, + kdf_params: &RegistryConfigKdf, + ) -> Result { + self.hashed_record_key + .derive_fragment_file_tag(kdf_params, &self.fragment_parameters) + .await + } + pub async fn derive_encryption_key( &self, kdf_params: &RegistryConfigKdf, @@ -103,6 +112,7 @@ pub enum KdfUsage { pub enum KdfUsageFragmentUsage { EncryptionKey, FileName, + FileTag, } #[derive(Clone, Debug, Serialize, PartialEq, Eq, Zeroize, ZeroizeOnDrop, Arbitrary)] @@ -194,9 +204,21 @@ impl DerefMut for FragmentEncryptionKeyBytes { } } -pub struct HashedFragmentKey { - pub file_name: FragmentFileNameBytes, - pub encryption_key: FragmentEncryptionKeyBytes, +#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] +pub struct FragmentFileTagBytes(pub BytesOrHexString>); + +impl Deref for FragmentFileTagBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for FragmentFileTagBytes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } /// A CBOR map of metadata. @@ -204,7 +226,28 @@ pub struct HashedFragmentKey { pub struct SegmentMetadata(pub cbor::Map); impl SegmentMetadata { - pub const KEY_LAST: u64 = 1; + pub const KEY_FILE_TAG: u64 = 1; + pub const KEY_LAST: u64 = 2; + + pub fn get_file_tag(&self) -> Result { + let tag_bytes = self + .get(Self::KEY_FILE_TAG) + .ok_or(Error::MalformedSegment)? + .as_bytes() + .ok_or(Error::MalformedSegment)? + .clone() + .into(); + + Ok(FragmentFileTagBytes(BytesOrHexString(tag_bytes))) + } + + pub fn insert_file_tag(&mut self, tag: FragmentFileTagBytes) -> Option { + self.insert(Self::KEY_FILE_TAG, cbor::Value::Bytes(tag.as_ref().into())) + } + + pub fn shift_remove_file_tag(&mut self) -> Option { + self.shift_remove(Self::KEY_FILE_TAG) + } pub fn get_last(&self) -> Result { let Some(value) = self.get(Self::KEY_LAST) else { @@ -225,11 +268,16 @@ impl SegmentMetadata { } prop_compose! { - fn arb_segment_metadata()( + pub fn arb_segment_metadata( + kdf: &RegistryConfigKdf, + )( + file_tag in vec(any::(), kdf.get_file_tag_length_in_bytes() as usize), last in any::(), ) -> SegmentMetadata { let mut result = SegmentMetadata::default(); + result.insert_file_tag(FragmentFileTagBytes(BytesOrHexString(file_tag.into()))); + if last { result.insert_last(); } @@ -238,27 +286,31 @@ prop_compose! { } } -impl Arbitrary for SegmentMetadata { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - arb_segment_metadata().boxed() - } -} - pub type SegmentData = BytesOrAscii, 32>; #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub(crate) struct SegmentSerde<'a>(Cow<'a, SegmentMetadata>, Cow<'a, SegmentData>); /// A record, loaded from its CBOR representation. -#[derive(Arbitrary, Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct Segment { pub metadata: SegmentMetadata, pub data: SegmentData, } +prop_compose! { + pub fn arb_segment( + kdf: &RegistryConfigKdf, + )( + metadata in arb_segment_metadata(kdf), + data in any::(), + ) -> Segment { + Segment { + metadata, data + } + } +} + impl Serialize for Segment { fn serialize(&self, serializer: S) -> std::result::Result where diff --git a/src/registry.rs b/src/registry.rs index 8d3a25c..4aed115 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -212,6 +212,7 @@ define_config_params! { output_length_in_bytes: u64 = 32 >= 16, succession_nonce_length_in_bytes: u64 = 32 >= 16, file_name_length_in_bytes: u64 = 8 >= 1, + file_tag_length_in_bytes: u64 = 32 >= 16, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -244,6 +245,7 @@ impl Arbitrary for RegistryConfigHash { pub struct RegistryConfigKdfBuilder { algorithm: Option, file_name_length_in_bytes: ::T, + file_tag_length_in_bytes: ::T, succession_nonce_length_in_bytes: ::T, } @@ -252,6 +254,7 @@ impl Default for RegistryConfigKdfBuilder { Self { algorithm: Default::default(), file_name_length_in_bytes: ::RECOMMENDED, + file_tag_length_in_bytes: ::RECOMMENDED, succession_nonce_length_in_bytes: ::RECOMMENDED, } @@ -272,6 +275,14 @@ impl RegistryConfigKdfBuilder { self } + pub fn with_file_tag_length_in_bytes( + &mut self, + file_tag_length_in_bytes: ::T, + ) -> &mut Self { + self.file_tag_length_in_bytes = file_tag_length_in_bytes; + self + } + pub fn with_succession_nonce_length_in_bytes( &mut self, succession_nonce_length_in_bytes: ::T, @@ -287,6 +298,8 @@ impl RegistryConfigKdfBuilder { let algorithm = self.algorithm.unwrap_builder_parameter("algorithm")?; let file_name_length_in_bytes = ConfigParam::try_from(self.file_name_length_in_bytes) .map_err(InvalidParameterError::from)?; + let file_tag_length_in_bytes = ConfigParam::try_from(self.file_tag_length_in_bytes) + .map_err(InvalidParameterError::from)?; let succession_nonce_length_in_bytes = ConfigParam::try_from(self.succession_nonce_length_in_bytes) .map_err(InvalidParameterError::from)?; @@ -299,6 +312,7 @@ impl RegistryConfigKdfBuilder { }, algorithm, file_name_length_in_bytes, + file_tag_length_in_bytes, succession_nonce_length_in_bytes, }) } @@ -310,6 +324,7 @@ impl RegistryConfigKdfBuilder {} pub struct RegistryConfigKdf { algorithm: KdfAlgorithm, file_name_length_in_bytes: ConfigParam, + file_tag_length_in_bytes: ConfigParam, succession_nonce_length_in_bytes: ConfigParam, root_predecessor_nonce: SuccessionNonce, } @@ -327,6 +342,10 @@ impl RegistryConfigKdf { *self.file_name_length_in_bytes } + pub fn get_file_tag_length_in_bytes(&self) -> ::T { + *self.file_tag_length_in_bytes + } + pub fn get_succession_nonce_length_in_bytes( &self, ) -> ::T { @@ -343,15 +362,18 @@ prop_compose! { algorithm in any::(), succession_nonce_length_in_bytes in arb_config_param(128), file_name_length_in_bytes in arb_config_param(64), + file_tag_length_in_bytes in arb_config_param(64), )( root_predecessor_nonce in vec(any::(), (*succession_nonce_length_in_bytes as usize)..=128), algorithm in Just(algorithm), succession_nonce_length_in_bytes in Just(succession_nonce_length_in_bytes), file_name_length_in_bytes in Just(file_name_length_in_bytes), + file_tag_length_in_bytes in Just(file_tag_length_in_bytes), ) -> RegistryConfigKdf { RegistryConfigKdf { algorithm, file_name_length_in_bytes, + file_tag_length_in_bytes, succession_nonce_length_in_bytes, root_predecessor_nonce: SuccessionNonce(Secret(BytesOrHexString(root_predecessor_nonce.into()))), } diff --git a/tests/cddl.rs b/tests/cddl.rs index 8f71fe2..fd88b55 100644 --- a/tests/cddl.rs +++ b/tests/cddl.rs @@ -10,15 +10,17 @@ use itertools::Itertools; use rrr::{ cbor::{SerializeExt, Value, ValueExt}, crypto::encryption::EncryptionAlgorithm, - record::segment::{ - FragmentKey, KdfUsage, KdfUsageFragmentParameters, RecordParameters, Segment, + record::{ + segment::{ + FragmentKey, KdfUsage, KdfUsageFragmentParameters, RecordParameters, + }, + Record, RecordKey, }, - record::{Record, RecordKey}, }; use std::{fs, path::Path}; use test_strategy::proptest; use tracing_test::traced_test; -use util::RegistryConfigWithSigningKeys; +use util::{RegistryConfigWithSigningKeys, RegistryConfigWithSigningKeysAndSegment}; mod util; @@ -88,21 +90,25 @@ fn verify_cddl_registry(config_with_signing_keys: RegistryConfigWithSigningKeys) #[proptest] #[traced_test] -fn verify_cddl_segment(segment: Segment) { +fn verify_cddl_segment(args: RegistryConfigWithSigningKeysAndSegment) { + let RegistryConfigWithSigningKeysAndSegment { segment, .. } = args; validate_with_cddl("cddl/segment.cddl", Value::serialized(&segment).unwrap()); } #[proptest(async = "tokio")] #[traced_test] async fn verify_cddl_fragment( - config_with_signing_keys: RegistryConfigWithSigningKeys, - segment: Segment, + args: RegistryConfigWithSigningKeysAndSegment, encryption_algorithm: Option, ) { - let RegistryConfigWithSigningKeys { - config, - signing_keys, - } = config_with_signing_keys; + let RegistryConfigWithSigningKeysAndSegment { + registry_config_with_signing_keys: + RegistryConfigWithSigningKeys { + config, + signing_keys, + }, + segment, + } = args; let record_key = RecordKey { record_name: Default::default(), predecessor_nonce: config.kdf.get_root_record_predecessor_nonce().clone(), diff --git a/tests/test.proptest-regressions b/tests/test.proptest-regressions index 2104f92..24a8ab4 100644 --- a/tests/test.proptest-regressions +++ b/tests/test.proptest-regressions @@ -8,3 +8,4 @@ cc d98fba4b2651555a12de3ec0808ad6225a505fcd47a1799b320559b9a38fc240 # shrinks to cc 99ac138e132ce6c5c6bf0e1190d4ce41a660217e2336fc0d0ded66e3d97fd2b0 # shrinks to input = _PropRegistryArgs { config_with_signing_keys: RegistryConfigWithSigningKeys { signing_keys: [], config: RegistryConfig { hash: RegistryConfigHash { algorithm: Argon2(Argon2Params { variant: Argon2d, m_cost: 416, t_cost: 36, p_cost: 52, pepper: Secret }), output_length_in_bytes: ConfigParam(49) }, kdf: RegistryConfigKdf { algorithm: Hkdf(HkdfParams { prf: Sha256 }), succession_nonce_length_in_bytes: ConfigParam(71), file_name_length_in_bytes: ConfigParam(1) }, verifying_keys: [] } }, record_test_tree: RecordTestNode { record_name: b"\x0c\xd1\xab\x82\xbd&\x852\xab\x8a7\xb69\xf4\xcd\xc5\x04\xa1\'\x97\xbe\x92\x135c)0\xd1x\xf2\x80%<\xfb\"$*\xde\x93", occurrences: 2, successors: [RecordTestNode { record_name: b"\xd8\x08\x9b\xfa\xed\xe0W\xb4\x91\x00\x11\xda\xc0\x19\x84\rb\x97G\xdf\x12\xb9:\xa9\xc8\t\xb9F\xc3\xf0\xbb\xa6\x84\xa9\xce\x06F\xa1b\x99kj\x86\xf3\x10\n\xa88\x17=\xb6r3\xa7@\xf4\x95\xac\xa0$J\xaf,t\xf4MC\xab\xdf\x12\x08\x06\'\x0f\"\xebu\xbaC\xbd<\xcb\x023\xe0p\xf2\xce\xef\xfa\xfejBM\x06;\x84\xbbz\xa0\xd0\xe5\x0c\xd2J^\"f\xcb\xf9\xdc\xabei\xc1\xa3\x1bw\xcc\xcd\x9bM\x9evq=\xf5\x97\x85\xdf\x91\xbb3\x01\xc3\xfev?MB\x17\xa2a\xf0\x96v", occurrences: 3, successors: [RecordTestNode { record_name: b"\xa8\x9b\x13\xabbn\x8d\xc7F\xe1e)\xf3\xfa\x01\x9d\x10\xd9\xda\xf2\xb8}@\xd3\xac\xd2\xea\xa9\xd4a\x86\xd2E=(21) }, kdf: RegistryConfigKdf { algorithm: Hkdf(HkdfParams { prf: Sha512 }), file_name_length_in_bytes: ConfigParam(1), succession_nonce_length_in_bytes: ConfigParam(44), root_predecessor_nonce: SuccessionNonce(Secret) }, verifying_keys: [] } }, record_test_tree: RecordTestNode { record_name: b"\x82d\xe9M\x11\x97p\xc6\x8fo\xa4\x1bh\x87H\xa8\x8bWI\xe3E\xbci\xc0\x9e2j\xff[\xbe,D\xed\x81\x8e\xd4D\xb8\xe1PWF\xaa\xcd\x1b}$\x18+]&\x86\xa6?,\xc9\x00\xb9\x99[\x95V\'\xf7\xbe\xc2\xca\xcd\xdb\x1d\xe3\xc3k\x87\x8b\x0c\x96\x03\xfd\x9b\x91Cv\xe1\xc4\x89\xd7M\xd6\xd1T\xe4\x15b\xb1\'\xe13\x91N\xe0\xee\x01\x02\']#e\xc9&\xca\x1fVE\xe7\x8a\xf1$\xcb\x9f\xd7\x00\xc6\xd03\xec\xe4#\xc0\xb2\x83-\xaeE\xe7@\n3Z", version_contents: [[2, 126, 187, 150, 67, 179, 28, 246, 75, 98, 35, 123, 17, 37, 154, 72, 41, 42, 51, 172, 213, 41, 207, 208, 88, 155, 74, 20, 255, 218, 247, 144, 49, 34, 32, 31, 188, 127, 96, 210, 65, 223, 245, 191, 32, 159, 159, 231, 29, 132, 235, 253, 33, 68, 245, 206, 95, 207, 226, 216, 236, 5, 74, 32, 2, 51, 146, 151, 73, 48, 77, 230, 6, 139, 200, 133, 173, 66, 36, 118, 216, 85, 4, 58, 98, 83, 4, 246, 13, 134, 146, 48, 198, 110, 133, 24, 106, 148, 239, 151, 49, 225, 104, 117, 98, 220, 230, 50, 180, 201, 219, 154, 36], [219, 216, 22, 207, 144, 181, 159, 56, 86, 193, 201, 53, 77, 101, 33, 203, 181, 104, 143, 188, 187, 31, 230, 179, 102, 126, 35, 145, 222, 117, 154, 29, 45, 92, 236, 71, 119, 48, 232, 200, 147, 58, 84, 241, 143, 1, 37, 217, 67, 21, 207, 33, 243, 150, 146, 49, 17, 5, 9, 49, 75, 10, 13, 38, 70, 160, 141, 239, 31, 211, 255, 131, 116, 114, 38, 1, 102, 189, 66, 161, 159, 139, 185, 252, 70, 30, 232, 221, 145, 97, 72, 136, 87, 91, 125, 216, 192, 250, 173, 217, 31, 117, 15, 214, 236, 46, 70, 228, 202, 231, 20, 187, 55, 215, 149, 31, 122, 209], [235, 155, 142, 73, 188, 152, 161, 127, 20, 86, 220, 9, 45, 194, 66, 181, 170, 236, 72, 208, 72, 71, 180, 214, 177, 10, 119, 232, 212, 40, 40, 240, 53, 197, 177, 232]], successors: [] }, encryption_algorithm: None } cc 63b3c200d4b275f70fbbe83ce8e5f7597ec5ded39554b84e93b70f8f1efac812 # shrinks to input = _PropRegistryArgs { config_with_signing_keys: RegistryConfigWithSigningKeys { signing_keys: [], config: RegistryConfig { hash: RegistryConfigHash { algorithm: Argon2(Argon2Params { variant: Argon2d, m_cost: 72, t_cost: 11, p_cost: 9, pepper: Some(Secret) }), output_length_in_bytes: ConfigParam(71) }, kdf: RegistryConfigKdf { algorithm: Hkdf(HkdfParams { prf: Sha256 }), file_name_length_in_bytes: ConfigParam(1), succession_nonce_length_in_bytes: ConfigParam(110), root_predecessor_nonce: SuccessionNonce(Secret) }, verifying_keys: [] } }, record_test_tree: RecordTestNode { record_name: b"\xa1\xe2\x95\xf7(\x82X\xd1L\xd7AY\x97\xb64s\x9c\xef\xb4T\xa6wtT\xda\xe6\x10\x14\xa5z&\x11\xf0\xcb\xbc\xba\x17>_\xebf\xda\xde3\xa0\xe2\xf6\xca\x00\xa8~\xfd\xe5\xd4\x9d\xcel\x1c|y\x86\xcf\x85R\x993%\x10~\t\xa4\xdc\x0f\x12\xca\xb4ZZ\x0c-\xae\xcb\x8f=\n\xcc+\xa9\x07\xfby)\xaf\xdaY\xea\xd7\xdc\x82\xd5\xf1\xd7\xc1\x80\x89I\x12E`\xc0t\xfb\xdc\x13\xfad\xfb\xe0#\xb3 \xae\x96\x81\x12x\x9d\x1b\x8a\xefv\xad\"\xc7\xe3\x8dig\xea\xd8\xf5\x95\xfa\x81\xab\r\x83\xab\xe7[\xeb/5\xf6x\xa8W\x9d7]\xcc\xc8\xcd?`\n{Y\xc8(\xe5B\xce\xc6\xbd\xff:\"\x9f", record_versions: [Record { metadata: RecordMetadata(Map({})), data: b"\x9d\xf9\xd3kN\x97\xc2m\xffSQ\x8f\x02<\xdakI\x0c\x065(\n\x0c\xd2#\xbb\x81\xce>8\x81\x8c"… }], successors: [RecordTestNode { record_name: b"\xd2\xb1f\x17\x92k\x05\x87b:\xb7Z\x17\xf0\x017$\x12~\x07g\x80;\x08@IJ\xd7c3\xc1{2@G\xbc9\xb8\xa5!\xdd\xd0\x0f\xb8B7\x87)\xe2N\xba\x95\x12\x18\xa66(\xf7DtZ\x89\x89\xa6\x08\xe3\xbf\xf1\xfc\xe5\x1b\x03\xe3\xf6\xff\xbd\x04\x0fZ\xb8MO*\x8f\x9dS\xdf\xc04?\xfc\xc1\xd4", record_versions: [Record { metadata: RecordMetadata(Map({})), data: b"N\xec67 \xc1k\xc3\xd2\x05W\x10\xef\xf7\xf5\'\xf6\x9dg\x94\x94R\x91W\xb8\xfb\x8a\xaa\xe3K\xf99\xbf\x0b\x9e\x94:w\x87e\xf7\x1b", record_versions: [Record { metadata: RecordMetadata(Map({})), data: b"Hv\xff\x95\xeb\xe4\x08\x1a\xcd\xde\xc3\x8d\xfc0jc\xae4BiK\xe9_\x8e\xeb\x81" }, Record { metadata: RecordMetadata(Map({})), data: b"\x98c\xaf\xb7M\x07\xa8\x9a\x92O\xe2\x9a\x91:31\xbc\x0bD\x9f\xc9\x82" }, Record { metadata: RecordMetadata(Map({})), data: b"\xf3\xd0\x9e\x04\xea\xf4!j\xc9\x97\x9aq\xc7&\x933\x89\xe4\xbe\x9b\xf6\xac\xaf\x04$]W&s\x1e\x84T"… }], successors: [] }] }, encryption_algorithm: None } +cc 9b36a2125fc7388712d0dc4b37d2fe70f3e029bf14dc5bcd1d6ef43636026f04 # shrinks to input = _PropRegistryArgs { config_with_signing_keys: RegistryConfigWithSigningKeys { signing_keys: [], config: RegistryConfig { hash: RegistryConfigHash { algorithm: Argon2(Argon2Params { variant: Argon2d, m_cost: 456, t_cost: 20, p_cost: 57, pepper: Some(Secret) }), output_length_in_bytes: ConfigParam(38) }, kdf: RegistryConfigKdf { algorithm: Hkdf(HkdfParams { prf: Sha512 }), file_name_length_in_bytes: ConfigParam(1), file_tag_length_in_bytes: ConfigParam(41), succession_nonce_length_in_bytes: ConfigParam(56), root_predecessor_nonce: SuccessionNonce(Secret) }, verifying_keys: [] } }, record_test_tree: RecordTestNode { record_name: b"].rV\x055\x88Z\x9e\xb1\xc4\r\xa8+\xd6\xe4\xd2O+L`\xc8D^\xc9Py]\xda:\xb5L\x02?\x93\xa9#o8\xb9\xea\xb0\xeb\x99\xf0\x8f\xe8\xdcto\xda\xfb\xed\xe9\x14\xce\xc23\xd7\xea@!@\x00\x89\x9f\x03\xf9\x02\xcb\xe7\xe2\xaa\xb0\xa2\xe8\xda`\x19", record_versions: [Record { metadata: RecordMetadata(Map({})), data: b"L\xc7\xc4+\x15\xcf\x81\xfb\xf6n\xe9\x19\xf8;.\xb7m[\xbb\x92\x92V\x18\xfc\x1ct\xe8\xfeR\xc5?@"… }], successors: [RecordTestNode { record_name: b"\xcd\x0c0\xb7\x95\xf8\xef\xaf\xd3g\xba:\x01\x88\x91\x8cb\tG\xee$\x07\xa4=\x1c\x12\xaa", record_versions: [Record { metadata: RecordMetadata(Map({})), data: b"\xf5X\xbe\xba\x12`\xe2\xb9, pub config: RegistryConfig, @@ -39,3 +40,33 @@ impl Arbitrary for RegistryConfigWithSigningKeys { arb_registry_config_with_signing_keys().boxed() } } + +#[allow(unused)] +#[derive(Debug)] +pub struct RegistryConfigWithSigningKeysAndSegment { + pub registry_config_with_signing_keys: RegistryConfigWithSigningKeys, + pub segment: Segment, +} + +prop_compose! { + fn arb_registry_config_with_signing_keys_and_segment()( + registry_config_with_signing_keys in any::(), + )( + segment in arb_segment(®istry_config_with_signing_keys.config.kdf), + registry_config_with_signing_keys in Just(registry_config_with_signing_keys), + ) -> RegistryConfigWithSigningKeysAndSegment { + RegistryConfigWithSigningKeysAndSegment { + registry_config_with_signing_keys, + segment, + } + } +} + +impl Arbitrary for RegistryConfigWithSigningKeysAndSegment { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + arb_registry_config_with_signing_keys_and_segment().boxed() + } +}