diff --git a/rust/cbork-utils/Cargo.toml b/rust/cbork-utils/Cargo.toml index 478435b5b7..42be8d4828 100644 --- a/rust/cbork-utils/Cargo.toml +++ b/rust/cbork-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cbork-utils" -version = "0.0.2" +version = "0.0.3" edition.workspace = true license.workspace = true authors.workspace = true diff --git a/rust/cbork-utils/src/array.rs b/rust/cbork-utils/src/array.rs index 9eac6f166d..62d26a0b17 100644 --- a/rust/cbork-utils/src/array.rs +++ b/rust/cbork-utils/src/array.rs @@ -133,7 +133,7 @@ fn check_array_minimal_length( fn decode_array_elements( d: &mut minicbor::Decoder, length: u64, - _ctx: &mut DecodeCtx, + ctx: &mut DecodeCtx, ) -> Result>, minicbor::decode::Error> { let capacity = usize::try_from(length).map_err(|_| { minicbor::decode::Error::message("Array length too large for current platform") @@ -155,9 +155,43 @@ fn decode_array_elements( elements.push(element_bytes); } + if matches!(ctx, DecodeCtx::ArrayDeterministic) { + ctx.try_check(|| validate_array_ordering(&elements))?; + } + Ok(elements) } +/// Validates array elements are properly ordered according to RFC 8949 Section 4.2.3. +/// +/// Ordering rules: +/// 1. If two items differ in length → shorter sorts first. +/// 2. If lengths are equal → compare byte-wise lexicographically. +/// +/// Returns Ok(()) if elements are correctly ordered. +/// Returns Err(...) if any adjacent pair violates ordering. +fn validate_array_ordering(elements: &[Vec]) -> Result<(), minicbor::decode::Error> { + for pair in elements.windows(2) { + let [prev, current] = pair else { + // fails if the array has 0-1 element + return Ok(()); + }; + + let ord = match prev.len().cmp(¤t.len()) { + std::cmp::Ordering::Equal => prev.as_slice().cmp(current.as_slice()), + other => other, + }; + + if ord == std::cmp::Ordering::Greater { + return Err(minicbor::decode::Error::message( + "Array elements are not ordered deterministically", + )); + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use minicbor::{Decode, Decoder}; @@ -321,4 +355,29 @@ mod tests { // Even it's non-deterministic, this should fail, as we enforce for the defined length. assert!(Array::decode(&mut decoder.clone(), &mut DecodeCtx::non_deterministic()).is_err()); } + + #[test] + fn test_deterministic_array_decoding() { + let invalid_bytes = [ + 0x83, // array of length 3 + 0x41, 0x02, // element: [0x02] + 0x42, 0x01, 0x01, // element: [0x01, 0x01] + 0x41, 0x01, // element: [0x01] + ]; + + let mut decoder = Decoder::new(&invalid_bytes); + let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic); + assert!(result.is_err()); + + // should not affect to other decoding ctx + let mut decoder = Decoder::new(&invalid_bytes); + let result = Array::decode(&mut decoder, &mut DecodeCtx::Deterministic); + assert!(result.is_ok()); + + let valid_bytes = [0x83, 0x41, 0x01, 0x41, 0x02, 0x42, 0x01, 0x01]; + + let mut decoder = Decoder::new(&valid_bytes); + let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic); + assert!(result.is_ok()); + } } diff --git a/rust/cbork-utils/src/decode_context.rs b/rust/cbork-utils/src/decode_context.rs index ff58faa66a..bf563c5eb7 100644 --- a/rust/cbork-utils/src/decode_context.rs +++ b/rust/cbork-utils/src/decode_context.rs @@ -9,6 +9,11 @@ pub enum DecodeCtx { /// Decode a CBOR object applying deterministic decoding rules (RFC 8949 /// Section 4.2). Deterministic, + /// Decode a CBOR object applying deterministic decoding rules (RFC 8949 + /// Section 4.2). + /// Additional apply `RFC 8949 Section 4.2.3` for array entries, so it become + /// deterministically sorted. + ArrayDeterministic, /// Decode a CBOR object **NOT** applying deterministic decoding rules (RFC 8949 /// Section 4.2). /// @@ -47,7 +52,7 @@ impl DecodeCtx { f: impl FnOnce() -> Result<(), minicbor::decode::Error>, ) -> Result<(), minicbor::decode::Error> { match self { - Self::Deterministic => f(), + Self::Deterministic | Self::ArrayDeterministic => f(), Self::NonDeterministic(None) => Ok(()), Self::NonDeterministic(Some(h)) => { if let Err(err) = f() {