From 6f51efe03c406d2361b755aa79e17a164b202929 Mon Sep 17 00:00:00 2001 From: Patrick Garvey Date: Fri, 29 Mar 2024 12:46:38 +0100 Subject: [PATCH] Add security module (optional feature): With Block Integrity Block defined in RFC 9172 and a test case defined in RFC 9173 --- Cargo.toml | 6 + src/lib.rs | 2 + src/security.rs | 692 ++++++++++++++++++++++++++++++++++++++++ tests/security_tests.rs | 195 +++++++++++ 4 files changed, 895 insertions(+) create mode 100644 src/security.rs create mode 100644 tests/security_tests.rs diff --git a/Cargo.toml b/Cargo.toml index a0e0114..53ca541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ harness = false default = ["binary-build"] binary-build = ["instant"] benchmark-helpers = ["instant"] +bpsec = ["dep:sha2", "dep:hmac", "dep:hex-literal"] [dependencies] humantime = "2.1.0" @@ -45,6 +46,11 @@ crc = "3.0.1" thiserror = "1.0.23" bitflags = "2.2.1" +# bpsec dependencies +sha2 = { version ="0.10.6", optional = true } +hmac = { version ="0.12.1", optional = true } +hex-literal = { version ="0.3.4", optional = true } + # non wasm config [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/lib.rs b/src/lib.rs index febfedb..93696a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,8 @@ pub mod ffi; pub mod flags; pub mod helpers; pub mod primary; +#[cfg(feature = "bpsec")] +pub mod security; #[cfg(target_arch = "wasm32")] pub mod wasm; diff --git a/src/security.rs b/src/security.rs new file mode 100644 index 0000000..cb6dfc9 --- /dev/null +++ b/src/security.rs @@ -0,0 +1,692 @@ +//use std::convert::TryInto; +use std::fmt; + +use super::bundle::ByteBuffer; +use super::flags::BlockControlFlags; +//use super::flags::BlockControlFlagsType; +use super::primary::PrimaryBlock; +use super::*; + +use bitflags::bitflags; +use thiserror::Error; + +// use aes_gcm::aead::{ +// generic_array::{typenum, GenericArray}, +// Aead, KeyInit, Payload, +// }; +// use aes_gcm::aes::{Aes128, Aes256}; +// use aes_gcm::AesGcm; + +// use aes_kw::Kek; + + +use sha2::{Sha256, Sha384, Sha512}; +use hmac::{Hmac, Mac}; + +use serde::de::{SeqAccess, Visitor}; +use serde::ser::{SerializeSeq, Serializer}; +use serde::{de, Deserialize, Deserializer, Serialize}; + + + +// https://www.rfc-editor.org/rfc/rfc9172.html#BlockType +pub const INTEGRITY_BLOCK: CanonicalBlockType = 11; +pub const CONFIDENTIALITY_BLOCK: CanonicalBlockType = 12; + +// SHA Variant +// https://www.rfc-editor.org/rfc/rfc9173.html#name-sha-variant +pub type ShaVariantType = u16; +pub const HMAC_SHA_256: ShaVariantType = 5; +pub const HMAC_SHA_384: ShaVariantType = 6; // default +pub const HMAC_SHA_512: ShaVariantType = 7; + +// Security Context Id +// https://www.rfc-editor.org/rfc/rfc9173.html#name-security-context-identifier +// https://www.rfc-editor.org/rfc/rfc9172.html#SecCtx +pub type SecurityContextId = i16; +pub const BIB_HMAC_SHA2_ID: SecurityContextId = 1; // BIB-HMAC-SHA2 +pub const BCB_AES_GCM_ID: SecurityContextId = 2; // BCB-AES-GCM + +// Security Context Flags +// +pub type SecurityContextFlag = u8; +pub const SEC_CONTEXT_ABSENT: SecurityContextFlag = 0; // Security context parameters should be empty +pub const SEC_CONTEXT_PRESENT: SecurityContextFlag = 1; // Security context parameters are defined + + +// AES Variant +// https://www.rfc-editor.org/rfc/rfc9173.html#name-aes-gcm +pub type AesVariantType = u16; +pub const AES_128_GCM: AesVariantType = 1; +pub const AES_256_GCM: AesVariantType = 3; // default + +pub type SecurityBlockHeader = (CanonicalBlockType, u64, flags::BlockControlFlagsType); + +/// IntegrityProtectedPlaintext Builder. See IntegrityProtectedPlaintext Doc for usage. +#[derive(Debug, Clone, PartialEq)] +pub struct IpptBuilder { + scope_flags: IntegrityScopeFlagsType, + // canonical forms + primary_block: Option, + security_header: Option, + security_target_contents: Vec, +} + +impl IpptBuilder { + pub fn new() -> IpptBuilder { + IpptBuilder { + scope_flags: 0x0007, // default value + primary_block: None, + security_header: None, + security_target_contents: Vec::new(), + } + } + pub fn scope_flags(mut self, scope_flags: IntegrityScopeFlagsType) -> Self { + self.scope_flags = scope_flags; + self + } + pub fn primary_block(mut self, primary_block: PrimaryBlock) -> Self { + self.primary_block = Some(primary_block); + self + } + pub fn security_header(mut self, security_header: SecurityBlockHeader) -> Self { + self.security_header = Some(security_header); + self + } + pub fn security_target_contents(mut self, security_target_contents: Vec) -> Self { + self.security_target_contents = security_target_contents; + self + } + pub fn build(self) -> IntegrityProtectedPlaintext { + + IntegrityProtectedPlaintext { + scope_flags: self.scope_flags, + primary_block: self.primary_block, + security_header: self.security_header, + security_target_contents: self.security_target_contents, + } + } +} + + +/// Structure to hold the Integrity Protected Plaintext. The content +/// of the IPPT is constructed as the concatenation of information +/// whose integrity is being preserved. Can optionally protect the integrity of +/// the primary block, the payload block header, the security block header. +/// The payload of the security target itself is always protected. +/// +/// To function correctly the scope_flags have to be set accordingly. +/// The default value is 0x0007, which means all flags are set. The +/// other options for the scope_flags are:
+/// Bit 0 (the low-order bit, 0x0001): Include primary block flag
+/// Bit 1 (0x0002): Include target header flag
+/// Bit 2 (0x0004): Include security header flag
+/// Bits 3-15: Unassigned. Do NOT set.
+/// +/// # Fields +/// * `scope_flags` - Bit field +/// * `primary_block` - A reference to the primary block +/// * `security_header` - A tuple with the values of the block_type, +/// the block_number and the block_control_flags +/// * `security_target_contents` - A Vector with the result values +/// +/// # RFC references +/// [IPPT](https://www.rfc-editor.org/rfc/rfc9173.html#name-scope) +/// +/// # Results +/// +/// # Example +/// TODO: example +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct IntegrityProtectedPlaintext { + scope_flags: IntegrityScopeFlagsType, + // canonical forms + primary_block: Option, + security_header: Option, + security_target_contents: Vec, +} + +impl IntegrityProtectedPlaintext { + pub fn new() -> IntegrityProtectedPlaintext { + IntegrityProtectedPlaintext { + scope_flags: 0x0007, // default value + primary_block: None, + security_header: None, + security_target_contents: Vec::new(), + } + } + + pub fn create(&mut self, payload_block: &CanonicalBlock) -> ByteBuffer { + // If header data is not none and corresponding flag is set, include in MAC + let mut optional_ippt_data = Vec::::new(); + + if self.scope_flags.contains(IntegrityScopeFlags::INTEGRITY_PRIMARY_HEADER) { + if let Some(pb) = &self.primary_block { + optional_ippt_data.append( + serde_cbor::to_vec(pb) + .expect("Error creating canonical form of primary block") + .as_mut() + ); + } else { + eprintln!("Primary header flag set but no primary header given!") + } + } + if self.scope_flags.contains(IntegrityScopeFlags::INTEGRITY_PAYLOAD_HEADER) { + optional_ippt_data.append( + self.construct_payload_header(&payload_block) + .expect("Error constructing payload header") + .as_mut() + ); + } + if self.scope_flags.contains(IntegrityScopeFlags::INTEGRITY_SECURITY_HEADER) { + if let Some(sh) = &self.security_header { + optional_ippt_data.append( + self.construct_security_header(sh) + .expect("Error constructing security header") + .as_mut() + ); + } else { + eprintln!("Security header flag set but no security header given!") + } + } + + + + self.security_target_contents = serde_cbor::to_vec(&payload_block.data()).unwrap(); + + // create canonical form of other data + if !matches!(payload_block.data(), CanonicalData::Data(_)) { + let temp_bytes = serde_bytes::Bytes::new(&self.security_target_contents.as_slice()); + self.security_target_contents = serde_cbor::to_vec(&temp_bytes).unwrap(); + } + + let mut ippt = Vec::::new(); + ippt.append(&mut serde_cbor::to_vec(&self.scope_flags).expect("Error creating canonical form of scope flags")); + ippt.append(&mut optional_ippt_data); + ippt.append(&mut self.security_target_contents); + println!("ippt hex {:?}", hexify(&ippt)); + ippt + } + + fn construct_payload_header(&self, payload_block: &CanonicalBlock) -> Result{ + let mut header = Vec::::new(); + header.append(&mut serde_cbor::to_vec(&payload_block.block_type)?); + header.append(&mut serde_cbor::to_vec(&payload_block.block_number)?); + header.append(&mut serde_cbor::to_vec(&payload_block.block_control_flags)?); + //header.append(&mut serde_cbor::to_vec(&payload_block.crc.to_code())?); //TODO: check if not needed? + Ok(header) + } + + fn construct_security_header(&self, security_block_parameter: &SecurityBlockHeader) -> Result{ + let mut header = Vec::::new(); + header.append(&mut serde_cbor::to_vec(&security_block_parameter.0)?); + header.append(&mut serde_cbor::to_vec(&security_block_parameter.1)?); + header.append(&mut serde_cbor::to_vec(&security_block_parameter.2)?); + Ok(header) + } + +} + + +impl Default for IntegrityProtectedPlaintext { + fn default() -> Self { + IntegrityProtectedPlaintext::new() + } +} + +impl Default for IpptBuilder { + fn default() -> Self { + IpptBuilder::new() + } +} + + + + +// Integrity Scope Flags +// https://www.rfc-editor.org/rfc/rfc9173.html#name-integrity-scope-flags +pub type IntegrityScopeFlagsType = u16; + +bitflags! { + pub struct IntegrityScopeFlags: IntegrityScopeFlagsType { + // Include primary block flag + const INTEGRITY_PRIMARY_HEADER = 0x0001; + // Include target header flag + const INTEGRITY_PAYLOAD_HEADER = 0x0002; + // Include security header flag + const INTEGRITY_SECURITY_HEADER = 0x0004; + } +} + +pub trait ScopeValidation { + fn flags(&self) -> IntegrityScopeFlags; + fn contains(&self, flags: IntegrityScopeFlags) -> bool; +} +impl ScopeValidation for IntegrityScopeFlagsType { + fn flags(&self) -> IntegrityScopeFlags { + IntegrityScopeFlags::from_bits_truncate(*self) + } + fn contains(&self, flags: IntegrityScopeFlags) -> bool + where + Self: Sized, + { + self.flags().contains(flags) + } +} + + +// Abstract Security Block +// https://www.rfc-editor.org/rfc/rfc9172.html#name-abstract-security-block + + +// Security Context Parameters +// https://www.rfc-editor.org/rfc/rfc9173.html#name-enumerations + +// Create Builder? +#[derive(Debug, Clone, PartialEq)] +pub struct BibSecurityContextParameter { + pub sha_variant: Option<(u8, ShaVariantType)>, + pub wrapped_key: Option<(u8, Vec)>, // TODO: Wrapped Key //byte string + pub integrity_scope_flags: Option<(u8, IntegrityScopeFlagsType)>, +} + +impl BibSecurityContextParameter { + pub fn new( + sha_variant: Option<(u8, ShaVariantType)>, + wrapped_key: Option<(u8, Vec)>, + integrity_scope_flags: Option<(u8, IntegrityScopeFlagsType)> + ) -> Self { + Self { + sha_variant, + wrapped_key, + integrity_scope_flags, + } + } +} + +impl Default for BibSecurityContextParameter { + fn default() -> Self { + BibSecurityContextParameter { + sha_variant: Some((1, HMAC_SHA_384)), + wrapped_key: None, + integrity_scope_flags: Some((3, 0x0007)), + } + } +} + +impl Serialize for BibSecurityContextParameter { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut num_elems = 0; + if self.sha_variant.is_some() { num_elems+=1} + if self.wrapped_key.is_some() { num_elems+=1} + if self.integrity_scope_flags.is_some() { num_elems+=1} + + let mut seq = serializer.serialize_seq(Some(num_elems))?; + + if let Some(sv) = &self.sha_variant{ + seq.serialize_element(sv)?; + } + if let Some(wk) = &self.wrapped_key{ + seq.serialize_element(&(wk.0, serde_bytes::Bytes::new(&wk.1)))?; + } + if let Some(isf) = &self.integrity_scope_flags{ + seq.serialize_element(isf)?; + } + + seq.end() + } +} + + +impl<'de> Deserialize<'de> for BibSecurityContextParameter { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BibSecurityContexParameterVisitor; + + impl<'de> Visitor<'de> for BibSecurityContexParameterVisitor { + type Value = BibSecurityContextParameter; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte sequence") + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + let sha_variant = seq.next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + // TODO: deal with wrapped key + let wrapped_key = None; + let integrity_scope_flags = seq.next_element()? + .ok_or_else(|| de::Error::invalid_length(2, &self))?; + + Ok(BibSecurityContextParameter { + sha_variant, + wrapped_key, + integrity_scope_flags, + }) + } + } + + deserializer.deserialize_seq(BibSecurityContexParameterVisitor) + } +} + + + + + + + +#[derive(Error, Debug)] +pub enum IntegrityBlockBuilderError { + #[error("Security Tragets MUST have at least one enrty")] + MissingSecurityTargets, + #[error("Security Context Flag set but no context parameter given")] + FlagSetButNoParameter, +} + + +#[derive(Debug, Clone, PartialEq)] +pub struct IntegrityBlockBuilder { + security_targets: Option>, // array of block numbers TODO: MUST represent the block number of a block that exists in the bundle + security_context_id: SecurityContextId, + security_context_flags: SecurityContextFlag, // bit field + security_source: EndpointID, + security_context_parameters: Option, // optional + security_results: Vec>, // output of security operations +} + +impl IntegrityBlockBuilder { + pub fn new() -> IntegrityBlockBuilder { + IntegrityBlockBuilder { + security_targets: None, + security_context_id: BIB_HMAC_SHA2_ID, + security_context_flags: SEC_CONTEXT_ABSENT, + security_source: EndpointID::none(), + security_context_parameters: None, + security_results: Vec::new(), + } + } + + pub fn security_targets(mut self, security_targets: Vec) -> Self { + self.security_targets = Some(security_targets); + self + } + /* + pub fn security_context_id(mut self, security_context_id: SecurityContextId) -> Self { + self.security_context_id = security_context_id; + self + }*/ + pub fn security_context_flags(mut self, security_context_flags: SecurityContextFlag) -> Self { + self.security_context_flags = security_context_flags; + self + } + pub fn security_source(mut self, security_source: EndpointID) -> Self { + self.security_source = security_source; + self + } + pub fn security_context_parameters(mut self, security_context_parameters: BibSecurityContextParameter) -> Self { + self.security_context_parameters = Some(security_context_parameters); + self + } + pub fn security_results(mut self, security_results: Vec>) -> Self { + self.security_results = security_results; + self + } + pub fn build(self) -> Result { + if let Some(security_targets) = self.security_targets { + if let Some(_security_context_parameters) = self.security_context_parameters.clone() { + Ok(IntegrityBlock { + security_targets, + security_context_id: self.security_context_id, + security_context_flags: self.security_context_flags, + security_source: self.security_source, + security_context_parameters: self.security_context_parameters, + security_results: self.security_results, + }) + } else { + Err(IntegrityBlockBuilderError::FlagSetButNoParameter) + } + } else { + Err(IntegrityBlockBuilderError::MissingSecurityTargets) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct IntegrityBlock { + pub security_targets: Vec, // array of block numbers + pub security_context_id: SecurityContextId, + pub security_context_flags: SecurityContextFlag, // bit field + pub security_source: EndpointID, + pub security_context_parameters: Option, + pub security_results: Vec>, // output of security operations +} + + +impl IntegrityBlock { + pub fn new() -> IntegrityBlock { + IntegrityBlock { + security_targets: Vec::new(), + security_context_id: BIB_HMAC_SHA2_ID, + security_context_flags: SEC_CONTEXT_ABSENT, + security_source: EndpointID::none(), + security_context_parameters: None, + security_results: Vec::new(), + + } + } + fn hmac_sha384_compute(&self, key_bytes: &[u8; 16], payload: &ByteBuffer) -> Vec{ + let mut mac = as Mac>::new_from_slice(key_bytes).expect("HMAC can take key of any size"); + mac.update(&payload); + mac.finalize().into_bytes().to_vec() + + + // for testing only + // + // let result = mac.finalize(); + // let code_bytes = result.into_bytes(); + // println!("hmac: {:x}", code_bytes); + // code_bytes.to_vec() + } + + fn hmac_sha256_compute(&self, key_bytes: &[u8; 16], payload: &ByteBuffer) -> Vec{ + let mut mac = as Mac>::new_from_slice(key_bytes).expect("HMAC can take key of any size"); + //let mut mac = Hmac::::new_from_slice(key_bytes).expect("HMAC can take key of any size"); + + mac.update(&payload); + mac.finalize().into_bytes().to_vec() + } + + fn hmac_sha512_compute(&self, key_bytes: &[u8; 16], payload: &ByteBuffer) -> Vec{ + let mut mac = as Mac>::new_from_slice(key_bytes).expect("HMAC can take key of any size"); + //let mut mac = Hmac::::new_from_slice(key_bytes).expect("HMAC can take key of any size"); + mac.update(&payload); + mac.finalize().into_bytes().to_vec() + } + + + pub fn compute_hmac(&mut self, key_bytes: [u8; 16], ippt_list: Vec<(u64, &ByteBuffer)>){ + + // match ippt_list values to security targets + self.security_results = vec![]; + + for ippt in ippt_list { + if self.security_targets.contains(&ippt.0) { + //let key_bytes = hex!("1a2b1a2b1a2b1a2b1a2b1a2b1a2b1a2b"); + let result_value = match self.security_context_parameters.as_ref().unwrap().sha_variant.unwrap().1{ + 5 => self.hmac_sha256_compute(&key_bytes, ippt.1), + 6 => self.hmac_sha384_compute(&key_bytes, ippt.1), + 7 => self.hmac_sha512_compute(&key_bytes, ippt.1), + _ => panic!("Undefined Sha Variant."), + }; + + + + + // Integrity Security Context BIB-HMAC-SHA2 has only one result field + // that means for every target there will be only one vector entry + // with the result id set to 1 and the result value being the + // outcome of the security operation (-> the MAC) + // result_id always 1 https://www.rfc-editor.org/rfc/rfc9173.html#name-results + // +--------------------------+ +---------------------------+ + // | Target 1 | | Target N | + // +----------+----+----------+ +---------------------------+ + // | Result 1 | | Result M | ... | Result 1 | | Result K | + // +----+-----+ .. +----+-----+ +---+------+ .. +----+------+ + // | Id |Value| | Id |Value| | Id |Value| | Id | Value| + // +----+-----+ +----+-----+ +----+-----+ +----+------+ + + self.security_results.push(vec![(ippt.0, result_value)]); + } else { + eprint!("Security Target and Ippt mismatch. Make sure there is an ippt for each target.") + } + } + + + } + + pub fn to_cbor(&self) -> ByteBuffer { + + let mut cbor_format = Vec::::new(); + + cbor_format.append(&mut serde_cbor::to_vec(&self.security_targets).unwrap()); + cbor_format.append(&mut serde_cbor::to_vec(&self.security_context_id).unwrap()); + cbor_format.append(&mut serde_cbor::to_vec(&self.security_context_flags).unwrap()); + cbor_format.append(&mut serde_cbor::to_vec(&self.security_source).unwrap()); + cbor_format.append(&mut serde_cbor::to_vec(&self.security_context_parameters).unwrap()); + + + // iterate through each target. Create bytes for each signature and onstruct security results format again + let mut res = Vec::new(); + for i in 0..self.security_targets.len(){ + + let next_result = &self.security_results[i]; + let temp_mac = serde_bytes::Bytes::new(&next_result[0].1); + res.push(vec![(&next_result[0].0, temp_mac)]); + + } + cbor_format.append(&mut serde_cbor::to_vec(&res).unwrap()); + cbor_format + + } +} + +impl Default for IntegrityBlock { + fn default() -> Self { + IntegrityBlock::new() + } +} + +impl Default for IntegrityBlockBuilder { + fn default() -> Self { + IntegrityBlockBuilder::new() + } +} + +/* +impl Serialize for IntegrityBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let num_elems = 6; + println!("in serialize"); + + + let mut seq = serializer.serialize_seq(Some(num_elems))?; + seq.serialize_element(&self.security_targets)?; + seq.serialize_element(&self.security_context_id)?; + seq.serialize_element(&self.security_context_flags)?; + seq.serialize_element(&self.security_source)?; + seq.serialize_element(&self.security_context_parameters)?; + //seq.serialize_element(&self.security_results)?; + //let temp = &self.security_results; + let temp_mac = &serde_bytes::Bytes::new(&self.security_results[0][0].1); + + let test = vec![vec![(1, temp_mac)]]; + seq.serialize_element(&test)?; + + seq.end() + } +}*/ + +impl<'de> Deserialize<'de> for IntegrityBlock { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IntegrityBlockVisitor; + + impl<'de> Visitor<'de> for IntegrityBlockVisitor { + type Value = IntegrityBlock; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte sequence") + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + + let security_targets: Vec = seq.next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + + + let security_context_id = seq.next_element()?.unwrap(); + let security_context_flags = seq.next_element()?.unwrap(); + let security_source = seq.next_element()?.unwrap(); + + let security_context_parameters = seq.next_element()?.unwrap(); + + // TODO: deal with multiple targets + let results: Vec>; + results = seq.next_element()?.unwrap(); + let mut security_results = Vec::new(); + + for i in 0..security_targets.len(){ + + let next_result = &results[i]; + let temp_mac = next_result[0].1.to_vec(); + security_results.push(vec![(next_result[0].0, temp_mac)]); + + } + Ok(IntegrityBlock { + security_targets, + security_context_id, + security_context_flags, + security_source, + security_context_parameters, + security_results, + }) + } + } + + deserializer.deserialize_seq(IntegrityBlockVisitor) + } +} + + +pub fn new_integrity_block( + block_number: u64, + bcf: BlockControlFlags, + security_block: ByteBuffer +) -> CanonicalBlock { + CanonicalBlockBuilder::default() + .block_type(INTEGRITY_BLOCK) + .block_number(block_number) + .block_control_flags(bcf.bits()) + .data(CanonicalData::Unknown(security_block)) // The deserializer doesn't know the integrity block type, with no changes made this also has to be encoded as Unknown + .build() + .unwrap() +} + + diff --git a/tests/security_tests.rs b/tests/security_tests.rs new file mode 100644 index 0000000..96b3afc --- /dev/null +++ b/tests/security_tests.rs @@ -0,0 +1,195 @@ +use bp7::flags::*; +use helpers::*; +use bp7::*; +use std::convert::TryFrom; +use std::convert::TryInto; +use bp7::dtntime::DtnTimeHelpers; +use std::time::Duration; +#[cfg(feature = "bpsec")] +use hex_literal::hex; +use bp7::security::*; +//use bp7::security::AES_128_GCM; +use bp7::bundle::Block; + +#[test] +fn security_data_tests() { + let data = CanonicalData::Data(b"bla".to_vec()); + let encoded_data = serde_cbor::to_vec(&data).expect("encoding error"); + let decoded_data: CanonicalData = + serde_cbor::from_slice(&encoded_data).expect("decoding error"); + assert_eq!(data, decoded_data); +} + +fn encode_decode_test_canonical(data: CanonicalBlock) { + let encoded_data = serde_cbor::to_vec(&data).expect("encoding error"); + let decoded_data: CanonicalBlock = + serde_cbor::from_slice(&encoded_data).expect("decoding error"); + assert_eq!(data, decoded_data); + //println!("{:?}", hexify(&encoded_data)); + //println!("{:?}", hexify(&decoded_data)); + + //println!("{:?}", decoded_data.data()); + assert_eq!(decoded_data.data(), data.data()); +} + +#[test] +fn canonical_block_tests() { + let data = bp7::security::new_integrity_block(1, BlockControlFlags::empty(), b"ABCDEFG".to_vec()); + encode_decode_test_canonical(data); +} + +#[test] +fn rfc_example_tests() { + simple_integrity_test(); + + //simple_confidentiality_test(); + + //multiple_sources_test(); + + + + + // Example 4 - Security Blocks with Full Scope + // https://www.rfc-editor.org/rfc/rfc9173.html#name-example-4-security-blocks-w + //println!("Security Blocks with Full Scope"); + // TODO + //full_scope_test(); +} + +/// # Example 1 - Simple Integrity +/// +/// ## Original Bundle +/// +/// ``` +/// Block Block Block +/// in Bundle Type Number +/// +========================================+=======+========+ +/// | Primary Block | N/A | 0 | +/// +----------------------------------------+-------+--------+ +/// | Payload Block | 1 | 1 | +/// +----------------------------------------+-------+--------+ +/// ``` +/// +/// ## Resulting Bundle +/// +/// ``` +/// +========================================+=======+========+ +/// | Primary Block | N/A | 0 | +/// +----------------------------------------+-------+--------+ +/// | Block Integrity Block | 11 | 2 | +/// | OP(bib-integrity, target=1) | | | +/// +----------------------------------------+-------+--------+ +/// | Payload Block | 1 | 1 | +/// +----------------------------------------+-------+--------+ +/// ``` +/// +/// see rfc for more details: +/// https://www.rfc-editor.org/rfc/rfc9173.html#name-example-1-simple-integrity +fn simple_integrity_test(){ + + println!("Simple Integrity Test"); + + + + // Create Original bundle + let dst = eid::EndpointID::with_ipn(1,2).unwrap(); + let src = eid::EndpointID::with_ipn(2,1).unwrap(); + let now = dtntime::CreationTimestamp::with_time_and_seq(0, 40); + + let primary_block = primary::PrimaryBlockBuilder::default() + .destination(dst) + .source(src.clone()) + .report_to(src) + .creation_timestamp(now) + .lifetime(Duration::from_millis(1000000)) + .build() + .unwrap(); + let cbor_primary = serde_cbor::to_vec(&primary_block).unwrap(); + let cbor_primary = hexify(&cbor_primary); + let example_cbor_primary = "88070000820282010282028202018202820201820018281a000f4240"; + assert_eq!(cbor_primary, example_cbor_primary); + + let payload_block = bp7::new_payload_block( + BlockControlFlags::empty(), + b"Ready to generate a 32-byte payload".to_vec()); + let cbor_payload = serde_cbor::to_vec(&payload_block).unwrap(); + let cbor_payload = hexify(&cbor_payload); + let example_cbor_payload = "85010100005823526561647920746f2067656e657261746520612033322d62797465207061796c6f6164"; + assert_eq!(cbor_payload, example_cbor_payload); + + // Create Block Integrity Block + // Two Parts: First create IPPT then ASB + + // First Create Integrity-Protected Plaintext + let sec_block_header: (CanonicalBlockType, u64, bp7::flags::BlockControlFlagsType) = (bp7::security::INTEGRITY_BLOCK, 2, BlockControlFlags::empty().bits()); + + let sec_ctx_para = BibSecurityContextParameter { + sha_variant: Some((1, HMAC_SHA_512)), + wrapped_key: None, + integrity_scope_flags: Some((3, 0x0000)), + }; + + let mut ippt = bp7::security::IpptBuilder::default() + .primary_block(primary_block.clone()) + .security_header(sec_block_header) + .scope_flags(0x0000) + .build(); + let ippt_complete = ippt.create(&payload_block); + let ippt_list = vec![(payload_block.block_number, &ippt_complete)]; + + let cbor_ippt_complete = hexify(&ippt_complete); + let example_ippt_complete = "005823526561647920746f2067656e657261746520612033322d62797465207061796c6f6164"; + //println!("{:?}", cbor_ippt_complete); + + assert_eq!(cbor_ippt_complete, example_ippt_complete); + + + // Second Create Abstract Security Block + let sec_ctx_para = bp7::security::BibSecurityContextParameter::new(Some((1,7)),None,Some((3,0x0000))); + let mut sec_block_payload = bp7::security::IntegrityBlockBuilder::default() + .security_targets(vec![1]) // Payload block + .security_context_flags(1) // Parameters Present + .security_source(EndpointID::with_ipn(2,1).unwrap()) // ipn:2.1 + .security_context_parameters(sec_ctx_para) // 2 Parameters: HMAC 512/512 and No Additional Scope + .build() + .unwrap(); + let key = hex!("1a2b1a2b1a2b1a2b1a2b1a2b1a2b1a2b"); + sec_block_payload.compute_hmac(key, ippt_list); + + // TODO: key mgmt + // used key: 1a2b1a2b1a2b1a2b1a2b1a2b1a2b1a2b + // The Signature + let signature = hexify(&sec_block_payload.security_results[0][0].1); + let example_signature = "3bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e1"; + assert_eq!(signature, example_signature); + //println!("{:?}", hexify(&sec_block_payload.security_results[0][0].1)); + + + // The CBOR encoding of the BIB block-type-specific data field (the abstract security block): + let canonical_payload = sec_block_payload.to_cbor(); + let cbor_canonical_payload = hexify(&canonical_payload); + let example_canonical_payload = "810101018202820201828201078203008181820158403bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e1"; + assert_eq!(cbor_canonical_payload, example_canonical_payload); + + + // The BIB + let block_integrity_block = bp7::security::new_integrity_block(2, BlockControlFlags::empty(), canonical_payload); + let cbor_bib = serde_cbor::to_vec(&block_integrity_block).unwrap(); + let cbor_bib = hexify(&cbor_bib); + let example_bib = "850b0200005856810101018202820201828201078203008181820158403bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e1"; + assert_eq!(cbor_bib, example_bib); + + + // The CBOR encoding of the full output bundle, with the BIB: + let mut b = bundle::BundleBuilder::default() + .primary(primary_block) + .canonicals(vec![payload_block, block_integrity_block]) + .build() + .unwrap(); + b.set_crc(crc::CRC_NO); + b.calculate_crc(); + + let cbor_bundle = hexify(&b.to_cbor()); + let example_bundle = "9f88070000820282010282028202018202820201820018281a000f4240850b0200005856810101018202820201828201078203008181820158403bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e185010100005823526561647920746f2067656e657261746520612033322d62797465207061796c6f6164ff"; + assert_eq!(cbor_bundle, example_bundle); +}