-
Notifications
You must be signed in to change notification settings - Fork 172
der, x509-cert: fix handling of negative integers #831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
130f52c
b9ee5e4
054d0ca
234f858
5652f96
20f0dd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| //! Support for encoding negative integers | ||
| //! Support for encoding signed integers | ||
|
|
||
| use super::is_highest_bit_set; | ||
| use crate::{ErrorKind, Length, Result, Tag, Writer}; | ||
|
|
@@ -46,9 +46,9 @@ where | |
| writer.write(strip_leading_ones(bytes)) | ||
| } | ||
|
|
||
| /// Get the encoded length for the given signed integer serialized as bytes. | ||
| /// Get the encoded length for the given **negative** integer serialized as bytes. | ||
| #[inline] | ||
| pub(super) fn encoded_len(bytes: &[u8]) -> Result<Length> { | ||
| pub(super) fn negative_encoded_len(bytes: &[u8]) -> Result<Length> { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. N.B.: I renamed this to emphasize what this function is actually doing, as currently implemented: it doesn't calculate the encoded length of any signed integer represented as bytes, but only negative signed integers. Passing a positive signed integer here would result in the wrong length, since we only trim leading ones, not zeroes. (I confirmed that the one remaining callsite for this only calls it on negative integers, and removed the other misleading callsites.) |
||
| Length::try_from(strip_leading_ones(bytes).len()) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,8 @@ | |
| use core::fmt::Display; | ||
|
|
||
| use der::{ | ||
| asn1::Int, DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, | ||
| ValueOrd, Writer, | ||
| asn1::Int, asn1::Uint, DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, | ||
| Result, Tag, ValueOrd, Writer, | ||
| }; | ||
|
|
||
| /// [RFC 5280 Section 4.1.2.2.] Serial Number | ||
|
|
@@ -32,15 +32,27 @@ impl SerialNumber { | |
| /// Maximum length in bytes for a [`SerialNumber`] | ||
| pub const MAX_LEN: Length = Length::new(20); | ||
|
|
||
| /// See notes in `SerialNumber::new` and `SerialNumber::decode_value`. | ||
| const MAX_DECODE_LEN: Length = Length::new(21); | ||
|
|
||
| /// Create a new [`SerialNumber`] from a byte slice. | ||
| /// | ||
| /// The byte slice **must** represent a positive integer. | ||
| pub fn new(bytes: &[u8]) -> Result<Self> { | ||
| let inner = Int::new(bytes)?; | ||
|
|
||
| if inner.len() > SerialNumber::MAX_LEN { | ||
| let inner = Uint::new(bytes)?; | ||
|
|
||
| // The user might give us a 20 byte unsigned integer with a high MSB, | ||
| // which we'd then encode with 21 octets to preserve the sign bit. | ||
| // RFC 5280 is ambiguous about whether this is valid, so we limit | ||
| // `SerialNumber` *encodings* tp 20 bytes or fewer while permitting | ||
| // `SerialNumber` *decodings* to have up to 21 bytes below. | ||
| if inner.value_len()? > SerialNumber::MAX_LEN { | ||
| return Err(ErrorKind::Overlength.into()); | ||
| } | ||
|
|
||
| Ok(Self { inner }) | ||
| Ok(Self { | ||
| inner: inner.into(), | ||
| }) | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This ensures that we never create a new negative serial number, but the way it does that is potentially surprising because of how the I've updated the tests below to show this behavior.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (This arguably violates POLA, since we're changing the serial number given to us by the user. But it's how the current
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curious: what does POLA stands for?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Principle of least astonishment
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep! Sorry for the acronym abuse. By POLA in this case I meant that users might consider it astonishing that the serial they provide doesn't end up in the cert, even if the result is technically more correct. |
||
|
|
||
| /// Borrow the inner byte slice which contains the least significant bytes | ||
|
|
@@ -64,7 +76,10 @@ impl<'a> DecodeValue<'a> for SerialNumber { | |
| fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> { | ||
| let inner = Int::decode_value(reader, header)?; | ||
|
|
||
| if inner.len() > SerialNumber::MAX_LEN { | ||
| // See the note in `SerialNumber::new`: we permit lengths of 21 bytes here, | ||
| // since some X.509 implementations interpret the limit of 20 bytes to refer | ||
| // to the pre-encoded value. | ||
| if inner.len() > SerialNumber::MAX_DECODE_LEN { | ||
| return Err(ErrorKind::Overlength.into()); | ||
| } | ||
|
|
||
|
|
@@ -112,10 +127,44 @@ mod tests { | |
|
|
||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn serial_number_invariants() { | ||
| // Creating a new serial with an oversized encoding (due to high MSB) fails. | ||
| { | ||
| let too_big = [0x80; 20]; | ||
| assert!(SerialNumber::new(&too_big).is_err()); | ||
| } | ||
|
|
||
| // Creating a new serial with the maximum encoding succeeds. | ||
| { | ||
| let just_enough = [ | ||
| 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
| ]; | ||
| assert!(SerialNumber::new(&just_enough).is_ok()); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn serial_number_display() { | ||
| let sn = SerialNumber::new(&[0xAA, 0xBB, 0xCC, 0x01, 0x10, 0x00, 0x11]).unwrap(); | ||
| { | ||
| let sn = SerialNumber::new(&[0x11, 0x22, 0x33]).unwrap(); | ||
|
|
||
| assert_eq!(sn.to_string(), "11:22:33") | ||
| } | ||
|
|
||
| { | ||
| let sn = SerialNumber::new(&[0xAA, 0xBB, 0xCC, 0x01, 0x10, 0x00, 0x11]).unwrap(); | ||
|
|
||
| assert_eq!(sn.to_string(), "AA:BB:CC:01:10:00:11") | ||
| // We force the user's serial to be positive if they give us a negative one. | ||
| assert_eq!(sn.to_string(), "00:AA:BB:CC:01:10:00:11") | ||
| } | ||
|
|
||
| { | ||
| let sn = SerialNumber::new(&[0x00, 0x00, 0x01]).unwrap(); | ||
|
|
||
| // Leading zeroes are ignored, due to canonicalization. | ||
| assert_eq!(sn.to_string(), "01") | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.