Skip to content

Commit

Permalink
Fragment record association verification with a file_tag
Browse files Browse the repository at this point in the history
  • Loading branch information
Limeth committed Jul 16, 2024
1 parent 00b82ed commit d6a1b4f
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 37 deletions.
2 changes: 1 addition & 1 deletion cddl/kdf_usage.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions cddl/registry.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
4 changes: 3 additions & 1 deletion cddl/segment.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum Error {
CollisionResolutionFailed,
#[error(transparent)]
InvalidParameter(#[from] InvalidParameterError),
#[error("File tag mismatch")]
FileTagMismatch,
}

impl<T> From<LockError<T>> for Error {
Expand Down
18 changes: 18 additions & 0 deletions src/record/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<FragmentFileTagBytes> {
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,
Expand Down
19 changes: 14 additions & 5 deletions src/record/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,26 @@ impl Record {
};
let fragment_file_name =
fragment_key.derive_file_name(&registry.config.kdf).await?;
let fragment_file_tag = fragment_key.derive_file_tag(&registry.config.kdf).await?;
let fragment_path = registry.get_fragment_path(&fragment_file_name);

let segment_result: Result<FragmentReadSuccess> = 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(
&registry.config.verifying_keys,
&registry.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!(
Expand Down Expand Up @@ -273,7 +280,7 @@ impl Record {
if let Error::Io(error) = error {
error.kind() != io::ErrorKind::NotFound
} else {
true
!matches!(error, &Error::FileTagMismatch)
}
});

Expand Down Expand Up @@ -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;
}

Expand All @@ -392,6 +399,8 @@ impl Record {
let segment = Segment {
metadata: {
let mut metadata = SegmentMetadata::default();
metadata
.insert_file_tag(fragment_key.derive_file_tag(&registry.config.kdf).await?);
if segment_index == segment_sizes.len() - 1 {
metadata.insert_last();
}
Expand Down
86 changes: 69 additions & 17 deletions src/record/segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -73,6 +73,15 @@ impl FragmentKey {
.await
}

pub async fn derive_file_tag(
&self,
kdf_params: &RegistryConfigKdf,
) -> Result<FragmentFileTagBytes> {
self.hashed_record_key
.derive_fragment_file_tag(kdf_params, &self.fragment_parameters)
.await
}

pub async fn derive_encryption_key(
&self,
kdf_params: &RegistryConfigKdf,
Expand Down Expand Up @@ -103,6 +112,7 @@ pub enum KdfUsage {
pub enum KdfUsageFragmentUsage {
EncryptionKey,
FileName,
FileTag,
}

#[derive(Clone, Debug, Serialize, PartialEq, Eq, Zeroize, ZeroizeOnDrop, Arbitrary)]
Expand Down Expand Up @@ -194,17 +204,50 @@ 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<Box<[u8]>>);

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.
#[derive(Clone, Debug, Default, Deref, DerefMut, PartialEq, Serialize, Deserialize)]
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<FragmentFileTagBytes> {
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<cbor::Value> {
self.insert(Self::KEY_FILE_TAG, cbor::Value::Bytes(tag.as_ref().into()))
}

pub fn shift_remove_file_tag(&mut self) -> Option<cbor::Value> {
self.shift_remove(Self::KEY_FILE_TAG)
}

pub fn get_last(&self) -> Result<bool> {
let Some(value) = self.get(Self::KEY_LAST) else {
Expand All @@ -225,11 +268,16 @@ impl SegmentMetadata {
}

prop_compose! {
fn arb_segment_metadata()(
pub fn arb_segment_metadata(
kdf: &RegistryConfigKdf,
)(
file_tag in vec(any::<u8>(), kdf.get_file_tag_length_in_bytes() as usize),
last in any::<bool>(),
) -> SegmentMetadata {
let mut result = SegmentMetadata::default();

result.insert_file_tag(FragmentFileTagBytes(BytesOrHexString(file_tag.into())));

if last {
result.insert_last();
}
Expand All @@ -238,27 +286,31 @@ prop_compose! {
}
}

impl Arbitrary for SegmentMetadata {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
arb_segment_metadata().boxed()
}
}

pub type SegmentData = BytesOrAscii<Vec<u8>, 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::<SegmentData>(),
) -> Segment {
Segment {
metadata, data
}
}
}

impl Serialize for Segment {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
Expand Down
22 changes: 22 additions & 0 deletions src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -244,6 +245,7 @@ impl Arbitrary for RegistryConfigHash {
pub struct RegistryConfigKdfBuilder {
algorithm: Option<KdfAlgorithm>,
file_name_length_in_bytes: <FileNameLengthInBytes as ConfigParamTrait>::T,
file_tag_length_in_bytes: <FileTagLengthInBytes as ConfigParamTrait>::T,
succession_nonce_length_in_bytes: <SuccessionNonceLengthInBytes as ConfigParamTrait>::T,
}

Expand All @@ -252,6 +254,7 @@ impl Default for RegistryConfigKdfBuilder {
Self {
algorithm: Default::default(),
file_name_length_in_bytes: <FileNameLengthInBytes as ConfigParamTrait>::RECOMMENDED,
file_tag_length_in_bytes: <FileTagLengthInBytes as ConfigParamTrait>::RECOMMENDED,
succession_nonce_length_in_bytes:
<SuccessionNonceLengthInBytes as ConfigParamTrait>::RECOMMENDED,
}
Expand All @@ -272,6 +275,14 @@ impl RegistryConfigKdfBuilder {
self
}

pub fn with_file_tag_length_in_bytes(
&mut self,
file_tag_length_in_bytes: <FileTagLengthInBytes as ConfigParamTrait>::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: <SuccessionNonceLengthInBytes as ConfigParamTrait>::T,
Expand All @@ -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)?;
Expand All @@ -299,6 +312,7 @@ impl RegistryConfigKdfBuilder {
},
algorithm,
file_name_length_in_bytes,
file_tag_length_in_bytes,
succession_nonce_length_in_bytes,
})
}
Expand All @@ -310,6 +324,7 @@ impl RegistryConfigKdfBuilder {}
pub struct RegistryConfigKdf {
algorithm: KdfAlgorithm,
file_name_length_in_bytes: ConfigParam<FileNameLengthInBytes>,
file_tag_length_in_bytes: ConfigParam<FileTagLengthInBytes>,
succession_nonce_length_in_bytes: ConfigParam<SuccessionNonceLengthInBytes>,
root_predecessor_nonce: SuccessionNonce,
}
Expand All @@ -327,6 +342,10 @@ impl RegistryConfigKdf {
*self.file_name_length_in_bytes
}

pub fn get_file_tag_length_in_bytes(&self) -> <FileTagLengthInBytes as ConfigParamTrait>::T {
*self.file_tag_length_in_bytes
}

pub fn get_succession_nonce_length_in_bytes(
&self,
) -> <SuccessionNonceLengthInBytes as ConfigParamTrait>::T {
Expand All @@ -343,15 +362,18 @@ prop_compose! {
algorithm in any::<KdfAlgorithm>(),
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::<u8>(), (*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()))),
}
Expand Down
Loading

0 comments on commit d6a1b4f

Please sign in to comment.