Skip to content

Commit 0bc3474

Browse files
committed
Merge branch 'jbp-improve-name-constraints-testing' into main
2 parents 0e93500 + 8e9de6e commit 0bc3474

File tree

65 files changed

+1223
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1223
-42
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ Changelog
7070
- Allow verification of certificates with IP address subjectAltNames.
7171
`EndEntityCert::verify_is_valid_for_subject_name` was added, and
7272
`EndEntityCert::verify_is_valid_for_dns_name` was removed.
73+
- Make `Error` type non-exhaustive.
74+
- Reject non-contiguous netmasks in IP address name constraints.
75+
- Name constraints of type dNSName and iPAddress now work and are tested.
76+
directoryName name constraints are not implemented and will prevent
77+
path building where they appear.
7378
* 0.22.0 (2021-04-10) - last upstream release of `webpki` crate.
7479

7580

src/der.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,71 @@
1414

1515
use crate::{calendar, time, Error};
1616
pub(crate) use ring::io::{
17-
der::{nested, Tag},
17+
der::{CONSTRUCTED, CONTEXT_SPECIFIC},
1818
Positive,
1919
};
2020

21+
// Copied (and extended) from ring's src/der.rs
22+
#[allow(clippy::upper_case_acronyms)]
23+
#[derive(Clone, Copy, Eq, PartialEq)]
24+
#[repr(u8)]
25+
pub(crate) enum Tag {
26+
Boolean = 0x01,
27+
Integer = 0x02,
28+
BitString = 0x03,
29+
OctetString = 0x04,
30+
OID = 0x06,
31+
UTF8String = 0x0C,
32+
Sequence = CONSTRUCTED | 0x10, // 0x30
33+
Set = CONSTRUCTED | 0x11, // 0x31
34+
UTCTime = 0x17,
35+
GeneralizedTime = 0x18,
36+
37+
#[allow(clippy::identity_op)]
38+
ContextSpecificConstructed0 = CONTEXT_SPECIFIC | CONSTRUCTED | 0,
39+
ContextSpecificConstructed1 = CONTEXT_SPECIFIC | CONSTRUCTED | 1,
40+
ContextSpecificConstructed3 = CONTEXT_SPECIFIC | CONSTRUCTED | 3,
41+
}
42+
43+
impl From<Tag> for usize {
44+
#[allow(clippy::as_conversions)]
45+
fn from(tag: Tag) -> Self {
46+
tag as Self
47+
}
48+
}
49+
50+
impl From<Tag> for u8 {
51+
#[allow(clippy::as_conversions)]
52+
fn from(tag: Tag) -> Self {
53+
tag as Self
54+
} // XXX: narrowing conversion.
55+
}
56+
2157
#[inline(always)]
2258
pub(crate) fn expect_tag_and_get_value<'a>(
2359
input: &mut untrusted::Reader<'a>,
2460
tag: Tag,
2561
) -> Result<untrusted::Input<'a>, Error> {
26-
ring::io::der::expect_tag_and_get_value(input, tag).map_err(|_| Error::BadDER)
62+
let (actual_tag, inner) = read_tag_and_get_value(input)?;
63+
if usize::from(tag) != usize::from(actual_tag) {
64+
return Err(Error::BadDER);
65+
}
66+
Ok(inner)
67+
}
68+
69+
// TODO: investigate taking decoder as a reference to reduce generated code
70+
// size.
71+
pub(crate) fn nested<'a, F, R, E: Copy>(
72+
input: &mut untrusted::Reader<'a>,
73+
tag: Tag,
74+
error: E,
75+
decoder: F,
76+
) -> Result<R, E>
77+
where
78+
F: FnOnce(&mut untrusted::Reader<'a>) -> Result<R, E>,
79+
{
80+
let inner = expect_tag_and_get_value(input, tag).map_err(|_| error)?;
81+
inner.read_all(error, decoder)
2782
}
2883

2984
pub struct Value<'a> {

src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use core::fmt;
1616

1717
/// An error that occurs during certificate validation or name validation.
1818
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19+
#[non_exhaustive]
1920
pub enum Error {
2021
/// The encoding of some ASN.1 DER-encoded item is invalid.
2122
// TODO: Rename to `BadDer` in the next release.
@@ -97,6 +98,11 @@ pub enum Error {
9798
/// The signature algorithm for a signature is not in the set of supported
9899
/// signature algorithms given.
99100
UnsupportedSignatureAlgorithm,
101+
102+
/// A iPAddress name constraint was invalid:
103+
/// - it had a sparse network mask (ie, cannot be written in CIDR form).
104+
/// - it was too long or short
105+
InvalidNetworkMaskConstraint,
100106
}
101107

102108
impl fmt::Display for Error {

src/subject_name/dns_name.rs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -792,11 +792,68 @@ mod tests {
792792
untrusted::Input::from(reference),
793793
);
794794
assert_eq!(
795-
actual_result,
796-
expected_result,
797-
"presented_dns_id_matches_reference_dns_id(\"{:?}\", IDRole::ReferenceID, \"{:?}\")",
798-
presented,
799-
reference
795+
actual_result, expected_result,
796+
"presented_id_matches_reference_id(\"{:?}\", \"{:?}\")",
797+
presented, reference
798+
);
799+
}
800+
}
801+
802+
// (presented_name, constraint, expected_matches)
803+
const PRESENTED_MATCHES_CONSTRAINT: &[(&[u8], &[u8], Option<bool>)] = &[
804+
// No absolute presented IDs allowed
805+
(b".", b"", None),
806+
(b"www.example.com.", b"", None),
807+
(b"www.example.com.", b"www.example.com.", None),
808+
// No absolute constraints allowed
809+
(b"www.example.com", b".", None),
810+
(b"www.example.com", b"www.example.com.", None),
811+
// No wildcard in constraints allowed
812+
(b"www.example.com", b"*.example.com", None),
813+
// No empty presented IDs allowed
814+
(b"", b"", None),
815+
// Empty constraints match everything allowed
816+
(b"example.com", b"", Some(true)),
817+
(b"*.example.com", b"", Some(true)),
818+
// Constraints that start with a dot
819+
(b"www.example.com", b".example.com", Some(true)),
820+
(b"www.example.com", b".EXAMPLE.COM", Some(true)),
821+
(b"www.example.com", b".axample.com", Some(false)),
822+
(b"www.example.com", b".xample.com", Some(false)),
823+
(b"www.example.com", b".exampl.com", Some(false)),
824+
(b"badexample.com", b".example.com", Some(false)),
825+
// Constraints that do not start with a dot
826+
(b"www.example.com", b"example.com", Some(true)),
827+
(b"www.example.com", b"EXAMPLE.COM", Some(true)),
828+
(b"www.example.com", b"axample.com", Some(false)),
829+
(b"www.example.com", b"xample.com", Some(false)),
830+
(b"www.example.com", b"exampl.com", Some(false)),
831+
(b"badexample.com", b"example.com", Some(false)),
832+
// Presented IDs with wildcard
833+
(b"*.example.com", b".example.com", Some(true)),
834+
(b"*.example.com", b"example.com", Some(true)),
835+
(b"*.example.com", b"www.example.com", Some(true)),
836+
(b"*.example.com", b"www.EXAMPLE.COM", Some(true)),
837+
(b"*.example.com", b"www.axample.com", Some(false)),
838+
(b"*.example.com", b".xample.com", Some(false)),
839+
(b"*.example.com", b"xample.com", Some(false)),
840+
(b"*.example.com", b".exampl.com", Some(false)),
841+
(b"*.example.com", b"exampl.com", Some(false)),
842+
// Matching IDs
843+
(b"www.example.com", b"www.example.com", Some(true)),
844+
];
845+
846+
#[test]
847+
fn presented_matches_constraint_test() {
848+
for &(presented, constraint, expected_result) in PRESENTED_MATCHES_CONSTRAINT {
849+
let actual_result = presented_id_matches_constraint(
850+
untrusted::Input::from(presented),
851+
untrusted::Input::from(constraint),
852+
);
853+
assert_eq!(
854+
actual_result, expected_result,
855+
"presented_id_matches_constraint(\"{:?}\", \"{:?}\")",
856+
presented, constraint
800857
);
801858
}
802859
}

0 commit comments

Comments
 (0)