-
Notifications
You must be signed in to change notification settings - Fork 708
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
Use a more specific error instead of unspecified::Error for DER encoding errors, as per #379 #384
Changes from all commits
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 |
---|---|---|
|
@@ -17,11 +17,14 @@ | |
//! This module contains the foundational parts of an ASN.1 DER parser. | ||
|
||
use untrusted; | ||
use error; | ||
|
||
pub const CONSTRUCTED: u8 = 1 << 5; | ||
pub const CONTEXT_SPECIFIC: u8 = 2 << 6; | ||
|
||
/// An error in ASN1 parsing. | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct ASN1Error; | ||
|
||
#[derive(Clone, Copy, PartialEq)] | ||
#[repr(u8)] | ||
pub enum Tag { | ||
|
@@ -43,49 +46,49 @@ pub enum Tag { | |
pub fn expect_tag_and_get_value<'a>(input: &mut untrusted::Reader<'a>, | ||
tag: Tag) | ||
-> Result<untrusted::Input<'a>, | ||
error::Unspecified> { | ||
ASN1Error> { | ||
let (actual_tag, inner) = try!(read_tag_and_get_value(input)); | ||
if (tag as usize) != (actual_tag as usize) { | ||
return Err(error::Unspecified); | ||
return Err(ASN1Error); | ||
} | ||
Ok(inner) | ||
} | ||
|
||
pub fn read_tag_and_get_value<'a>(input: &mut untrusted::Reader<'a>) | ||
-> Result<(u8, untrusted::Input<'a>), | ||
error::Unspecified> { | ||
let tag = try!(input.read_byte()); | ||
ASN1Error> { | ||
let tag = try!(read_expected_byte(input)); | ||
if (tag & 0x1F) == 0x1F { | ||
return Err(error::Unspecified); // High tag number form is not allowed. | ||
return Err(ASN1Error); // High tag number form is not allowed. | ||
} | ||
|
||
// If the high order bit of the first byte is set to zero then the length | ||
// is encoded in the seven remaining bits of that byte. Otherwise, those | ||
// seven bits represent the number of bytes used to encode the length. | ||
let length = match try!(input.read_byte()) { | ||
let length = match try!(read_expected_byte(input)) { | ||
n if (n & 0x80) == 0 => n as usize, | ||
0x81 => { | ||
let second_byte = try!(input.read_byte()); | ||
let second_byte = try!(read_expected_byte(input)); | ||
if second_byte < 128 { | ||
return Err(error::Unspecified); // Not the canonical encoding. | ||
return Err(ASN1Error); // Not the canonical encoding. | ||
} | ||
second_byte as usize | ||
}, | ||
0x82 => { | ||
let second_byte = try!(input.read_byte()) as usize; | ||
let third_byte = try!(input.read_byte()) as usize; | ||
let second_byte = try!(read_expected_byte(input)) as usize; | ||
let third_byte = try!(read_expected_byte(input)) as usize; | ||
let combined = (second_byte << 8) | third_byte; | ||
if combined < 256 { | ||
return Err(error::Unspecified); // Not the canonical encoding. | ||
return Err(ASN1Error); // Not the canonical encoding. | ||
} | ||
combined | ||
}, | ||
_ => { | ||
return Err(error::Unspecified); // We don't support longer lengths. | ||
return Err(ASN1Error); // We don't support longer lengths. | ||
}, | ||
}; | ||
|
||
let inner = try!(input.skip_and_get_input(length)); | ||
let inner = try!(input.skip_and_get_input(length).map_err(|_| ASN1Error)); | ||
Ok((tag, inner)) | ||
} | ||
|
||
|
@@ -100,15 +103,15 @@ pub fn nested<'a, F, R, E: Copy>(input: &mut untrusted::Reader<'a>, tag: Tag, | |
} | ||
|
||
fn nonnegative_integer<'a>(input: &mut untrusted::Reader<'a>, min_value: u8) | ||
-> Result<untrusted::Input<'a>, error::Unspecified> { | ||
-> Result<untrusted::Input<'a>, ASN1Error> { | ||
// Verify that |input|, which has had any leading zero stripped off, is the | ||
// encoding of a value of at least |min_value|. | ||
fn check_minimum(input: untrusted::Input, min_value: u8) | ||
-> Result<(), error::Unspecified> { | ||
input.read_all(error::Unspecified, |input| { | ||
let first_byte = try!(input.read_byte()); | ||
-> Result<(), ASN1Error> { | ||
input.read_all(ASN1Error, |input| { | ||
let first_byte = try!(read_expected_byte(input)); | ||
if input.at_end() && first_byte < min_value { | ||
return Err(error::Unspecified); | ||
return Err(ASN1Error); | ||
} | ||
let _ = input.skip_to_end(); | ||
Ok(()) | ||
|
@@ -117,26 +120,26 @@ fn nonnegative_integer<'a>(input: &mut untrusted::Reader<'a>, min_value: u8) | |
|
||
let value = try!(expect_tag_and_get_value(input, Tag::Integer)); | ||
|
||
value.read_all(error::Unspecified, |input| { | ||
value.read_all(ASN1Error, |input| { | ||
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. I don't think all the errors returned by this function are ASN.1 decoding errors. Some of them are value-out-of-range errors. Since the point of this PR is to allow us to differentiate different kinds of errors, it seems like we should differentiate out-of-range vs. bad-DER. WDYT? The end-goal of this specific PR is to make it possible to rebase the webpki crate on top of the new version of ring that has #378 integrated, right? Presumably, in the future we'll change some public APIs in ring to return more specific errors, by extending the changes in this PR, but that's not a goal of the present PR, right? Or is the purpose different? The intended purpose matters as far as this review is concerned because (1) this change may not be necessary for rebasing webpki on top of #378 and/or (2) this PR may not be sufficient for solving the intended problem if it doesn't differentiate bad-DER from other kinds of errors. 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.
I would love to; the only reason I didn't do so was to hew more closely to the "expose as little as possible" goal. More on this below.
It would be possible, though a trifle code-smelly, to use this (plus #378) in webpki. The issue is the, ahem, approximate error handling like this, which is currently residing in #378:
It would be nice to not map
Mapping Anyway, consider webpki's
In practice, the only In summary: no, I don't think we need more detailed errors to make it possible to use this in webpki. However, I wholeheartedly agree that it is not a very nice state of the world to report an value-out-of-bounds error the same way as an unexpected EOF, so I would happily make |
||
// Empty encodings are not allowed. | ||
let first_byte = try!(input.read_byte()); | ||
let first_byte = try!(read_expected_byte(input)); | ||
|
||
if first_byte == 0 { | ||
if input.at_end() { | ||
// |value| is the legal encoding of zero. | ||
if min_value > 0 { | ||
return Err(error::Unspecified); | ||
return Err(ASN1Error); | ||
} | ||
return Ok(value); | ||
} | ||
|
||
let r = input.skip_to_end(); | ||
try!(r.read_all(error::Unspecified, |input| { | ||
let second_byte = try!(input.read_byte()); | ||
try!(r.read_all(ASN1Error, |input| { | ||
let second_byte = try!(read_expected_byte(input)); | ||
if (second_byte & 0x80) == 0 { | ||
// A leading zero is only allowed when the value's high bit | ||
// is set. | ||
return Err(error::Unspecified); | ||
return Err(ASN1Error); | ||
} | ||
let _ = input.skip_to_end(); | ||
Ok(()) | ||
|
@@ -147,7 +150,7 @@ fn nonnegative_integer<'a>(input: &mut untrusted::Reader<'a>, min_value: u8) | |
|
||
// Negative values are not allowed. | ||
if (first_byte & 0x80) != 0 { | ||
return Err(error::Unspecified); | ||
return Err(ASN1Error); | ||
} | ||
|
||
let _ = input.skip_to_end(); | ||
|
@@ -160,10 +163,10 @@ fn nonnegative_integer<'a>(input: &mut untrusted::Reader<'a>, min_value: u8) | |
/// numeric value. This is typically used for parsing version numbers. | ||
#[inline] | ||
pub fn small_nonnegative_integer(input: &mut untrusted::Reader) | ||
-> Result<u8, error::Unspecified> { | ||
-> Result<u8, ASN1Error> { | ||
let value = try!(nonnegative_integer(input, 0)); | ||
value.read_all(error::Unspecified, |input| { | ||
let r = try!(input.read_byte()); | ||
value.read_all(ASN1Error, |input| { | ||
let r = try!(read_expected_byte(input)); | ||
Ok(r) | ||
}) | ||
} | ||
|
@@ -172,28 +175,32 @@ pub fn small_nonnegative_integer(input: &mut untrusted::Reader) | |
/// any leading zero byte. | ||
#[inline] | ||
pub fn positive_integer<'a>(input: &mut untrusted::Reader<'a>) | ||
-> Result<untrusted::Input<'a>, error::Unspecified> { | ||
-> Result<untrusted::Input<'a>, ASN1Error> { | ||
nonnegative_integer(input, 1) | ||
} | ||
|
||
/// Maps EOF to ASN1Error as a convenience when EOF is not expected. | ||
fn read_expected_byte<'a>(input: &mut untrusted::Reader<'a>) | ||
-> Result<u8, ASN1Error>{ | ||
input.read_byte().map_err(|_| ASN1Error) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use error; | ||
use super::*; | ||
use untrusted; | ||
|
||
fn with_good_i<F, R>(value: &[u8], f: F) | ||
where F: FnOnce(&mut untrusted::Reader) | ||
-> Result<R, error::Unspecified> { | ||
let r = untrusted::Input::from(value).read_all(error::Unspecified, f); | ||
-> Result<R, ASN1Error> { | ||
let r = untrusted::Input::from(value).read_all(ASN1Error, f); | ||
assert!(r.is_ok()); | ||
} | ||
|
||
fn with_bad_i<F, R>(value: &[u8], f: F) | ||
where F: FnOnce(&mut untrusted::Reader) | ||
-> Result<R, error::Unspecified> { | ||
let r = untrusted::Input::from(value).read_all(error::Unspecified, f); | ||
-> Result<R, ASN1Error> { | ||
let r = untrusted::Input::from(value).read_all(ASN1Error, f); | ||
assert!(r.is_err()); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -338,7 +338,7 @@ pub struct PublicScalarOps { | |
impl PublicScalarOps { | ||
pub fn scalar_parse(&self, input: &mut untrusted::Reader) | ||
-> Result<Scalar, error::Unspecified> { | ||
let encoded_value = try!(der::positive_integer(input)); | ||
let encoded_value = try!(der::positive_integer(input).map_err(|_| error::Unspecified)); | ||
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. There were only a few places that needed to 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. It's OK to punt on that for now. |
||
let limbs = try!(parse_big_endian_value_in_range( | ||
encoded_value, 1, | ||
&self.public_key_ops.common.n.limbs[ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was only one usage of skip_and_get_input so I just mapped the error inline rather than with a wrapper function.