From bdf1142dc53c8d978aca7ccc3a24f030d2405879 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 3 May 2021 15:20:18 +0200 Subject: [PATCH 1/3] Support creation of arbitrary length Unsigned BER integers from given bytes. --- src/encode/primitive.rs | 26 ++++++++++++++++++++++++++ src/int.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/encode/primitive.rs b/src/encode/primitive.rs index b2d876d..f903b53 100644 --- a/src/encode/primitive.rs +++ b/src/encode/primitive.rs @@ -382,6 +382,7 @@ impl Values for Primitive

{ #[cfg(test)] mod test { use super::*; + use crate::Unsigned; fn test_der(value: T, expected: &[u8]) { assert_eq!(value.encoded_len(Mode::Der), expected.len()); @@ -419,4 +420,29 @@ mod test { test_der(-12000i32, b"\xD1\x20"); test_der(-1200000i32, b"\xED\xB0\x80"); } + + #[test] + fn encode_variable_length_unsigned() { + test_der(&Unsigned::from_be_bytes(254u8.to_be_bytes()), b"\x00\xFE"); + test_der(&Unsigned::from_be_bytes(255u8.to_be_bytes()), b"\x00\xFF"); + test_der(&Unsigned::from_be_bytes(256u16.to_be_bytes()), b"\x01\x00"); + + test_der(&Unsigned::from_be_bytes(32767u16.to_be_bytes()), b"\x7F\xFF"); + test_der(&Unsigned::from_be_bytes(32768u16.to_be_bytes()), b"\x00\x80\x00"); + test_der(&Unsigned::from_be_bytes(32769u16.to_be_bytes()), b"\x00\x80\x01"); + + test_der(&Unsigned::from_be_bytes(32767u32.to_be_bytes()), b"\x7F\xFF"); + test_der(&Unsigned::from_be_bytes(32768u32.to_be_bytes()), b"\x00\x80\x00"); + test_der(&Unsigned::from_be_bytes(32769u32.to_be_bytes()), b"\x00\x80\x01"); + + test_der(&Unsigned::from_be_bytes(32767u64.to_be_bytes()), b"\x7F\xFF"); + test_der(&Unsigned::from_be_bytes(32768u64.to_be_bytes()), b"\x00\x80\x00"); + test_der(&Unsigned::from_be_bytes(32769u64.to_be_bytes()), b"\x00\x80\x01"); + + test_der(&Unsigned::from_be_bytes(&[0xFF]), b"\x00\xFF"); + test_der(&Unsigned::from_be_bytes(&[0x00, 0xFF]), b"\x00\xFF"); + test_der(&Unsigned::from_be_bytes(&[0x00, 0x00, 0xFF]), b"\x00\xFF"); + + test_der(&Unsigned::from_be_bytes(&[0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]), b"\x00\xDE\xAD\xBE\xEF"); + } } diff --git a/src/int.rs b/src/int.rs index 207be07..e144889 100644 --- a/src/int.rs +++ b/src/int.rs @@ -446,6 +446,41 @@ impl Unsigned { Unsigned(Integer::from_bytes_unchecked(bytes)) } + /// Given a sequence of unsigned (lacking a sign bit) big-endian bytes, + /// ordered most-significant byte first, create the equivalent `Unsigned` + /// integer. One cannot use PrimitiveContent::write_encoded() for &'a [u8] + /// for this because that is already defined to mean that the bytes should + /// be encoded as a BER OCTET STRING, not as a BER INTEGER. + pub fn from_be_bytes>(bytes: B) -> Self { + let whole_slice = bytes.as_ref(); + + // Skip any leading zero bytes. + let num_leading_zero_bytes = whole_slice.iter().take_while(|&&b| b == 0x00).count(); + let value_slice = &whole_slice[num_leading_zero_bytes..]; + + let is_most_significant_bit_zero = value_slice[0] & 0x80 == 0; + + // Create a new Unsigned integer from the given value bytes, ensuring + // that the most-significant bit is zero. + let new_bytes = if is_most_significant_bit_zero { + // Use the source value bytes as-is. + Bytes::copy_from_slice(value_slice) + } else if num_leading_zero_bytes > 0 { + // Use the value byte sequence and one preceeding zero byte. + Bytes::copy_from_slice(&whole_slice[num_leading_zero_bytes-1..]) + } else { + // Prefix a copy of the slice with a leading zero byte. + let mut v: Vec = Vec::with_capacity(value_slice.len() + 1); + v.push(0x00); + v.extend(value_slice.iter()); + Bytes::copy_from_slice(v.as_slice()) + }; + + unsafe { + Unsigned::from_bytes_unchecked(new_bytes) + } + } + pub fn take_from( cons: &mut decode::Constructed ) -> Result { From abf8033c0f6e661ea593031e962c268201a5d74b Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 30 Aug 2021 10:02:15 +0200 Subject: [PATCH 2/3] Clippy. --- src/string/octet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string/octet.rs b/src/string/octet.rs index 2d49038..59a994e 100644 --- a/src/string/octet.rs +++ b/src/string/octet.rs @@ -398,7 +398,7 @@ impl> PartialOrd for OctetString { impl Ord for OctetString { fn cmp(&self, other: &Self) -> cmp::Ordering { if let (Some(l), Some(r)) = (self.as_slice(), other.as_slice()) { - return l.cmp(&r) + return l.cmp(r) } let mut siter = self.iter(); From ab82a8217761cb7992575c2f27d46cffe3081e45 Mon Sep 17 00:00:00 2001 From: Ximon Eighteen <3304436+ximon18@users.noreply.github.com> Date: Mon, 30 Aug 2021 10:28:26 +0200 Subject: [PATCH 3/3] Implement review feedback. --- src/encode/primitive.rs | 26 ------------ src/int.rs | 94 +++++++++++++++++++++++++++++++---------- 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/src/encode/primitive.rs b/src/encode/primitive.rs index f903b53..b2d876d 100644 --- a/src/encode/primitive.rs +++ b/src/encode/primitive.rs @@ -382,7 +382,6 @@ impl Values for Primitive

{ #[cfg(test)] mod test { use super::*; - use crate::Unsigned; fn test_der(value: T, expected: &[u8]) { assert_eq!(value.encoded_len(Mode::Der), expected.len()); @@ -420,29 +419,4 @@ mod test { test_der(-12000i32, b"\xD1\x20"); test_der(-1200000i32, b"\xED\xB0\x80"); } - - #[test] - fn encode_variable_length_unsigned() { - test_der(&Unsigned::from_be_bytes(254u8.to_be_bytes()), b"\x00\xFE"); - test_der(&Unsigned::from_be_bytes(255u8.to_be_bytes()), b"\x00\xFF"); - test_der(&Unsigned::from_be_bytes(256u16.to_be_bytes()), b"\x01\x00"); - - test_der(&Unsigned::from_be_bytes(32767u16.to_be_bytes()), b"\x7F\xFF"); - test_der(&Unsigned::from_be_bytes(32768u16.to_be_bytes()), b"\x00\x80\x00"); - test_der(&Unsigned::from_be_bytes(32769u16.to_be_bytes()), b"\x00\x80\x01"); - - test_der(&Unsigned::from_be_bytes(32767u32.to_be_bytes()), b"\x7F\xFF"); - test_der(&Unsigned::from_be_bytes(32768u32.to_be_bytes()), b"\x00\x80\x00"); - test_der(&Unsigned::from_be_bytes(32769u32.to_be_bytes()), b"\x00\x80\x01"); - - test_der(&Unsigned::from_be_bytes(32767u64.to_be_bytes()), b"\x7F\xFF"); - test_der(&Unsigned::from_be_bytes(32768u64.to_be_bytes()), b"\x00\x80\x00"); - test_der(&Unsigned::from_be_bytes(32769u64.to_be_bytes()), b"\x00\x80\x01"); - - test_der(&Unsigned::from_be_bytes(&[0xFF]), b"\x00\xFF"); - test_der(&Unsigned::from_be_bytes(&[0x00, 0xFF]), b"\x00\xFF"); - test_der(&Unsigned::from_be_bytes(&[0x00, 0x00, 0xFF]), b"\x00\xFF"); - - test_der(&Unsigned::from_be_bytes(&[0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]), b"\x00\xDE\xAD\xBE\xEF"); - } } diff --git a/src/int.rs b/src/int.rs index e144889..a8ad423 100644 --- a/src/int.rs +++ b/src/int.rs @@ -446,39 +446,46 @@ impl Unsigned { Unsigned(Integer::from_bytes_unchecked(bytes)) } - /// Given a sequence of unsigned (lacking a sign bit) big-endian bytes, - /// ordered most-significant byte first, create the equivalent `Unsigned` - /// integer. One cannot use PrimitiveContent::write_encoded() for &'a [u8] - /// for this because that is already defined to mean that the bytes should - /// be encoded as a BER OCTET STRING, not as a BER INTEGER. - pub fn from_be_bytes>(bytes: B) -> Self { - let whole_slice = bytes.as_ref(); + /// Constructs `Unsigned` by copying from a `&[u8]`. + /// + /// # Errors + /// + /// Will return `Error::Malformed` if the given slice is empty. + pub fn from_slice(slice: &[u8]) -> Result { + Self::from_bytes(Bytes::copy_from_slice(slice)) + } - // Skip any leading zero bytes. - let num_leading_zero_bytes = whole_slice.iter().take_while(|&&b| b == 0x00).count(); - let value_slice = &whole_slice[num_leading_zero_bytes..]; + /// Constructs `Unsigned` from `Bytes`, copying only if needed. + /// + /// # Errors + /// + /// Will return `Error::Malformed` if the given Bytes value is empty. + pub fn from_bytes(bytes: Bytes) -> Result { + if bytes.is_empty() { + return Err(crate::decode::Error::Malformed); + } - let is_most_significant_bit_zero = value_slice[0] & 0x80 == 0; + // Skip any leading zero bytes. + let num_leading_zero_bytes = bytes.as_ref().iter().take_while(|&&b| b == 0x00).count(); + let value = bytes.slice(num_leading_zero_bytes..); // Create a new Unsigned integer from the given value bytes, ensuring // that the most-significant bit is zero. - let new_bytes = if is_most_significant_bit_zero { - // Use the source value bytes as-is. - Bytes::copy_from_slice(value_slice) + let new_bytes = if value[0] & 0x80 == 0 { + // Use the value bytes as-is. + value } else if num_leading_zero_bytes > 0 { - // Use the value byte sequence and one preceeding zero byte. - Bytes::copy_from_slice(&whole_slice[num_leading_zero_bytes-1..]) + // Use the value bytes and one of the preceeding zero "sign" bytes. + bytes.slice(num_leading_zero_bytes - 1..) } else { - // Prefix a copy of the slice with a leading zero byte. - let mut v: Vec = Vec::with_capacity(value_slice.len() + 1); + // Copy the bytes in order to prepend a zero "sign" byte. + let mut v: Vec = Vec::with_capacity(value.len() + 1); v.push(0x00); - v.extend(value_slice.iter()); - Bytes::copy_from_slice(v.as_slice()) + v.extend(value.iter()); + Bytes::from(v) }; - unsafe { - Unsigned::from_bytes_unchecked(new_bytes) - } + unsafe { Ok(Unsigned::from_bytes_unchecked(new_bytes)) } } pub fn take_from( @@ -619,6 +626,14 @@ builtin_from!(unsigned, Unsigned, u64, 8); builtin_from!(unsigned, Unsigned, u128, 16); +impl<'a> TryFrom for Unsigned { + type Error = crate::decode::Error; + + fn try_from(value: Bytes) -> Result { + Unsigned::from_bytes(value) + } +} + //--- AsRef impl AsRef for Unsigned { @@ -681,6 +696,13 @@ mod test { use crate::Mode; use crate::decode::Primitive; + fn test_der(value: T, expected: &[u8]) { + assert_eq!(value.encoded_len(Mode::Der), expected.len()); + let mut target = Vec::new(); + value.write_encoded(Mode::Der, &mut target).unwrap(); + assert_eq!(target, expected); + } + #[test] fn is_positive_negative() { let neg = [-0xF74402, -0xF744, -0xF7]; @@ -1115,5 +1137,31 @@ mod test { assert_eq!(u64::try_from(&int).unwrap(), 0xA2345678); assert_eq!(u128::try_from(&int).unwrap(), 0xA2345678); } + + #[test] + fn encode_variable_length_unsigned_from_slice() { + assert_eq!(Unsigned::from_slice(&[]), Err(crate::decode::Error::Malformed)); + test_der(&Unsigned::from_slice(&[0xFF]).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::from_slice(&[0x00, 0xFF]).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::from_slice(&[0x00, 0x00, 0xFF]).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::from_slice(&[0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]).unwrap(), b"\x00\xDE\xAD\xBE\xEF"); + } + #[test] + fn encode_variable_length_unsigned_from_bytes() { + assert_eq!(Unsigned::from_bytes(Bytes::new()), Err(crate::decode::Error::Malformed)); + test_der(&Unsigned::from_bytes(Bytes::from(vec![0xFF])).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::from_bytes(Bytes::from(vec![0x00, 0xFF])).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::from_bytes(Bytes::from(vec![0x00, 0x00, 0xFF])).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::from_bytes(Bytes::from(vec![0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF])).unwrap(), b"\x00\xDE\xAD\xBE\xEF"); + } + + #[test] + fn encode_variable_length_unsigned_try_from_bytes() { + assert_eq!(Unsigned::try_from(Bytes::new()), Err(crate::decode::Error::Malformed)); + test_der(&Unsigned::try_from(Bytes::from(vec![0xFF])).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::try_from(Bytes::from(vec![0x00, 0xFF])).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::try_from(Bytes::from(vec![0x00, 0x00, 0xFF])).unwrap(), b"\x00\xFF"); + test_der(&Unsigned::try_from(Bytes::from(vec![0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF])).unwrap(), b"\x00\xDE\xAD\xBE\xEF"); + } }