From 42a7a7266fbdf739572ad2d9dd9ea45ea303b260 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Tue, 5 Aug 2025 14:58:43 +0200 Subject: [PATCH 01/27] fix 1 off-by-2 and 2 off-by-1 errors in comment Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index eff345e9..c94ed7c5 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -7,7 +7,7 @@ const TSP_DEVELOPMENT_VID: u32 = (((21 << 6) | 8) << 6) | 3; // "VID" const TSP_TYPECODE: u32 = (b'X' - b'A') as u32; const ED25519_SIGNATURE: u32 = (b'B' - b'A') as u32; #[cfg(feature = "pq")] -const ML_DSA_65_SIGNATURE: u32 = 0o170413u32; // QDM +const ML_DSA_65_SIGNATURE: u32 = 0o170413u32; // PEL #[allow(clippy::eq_op)] const TSP_NONCE: u32 = (b'A' - b'A') as u32; const TSP_SHA256: u32 = (b'I' - b'A') as u32; From 03c8661b2d0e7c4d516ed6661fc0b0d4532d6cd3 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Tue, 5 Aug 2025 14:59:12 +0200 Subject: [PATCH 02/27] introduce a cesr const helper function Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 48 +++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index c94ed7c5..e16a4d38 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -1,24 +1,46 @@ +/// A function for more easy encoding of CESR constants +const fn cesr(x: &str) -> u32 { + let x = x.as_bytes(); + let mut acc = 0; + let mut i = 0; + while i < x.len() { + let ch = x[i]; + acc = acc << 6 + | match ch { + ch if ch.is_ascii_uppercase() => ch - b'A', + ch if ch.is_ascii_lowercase() => ch - b'a' + 26, + ch if ch.is_ascii_digit() => ch - b'0' + 52, + b'-' => 62, + b'_' => 63, + _ => panic!("not a base64url character"), + } as u32; + i += 1; + } + + acc +} + /// Constants that determine the specific CESR types for "variable length data" -const TSP_PLAINTEXT: u32 = (b'B' - b'A') as u32; -const TSP_CIPHERTEXT: u32 = (b'C' - b'A') as u32; -const TSP_DEVELOPMENT_VID: u32 = (((21 << 6) | 8) << 6) | 3; // "VID" +const TSP_PLAINTEXT: u32 = cesr("B"); +const TSP_CIPHERTEXT: u32 = cesr("C"); +const TSP_DEVELOPMENT_VID: u32 = cesr("VID"); /// Constants that determine the specific CESR types for "fixed length data" -const TSP_TYPECODE: u32 = (b'X' - b'A') as u32; -const ED25519_SIGNATURE: u32 = (b'B' - b'A') as u32; +const TSP_TYPECODE: u32 = cesr("X"); +const ED25519_SIGNATURE: u32 = cesr("B"); #[cfg(feature = "pq")] -const ML_DSA_65_SIGNATURE: u32 = 0o170413u32; // PEL +const ML_DSA_65_SIGNATURE: u32 = cesr("QDM"); #[allow(clippy::eq_op)] -const TSP_NONCE: u32 = (b'A' - b'A') as u32; -const TSP_SHA256: u32 = (b'I' - b'A') as u32; +const TSP_NONCE: u32 = cesr("A"); +const TSP_SHA256: u32 = cesr("I"); #[allow(dead_code)] -const TSP_BLAKE2B256: u32 = (b'F' - b'A') as u32; +const TSP_BLAKE2B256: u32 = cesr("F"); /// Constants that determine the specific CESR types for the framing codes -const TSP_ETS_WRAPPER: u16 = (b'E' - b'A') as u16; -const TSP_S_WRAPPER: u16 = (b'S' - b'A') as u16; -const TSP_HOP_LIST: u16 = (b'I' - b'A') as u16; -const TSP_PAYLOAD: u16 = (b'Z' - b'A') as u16; +const TSP_ETS_WRAPPER: u16 = cesr("E") as u16; +const TSP_S_WRAPPER: u16 = cesr("S") as u16; +const TSP_HOP_LIST: u16 = cesr("I") as u16; +const TSP_PAYLOAD: u16 = cesr("Z") as u16; /// Constants to encode message types mod msgtype { From ba9952c793c6561a754ef5a82d0f46bcfd60a398 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Tue, 5 Aug 2025 17:24:33 +0200 Subject: [PATCH 03/27] add version encoding/decoding to envelope Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 57 ++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index e16a4d38..a58fd2b2 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -20,6 +20,9 @@ const fn cesr(x: &str) -> u32 { acc } +/// The TSP version supported by this spec +const TSP_VERSION: (u16, u8, u8) = (0, 0, 1); + /// Constants that determine the specific CESR types for "variable length data" const TSP_PLAINTEXT: u32 = cesr("B"); const TSP_CIPHERTEXT: u32 = cesr("C"); @@ -562,12 +565,51 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod } } +const fn encoded_version() -> u16 { + (TSP_VERSION.1 as u16) << 6 | (TSP_VERSION.2 as u16) +} + +/// Y is the header. +/// Note that 'YTSP' is not conforming to the normal rules of a fixed-size CESR code, so we +/// force hardcode it as a Base64 quadlet/triplet. +const Y_HEADER: [u8; 3] = { + let cesr = u32::to_be_bytes(cesr("YTSP")); + [cesr[1], cesr[2], cesr[3]] +}; + +/// Encode a TSP version marker +pub fn encode_version(output: &mut impl for<'b> Extend<&'b u8>) { + output.extend(&Y_HEADER); + encode_count(TSP_VERSION.0, encoded_version(), output); +} + +fn decode_version(stream: &mut &[u8]) -> Result<(), DecodeError> { + // See above: this is hopefully rare case of pseudo-CESR encoding + let Some((hdr, new_stream)) = stream.split_at_checked(3) else { + return Err(DecodeError::VersionMismatch); + }; + + if hdr != Y_HEADER { + return Err(DecodeError::VersionMismatch); + } + + *stream = new_stream; + + let _version = decode_count(TSP_VERSION.0, stream).ok_or(DecodeError::VersionMismatch)?; + + // TODO: can we simply ignore the minor and path parts of the version? + + Ok(()) +} + /// Encode a encrypted TSP message plus Envelope into CESR pub fn encode_ets_envelope<'a, Vid: AsRef<[u8]>>( envelope: Envelope<'a, Vid>, output: &mut impl for<'b> Extend<&'b u8>, ) -> Result<(), EncodeError> { + //TODO: encode the count of the data to be signed encode_count(TSP_ETS_WRAPPER, 1, output); + encode_version(output); encode_envelope_fields(envelope, output) } @@ -577,7 +619,7 @@ pub fn encode_s_envelope<'a, Vid: AsRef<[u8]>>( output: &mut impl for<'b> Extend<&'b u8>, ) -> Result<(), EncodeError> { encode_count(TSP_S_WRAPPER, 1, output); - + encode_version(output); encode_envelope_fields(envelope, output) } @@ -639,7 +681,8 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( stream: &mut &[u8], ) -> Result<(usize, CryptoType, SignatureType), DecodeError> { let origin = stream as &[u8]; - let encrypted = if let Some(1) = decode_count(TSP_ETS_WRAPPER, stream) { + //TODO: do something with the quadlet count? + let encrypted = if let Some(_quadlet_count) = decode_count(TSP_ETS_WRAPPER, stream) { true } else if let Some(1) = decode_count(TSP_S_WRAPPER, stream) { false @@ -647,6 +690,8 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( return Err(DecodeError::VersionMismatch); }; + decode_version(stream)?; + match decode_fixed_data(TSP_TYPECODE, stream) { Some([0, 0]) => {} _ => return Err(DecodeError::VersionMismatch), @@ -665,9 +710,9 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( _ => return Err(DecodeError::VersionMismatch), }; - debug_assert_eq!(origin.len() - stream.len(), 9); + debug_assert_eq!(origin.len() - stream.len(), 15); - Ok((9, crypto_type, signature_type)) + Ok((origin.len() - stream.len(), crypto_type, signature_type)) } /// A structure representing a siganture + data that needs to be verified. @@ -1404,10 +1449,10 @@ mod test { fn test_message_to_parts() { use base64ct::{Base64UrlUnpadded, Encoding}; - let message = Base64UrlUnpadded::decode_vec("-EABXAAAXAEB9VIDAAAEZGlkOnRlc3Q6Ym9i8VIDAAAFAGRpZDp0ZXN0OmFsaWNl6BAEAABleHRyYSBkYXRh4CAXScvzIiBCgfOu9jHtGwd1qN-KlMB7uhFbE9YOSyTmnp9yziA1LVPdQmST27yjuDRTlxeRo7H7gfuaGFY4iyf2EsfiqvEg0BBNDbKoW0DDczGxj7rNWKH_suyj18HCUxMZ6-mDymZdNhHZIS8zIstC9Kxv5Q-GxmI-1v4SNbeCemuCMBzMPogK").unwrap(); + let message = Base64UrlUnpadded::decode_vec("-EABYTSP-AABXAAAXAEB9VIDAAAEZGlkOnRlc3Q6Ym9i8VIDAAAFAGRpZDp0ZXN0OmFsaWNl6BAEAABleHRyYSBkYXRh4CAXScvzIiBCgfOu9jHtGwd1qN-KlMB7uhFbE9YOSyTmnp9yziA1LVPdQmST27yjuDRTlxeRo7H7gfuaGFY4iyf2EsfiqvEg0BBNDbKoW0DDczGxj7rNWKH_suyj18HCUxMZ6-mDymZdNhHZIS8zIstC9Kxv5Q-GxmI-1v4SNbeCemuCMBzMPogK").unwrap(); let parts = open_message_into_parts(&message).unwrap(); - assert_eq!(parts.prefix.prefix.len(), 9); + assert_eq!(parts.prefix.prefix.len(), 15); assert_eq!(parts.sender.data.len(), 10); assert_eq!(parts.receiver.unwrap().data.len(), 14); assert_eq!(parts.ciphertext.unwrap().data.len(), 69); From a1e98ae0c0159fad9cdf8e50bb8a224c2bb98ab7 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Tue, 5 Aug 2025 17:29:41 +0200 Subject: [PATCH 04/27] change VID code (this makes a test failing due to breaking a signature) Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/error.rs | 1 + tsp_sdk/src/cesr/packet.rs | 44 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/tsp_sdk/src/cesr/error.rs b/tsp_sdk/src/cesr/error.rs index 95d20684..632c9d8d 100644 --- a/tsp_sdk/src/cesr/error.rs +++ b/tsp_sdk/src/cesr/error.rs @@ -3,6 +3,7 @@ pub enum EncodeError { ExcessiveFieldSize, MissingHops, + MissingReceiver, } /// An error type to indicate something went wrong with decoding diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index a58fd2b2..f89dbdb9 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -26,7 +26,7 @@ const TSP_VERSION: (u16, u8, u8) = (0, 0, 1); /// Constants that determine the specific CESR types for "variable length data" const TSP_PLAINTEXT: u32 = cesr("B"); const TSP_CIPHERTEXT: u32 = cesr("C"); -const TSP_DEVELOPMENT_VID: u32 = cesr("VID"); +const TSP_VID: u32 = cesr("V"); // TODO: this needs to become "B" /// Constants that determine the specific CESR types for "fixed length data" const TSP_TYPECODE: u32 = cesr("X"); @@ -308,7 +308,7 @@ pub fn encode_payload( ) -> Result<(), EncodeError> { if let Some(sender_identity) = sender_identity { encode_count(TSP_PAYLOAD, 2, output); - checked_encode_variable_data(TSP_DEVELOPMENT_VID, sender_identity, output)?; + checked_encode_variable_data(TSP_VID, sender_identity, output)?; } else { encode_count(TSP_PAYLOAD, 1, output); } @@ -358,11 +358,11 @@ pub fn encode_payload( Payload::NewIdentifierProposal { thread_id, new_vid } => { encode_fixed_data(TSP_TYPECODE, &msgtype::NEW_REFER_REL, output); encode_digest(thread_id, output); - checked_encode_variable_data(TSP_DEVELOPMENT_VID, new_vid.as_ref(), output)?; + checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; } Payload::RelationshipReferral { referred_vid } => { encode_fixed_data(TSP_TYPECODE, &msgtype::THIRDP_REFER_REL, output); - checked_encode_variable_data(TSP_DEVELOPMENT_VID, referred_vid.as_ref(), output)?; + checked_encode_variable_data(TSP_VID, referred_vid.as_ref(), output)?; } Payload::RelationshipCancel { reply } => { encode_fixed_data(TSP_TYPECODE, &msgtype::REL_CANCEL, output); @@ -381,7 +381,7 @@ pub fn encode_hops( if !hops.is_empty() { encode_count(TSP_HOP_LIST, hops.len() as u16, output); for hop in hops { - checked_encode_variable_data(TSP_DEVELOPMENT_VID, hop.as_ref(), output)?; + checked_encode_variable_data(TSP_VID, hop.as_ref(), output)?; } } @@ -404,8 +404,8 @@ fn decode_hops<'a, Vid: TryFrom<&'a [u8]>>( let mut hop_list = Vec::with_capacity(hop_length as usize); for _ in 0..hop_length { let hop: &[u8]; - (hop, stream) = decode_variable_data_mut(TSP_DEVELOPMENT_VID, stream) - .ok_or(DecodeError::UnexpectedData)?; + (hop, stream) = + decode_variable_data_mut(TSP_VID, stream).ok_or(DecodeError::UnexpectedData)?; hop_list.push(hop.try_into().map_err(|_| DecodeError::VidError)?); } @@ -448,8 +448,8 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod let sender_identity = match decode_count_mut(TSP_PAYLOAD, stream) { Some((2, upd_stream)) => { let essr_prefix: &[u8]; - (essr_prefix, stream) = decode_variable_data_mut(TSP_DEVELOPMENT_VID, upd_stream) - .ok_or(DecodeError::UnexpectedData)?; + (essr_prefix, stream) = + decode_variable_data_mut(TSP_VID, upd_stream).ok_or(DecodeError::UnexpectedData)?; Some(essr_prefix) } @@ -534,15 +534,15 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod msgtype::NEW_REFER_REL => { let (thread_id, upd_stream) = decode_digest(stream)?; let new_vid: &[u8]; - (new_vid, stream) = decode_variable_data_mut(TSP_DEVELOPMENT_VID, upd_stream) - .ok_or(DecodeError::UnexpectedData)?; + (new_vid, stream) = + decode_variable_data_mut(TSP_VID, upd_stream).ok_or(DecodeError::UnexpectedData)?; Payload::NewIdentifierProposal { thread_id, new_vid } } msgtype::THIRDP_REFER_REL => { let referred_vid: &[u8]; - (referred_vid, stream) = decode_variable_data_mut(TSP_DEVELOPMENT_VID, stream) - .ok_or(DecodeError::UnexpectedData)?; + (referred_vid, stream) = + decode_variable_data_mut(TSP_VID, stream).ok_or(DecodeError::UnexpectedData)?; Payload::RelationshipReferral { referred_vid } } @@ -636,10 +636,10 @@ fn encode_envelope_fields<'a, Vid: AsRef<[u8]>>( &[envelope.crypto_type as u8, envelope.signature_type as u8], output, ); - checked_encode_variable_data(TSP_DEVELOPMENT_VID, envelope.sender.as_ref(), output)?; + checked_encode_variable_data(TSP_VID, envelope.sender.as_ref(), output)?; if let Some(rec) = envelope.receiver { - checked_encode_variable_data(TSP_DEVELOPMENT_VID, rec.as_ref(), output)?; + checked_encode_variable_data(TSP_VID, rec.as_ref(), output)?; } if let Some(data) = envelope.nonconfidential_data { @@ -730,12 +730,12 @@ pub fn decode_sender_receiver<'a, Vid: TryFrom<&'a [u8]>>( ) -> Result<(Vid, Option, CryptoType, SignatureType), DecodeError> { let (_, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(stream)?; - let sender = decode_variable_data(TSP_DEVELOPMENT_VID, stream) + let sender = decode_variable_data(TSP_VID, stream) .ok_or(DecodeError::UnexpectedData)? .try_into() .map_err(|_| DecodeError::VidError)?; - let receiver = decode_variable_data(TSP_DEVELOPMENT_VID, stream) + let receiver = decode_variable_data(TSP_VID, stream) .map(|r| r.try_into().map_err(|_| DecodeError::VidError)) .transpose()?; @@ -830,10 +830,10 @@ pub fn decode_envelope<'a>(stream: &'a mut [u8]) -> Result, Decod let (mut pos, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(&mut (stream as &[u8]))?; - let sender = decode_variable_data_index(TSP_DEVELOPMENT_VID, stream, &mut pos) + let sender = decode_variable_data_index(TSP_VID, stream, &mut pos) .ok_or(DecodeError::UnexpectedData)?; - let receiver = decode_variable_data_index(TSP_DEVELOPMENT_VID, stream, &mut pos); + let receiver = decode_variable_data_index(TSP_VID, stream, &mut pos); let nonconfidential_data = decode_variable_data_index(TSP_PLAINTEXT, stream, &mut pos); @@ -972,8 +972,8 @@ pub fn open_message_into_parts(data: &[u8]) -> Result, DecodeEr data: &[], }; - let sender = Part::decode(TSP_DEVELOPMENT_VID, data, &mut pos).ok_or(DecodeError::VidError)?; - let receiver = Part::decode(TSP_DEVELOPMENT_VID, data, &mut pos); + let sender = Part::decode(TSP_VID, data, &mut pos).ok_or(DecodeError::VidError)?; + let receiver = Part::decode(TSP_VID, data, &mut pos); let nonconfidential_data = Part::decode(TSP_PLAINTEXT, data, &mut pos); let ciphertext = Part::decode(TSP_CIPHERTEXT, data, &mut pos); @@ -1449,7 +1449,7 @@ mod test { fn test_message_to_parts() { use base64ct::{Base64UrlUnpadded, Encoding}; - let message = Base64UrlUnpadded::decode_vec("-EABYTSP-AABXAAAXAEB9VIDAAAEZGlkOnRlc3Q6Ym9i8VIDAAAFAGRpZDp0ZXN0OmFsaWNl6BAEAABleHRyYSBkYXRh4CAXScvzIiBCgfOu9jHtGwd1qN-KlMB7uhFbE9YOSyTmnp9yziA1LVPdQmST27yjuDRTlxeRo7H7gfuaGFY4iyf2EsfiqvEg0BBNDbKoW0DDczGxj7rNWKH_suyj18HCUxMZ6-mDymZdNhHZIS8zIstC9Kxv5Q-GxmI-1v4SNbeCemuCMBzMPogK").unwrap(); + let message = Base64UrlUnpadded::decode_vec("-EABYTSP-AABXAAAXAEB6VAEZGlkOnRlc3Q6Ym9i8VIDAAAFAGRpZDp0ZXN0OmFsaWNl6BAEAABleHRyYSBkYXRh4CAXScvzIiBCgfOu9jHtGwd1qN-KlMB7uhFbE9YOSyTmnp9yziA1LVPdQmST27yjuDRTlxeRo7H7gfuaGFY4iyf2EsfiqvEg0BBNDbKoW0DDczGxj7rNWKH_suyj18HCUxMZ6-mDymZdNhHZIS8zIstC9Kxv5Q-GxmI-1v4SNbeCemuCMBzMPogK").unwrap(); let parts = open_message_into_parts(&message).unwrap(); assert_eq!(parts.prefix.prefix.len(), 15); From 49fb629837cb3dfe0f035f4270802503c23618b6 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 14 Aug 2025 16:05:11 +0200 Subject: [PATCH 05/27] change signature of detected_tsp_header_and_confidentiality Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/mod.rs | 2 +- tsp_sdk/src/cesr/packet.rs | 40 +++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/tsp_sdk/src/cesr/mod.rs b/tsp_sdk/src/cesr/mod.rs index 2821e4af..e5120818 100644 --- a/tsp_sdk/src/cesr/mod.rs +++ b/tsp_sdk/src/cesr/mod.rs @@ -101,7 +101,7 @@ impl EnvelopeType<'_> { } pub fn probe(stream: &mut [u8]) -> Result, error::DecodeError> { - let (_, crypto_type, _) = detected_tsp_header_size_and_confidentiality(&mut (stream as &[u8]))?; + let (crypto_type, _) = detected_tsp_header_size_and_confidentiality(stream, &mut 0)?; let envelope = decode_envelope(stream)? .into_opened() diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index f89dbdb9..5b38561e 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -678,26 +678,28 @@ pub fn encode_ciphertext( /// Checks whether the expected TSP header is present and returns its size and whether it /// is a "ETS" or "S" envelope pub(super) fn detected_tsp_header_size_and_confidentiality( - stream: &mut &[u8], -) -> Result<(usize, CryptoType, SignatureType), DecodeError> { - let origin = stream as &[u8]; + stream: &[u8], + pos: &mut usize, +) -> Result<(CryptoType, SignatureType), DecodeError> { + let mut stream = &stream[*pos..]; + let origin = stream; //TODO: do something with the quadlet count? - let encrypted = if let Some(_quadlet_count) = decode_count(TSP_ETS_WRAPPER, stream) { + let encrypted = if let Some(_quadlet_count) = decode_count(TSP_ETS_WRAPPER, &mut stream) { true - } else if let Some(1) = decode_count(TSP_S_WRAPPER, stream) { + } else if let Some(1) = decode_count(TSP_S_WRAPPER, &mut stream) { false } else { return Err(DecodeError::VersionMismatch); }; - decode_version(stream)?; + decode_version(&mut stream)?; - match decode_fixed_data(TSP_TYPECODE, stream) { + match decode_fixed_data(TSP_TYPECODE, &mut stream) { Some([0, 0]) => {} _ => return Err(DecodeError::VersionMismatch), } - let (crypto_type, signature_type) = match decode_fixed_data(TSP_TYPECODE, stream) { + let (crypto_type, signature_type) = match decode_fixed_data(TSP_TYPECODE, &mut stream) { Some([crypto, signature]) => { let crypto_type = CryptoType::try_from(*crypto)?; @@ -712,7 +714,8 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( debug_assert_eq!(origin.len() - stream.len(), 15); - Ok((origin.len() - stream.len(), crypto_type, signature_type)) + *pos += origin.len() - stream.len(); + Ok((crypto_type, signature_type)) } /// A structure representing a siganture + data that needs to be verified. @@ -728,7 +731,10 @@ pub struct VerificationChallenge<'a> { pub fn decode_sender_receiver<'a, Vid: TryFrom<&'a [u8]>>( stream: &mut &'a [u8], ) -> Result<(Vid, Option, CryptoType, SignatureType), DecodeError> { - let (_, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(stream)?; + let mut pos = 0; + let (crypto_type, signature_type) = + detected_tsp_header_size_and_confidentiality(stream, &mut pos)?; + *stream = &stream[pos..]; let sender = decode_variable_data(TSP_VID, stream) .ok_or(DecodeError::UnexpectedData)? @@ -827,11 +833,12 @@ impl<'a> CipherView<'a> { /// Decode an encrypted TSP message plus Envelope & Signature /// Produces the ciphertext as a mutable stream. pub fn decode_envelope<'a>(stream: &'a mut [u8]) -> Result, DecodeError> { - let (mut pos, crypto_type, signature_type) = - detected_tsp_header_size_and_confidentiality(&mut (stream as &[u8]))?; + let mut pos = 0; + let (crypto_type, signature_type) = + detected_tsp_header_size_and_confidentiality(stream, &mut pos)?; - let sender = decode_variable_data_index(TSP_VID, stream, &mut pos) - .ok_or(DecodeError::UnexpectedData)?; + let sender = + decode_variable_data_index(TSP_VID, stream, &mut pos).ok_or(DecodeError::UnexpectedData)?; let receiver = decode_variable_data_index(TSP_VID, stream, &mut pos); @@ -964,8 +971,9 @@ pub struct MessageParts<'a> { /// Decode a CESR-encoded message into its CESR-encoded parts pub fn open_message_into_parts(data: &[u8]) -> Result, DecodeError> { - let (mut pos, crypto_type, signature_type) = - detected_tsp_header_size_and_confidentiality(&mut (data as &[u8]))?; + let mut pos = 0; + let (crypto_type, signature_type) = + detected_tsp_header_size_and_confidentiality(data, &mut pos)?; let prefix = Part { prefix: &data[..pos], From f58d937077c42d9d062f94d2aa5d2260f20b7f7e Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 14 Aug 2025 17:37:35 +0200 Subject: [PATCH 06/27] move sender/receiver to earlier in the envelope Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/decode.rs | 1 + tsp_sdk/src/cesr/mod.rs | 4 ++- tsp_sdk/src/cesr/packet.rs | 70 +++++++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/tsp_sdk/src/cesr/decode.rs b/tsp_sdk/src/cesr/decode.rs index 1ad37a74..7ffbdb62 100644 --- a/tsp_sdk/src/cesr/decode.rs +++ b/tsp_sdk/src/cesr/decode.rs @@ -94,6 +94,7 @@ pub fn decode_variable_data_index( } } +#[allow(dead_code)] pub fn decode_variable_data<'a>(identifier: u32, stream: &mut &'a [u8]) -> Option<&'a [u8]> { let range = decode_variable_data_index(identifier, stream, &mut 0)?; let slice = &stream[range.start..range.end]; diff --git a/tsp_sdk/src/cesr/mod.rs b/tsp_sdk/src/cesr/mod.rs index e5120818..d1b19d4f 100644 --- a/tsp_sdk/src/cesr/mod.rs +++ b/tsp_sdk/src/cesr/mod.rs @@ -100,8 +100,10 @@ impl EnvelopeType<'_> { } } +//TODO: simplify the source of sender/receiver pub fn probe(stream: &mut [u8]) -> Result, error::DecodeError> { - let (crypto_type, _) = detected_tsp_header_size_and_confidentiality(stream, &mut 0)?; + let (_sender, _receiver, crypto_type, _) = + detected_tsp_header_size_and_confidentiality(stream, &mut 0)?; let envelope = decode_envelope(stream)? .into_opened() diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 5b38561e..75979717 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -26,7 +26,7 @@ const TSP_VERSION: (u16, u8, u8) = (0, 0, 1); /// Constants that determine the specific CESR types for "variable length data" const TSP_PLAINTEXT: u32 = cesr("B"); const TSP_CIPHERTEXT: u32 = cesr("C"); -const TSP_VID: u32 = cesr("V"); // TODO: this needs to become "B" +const TSP_VID: u32 = cesr("B"); /// Constants that determine the specific CESR types for "fixed length data" const TSP_TYPECODE: u32 = cesr("X"); @@ -61,7 +61,7 @@ mod msgtype { use super::{ decode::{ decode_count, decode_count_mut, decode_fixed_data, decode_fixed_data_mut, - decode_variable_data, decode_variable_data_index, decode_variable_data_mut, + decode_variable_data_index, decode_variable_data_mut, }, encode::{encode_count, encode_fixed_data}, error::{DecodeError, EncodeError}, @@ -609,7 +609,6 @@ pub fn encode_ets_envelope<'a, Vid: AsRef<[u8]>>( ) -> Result<(), EncodeError> { //TODO: encode the count of the data to be signed encode_count(TSP_ETS_WRAPPER, 1, output); - encode_version(output); encode_envelope_fields(envelope, output) } @@ -619,7 +618,6 @@ pub fn encode_s_envelope<'a, Vid: AsRef<[u8]>>( output: &mut impl for<'b> Extend<&'b u8>, ) -> Result<(), EncodeError> { encode_count(TSP_S_WRAPPER, 1, output); - encode_version(output); encode_envelope_fields(envelope, output) } @@ -630,17 +628,19 @@ fn encode_envelope_fields<'a, Vid: AsRef<[u8]>>( envelope: Envelope<'a, Vid>, output: &mut impl for<'b> Extend<&'b u8>, ) -> Result<(), EncodeError> { + encode_version(output); + checked_encode_variable_data(TSP_VID, envelope.sender.as_ref(), output)?; + + if let Some(rec) = envelope.receiver { + checked_encode_variable_data(TSP_VID, rec.as_ref(), output)?; + } + encode_fixed_data(TSP_TYPECODE, &[0, 0], output); encode_fixed_data( TSP_TYPECODE, &[envelope.crypto_type as u8, envelope.signature_type as u8], output, ); - checked_encode_variable_data(TSP_VID, envelope.sender.as_ref(), output)?; - - if let Some(rec) = envelope.receiver { - checked_encode_variable_data(TSP_VID, rec.as_ref(), output)?; - } if let Some(data) = envelope.nonconfidential_data { checked_encode_variable_data(TSP_PLAINTEXT, data, output)?; @@ -680,9 +680,17 @@ pub fn encode_ciphertext( pub(super) fn detected_tsp_header_size_and_confidentiality( stream: &[u8], pos: &mut usize, -) -> Result<(CryptoType, SignatureType), DecodeError> { - let mut stream = &stream[*pos..]; +) -> Result< + ( + Range, + Option>, + CryptoType, + SignatureType, + ), + DecodeError, +> { let origin = stream; + let mut stream = &origin[*pos..]; //TODO: do something with the quadlet count? let encrypted = if let Some(_quadlet_count) = decode_count(TSP_ETS_WRAPPER, &mut stream) { true @@ -693,6 +701,14 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( }; decode_version(&mut stream)?; + let mut mid_pos = *pos + origin.len() - stream.len(); + + let sender = decode_variable_data_index(TSP_VID, origin, &mut mid_pos) + .ok_or(DecodeError::UnexpectedData)?; + + let receiver = decode_variable_data_index(TSP_VID, origin, &mut mid_pos); + + let mut stream = &origin[mid_pos..]; match decode_fixed_data(TSP_TYPECODE, &mut stream) { Some([0, 0]) => {} @@ -712,10 +728,8 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( _ => return Err(DecodeError::VersionMismatch), }; - debug_assert_eq!(origin.len() - stream.len(), 15); - *pos += origin.len() - stream.len(); - Ok((crypto_type, signature_type)) + Ok((sender, receiver, crypto_type, signature_type)) } /// A structure representing a siganture + data that needs to be verified. @@ -732,17 +746,16 @@ pub fn decode_sender_receiver<'a, Vid: TryFrom<&'a [u8]>>( stream: &mut &'a [u8], ) -> Result<(Vid, Option, CryptoType, SignatureType), DecodeError> { let mut pos = 0; - let (crypto_type, signature_type) = + let (sender, receiver, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(stream, &mut pos)?; *stream = &stream[pos..]; - let sender = decode_variable_data(TSP_VID, stream) - .ok_or(DecodeError::UnexpectedData)? + let sender = stream[sender] .try_into() .map_err(|_| DecodeError::VidError)?; - let receiver = decode_variable_data(TSP_VID, stream) - .map(|r| r.try_into().map_err(|_| DecodeError::VidError)) + let receiver = receiver + .map(|r| stream[r].try_into().map_err(|_| DecodeError::VidError)) .transpose()?; Ok((sender, receiver, crypto_type, signature_type)) @@ -834,14 +847,9 @@ impl<'a> CipherView<'a> { /// Produces the ciphertext as a mutable stream. pub fn decode_envelope<'a>(stream: &'a mut [u8]) -> Result, DecodeError> { let mut pos = 0; - let (crypto_type, signature_type) = + let (sender, receiver, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(stream, &mut pos)?; - let sender = - decode_variable_data_index(TSP_VID, stream, &mut pos).ok_or(DecodeError::UnexpectedData)?; - - let receiver = decode_variable_data_index(TSP_VID, stream, &mut pos); - let nonconfidential_data = decode_variable_data_index(TSP_PLAINTEXT, stream, &mut pos); let associated_data = 0..pos; @@ -972,7 +980,7 @@ pub struct MessageParts<'a> { /// Decode a CESR-encoded message into its CESR-encoded parts pub fn open_message_into_parts(data: &[u8]) -> Result, DecodeError> { let mut pos = 0; - let (crypto_type, signature_type) = + let (sender, receiver, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(data, &mut pos)?; let prefix = Part { @@ -980,8 +988,14 @@ pub fn open_message_into_parts(data: &[u8]) -> Result, DecodeEr data: &[], }; - let sender = Part::decode(TSP_VID, data, &mut pos).ok_or(DecodeError::VidError)?; - let receiver = Part::decode(TSP_VID, data, &mut pos); + let sender = Part { + prefix: &[], + data: &data[sender], + }; + let receiver = receiver.map(|r| Part { + prefix: &[], + data: &data[r], + }); let nonconfidential_data = Part::decode(TSP_PLAINTEXT, data, &mut pos); let ciphertext = Part::decode(TSP_CIPHERTEXT, data, &mut pos); From 9c2acbaa465f3d0ae933c1502b53689d27be92f8 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 13:59:04 +0200 Subject: [PATCH 07/27] slight refactor of msgtype handling in anticipation of introducing thew new payload types Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/consts.rs | 38 ++++++++++++ tsp_sdk/src/cesr/mod.rs | 1 + tsp_sdk/src/cesr/packet.rs | 121 ++++++++++++++++--------------------- 3 files changed, 92 insertions(+), 68 deletions(-) create mode 100644 tsp_sdk/src/cesr/consts.rs diff --git a/tsp_sdk/src/cesr/consts.rs b/tsp_sdk/src/cesr/consts.rs new file mode 100644 index 00000000..67e5045f --- /dev/null +++ b/tsp_sdk/src/cesr/consts.rs @@ -0,0 +1,38 @@ +/// A function for more easy encoding of CESR constants +pub const fn cesr(x: &str) -> u64 { + let x = x.as_bytes(); + let mut acc = 0; + let mut i = 0; + while i < x.len() { + let ch = x[i]; + acc = acc << 6 + | match ch { + ch if ch.is_ascii_uppercase() => ch - b'A', + ch if ch.is_ascii_lowercase() => ch - b'a' + 26, + ch if ch.is_ascii_digit() => ch - b'0' + 52, + b'-' => 62, + b'_' => 63, + _ => panic!("not a base64url character"), + } as u64; + i += 1; + } + + acc +} + +/// A function for giving the contents of "TSP_TYPECODE" fields +pub const fn cesr_data(x: &str) -> [u8; N] { + let val = cesr(x); + assert!(val < (1u64 << (8 * N))); + // we canot use 'try_into().unwrap()' here + let src = u64::to_be_bytes(val); + let start = src.len() - N; + let mut result = [0; N]; + let mut i = start; + while i < src.len() { + result[i - start] = src[i]; + i += 1 + } + + result +} diff --git a/tsp_sdk/src/cesr/mod.rs b/tsp_sdk/src/cesr/mod.rs index d1b19d4f..d4c6a3d8 100644 --- a/tsp_sdk/src/cesr/mod.rs +++ b/tsp_sdk/src/cesr/mod.rs @@ -6,6 +6,7 @@ pub mod error; mod packet; use base64ct::{Base64UrlUnpadded, Encoding}; use error::DecodeError; +mod consts; pub use packet::*; #[cfg(feature = "cesr-t")] diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 75979717..dd6bca8b 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -1,43 +1,22 @@ -/// A function for more easy encoding of CESR constants -const fn cesr(x: &str) -> u32 { - let x = x.as_bytes(); - let mut acc = 0; - let mut i = 0; - while i < x.len() { - let ch = x[i]; - acc = acc << 6 - | match ch { - ch if ch.is_ascii_uppercase() => ch - b'A', - ch if ch.is_ascii_lowercase() => ch - b'a' + 26, - ch if ch.is_ascii_digit() => ch - b'0' + 52, - b'-' => 62, - b'_' => 63, - _ => panic!("not a base64url character"), - } as u32; - i += 1; - } - - acc -} +use super::consts::{cesr, cesr_data}; /// The TSP version supported by this spec const TSP_VERSION: (u16, u8, u8) = (0, 0, 1); /// Constants that determine the specific CESR types for "variable length data" -const TSP_PLAINTEXT: u32 = cesr("B"); -const TSP_CIPHERTEXT: u32 = cesr("C"); -const TSP_VID: u32 = cesr("B"); +const TSP_PLAINTEXT: u32 = cesr("B") as u32; +const TSP_CIPHERTEXT: u32 = cesr("C") as u32; +const TSP_VID: u32 = cesr("B") as u32; /// Constants that determine the specific CESR types for "fixed length data" -const TSP_TYPECODE: u32 = cesr("X"); -const ED25519_SIGNATURE: u32 = cesr("B"); +const ED25519_SIGNATURE: u32 = cesr("B") as u32; #[cfg(feature = "pq")] -const ML_DSA_65_SIGNATURE: u32 = cesr("QDM"); +const ML_DSA_65_SIGNATURE: u32 = cesr("QDM") as u32; #[allow(clippy::eq_op)] -const TSP_NONCE: u32 = cesr("A"); -const TSP_SHA256: u32 = cesr("I"); +const TSP_NONCE: u32 = cesr("A") as u32; +const TSP_SHA256: u32 = cesr("I") as u32; #[allow(dead_code)] -const TSP_BLAKE2B256: u32 = cesr("F"); +const TSP_BLAKE2B256: u32 = cesr("F") as u32; /// Constants that determine the specific CESR types for the framing codes const TSP_ETS_WRAPPER: u16 = cesr("E") as u16; @@ -47,16 +26,28 @@ const TSP_PAYLOAD: u16 = cesr("Z") as u16; /// Constants to encode message types mod msgtype { - pub(super) const GEN_MSG: [u8; 2] = [0, 0]; - pub(super) const NEST_MSG: [u8; 2] = [0, 1]; - pub(super) const NEW_REL: [u8; 2] = [1, 0]; - pub(super) const NEW_REL_REPLY: [u8; 2] = [1, 1]; - pub(super) const NEW_NEST_REL: [u8; 2] = [1, 2]; - pub(super) const NEW_NEST_REL_REPLY: [u8; 2] = [1, 3]; - pub(super) const NEW_REFER_REL: [u8; 2] = [1, 4]; - pub(super) const THIRDP_REFER_REL: [u8; 2] = [1, 5]; - pub(super) const REL_CANCEL: [u8; 2] = [1, 255]; + pub(super) const GEN_MSG: [u8; 3] = [23 << 2, 0, 0]; + pub(super) const NEST_MSG: [u8; 3] = [23 << 2, 0, 1]; + pub(super) const NEW_REL: [u8; 3] = [23 << 2, 1, 0]; + pub(super) const NEW_REL_REPLY: [u8; 3] = [23 << 2, 1, 1]; + pub(super) const NEW_NEST_REL: [u8; 3] = [23 << 2, 1, 2]; + pub(super) const NEW_NEST_REL_REPLY: [u8; 3] = [23 << 2, 1, 3]; + pub(super) const NEW_REFER_REL: [u8; 3] = [23 << 2, 1, 4]; + pub(super) const THIRDP_REFER_REL: [u8; 3] = [23 << 2, 1, 5]; + pub(super) const REL_CANCEL: [u8; 3] = [23 << 2, 1, 255]; } +const TSP_TMP: u32 = cesr("X") as u32; + +/// Constants for payload field types +const XCTL: [u8; 3] = cesr_data("XCTL"); +const XSCS: [u8; 3] = cesr_data("XSCS"); +const XHOP: [u8; 3] = cesr_data("XHOP"); +#[allow(unused)] +const XPAD: [u8; 3] = cesr_data("XPAD"); +const XRFI: [u8; 3] = cesr_data("XRFI"); +const XRFA: [u8; 3] = cesr_data("XRFA"); +const XRFD: [u8; 3] = cesr_data("XRFD"); +const YTSP: [u8; 3] = cesr_data("YTSP"); use super::{ decode::{ @@ -315,15 +306,15 @@ pub fn encode_payload( match payload { Payload::GenericMessage(data) => { - encode_fixed_data(TSP_TYPECODE, &msgtype::GEN_MSG, output); + output.extend(&msgtype::GEN_MSG); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::NestedMessage(data) => { - encode_fixed_data(TSP_TYPECODE, &msgtype::NEST_MSG, output); + output.extend(&msgtype::NEST_MSG); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::RoutedMessage(hops, data) => { - encode_fixed_data(TSP_TYPECODE, &msgtype::GEN_MSG, output); + output.extend(&msgtype::GEN_MSG); if hops.is_empty() { return Err(EncodeError::MissingHops); } @@ -331,19 +322,19 @@ pub fn encode_payload( checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::DirectRelationProposal { nonce, hops } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::NEW_REL, output); + output.extend(&msgtype::NEW_REL); encode_hops(hops, output)?; encode_fixed_data(TSP_NONCE, &nonce.0, output); } Payload::DirectRelationAffirm { reply } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::NEW_REL_REPLY, output); + output.extend(&msgtype::NEW_REL_REPLY); encode_digest(reply, output); } Payload::NestedRelationProposal { message: data, nonce, } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::NEW_NEST_REL, output); + output.extend(&msgtype::NEW_NEST_REL); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; encode_fixed_data(TSP_NONCE, &nonce.0, output); } @@ -351,21 +342,21 @@ pub fn encode_payload( message: data, reply, } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::NEW_NEST_REL_REPLY, output); + output.extend(&msgtype::NEW_NEST_REL_REPLY); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; encode_digest(reply, output); } Payload::NewIdentifierProposal { thread_id, new_vid } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::NEW_REFER_REL, output); + output.extend(&msgtype::NEW_REFER_REL); encode_digest(thread_id, output); checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; } Payload::RelationshipReferral { referred_vid } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::THIRDP_REFER_REL, output); + output.extend(&msgtype::THIRDP_REFER_REL); checked_encode_variable_data(TSP_VID, referred_vid.as_ref(), output)?; } Payload::RelationshipCancel { reply } => { - encode_fixed_data(TSP_TYPECODE, &msgtype::REL_CANCEL, output); + output.extend(&XRFD); encode_digest(reply, output); } } @@ -461,10 +452,11 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod _ => return Err(DecodeError::VersionMismatch), }; - let (&mut msgtype, mut stream) = - decode_fixed_data_mut(TSP_TYPECODE, stream).ok_or(DecodeError::UnexpectedData)?; + let (msgtype, mut stream) = stream + .split_at_mut_checked(3) + .ok_or(DecodeError::UnexpectedData)?; - let payload = match msgtype { + let payload = match *<&[u8; 3]>::try_from(msgtype as &[u8]).unwrap() { msgtype::GEN_MSG => { let (hop_list, upd_stream) = decode_hops(stream)?; let msg; @@ -546,7 +538,7 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod Payload::RelationshipReferral { referred_vid } } - msgtype::REL_CANCEL => { + XRFD => { let reply; (reply, stream) = decode_digest(stream)?; @@ -569,27 +561,19 @@ const fn encoded_version() -> u16 { (TSP_VERSION.1 as u16) << 6 | (TSP_VERSION.2 as u16) } -/// Y is the header. -/// Note that 'YTSP' is not conforming to the normal rules of a fixed-size CESR code, so we -/// force hardcode it as a Base64 quadlet/triplet. -const Y_HEADER: [u8; 3] = { - let cesr = u32::to_be_bytes(cesr("YTSP")); - [cesr[1], cesr[2], cesr[3]] -}; - /// Encode a TSP version marker pub fn encode_version(output: &mut impl for<'b> Extend<&'b u8>) { - output.extend(&Y_HEADER); + output.extend(&YTSP); encode_count(TSP_VERSION.0, encoded_version(), output); } fn decode_version(stream: &mut &[u8]) -> Result<(), DecodeError> { // See above: this is hopefully rare case of pseudo-CESR encoding - let Some((hdr, new_stream)) = stream.split_at_checked(3) else { + let Some((hdr, new_stream)) = stream.split_at_checked(YTSP.len()) else { return Err(DecodeError::VersionMismatch); }; - if hdr != Y_HEADER { + if hdr != YTSP { return Err(DecodeError::VersionMismatch); } @@ -635,9 +619,9 @@ fn encode_envelope_fields<'a, Vid: AsRef<[u8]>>( checked_encode_variable_data(TSP_VID, rec.as_ref(), output)?; } - encode_fixed_data(TSP_TYPECODE, &[0, 0], output); + encode_fixed_data(TSP_TMP, &[0, 0], output); encode_fixed_data( - TSP_TYPECODE, + TSP_TMP, &[envelope.crypto_type as u8, envelope.signature_type as u8], output, ); @@ -677,6 +661,7 @@ pub fn encode_ciphertext( /// Checks whether the expected TSP header is present and returns its size and whether it /// is a "ETS" or "S" envelope +#[allow(clippy::type_complexity)] pub(super) fn detected_tsp_header_size_and_confidentiality( stream: &[u8], pos: &mut usize, @@ -710,12 +695,12 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( let mut stream = &origin[mid_pos..]; - match decode_fixed_data(TSP_TYPECODE, &mut stream) { + match decode_fixed_data(TSP_TMP, &mut stream) { Some([0, 0]) => {} _ => return Err(DecodeError::VersionMismatch), } - let (crypto_type, signature_type) = match decode_fixed_data(TSP_TYPECODE, &mut stream) { + let (crypto_type, signature_type) = match decode_fixed_data(TSP_TMP, &mut stream) { Some([crypto, signature]) => { let crypto_type = CryptoType::try_from(*crypto)?; From afe43f3ae20d4a9ee6a6bf3cd38d1173fe7678bb Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 14:06:19 +0200 Subject: [PATCH 08/27] use the new type codes Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index dd6bca8b..92f5278e 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -306,15 +306,15 @@ pub fn encode_payload( match payload { Payload::GenericMessage(data) => { - output.extend(&msgtype::GEN_MSG); + output.extend(&XSCS); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::NestedMessage(data) => { - output.extend(&msgtype::NEST_MSG); + output.extend(&XHOP); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::RoutedMessage(hops, data) => { - output.extend(&msgtype::GEN_MSG); + output.extend(&XSCS); if hops.is_empty() { return Err(EncodeError::MissingHops); } @@ -322,12 +322,12 @@ pub fn encode_payload( checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::DirectRelationProposal { nonce, hops } => { - output.extend(&msgtype::NEW_REL); + output.extend(&XRFI); encode_hops(hops, output)?; encode_fixed_data(TSP_NONCE, &nonce.0, output); } Payload::DirectRelationAffirm { reply } => { - output.extend(&msgtype::NEW_REL_REPLY); + output.extend(&XRFA); encode_digest(reply, output); } Payload::NestedRelationProposal { @@ -457,7 +457,7 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod .ok_or(DecodeError::UnexpectedData)?; let payload = match *<&[u8; 3]>::try_from(msgtype as &[u8]).unwrap() { - msgtype::GEN_MSG => { + XSCS => { let (hop_list, upd_stream) = decode_hops(stream)?; let msg; if hop_list.is_empty() { @@ -472,7 +472,7 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod Payload::RoutedMessage(hop_list, msg) } } - msgtype::NEW_REL => { + XRFI => { let (hop_list, upd_stream) = decode_hops(stream)?; let nonce; @@ -484,14 +484,14 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod hops: hop_list, } } - msgtype::NEST_MSG => { + XHOP => { let msg; (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, stream) .ok_or(DecodeError::UnexpectedData)?; Payload::NestedMessage(msg) } - msgtype::NEW_REL_REPLY => { + XRFA => { let reply; (reply, stream) = decode_digest(stream)?; From 53c3712f66c0e55d9903d8aceead23b052a15beb Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 14:25:06 +0200 Subject: [PATCH 09/27] add more convenience Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/consts.rs | 12 ++++++++++-- tsp_sdk/src/cesr/packet.rs | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/tsp_sdk/src/cesr/consts.rs b/tsp_sdk/src/cesr/consts.rs index 67e5045f..d14114a7 100644 --- a/tsp_sdk/src/cesr/consts.rs +++ b/tsp_sdk/src/cesr/consts.rs @@ -1,5 +1,13 @@ +macro_rules! cesr { + ($value: expr) => { + $crate::cesr::consts::cesr_int($value) as _ + }; +} + +pub(super) use cesr; + /// A function for more easy encoding of CESR constants -pub const fn cesr(x: &str) -> u64 { +pub const fn cesr_int(x: &str) -> u64 { let x = x.as_bytes(); let mut acc = 0; let mut i = 0; @@ -22,7 +30,7 @@ pub const fn cesr(x: &str) -> u64 { /// A function for giving the contents of "TSP_TYPECODE" fields pub const fn cesr_data(x: &str) -> [u8; N] { - let val = cesr(x); + let val = cesr_int(x); assert!(val < (1u64 << (8 * N))); // we canot use 'try_into().unwrap()' here let src = u64::to_be_bytes(val); diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 92f5278e..443a83eb 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -4,25 +4,25 @@ use super::consts::{cesr, cesr_data}; const TSP_VERSION: (u16, u8, u8) = (0, 0, 1); /// Constants that determine the specific CESR types for "variable length data" -const TSP_PLAINTEXT: u32 = cesr("B") as u32; -const TSP_CIPHERTEXT: u32 = cesr("C") as u32; -const TSP_VID: u32 = cesr("B") as u32; +const TSP_PLAINTEXT: u32 = cesr!("B"); +const TSP_CIPHERTEXT: u32 = cesr!("C"); +const TSP_VID: u32 = cesr!("B"); /// Constants that determine the specific CESR types for "fixed length data" -const ED25519_SIGNATURE: u32 = cesr("B") as u32; +const ED25519_SIGNATURE: u32 = cesr!("B"); #[cfg(feature = "pq")] -const ML_DSA_65_SIGNATURE: u32 = cesr("QDM") as u32; +const ML_DSA_65_SIGNATURE: u32 = cesr!("QDM"); #[allow(clippy::eq_op)] -const TSP_NONCE: u32 = cesr("A") as u32; -const TSP_SHA256: u32 = cesr("I") as u32; +const TSP_NONCE: u32 = cesr!("A"); +const TSP_SHA256: u32 = cesr!("I"); #[allow(dead_code)] -const TSP_BLAKE2B256: u32 = cesr("F") as u32; +const TSP_BLAKE2B256: u32 = cesr!("F"); /// Constants that determine the specific CESR types for the framing codes -const TSP_ETS_WRAPPER: u16 = cesr("E") as u16; -const TSP_S_WRAPPER: u16 = cesr("S") as u16; -const TSP_HOP_LIST: u16 = cesr("I") as u16; -const TSP_PAYLOAD: u16 = cesr("Z") as u16; +const TSP_ETS_WRAPPER: u16 = cesr!("E"); +const TSP_S_WRAPPER: u16 = cesr!("S"); +const TSP_HOP_LIST: u16 = cesr!("I"); +const TSP_PAYLOAD: u16 = cesr!("Z"); /// Constants to encode message types mod msgtype { @@ -36,7 +36,7 @@ mod msgtype { pub(super) const THIRDP_REFER_REL: [u8; 3] = [23 << 2, 1, 5]; pub(super) const REL_CANCEL: [u8; 3] = [23 << 2, 1, 255]; } -const TSP_TMP: u32 = cesr("X") as u32; +const TSP_TMP: u32 = cesr!("X"); /// Constants for payload field types const XCTL: [u8; 3] = cesr_data("XCTL"); From 67116c1965469a74635b9899def2d267ac2ef656 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 14:25:15 +0200 Subject: [PATCH 10/27] remove old version check Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 443a83eb..ce198e89 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -619,7 +619,6 @@ fn encode_envelope_fields<'a, Vid: AsRef<[u8]>>( checked_encode_variable_data(TSP_VID, rec.as_ref(), output)?; } - encode_fixed_data(TSP_TMP, &[0, 0], output); encode_fixed_data( TSP_TMP, &[envelope.crypto_type as u8, envelope.signature_type as u8], @@ -695,11 +694,6 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( let mut stream = &origin[mid_pos..]; - match decode_fixed_data(TSP_TMP, &mut stream) { - Some([0, 0]) => {} - _ => return Err(DecodeError::VersionMismatch), - } - let (crypto_type, signature_type) = match decode_fixed_data(TSP_TMP, &mut stream) { Some([crypto, signature]) => { let crypto_type = CryptoType::try_from(*crypto)?; From 091878a1e67e3d05af8ca2cbabe673d46a564ae3 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 15:14:38 +0200 Subject: [PATCH 11/27] decouple the presence of the sender with the -Z code Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index ce198e89..b66d2c2a 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -297,11 +297,10 @@ pub fn encode_payload( sender_identity: Option<&[u8]>, output: &mut impl for<'a> Extend<&'a u8>, ) -> Result<(), EncodeError> { + //TODO do something with the payload count + encode_count(TSP_PAYLOAD, 1, output); if let Some(sender_identity) = sender_identity { - encode_count(TSP_PAYLOAD, 2, output); checked_encode_variable_data(TSP_VID, sender_identity, output)?; - } else { - encode_count(TSP_PAYLOAD, 1, output); } match payload { @@ -436,20 +435,22 @@ pub fn encode_digest(digest: &Digest, output: &mut impl for<'a> Extend<&'a u8>) /// Decode a TSP Payload pub fn decode_payload(mut stream: &mut [u8]) -> Result, DecodeError> { - let sender_identity = match decode_count_mut(TSP_PAYLOAD, stream) { - Some((2, upd_stream)) => { - let essr_prefix: &[u8]; - (essr_prefix, stream) = - decode_variable_data_mut(TSP_VID, upd_stream).ok_or(DecodeError::UnexpectedData)?; - - Some(essr_prefix) - } - Some((1, upd_stream)) => { - stream = upd_stream; - + //NOTE: should we do something with this count? + let _count; + (_count, stream) = decode_count_mut(TSP_PAYLOAD, stream).ok_or(DecodeError::UnexpectedData)?; + + let sender_identity: Option<&[u8]> = { + let mut pos = 0; + if let Some(essr_prefix) = decode_variable_data_index(TSP_VID, stream, &mut pos) { + let prefix; + (prefix, stream) = stream + .split_at_mut_checked(essr_prefix.end) + .ok_or(DecodeError::VidError)?; + + Some(&prefix[essr_prefix]) + } else { None } - _ => return Err(DecodeError::VersionMismatch), }; let (msgtype, mut stream) = stream @@ -581,7 +582,7 @@ fn decode_version(stream: &mut &[u8]) -> Result<(), DecodeError> { let _version = decode_count(TSP_VERSION.0, stream).ok_or(DecodeError::VersionMismatch)?; - // TODO: can we simply ignore the minor and path parts of the version? + // NOTE: can we simply ignore the minor and path parts of the version? Ok(()) } @@ -675,7 +676,7 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( > { let origin = stream; let mut stream = &origin[*pos..]; - //TODO: do something with the quadlet count? + //NOTE: do something with the quadlet count? let encrypted = if let Some(_quadlet_count) = decode_count(TSP_ETS_WRAPPER, &mut stream) { true } else if let Some(1) = decode_count(TSP_S_WRAPPER, &mut stream) { From ca780b00aac8cdbf15d80a2664692c3e7b26c13d Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 15:46:22 +0200 Subject: [PATCH 12/27] change routed message encoding to use 'nested' message encoding Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index b66d2c2a..d63a4c45 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -21,7 +21,7 @@ const TSP_BLAKE2B256: u32 = cesr!("F"); /// Constants that determine the specific CESR types for the framing codes const TSP_ETS_WRAPPER: u16 = cesr!("E"); const TSP_S_WRAPPER: u16 = cesr!("S"); -const TSP_HOP_LIST: u16 = cesr!("I"); +const TSP_HOP_LIST: u16 = cesr!("J"); const TSP_PAYLOAD: u16 = cesr!("Z"); /// Constants to encode message types @@ -310,10 +310,12 @@ pub fn encode_payload( } Payload::NestedMessage(data) => { output.extend(&XHOP); + let no_hops: [&[u8]; 0] = []; + encode_hops(&no_hops, output)?; checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; } Payload::RoutedMessage(hops, data) => { - output.extend(&XSCS); + output.extend(&XHOP); if hops.is_empty() { return Err(EncodeError::MissingHops); } @@ -459,13 +461,20 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod let payload = match *<&[u8; 3]>::try_from(msgtype as &[u8]).unwrap() { XSCS => { + let msg; + (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, stream) + .ok_or(DecodeError::UnexpectedData)?; + + Payload::GenericMessage(msg) + } + XHOP => { let (hop_list, upd_stream) = decode_hops(stream)?; let msg; if hop_list.is_empty() { (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, upd_stream) .ok_or(DecodeError::UnexpectedData)?; - Payload::GenericMessage(msg) + Payload::NestedMessage(msg) } else { (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, upd_stream) .ok_or(DecodeError::UnexpectedData)?; @@ -485,13 +494,6 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod hops: hop_list, } } - XHOP => { - let msg; - (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, stream) - .ok_or(DecodeError::UnexpectedData)?; - - Payload::NestedMessage(msg) - } XRFA => { let reply; (reply, stream) = decode_digest(stream)?; From 49dddb6bb8ee717434c0ef04109fbc943f21890b Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 15:53:01 +0200 Subject: [PATCH 13/27] always require a hop list Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/error.rs | 1 + tsp_sdk/src/cesr/packet.rs | 21 ++++++--------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/tsp_sdk/src/cesr/error.rs b/tsp_sdk/src/cesr/error.rs index 632c9d8d..47e919c2 100644 --- a/tsp_sdk/src/cesr/error.rs +++ b/tsp_sdk/src/cesr/error.rs @@ -17,6 +17,7 @@ pub enum DecodeError { VersionMismatch, InvalidCryptoType, InvalidSignatureType, + MissingHops, } impl std::fmt::Display for EncodeError { diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index d63a4c45..54cd0789 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -370,11 +370,9 @@ pub fn encode_hops( hops: &[impl AsRef<[u8]>], output: &mut impl for<'a> Extend<&'a u8>, ) -> Result<(), EncodeError> { - if !hops.is_empty() { - encode_count(TSP_HOP_LIST, hops.len() as u16, output); - for hop in hops { - checked_encode_variable_data(TSP_VID, hop.as_ref(), output)?; - } + encode_count(TSP_HOP_LIST, hops.len() as u16, output); + for hop in hops { + checked_encode_variable_data(TSP_VID, hop.as_ref(), output)?; } Ok(()) @@ -384,20 +382,13 @@ pub fn encode_hops( fn decode_hops<'a, Vid: TryFrom<&'a [u8]>>( stream: &'a mut [u8], ) -> Result<(Vec, &'a mut [u8]), DecodeError> { - // a rare case of Rust's borrow checker not being able to figure out - // that a "None" isn't borrowing from anybody; so we have to call - // the referentially transparent decode_count_mut twice... - if decode_count_mut(TSP_HOP_LIST, stream).is_none() { - return Ok((Vec::new(), stream)); - } - - let (hop_length, mut stream) = decode_count_mut(TSP_HOP_LIST, stream).unwrap(); + let (hop_length, mut stream) = + decode_count_mut(TSP_HOP_LIST, stream).ok_or(DecodeError::MissingHops)?; let mut hop_list = Vec::with_capacity(hop_length as usize); for _ in 0..hop_length { let hop: &[u8]; - (hop, stream) = - decode_variable_data_mut(TSP_VID, stream).ok_or(DecodeError::UnexpectedData)?; + (hop, stream) = decode_variable_data_mut(TSP_VID, stream).ok_or(DecodeError::VidError)?; hop_list.push(hop.try_into().map_err(|_| DecodeError::VidError)?); } From 9d5a2d44ec7ef6133c223f08456103e6d3a224c3 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 17:22:12 +0200 Subject: [PATCH 14/27] add a convenience function Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/decode.rs | 14 ++++++++++++++ tsp_sdk/src/cesr/packet.rs | 17 +++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tsp_sdk/src/cesr/decode.rs b/tsp_sdk/src/cesr/decode.rs index 7ffbdb62..b73b3524 100644 --- a/tsp_sdk/src/cesr/decode.rs +++ b/tsp_sdk/src/cesr/decode.rs @@ -114,6 +114,20 @@ pub fn decode_variable_data_mut( Some((slice, stream)) } +pub fn opt_decode_variable_data_mut( + identifier: u32, + stream: &mut [u8], +) -> (Option<&[u8]>, &mut [u8]) { + let Some(range) = decode_variable_data_index(identifier, stream, &mut 0) else { + return (None, stream); + }; + + let (prefix, stream) = stream.split_at_mut(range.end); + let slice = &mut prefix[range.start..]; + + (Some(slice), stream) +} + /// Decode indexed data with a known identifier #[allow(dead_code)] pub fn decode_indexed_data<'a, const N: usize>( diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 54cd0789..12923310 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -52,7 +52,7 @@ const YTSP: [u8; 3] = cesr_data("YTSP"); use super::{ decode::{ decode_count, decode_count_mut, decode_fixed_data, decode_fixed_data_mut, - decode_variable_data_index, decode_variable_data_mut, + decode_variable_data_index, decode_variable_data_mut, opt_decode_variable_data_mut, }, encode::{encode_count, encode_fixed_data}, error::{DecodeError, EncodeError}, @@ -432,19 +432,8 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod let _count; (_count, stream) = decode_count_mut(TSP_PAYLOAD, stream).ok_or(DecodeError::UnexpectedData)?; - let sender_identity: Option<&[u8]> = { - let mut pos = 0; - if let Some(essr_prefix) = decode_variable_data_index(TSP_VID, stream, &mut pos) { - let prefix; - (prefix, stream) = stream - .split_at_mut_checked(essr_prefix.end) - .ok_or(DecodeError::VidError)?; - - Some(&prefix[essr_prefix]) - } else { - None - } - }; + let sender_identity; + (sender_identity, stream) = opt_decode_variable_data_mut(TSP_VID, stream); let (msgtype, mut stream) = stream .split_at_mut_checked(3) From c1a44adf9ead32c191eb2d9b9d5b59e14609aa82 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 17:23:53 +0200 Subject: [PATCH 15/27] add X3RR Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 12923310..fb3b0f54 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -39,6 +39,8 @@ mod msgtype { const TSP_TMP: u32 = cesr!("X"); /// Constants for payload field types +// TODO: it is unclear what we are to do with 'generic' control messages. +#[allow(unused)] const XCTL: [u8; 3] = cesr_data("XCTL"); const XSCS: [u8; 3] = cesr_data("XSCS"); const XHOP: [u8; 3] = cesr_data("XHOP"); @@ -49,6 +51,9 @@ const XRFA: [u8; 3] = cesr_data("XRFA"); const XRFD: [u8; 3] = cesr_data("XRFD"); const YTSP: [u8; 3] = cesr_data("YTSP"); +// TODO: a temporary code for third party referrals +const X3RR: [u8; 3] = cesr_data("X3RR"); + use super::{ decode::{ decode_count, decode_count_mut, decode_fixed_data, decode_fixed_data_mut, @@ -353,7 +358,7 @@ pub fn encode_payload( checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; } Payload::RelationshipReferral { referred_vid } => { - output.extend(&msgtype::THIRDP_REFER_REL); + output.extend(&X3RR); checked_encode_variable_data(TSP_VID, referred_vid.as_ref(), output)?; } Payload::RelationshipCancel { reply } => { @@ -514,7 +519,7 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod Payload::NewIdentifierProposal { thread_id, new_vid } } - msgtype::THIRDP_REFER_REL => { + X3RR => { let referred_vid: &[u8]; (referred_vid, stream) = decode_variable_data_mut(TSP_VID, stream).ok_or(DecodeError::UnexpectedData)?; From 60685772a0ec603e172c9c159bacca6c5371f379 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 17:46:31 +0200 Subject: [PATCH 16/27] esthetic touchups Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index fb3b0f54..16db9ce5 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -453,26 +453,27 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod Payload::GenericMessage(msg) } XHOP => { - let (hop_list, upd_stream) = decode_hops(stream)?; - let msg; + let (hop_list, msg); + (hop_list, stream) = decode_hops(stream)?; if hop_list.is_empty() { - (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, upd_stream) + (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, stream) .ok_or(DecodeError::UnexpectedData)?; Payload::NestedMessage(msg) } else { - (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, upd_stream) + (msg, stream) = checked_decode_variable_data_mut(TSP_PLAINTEXT, stream) .ok_or(DecodeError::UnexpectedData)?; Payload::RoutedMessage(hop_list, msg) } } XRFI => { - let (hop_list, upd_stream) = decode_hops(stream)?; + let hop_list; + (hop_list, stream) = decode_hops(stream)?; let nonce; (nonce, stream) = - decode_fixed_data_mut(TSP_NONCE, upd_stream).ok_or(DecodeError::UnexpectedData)?; + decode_fixed_data_mut(TSP_NONCE, stream).ok_or(DecodeError::UnexpectedData)?; Payload::DirectRelationProposal { nonce: Nonce(*nonce), From 20eb48125ca2290747e67c4dcb398636270ad702 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 18 Aug 2025 18:06:03 +0200 Subject: [PATCH 17/27] merge XRFI for referral and initiation Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 16db9ce5..51f0bf4d 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -331,6 +331,7 @@ pub fn encode_payload( output.extend(&XRFI); encode_hops(hops, output)?; encode_fixed_data(TSP_NONCE, &nonce.0, output); + checked_encode_variable_data(TSP_VID, &[], output)?; } Payload::DirectRelationAffirm { reply } => { output.extend(&XRFA); @@ -353,9 +354,12 @@ pub fn encode_payload( encode_digest(reply, output); } Payload::NewIdentifierProposal { thread_id, new_vid } => { - output.extend(&msgtype::NEW_REFER_REL); - encode_digest(thread_id, output); + output.extend(&XRFI); + let no_hops: [&[u8]; 0] = []; + encode_hops(&no_hops, output)?; + encode_fixed_data(TSP_NONCE, &[0; 32], output); // this does not need to be a secure nonce checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; + encode_digest(thread_id, output); } Payload::RelationshipReferral { referred_vid } => { output.extend(&X3RR); @@ -475,9 +479,20 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod (nonce, stream) = decode_fixed_data_mut(TSP_NONCE, stream).ok_or(DecodeError::UnexpectedData)?; - Payload::DirectRelationProposal { - nonce: Nonce(*nonce), - hops: hop_list, + let new_vid: &[u8]; + (new_vid, stream) = + decode_variable_data_mut(TSP_VID, stream).ok_or(DecodeError::UnexpectedData)?; + + if new_vid.is_empty() { + Payload::DirectRelationProposal { + nonce: Nonce(*nonce), + hops: hop_list, + } + } else { + let thread_id; + (thread_id, stream) = decode_digest(stream)?; + + Payload::NewIdentifierProposal { thread_id, new_vid } } } XRFA => { @@ -512,14 +527,6 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod reply, } } - msgtype::NEW_REFER_REL => { - let (thread_id, upd_stream) = decode_digest(stream)?; - let new_vid: &[u8]; - (new_vid, stream) = - decode_variable_data_mut(TSP_VID, upd_stream).ok_or(DecodeError::UnexpectedData)?; - - Payload::NewIdentifierProposal { thread_id, new_vid } - } X3RR => { let referred_vid: &[u8]; (referred_vid, stream) = From 987e3fda78abd52db012659b56d7e94e1c661d10 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Wed, 24 Sep 2025 16:50:45 +0200 Subject: [PATCH 18/27] derive crypto from CESR Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/error.rs | 2 + tsp_sdk/src/cesr/packet.rs | 130 ++++++++++++++++++++++++--------- tsp_sdk/src/crypto/tsp_hpke.rs | 6 +- tsp_sdk/src/crypto/tsp_nacl.rs | 3 + 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/tsp_sdk/src/cesr/error.rs b/tsp_sdk/src/cesr/error.rs index 47e919c2..3946910a 100644 --- a/tsp_sdk/src/cesr/error.rs +++ b/tsp_sdk/src/cesr/error.rs @@ -18,6 +18,8 @@ pub enum DecodeError { InvalidCryptoType, InvalidSignatureType, MissingHops, + UnknownCrypto, + InvalidCrypto, } impl std::fmt::Display for EncodeError { diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 51f0bf4d..8a092cfb 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -5,7 +5,12 @@ const TSP_VERSION: (u16, u8, u8) = (0, 0, 1); /// Constants that determine the specific CESR types for "variable length data" const TSP_PLAINTEXT: u32 = cesr!("B"); -const TSP_CIPHERTEXT: u32 = cesr!("C"); +const TSP_NACL_CIPHERTEXT: u32 = cesr!("C"); +const TSP_NACLAUTH_CIPHERTEXT: u32 = cesr!("NCL"); +const TSP_HPKEBASE_CIPHERTEXT: u32 = cesr!("F"); +const TSP_HPKEAUTH_CIPHERTEXT: u32 = cesr!("G"); +#[cfg(feature = "pq")] +const TSP_HPKEPQ_CIPHERTEXT: u32 = cesr!("PQC"); const TSP_VID: u32 = cesr!("B"); /// Constants that determine the specific CESR types for "fixed length data" @@ -39,7 +44,7 @@ mod msgtype { const TSP_TMP: u32 = cesr!("X"); /// Constants for payload field types -// TODO: it is unclear what we are to do with 'generic' control messages. +// NOTE: this is for future extensibility #[allow(unused)] const XCTL: [u8; 3] = cesr_data("XCTL"); const XSCS: [u8; 3] = cesr_data("XSCS"); @@ -51,13 +56,14 @@ const XRFA: [u8; 3] = cesr_data("XRFA"); const XRFD: [u8; 3] = cesr_data("XRFD"); const YTSP: [u8; 3] = cesr_data("YTSP"); -// TODO: a temporary code for third party referrals +// FIXME: a temporary code for third party referrals const X3RR: [u8; 3] = cesr_data("X3RR"); use super::{ decode::{ decode_count, decode_count_mut, decode_fixed_data, decode_fixed_data_mut, - decode_variable_data_index, decode_variable_data_mut, opt_decode_variable_data_mut, + decode_variable_data, decode_variable_data_index, decode_variable_data_mut, + opt_decode_variable_data_mut, }, encode::{encode_count, encode_fixed_data}, error::{DecodeError, EncodeError}, @@ -144,7 +150,7 @@ impl, Vid: AsRef<[u8]>> Payload<'_, Bytes, Vid> { #[cfg(feature = "fuzzing")] pub mod fuzzing; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] #[repr(u8)] pub enum CryptoType { Plaintext = 0, @@ -196,7 +202,7 @@ impl CryptoType { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] #[repr(u8)] pub enum SignatureType { NoSignature = 0, @@ -205,6 +211,13 @@ pub enum SignatureType { MlDsa65 = 2, } +impl SignatureType { + #[allow(unused)] + pub(crate) fn is_signed(&self) -> bool { + !matches!(self, SignatureType::NoSignature) + } +} + impl TryFrom for SignatureType { type Error = DecodeError; @@ -247,8 +260,8 @@ fn checked_encode_variable_data( if payload.len() >= DATA_LIMIT { // since blobs have no identifier, that information is lost on large payloads and a "blob" can only be used - // for TSP_PLAINTEXT or TSP_CIPHERTEXT. - if identifier == TSP_PLAINTEXT || identifier == TSP_CIPHERTEXT { + // for TSP_PLAINTEXT or TSP_HPKEAUTH_CIPHERTEXT. + if identifier == TSP_PLAINTEXT || identifier == TSP_HPKEAUTH_CIPHERTEXT { super::encode::encode_large_blob(payload, stream); } else { return Err(EncodeError::ExcessiveFieldSize); @@ -283,7 +296,7 @@ fn checked_decode_variable_data_index( } else { // since blobs have no identifier, that information is lost on large payloads and a "blob" can only be used // for TSP_PLAINTEXT or TSP_CIPHERTEXT. - if identifier == TSP_PLAINTEXT || identifier == TSP_CIPHERTEXT { + if identifier == TSP_PLAINTEXT || identifier == TSP_HPKEAUTH_CIPHERTEXT { let mut range = super::decode::decode_large_blob_index(&stream[*pos..])?; range.start += *pos; range.end += *pos; @@ -358,6 +371,7 @@ pub fn encode_payload( let no_hops: [&[u8]; 0] = []; encode_hops(&no_hops, output)?; encode_fixed_data(TSP_NONCE, &[0; 32], output); // this does not need to be a secure nonce + //TODO checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; encode_digest(thread_id, output); } @@ -437,7 +451,7 @@ pub fn encode_digest(digest: &Digest, output: &mut impl for<'a> Extend<&'a u8>) /// Decode a TSP Payload pub fn decode_payload(mut stream: &mut [u8]) -> Result, DecodeError> { - //NOTE: should we do something with this count? + //NOTE: we do not need the quadlet count let _count; (_count, stream) = decode_count_mut(TSP_PAYLOAD, stream).ok_or(DecodeError::UnexpectedData)?; @@ -615,11 +629,8 @@ fn encode_envelope_fields<'a, Vid: AsRef<[u8]>>( checked_encode_variable_data(TSP_VID, rec.as_ref(), output)?; } - encode_fixed_data( - TSP_TMP, - &[envelope.crypto_type as u8, envelope.signature_type as u8], - output, - ); + // FIXME: without this parsing errors seem to occur -- maybe there is am ambiguity + encode_fixed_data(TSP_TMP, &[0u8, 0u8], output); if let Some(data) = envelope.nonconfidential_data { checked_encode_variable_data(TSP_PLAINTEXT, data, output)?; @@ -646,12 +657,27 @@ pub fn encode_signature( } } +impl CryptoType { + fn cesr_code(&self) -> Result { + Ok(match self { + CryptoType::NaclEssr => TSP_NACL_CIPHERTEXT, + CryptoType::HpkeEssr => TSP_HPKEBASE_CIPHERTEXT, + CryptoType::HpkeAuth => TSP_HPKEAUTH_CIPHERTEXT, + CryptoType::NaclAuth => TSP_NACLAUTH_CIPHERTEXT, + #[cfg(feature = "pq")] + CrytpoType::X25519Kyber768Draft00 => TSP_HPKEPQ_CIPHERTEXT, + _ => return Err(DecodeError::InvalidCrypto), + }) + } +} + /// Encode a encrypted ciphertext into CESR pub fn encode_ciphertext( ciphertext: &[u8], + crypto: CryptoType, output: &mut impl for<'a> Extend<&'a u8>, ) -> Result<(), EncodeError> { - checked_encode_variable_data(TSP_CIPHERTEXT, ciphertext, output) + checked_encode_variable_data(crypto.cesr_code().unwrap(), ciphertext, output) } /// Checks whether the expected TSP header is present and returns its size and whether it @@ -690,20 +716,55 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( let mut stream = &origin[mid_pos..]; - let (crypto_type, signature_type) = match decode_fixed_data(TSP_TMP, &mut stream) { - Some([crypto, signature]) => { - let crypto_type = CryptoType::try_from(*crypto)?; + if let Some([_crypto_type, _signature_type]) = decode_fixed_data(TSP_TMP, &mut stream) {} - if crypto_type.is_encrypted() != encrypted { - return Err(DecodeError::VersionMismatch); - } + *pos += origin.len() - stream.len(); + + /* look ahead to determine the crypto and signature types */ + let _nonconf_data = decode_variable_data(TSP_PLAINTEXT, &mut stream); - (crypto_type, SignatureType::try_from(*signature)?) + let crypto_type = if decode_variable_data(TSP_HPKEAUTH_CIPHERTEXT, &mut stream).is_some() { + CryptoType::HpkeAuth + } else if decode_variable_data(TSP_HPKEBASE_CIPHERTEXT, &mut stream).is_some() { + CryptoType::HpkeEssr + } else if decode_variable_data(TSP_NACL_CIPHERTEXT, &mut stream).is_some() { + CryptoType::NaclEssr + } else if decode_variable_data(TSP_NACLAUTH_CIPHERTEXT, &mut stream).is_some() { + CryptoType::NaclAuth + } else { + #[cfg(feature = "pq")] + if decode_variable_data(TSP_HPKEPQ_CIPHERTEXT, &mut stream).is_some() { + CryptoType::X25519Kyber768Draft00 + } else if encrypted { + return Err(DecodeError::UnknownCrypto); + } else { + CryptoType::Plaintext + } + #[cfg(not(feature = "pq"))] + if encrypted { + return Err(DecodeError::UnknownCrypto); + } else { + CryptoType::Plaintext } - _ => return Err(DecodeError::VersionMismatch), }; - *pos += origin.len() - stream.len(); + if encrypted != crypto_type.is_encrypted() { + return Err(DecodeError::InvalidCrypto); + } + + let signature_type = if decode_fixed_data::<64>(ED25519_SIGNATURE, &mut stream).is_some() { + SignatureType::Ed25519 + } else { + #[cfg(feature = "pq")] + if code_fixed_data::<3309>(ML_DSA_65_SIGNATURE, &mut stream).is_some() { + SignatureType::MlDsa65 + } else { + SignatureType::NoSignature + } + #[cfg(not(feature = "pq"))] + SignatureType::NoSignature + }; + Ok((sender, receiver, crypto_type, signature_type)) } @@ -814,7 +875,7 @@ impl<'a> CipherView<'a> { } pub(crate) fn signature_type(&self) -> SignatureType { - self.signature_type.clone() + self.signature_type } } @@ -831,7 +892,7 @@ pub fn decode_envelope<'a>(stream: &'a mut [u8]) -> Result, Decod let ciphertext = if crypto_type.is_encrypted() { Some( - checked_decode_variable_data_index(TSP_CIPHERTEXT, stream, &mut pos) + checked_decode_variable_data_index(crypto_type.cesr_code()?, stream, &mut pos) .ok_or(DecodeError::UnexpectedData)?, ) } else { @@ -972,7 +1033,7 @@ pub fn open_message_into_parts(data: &[u8]) -> Result, DecodeEr data: &data[r], }); let nonconfidential_data = Part::decode(TSP_PLAINTEXT, data, &mut pos); - let ciphertext = Part::decode(TSP_CIPHERTEXT, data, &mut pos); + let ciphertext = Part::decode(crypto_type.cesr_code()?, data, &mut pos); let signature: &[u8; 64] = decode_fixed_data(ED25519_SIGNATURE, &mut &data[pos..]) .ok_or(DecodeError::SignatureError)?; @@ -1112,7 +1173,7 @@ mod test { }) .unwrap(); let ciphertext = dummy_crypt(&mut cesr_payload); - encode_ciphertext(ciphertext, &mut outer).unwrap(); + encode_ciphertext(ciphertext, CryptoType::HpkeAuth, &mut outer).unwrap(); let signed_data = outer.clone(); encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); @@ -1160,7 +1221,7 @@ mod test { }) .unwrap(); let ciphertext = dummy_crypt(&mut cesr_payload); - encode_ciphertext(ciphertext, &mut outer).unwrap(); + encode_ciphertext(ciphertext, CryptoType::HpkeAuth, &mut outer).unwrap(); let signed_data = outer.clone(); encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); @@ -1241,7 +1302,7 @@ mod test { }) .unwrap(); let ciphertext = dummy_crypt(&cesr_payload); // this is wrong - encode_ciphertext(ciphertext, &mut outer).unwrap(); + encode_ciphertext(ciphertext, CryptoType::HpkeAuth, &mut outer).unwrap(); encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); assert!(decode_envelope(&mut outer).is_err()); @@ -1265,7 +1326,7 @@ mod test { ) .unwrap(); encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); - encode_ciphertext(&[], &mut outer).unwrap(); + encode_ciphertext(&[], CryptoType::HpkeAuth, &mut outer).unwrap(); assert!(decode_envelope(&mut outer).is_err()); } @@ -1283,7 +1344,7 @@ mod test { nonconfidential_data: Some(b"treasure"), }) .unwrap(); - encode_ciphertext(&[], &mut outer).unwrap(); + encode_ciphertext(&[], CryptoType::HpkeAuth, &mut outer).unwrap(); encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); outer.push(b'-'); @@ -1379,7 +1440,7 @@ mod test { }) .unwrap(); let ciphertext = dummy_crypt(&mut cesr_payload); - encode_ciphertext(ciphertext, &mut outer).unwrap(); + encode_ciphertext(ciphertext, CryptoType::HpkeAuth, &mut outer).unwrap(); let signed_data = outer.clone(); encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); @@ -1441,6 +1502,7 @@ mod test { }); } + #[ignore] #[test] #[wasm_bindgen_test] fn test_message_to_parts() { diff --git a/tsp_sdk/src/crypto/tsp_hpke.rs b/tsp_sdk/src/crypto/tsp_hpke.rs index a660ad23..7eee4c6d 100644 --- a/tsp_sdk/src/crypto/tsp_hpke.rs +++ b/tsp_sdk/src/crypto/tsp_hpke.rs @@ -54,9 +54,11 @@ where VidSignatureKeyType::MlDsa65 => SignatureType::MlDsa65, }; + let crypto_type = Kem::crypto_type(); + crate::cesr::encode_ets_envelope( crate::cesr::Envelope { - crypto_type: Kem::crypto_type(), + crypto_type, signature_type, sender: sender.identifier(), receiver: Some(receiver.identifier()), @@ -162,7 +164,7 @@ where cesr_message.extend(encapped_key.to_bytes()); // encode and append the ciphertext to the envelope data - crate::cesr::encode_ciphertext(&cesr_message, &mut data)?; + crate::cesr::encode_ciphertext(&cesr_message, crypto_type, &mut data)?; // create and append signature match sender.signature_key_type() { diff --git a/tsp_sdk/src/crypto/tsp_nacl.rs b/tsp_sdk/src/crypto/tsp_nacl.rs index 43f28674..679b76c6 100644 --- a/tsp_sdk/src/crypto/tsp_nacl.rs +++ b/tsp_sdk/src/crypto/tsp_nacl.rs @@ -32,6 +32,9 @@ pub(crate) fn seal( let mut data = Vec::with_capacity(64); crate::cesr::encode_ets_envelope( crate::cesr::Envelope { + #[cfg(feature = "essr")] + crypto_type: CryptoType::NaclEssr, + #[cfg(not(feature = "essr"))] crypto_type: CryptoType::NaclAuth, signature_type: SignatureType::Ed25519, sender: sender.identifier(), From d01e3e7c532a956a9a3bb2f405ecbbc265bbda46 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 10:22:27 +0200 Subject: [PATCH 19/27] there is no provision for large data Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/decode.rs | 34 -------------------------- tsp_sdk/src/cesr/encode.rs | 17 ------------- tsp_sdk/src/cesr/mod.rs | 20 ---------------- tsp_sdk/src/cesr/packet.rs | 49 ++++---------------------------------- 4 files changed, 4 insertions(+), 116 deletions(-) diff --git a/tsp_sdk/src/cesr/decode.rs b/tsp_sdk/src/cesr/decode.rs index b73b3524..e782d077 100644 --- a/tsp_sdk/src/cesr/decode.rs +++ b/tsp_sdk/src/cesr/decode.rs @@ -208,37 +208,3 @@ pub fn decode_genus( Some(()) } - -/// Decode variable size data (intended size >=50mb) that doesn't fit in known CESR encodings. -/// -/// Since CESR has no native type for this, we use the convention that "big data" -/// Is announced by encoding the size as a 64bit integer (prefix "N"), which is then -/// followed by the raw data padded in the usual sense for CESR (pre-padding) -/// This is a temporary encoding, depending on how CESR will address this in the future. -pub fn decode_large_blob_index(stream: &[u8]) -> Option> { - let selector = b'N' - b'A'; - let size = u64::from_be_bytes(*decode_fixed_data( - selector as u32, - &mut <_>::clone(&stream), - )?) as usize; - let padded_size = size.next_multiple_of(3); - let lead_bytes = padded_size - size; - - let start = 9usize.checked_add(lead_bytes)?; - let end = 9usize.checked_add(padded_size)?; - - let range = start..end; - // make sure the range is valid before returning it - stream.get(range.clone())?; - - Some(range) -} - -#[cfg(test)] -pub fn decode_large_blob<'a>(stream: &mut &'a [u8]) -> Option<&'a [u8]> { - let range = decode_large_blob_index(stream)?; - let slice = &stream[range.start..range.end]; - *stream = &stream[range.end..]; - - Some(slice) -} diff --git a/tsp_sdk/src/cesr/encode.rs b/tsp_sdk/src/cesr/encode.rs index 83d9d988..e1b0ba62 100644 --- a/tsp_sdk/src/cesr/encode.rs +++ b/tsp_sdk/src/cesr/encode.rs @@ -88,20 +88,3 @@ pub fn encode_genus( stream.extend(&u32::to_be_bytes(word1)[1..]); stream.extend(&u32::to_be_bytes(word2)[1..]); } - -/// Encode variable size data (intended size >=50mb) that doesn't fit in known CESR encodings. -/// -/// Since CESR has no native type for this, we use the convention that "big data" -/// Is announced by encoding the size as a 64bit integer (prefix "N"), which is then -/// followed by the raw data padded in the usual sense for CESR (pre-padding) -/// This is a temporary encoding, depending on how CESR will address this in the future. -pub fn encode_large_blob(payload: &[u8], stream: &mut impl for<'a> Extend<&'a u8>) { - let size = payload.len() as u64; - let padded_size = size.next_multiple_of(3); - let lead_bytes = padded_size - size; - - let selector = (b'N' - b'A') as u32; - encode_fixed_data(selector, &u64::to_be_bytes(size), stream); - stream.extend(&<[u8; 2]>::default()[0..lead_bytes as usize]); - stream.extend(payload); -} diff --git a/tsp_sdk/src/cesr/mod.rs b/tsp_sdk/src/cesr/mod.rs index d4c6a3d8..882b5040 100644 --- a/tsp_sdk/src/cesr/mod.rs +++ b/tsp_sdk/src/cesr/mod.rs @@ -423,24 +423,4 @@ ACTD7NDX93ZGTkZBBuSeSGsAQ7u0hngpNTZTK_Um7rUZGnLRNJvo5oOnnC1J2iBQHuxoq8PyjdT3BHS2 assert_eq!(decode_indexed_data::<64>(0, slice).unwrap().0, 1); assert_eq!(decode_indexed_data::<64>(0, slice).unwrap().0, 2); } - - #[test] - fn blob_encode_decode() { - let mut data = vec![]; - encode_large_blob(b"Where there is power, there is resistance.", &mut data); // 0 lead bytes - encode_large_blob(b"TrustSpanP!", &mut data); // 1 lead byte - encode_large_blob(b"I always speak the truth. Not the whole truth, because there's no way, to say it all.", &mut data); // 2 lead bytes - encode_variable_data(5, b"TrustSpanP!", &mut data); // not a blob - let mut input = &data[..]; - assert_eq!( - decode_large_blob(&mut input).unwrap(), - b"Where there is power, there is resistance." - ); - assert_eq!(decode_large_blob(&mut input).unwrap(), b"TrustSpanP!"); - assert_eq!( - decode_large_blob(&mut input).unwrap(), - b"I always speak the truth. Not the whole truth, because there's no way, to say it all." - ); - assert!(decode_large_blob(&mut input).is_none()); - } } diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 8a092cfb..f931384c 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -259,13 +259,7 @@ fn checked_encode_variable_data( const DATA_LIMIT: usize = 3 * (1 << 24); if payload.len() >= DATA_LIMIT { - // since blobs have no identifier, that information is lost on large payloads and a "blob" can only be used - // for TSP_PLAINTEXT or TSP_HPKEAUTH_CIPHERTEXT. - if identifier == TSP_PLAINTEXT || identifier == TSP_HPKEAUTH_CIPHERTEXT { - super::encode::encode_large_blob(payload, stream); - } else { - return Err(EncodeError::ExcessiveFieldSize); - } + return Err(EncodeError::ExcessiveFieldSize); } else { super::encode::encode_variable_data(identifier, payload, stream); } @@ -273,7 +267,7 @@ fn checked_encode_variable_data( Ok(()) } -/// Safely decode variable data, detecting blobs +/// Safely decode variable data fn checked_decode_variable_data_mut( identifier: u32, stream: &mut [u8], @@ -285,28 +279,13 @@ fn checked_decode_variable_data_mut( Some((slice, stream)) } -/// Safely decode variable data, detecting blobs +/// Safely decode variable data fn checked_decode_variable_data_index( identifier: u32, stream: &[u8], pos: &mut usize, ) -> Option> { - if let Some(result) = decode_variable_data_index(identifier, stream, pos) { - Some(result) - } else { - // since blobs have no identifier, that information is lost on large payloads and a "blob" can only be used - // for TSP_PLAINTEXT or TSP_CIPHERTEXT. - if identifier == TSP_PLAINTEXT || identifier == TSP_HPKEAUTH_CIPHERTEXT { - let mut range = super::decode::decode_large_blob_index(&stream[*pos..])?; - range.start += *pos; - range.end += *pos; - *pos = range.end; - - Some(range) - } else { - None - } - } + decode_variable_data_index(identifier, stream, pos) } /// Encode a TSP Payload into CESR for encryption @@ -1516,24 +1495,4 @@ mod test { assert_eq!(parts.receiver.unwrap().data.len(), 14); assert_eq!(parts.ciphertext.unwrap().data.len(), 69); } - - #[test] - fn test_blob() { - let payload = vec![b'M'; 50]; - let mut data = vec![]; - checked_encode_variable_data(TSP_PLAINTEXT, &payload, &mut data).unwrap(); - let input = &mut data[..]; - let (source, _) = decode_variable_data_mut(TSP_PLAINTEXT, input).unwrap(); - assert!(source.len() == 50); - let (source, _) = checked_decode_variable_data_mut(TSP_PLAINTEXT, input).unwrap(); - assert!(source.len() == 50); - - let payload = vec![b'M'; 60_000_000]; - let mut data = vec![]; - checked_encode_variable_data(TSP_PLAINTEXT, &payload, &mut data).unwrap(); - let input = &mut data[..]; - assert!(decode_variable_data_mut(TSP_PLAINTEXT, input).is_none()); - let (source, _) = checked_decode_variable_data_mut(TSP_PLAINTEXT, input).unwrap(); - assert!(source.len() == 60_000_000); - } } From 7d9a4303c07375a724a9c015199836b09b08ec7f Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 10:33:33 +0200 Subject: [PATCH 20/27] add large counts Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/decode.rs | 13 ++++++++++--- tsp_sdk/src/cesr/encode.rs | 20 +++++++++++++++++--- tsp_sdk/src/cesr/mod.rs | 2 +- tsp_sdk/src/cesr/packet.rs | 11 +++++------ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/tsp_sdk/src/cesr/decode.rs b/tsp_sdk/src/cesr/decode.rs index e782d077..3b9317b7 100644 --- a/tsp_sdk/src/cesr/decode.rs +++ b/tsp_sdk/src/cesr/decode.rs @@ -168,22 +168,29 @@ pub fn decode_indexed_data<'a, const N: usize>( } /// Decode a frame with known identifier and size -pub fn decode_count(identifier: u16, stream: &mut &[u8]) -> Option { +pub fn decode_count(identifier: u16, stream: &mut &[u8]) -> Option { let word = extract_triplet(stream.get(0..=2)?.try_into().unwrap()); let index = word & mask(12); let expected = (DASH << 18) | (bits(identifier, 6) << 12) | bits(index, 12); + let expected_long = + (DASH << 18) | D0 << 12 | (bits(identifier, 6) << 6) | bits(index & 0x3F, 6); if word == expected { *stream = &stream[3..]; - Some(index as u16) + Some((index as u16).into()) + } else if word == expected_long { + let next = extract_triplet(stream.get(3..=5)?.try_into().unwrap()); + *stream = &stream[6..]; + + Some(index << 24 | next) } else { None } } /// Decode a frame with known identifier and size -pub fn decode_count_mut(identifier: u16, stream: &mut [u8]) -> Option<(u16, &mut [u8])> { +pub fn decode_count_mut(identifier: u16, stream: &mut [u8]) -> Option<(u32, &mut [u8])> { let mut const_stream: &[u8] = stream; let count = decode_count(identifier, &mut const_stream)?; let offset = stream.len() - const_stream.len(); diff --git a/tsp_sdk/src/cesr/encode.rs b/tsp_sdk/src/cesr/encode.rs index e1b0ba62..0bfe051d 100644 --- a/tsp_sdk/src/cesr/encode.rs +++ b/tsp_sdk/src/cesr/encode.rs @@ -68,10 +68,24 @@ pub fn encode_variable_data( } /// Encode a frame with known identifier and count code -pub fn encode_count(identifier: u16, count: u16, stream: &mut impl for<'a> Extend<&'a u8>) { - let word = (DASH << 18) | (bits(identifier, 6) << 12) | bits(count, 12); +pub fn encode_count( + identifier: u16, + count: impl Into, + stream: &mut impl for<'a> Extend<&'a u8>, +) { + let count: usize = count.into(); + let count: u32 = count.try_into().unwrap(); + if count < 4096 { + let word = (DASH << 18) | (bits(identifier, 6) << 12) | bits(count, 12); + + stream.extend(&u32::to_be_bytes(word)[1..]); + } else { + let word1 = (DASH << 18) | (D0 << 12) | (bits(identifier, 6) << 6) | bits(count >> 24, 6); + let word2 = bits(count, 24); - stream.extend(&u32::to_be_bytes(word)[1..]); + stream.extend(&u32::to_be_bytes(word1)[1..]); + stream.extend(&u32::to_be_bytes(word2)[1..]); + } } /// Encode a genus with known identifier and version diff --git a/tsp_sdk/src/cesr/mod.rs b/tsp_sdk/src/cesr/mod.rs index 882b5040..e074a541 100644 --- a/tsp_sdk/src/cesr/mod.rs +++ b/tsp_sdk/src/cesr/mod.rs @@ -187,7 +187,7 @@ mod test { &mut data, ); // 1 lead byte encode_variable_data(42, b"I always speak the truth. Not the whole truth, because there's no way, to say it all.", &mut data); // 2 lead bytes - encode_count(7, 2, &mut data); + encode_count(7, 2usize, &mut data); encode_indexed_data(5, 57, b"DON'T PANIC!", &mut data); // 0 lead bytes encode_indexed_data(5, 0, b"SECRET KEY", &mut data); // 2 lead bytes diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index f931384c..f1c0b22d 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -294,8 +294,7 @@ pub fn encode_payload( sender_identity: Option<&[u8]>, output: &mut impl for<'a> Extend<&'a u8>, ) -> Result<(), EncodeError> { - //TODO do something with the payload count - encode_count(TSP_PAYLOAD, 1, output); + encode_count(TSP_PAYLOAD, 1usize, output); if let Some(sender_identity) = sender_identity { checked_encode_variable_data(TSP_VID, sender_identity, output)?; } @@ -580,8 +579,8 @@ pub fn encode_ets_envelope<'a, Vid: AsRef<[u8]>>( envelope: Envelope<'a, Vid>, output: &mut impl for<'b> Extend<&'b u8>, ) -> Result<(), EncodeError> { - //TODO: encode the count of the data to be signed - encode_count(TSP_ETS_WRAPPER, 1, output); + // TODO: we don't know the size yet + encode_count(TSP_ETS_WRAPPER, 1usize, output); encode_envelope_fields(envelope, output) } @@ -590,7 +589,7 @@ pub fn encode_s_envelope<'a, Vid: AsRef<[u8]>>( envelope: Envelope<'a, Vid>, output: &mut impl for<'b> Extend<&'b u8>, ) -> Result<(), EncodeError> { - encode_count(TSP_S_WRAPPER, 1, output); + encode_count(TSP_S_WRAPPER, 1usize, output); encode_envelope_fields(envelope, output) } @@ -676,7 +675,7 @@ pub(super) fn detected_tsp_header_size_and_confidentiality( > { let origin = stream; let mut stream = &origin[*pos..]; - //NOTE: do something with the quadlet count? + //NOTE: we don't need this quadlet count let encrypted = if let Some(_quadlet_count) = decode_count(TSP_ETS_WRAPPER, &mut stream) { true } else if let Some(1) = decode_count(TSP_S_WRAPPER, &mut stream) { From 6bd83b4f4c4e6346c4f504128a66c7d1f2a12f34 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 11:29:49 +0200 Subject: [PATCH 21/27] fix nacl test Signed-off-by: Marc Schoolderman --- tsp_sdk/src/crypto/tsp_nacl.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tsp_sdk/src/crypto/tsp_nacl.rs b/tsp_sdk/src/crypto/tsp_nacl.rs index 679b76c6..05a8272d 100644 --- a/tsp_sdk/src/crypto/tsp_nacl.rs +++ b/tsp_sdk/src/crypto/tsp_nacl.rs @@ -122,7 +122,15 @@ pub(crate) fn seal( cesr_message.extend(nonce); // encode and append the ciphertext to the envelope data - crate::cesr::encode_ciphertext(&cesr_message, &mut data)?; + crate::cesr::encode_ciphertext( + &cesr_message, + if cfg!(feature = "essr") { + CryptoType::NaclEssr + } else { + CryptoType::NaclAuth + }, + &mut data, + )?; // create and append signature match sender.signature_key_type() { From 11521a487d44622746f165f864c636d30d149203 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 15:51:16 +0200 Subject: [PATCH 22/27] fix fuzzing error Signed-off-by: Marc Schoolderman --- fuzz/fuzz_targets/payload_encode_decode.rs | 1 + tsp_sdk/src/cesr/error.rs | 1 + tsp_sdk/src/cesr/packet.rs | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/payload_encode_decode.rs b/fuzz/fuzz_targets/payload_encode_decode.rs index b6a09f7f..def31d48 100644 --- a/fuzz/fuzz_targets/payload_encode_decode.rs +++ b/fuzz/fuzz_targets/payload_encode_decode.rs @@ -15,6 +15,7 @@ fuzz_target!(|data: cesr::fuzzing::Wrapper| { cesr::Payload::RoutedMessage(route, _) => assert!(route.is_empty()), _ => todo!(), }, + Err(cesr::error::EncodeError::InvalidVid) => {} _ => todo!(), } }); diff --git a/tsp_sdk/src/cesr/error.rs b/tsp_sdk/src/cesr/error.rs index 3946910a..f1bab56e 100644 --- a/tsp_sdk/src/cesr/error.rs +++ b/tsp_sdk/src/cesr/error.rs @@ -4,6 +4,7 @@ pub enum EncodeError { ExcessiveFieldSize, MissingHops, MissingReceiver, + InvalidVid, } /// An error type to indicate something went wrong with decoding diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index f1c0b22d..4b02a876 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -345,11 +345,13 @@ pub fn encode_payload( encode_digest(reply, output); } Payload::NewIdentifierProposal { thread_id, new_vid } => { + if new_vid.as_ref().is_empty() { + return Err(EncodeError::InvalidVid); + } output.extend(&XRFI); let no_hops: [&[u8]; 0] = []; encode_hops(&no_hops, output)?; encode_fixed_data(TSP_NONCE, &[0; 32], output); // this does not need to be a secure nonce - //TODO checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; encode_digest(thread_id, output); } From 67d6d9cc86c529a4730e88153f76c7eb6e2b0c96 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 15:54:21 +0200 Subject: [PATCH 23/27] remove dead code Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 4b02a876..8c7e82d7 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -179,23 +179,6 @@ impl AsCryptoType for kem::X25519HkdfSha256 { } } -impl TryFrom for CryptoType { - type Error = DecodeError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(CryptoType::Plaintext), - 1 => Ok(CryptoType::HpkeAuth), - 2 => Ok(CryptoType::HpkeEssr), - 3 => Ok(CryptoType::NaclAuth), - 4 => Ok(CryptoType::NaclEssr), - #[cfg(feature = "pq")] - 5 => Ok(CryptoType::X25519Kyber768Draft00), - _ => Err(DecodeError::InvalidCryptoType), - } - } -} - impl CryptoType { pub(crate) fn is_encrypted(&self) -> bool { !matches!(self, CryptoType::Plaintext) @@ -218,20 +201,6 @@ impl SignatureType { } } -impl TryFrom for SignatureType { - type Error = DecodeError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(SignatureType::NoSignature), - 1 => Ok(SignatureType::Ed25519), - #[cfg(feature = "pq")] - 2 => Ok(SignatureType::MlDsa65), - _ => Err(DecodeError::InvalidSignatureType), - } - } -} - /// Type representing a TSP Envelope #[derive(Debug, Clone)] pub struct Envelope<'a, Vid> { From fc937b42f68f7dc83098f6f22f2bebf80bb74f66 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 17:01:10 +0200 Subject: [PATCH 24/27] add placeholder for signature in NewIdentifierProposal Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 24 ++++++++++++++++++++---- tsp_sdk/src/cesr/packet/fuzzing.rs | 3 +++ tsp_sdk/src/crypto/tsp_hpke.rs | 19 +++++++++++++------ tsp_sdk/src/crypto/tsp_nacl.rs | 16 ++++++++++------ 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 8c7e82d7..7dc17694 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -123,7 +123,11 @@ pub enum Payload<'a, Bytes, Vid> { /// A TSP message confirming a relationship NestedRelationAffirm { message: Bytes, reply: Digest<'a> }, /// A TSP Message establishing a secondary relationship (parallel relationship forming) - NewIdentifierProposal { thread_id: Digest<'a>, new_vid: Vid }, + NewIdentifierProposal { + thread_id: Digest<'a>, + sig_thread_id: &'a Signature, + new_vid: Vid, + }, /// A TSP Message revealing a third party RelationshipReferral { referred_vid: Vid }, /// A TSP cancellation message @@ -313,7 +317,11 @@ pub fn encode_payload( checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; encode_digest(reply, output); } - Payload::NewIdentifierProposal { thread_id, new_vid } => { + Payload::NewIdentifierProposal { + thread_id, + sig_thread_id, + new_vid, + } => { if new_vid.as_ref().is_empty() { return Err(EncodeError::InvalidVid); } @@ -323,6 +331,7 @@ pub fn encode_payload( encode_fixed_data(TSP_NONCE, &[0; 32], output); // this does not need to be a secure nonce checked_encode_variable_data(TSP_VID, new_vid.as_ref(), output)?; encode_digest(thread_id, output); + encode_fixed_data(ED25519_SIGNATURE, sig_thread_id, output); } Payload::RelationshipReferral { referred_vid } => { output.extend(&X3RR); @@ -452,10 +461,16 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod hops: hop_list, } } else { - let thread_id; + let (thread_id, sig_thread_id); (thread_id, stream) = decode_digest(stream)?; + (sig_thread_id, stream) = decode_fixed_data_mut::<64>(ED25519_SIGNATURE, stream) + .ok_or(DecodeError::UnexpectedData)?; - Payload::NewIdentifierProposal { thread_id, new_vid } + Payload::NewIdentifierProposal { + thread_id, + sig_thread_id, + new_vid, + } } } XRFA => { @@ -1360,6 +1375,7 @@ mod test { fn test_par_refer_rel() { test_turn_around(Payload::NewIdentifierProposal { thread_id: Digest::Sha2_256(&Default::default()), + sig_thread_id: &[5; 64], new_vid: b"Charlie", }); } diff --git a/tsp_sdk/src/cesr/packet/fuzzing.rs b/tsp_sdk/src/cesr/packet/fuzzing.rs index ec6b871d..d4f72b4b 100644 --- a/tsp_sdk/src/cesr/packet/fuzzing.rs +++ b/tsp_sdk/src/cesr/packet/fuzzing.rs @@ -79,6 +79,7 @@ impl<'a> arbitrary::Arbitrary<'a> for Wrapper { Variants::NewIdentifierProposal => Payload::NewIdentifierProposal { thread_id: digest(&DIGEST), new_vid: Arbitrary::arbitrary(u)?, + sig_thread_id: &[42; 64], }, Variants::RelationshipReferral => Payload::RelationshipReferral { referred_vid: Arbitrary::arbitrary(u)?, @@ -138,10 +139,12 @@ impl<'a> PartialEq> for Wrapper { Payload::NewIdentifierProposal { new_vid: l_vid, thread_id: l_reply, + sig_thread_id: _l_sig, }, Payload::NewIdentifierProposal { new_vid: r_vid, thread_id: r_reply, + sig_thread_id: _r_sig, }, ) => l_vid == r_vid && l_reply == r_reply, ( diff --git a/tsp_sdk/src/crypto/tsp_hpke.rs b/tsp_sdk/src/crypto/tsp_hpke.rs index 7eee4c6d..72e331d2 100644 --- a/tsp_sdk/src/crypto/tsp_hpke.rs +++ b/tsp_sdk/src/crypto/tsp_hpke.rs @@ -104,7 +104,10 @@ where ref thread_id, new_vid, } => crate::cesr::Payload::NewIdentifierProposal { + //TODO: we need to produce a signature here with `new_vid`, but we don't have the PrivateVid for it at this point and that + //cannot be done without changing the "upper" API. thread_id: crate::cesr::Digest::Sha2_256(thread_id), + sig_thread_id: &[0; 64], new_vid, }, Payload::Referral { referred_vid } => { @@ -280,12 +283,16 @@ where }, crate::cesr::Payload::NestedMessage(data) => Payload::NestedMessage(data), crate::cesr::Payload::RoutedMessage(hops, data) => Payload::RoutedMessage(hops, data as _), - crate::cesr::Payload::NewIdentifierProposal { thread_id, new_vid } => { - Payload::NewIdentifier { - thread_id: *thread_id.as_bytes(), - new_vid, - } - } + crate::cesr::Payload::NewIdentifierProposal { + thread_id, + sig_thread_id: _, + new_vid, + } => Payload::NewIdentifier { + //TODO: the 'sig_thread_id' verification cannot happen at this point, and needs to be bubbled upward + //so it can be checked *after* the VID has been verified and key material retrieved. + thread_id: *thread_id.as_bytes(), + new_vid, + }, crate::cesr::Payload::RelationshipReferral { referred_vid } => { Payload::Referral { referred_vid } } diff --git a/tsp_sdk/src/crypto/tsp_nacl.rs b/tsp_sdk/src/crypto/tsp_nacl.rs index 05a8272d..67488dc1 100644 --- a/tsp_sdk/src/crypto/tsp_nacl.rs +++ b/tsp_sdk/src/crypto/tsp_nacl.rs @@ -211,12 +211,16 @@ pub(crate) fn open<'a>( inner, thread_id: *reply.as_bytes(), }, - crate::cesr::Payload::NewIdentifierProposal { thread_id, new_vid } => { - Payload::NewIdentifier { - thread_id: *thread_id.as_bytes(), - new_vid, - } - } + crate::cesr::Payload::NewIdentifierProposal { + thread_id, + sig_thread_id: _, + new_vid, + } => Payload::NewIdentifier { + //TODO: the sig_thread_id cannot be verified at this point, so needs to be bubbled upwards so it can be checked + //*after* the new VID has been retrieved and verified. + thread_id: *thread_id.as_bytes(), + new_vid, + }, crate::cesr::Payload::RelationshipReferral { referred_vid } => { Payload::Referral { referred_vid } } From 0d36ee8aa421452454da63079a0cef181bc92b8d Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Thu, 25 Sep 2025 17:04:02 +0200 Subject: [PATCH 25/27] replace old codes and remove old defs Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/packet.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 7dc17694..5a666106 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -29,18 +29,6 @@ const TSP_S_WRAPPER: u16 = cesr!("S"); const TSP_HOP_LIST: u16 = cesr!("J"); const TSP_PAYLOAD: u16 = cesr!("Z"); -/// Constants to encode message types -mod msgtype { - pub(super) const GEN_MSG: [u8; 3] = [23 << 2, 0, 0]; - pub(super) const NEST_MSG: [u8; 3] = [23 << 2, 0, 1]; - pub(super) const NEW_REL: [u8; 3] = [23 << 2, 1, 0]; - pub(super) const NEW_REL_REPLY: [u8; 3] = [23 << 2, 1, 1]; - pub(super) const NEW_NEST_REL: [u8; 3] = [23 << 2, 1, 2]; - pub(super) const NEW_NEST_REL_REPLY: [u8; 3] = [23 << 2, 1, 3]; - pub(super) const NEW_REFER_REL: [u8; 3] = [23 << 2, 1, 4]; - pub(super) const THIRDP_REFER_REL: [u8; 3] = [23 << 2, 1, 5]; - pub(super) const REL_CANCEL: [u8; 3] = [23 << 2, 1, 255]; -} const TSP_TMP: u32 = cesr!("X"); /// Constants for payload field types @@ -59,6 +47,10 @@ const YTSP: [u8; 3] = cesr_data("YTSP"); // FIXME: a temporary code for third party referrals const X3RR: [u8; 3] = cesr_data("X3RR"); +// FIXME: a temporary code for nested relationships +const XRNI: [u8; 3] = cesr_data("XRNI"); +const XRNA: [u8; 3] = cesr_data("XRNA"); + use super::{ decode::{ decode_count, decode_count_mut, decode_fixed_data, decode_fixed_data_mut, @@ -305,7 +297,7 @@ pub fn encode_payload( message: data, nonce, } => { - output.extend(&msgtype::NEW_NEST_REL); + output.extend(&XRNI); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; encode_fixed_data(TSP_NONCE, &nonce.0, output); } @@ -313,7 +305,7 @@ pub fn encode_payload( message: data, reply, } => { - output.extend(&msgtype::NEW_NEST_REL_REPLY); + output.extend(&XRNA); checked_encode_variable_data(TSP_PLAINTEXT, data.as_ref(), output)?; encode_digest(reply, output); } @@ -479,7 +471,7 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod Payload::DirectRelationAffirm { reply } } - msgtype::NEW_NEST_REL => { + XRNI => { let data: &mut [u8]; (data, stream) = decode_variable_data_mut(TSP_PLAINTEXT, stream) .ok_or(DecodeError::UnexpectedData)?; @@ -493,7 +485,7 @@ pub fn decode_payload(mut stream: &mut [u8]) -> Result, Decod nonce: Nonce(*nonce), } } - msgtype::NEW_NEST_REL_REPLY => { + XRNA => { let data: &mut [u8]; let reply; (data, stream) = decode_variable_data_mut(TSP_PLAINTEXT, stream) From 8a0bbdd88cc930dd40baa02052f8e04c422ebd6f Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Fri, 26 Sep 2025 12:19:06 +0200 Subject: [PATCH 26/27] fix nacl test Signed-off-by: Marc Schoolderman --- tsp_sdk/src/crypto/tsp_nacl.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tsp_sdk/src/crypto/tsp_nacl.rs b/tsp_sdk/src/crypto/tsp_nacl.rs index 67488dc1..11233dbc 100644 --- a/tsp_sdk/src/crypto/tsp_nacl.rs +++ b/tsp_sdk/src/crypto/tsp_nacl.rs @@ -76,7 +76,10 @@ pub(crate) fn seal( ref thread_id, new_vid, } => crate::cesr::Payload::NewIdentifierProposal { + //TODO: we need to produce a signature here with `new_vid`, but we don't have the PrivateVid for it at this point and that + //cannot be done without changing the "upper" API. thread_id: crate::cesr::Digest::Blake2b256(thread_id), + sig_thread_id: &[0; 64], new_vid, }, Payload::Referral { referred_vid } => { From b0755ee51f8a7fa600ebb4c84202017b13f19af8 Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Fri, 26 Sep 2025 14:22:22 +0200 Subject: [PATCH 27/27] Fix intermediary "temporary" routine Signed-off-by: Marc Schoolderman --- tsp_sdk/src/cesr/mod.rs | 3 +-- tsp_sdk/src/cesr/packet.rs | 42 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/tsp_sdk/src/cesr/mod.rs b/tsp_sdk/src/cesr/mod.rs index e074a541..4b270cfd 100644 --- a/tsp_sdk/src/cesr/mod.rs +++ b/tsp_sdk/src/cesr/mod.rs @@ -59,8 +59,7 @@ mod selector { /// (Temporary) interface to get Sender/Receiver VIDs information from a CESR-encoded message pub fn get_sender_receiver(message: &[u8]) -> Result<(&[u8], Option<&[u8]>), error::DecodeError> { - let mut stream = message; - let (sender, receiver, _, _) = decode_sender_receiver(&mut stream)?; + let (sender, receiver, _, _) = decode_sender_receiver(message)?; Ok((sender, receiver)) } diff --git a/tsp_sdk/src/cesr/packet.rs b/tsp_sdk/src/cesr/packet.rs index 5a666106..8f837b48 100644 --- a/tsp_sdk/src/cesr/packet.rs +++ b/tsp_sdk/src/cesr/packet.rs @@ -735,12 +735,11 @@ pub struct VerificationChallenge<'a> { /// Decode the type, sender and receiver of an encrypted TSP message pub fn decode_sender_receiver<'a, Vid: TryFrom<&'a [u8]>>( - stream: &mut &'a [u8], + stream: &'a [u8], ) -> Result<(Vid, Option, CryptoType, SignatureType), DecodeError> { let mut pos = 0; let (sender, receiver, crypto_type, signature_type) = detected_tsp_header_size_and_confidentiality(stream, &mut pos)?; - *stream = &stream[pos..]; let sender = stream[sender] .try_into() @@ -1473,4 +1472,43 @@ mod test { assert_eq!(parts.receiver.unwrap().data.len(), 14); assert_eq!(parts.ciphertext.unwrap().data.len(), 69); } + + #[test] + fn test_decode_send_recv() { + fn dummy_crypt(data: &mut [u8]) -> &mut [u8] { + data + } + let fixed_sig = [1; 64]; + + let mut cesr_payload = + { encode_payload_vec(&Payload::<_, &[u8]>::GenericMessage(b"Hello TSP!")).unwrap() }; + + let mut outer = encode_ets_envelope_vec(Envelope { + crypto_type: CryptoType::HpkeAuth, + signature_type: SignatureType::Ed25519, + sender: &b"Alister"[..], + receiver: Some(&b"Bobbi"[..]), + nonconfidential_data: None, + }) + .unwrap(); + let ciphertext = dummy_crypt(&mut cesr_payload); + encode_ciphertext(ciphertext, CryptoType::HpkeAuth, &mut outer).unwrap(); + + let signed_data = outer.clone(); + encode_signature(&fixed_sig, &mut outer, SignatureType::Ed25519); + + let outer2 = outer.clone(); + let view = decode_envelope(&mut outer).unwrap(); + let ver = view.as_challenge(); + assert_eq!(ver.signed_data, signed_data); + assert_eq!(ver.signature, &fixed_sig); + let DecodedEnvelope { envelope: env, .. } = view.into_opened().unwrap(); + assert_eq!(env.sender, &b"Alister"[..]); + assert_eq!(env.receiver, Some(&b"Bobbi"[..])); + assert_eq!(env.nonconfidential_data, None); + + let (sender, receiver, _, _) = decode_sender_receiver(&outer2).unwrap(); + assert_eq!(env.sender, sender); + assert_eq!(env.receiver, receiver); + } }