Skip to content

Commit 286c891

Browse files
woodruffwpyca-boringbot[bot]
andauthored
x509/verification: allow DNS wildcard patterns to match NCs (#12253)
* x509/verification: allow DNS wildcard patterns to match NCs Fixes #12250. * Bump x509-limbo and/or wycheproof in CI * finish renaming * document inner_name --------- Co-authored-by: pyca-boringbot[bot] <pyca-boringbot[bot][email protected]>
1 parent 194c947 commit 286c891

File tree

3 files changed

+36
-16
lines changed

3 files changed

+36
-16
lines changed

.github/actions/fetch-vectors/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ runs:
1616
with:
1717
repository: "C2SP/x509-limbo"
1818
path: "x509-limbo"
19-
# Latest commit on the x509-limbo main branch, as of Dec 31, 2024.
20-
ref: "7a34e9bbe787eec72876bc39ad261f6ec036b9f1" # x509-limbo-ref
19+
# Latest commit on the x509-limbo main branch, as of Jan 08, 2025.
20+
ref: "eec7dc996cccf326db9cd4dfe1878d33f9efa0c8" # x509-limbo-ref

src/rust/cryptography-x509-verification/src/lib.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ use cryptography_x509::{
2323
name::GeneralName,
2424
oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID},
2525
};
26-
use types::{RFC822Constraint, RFC822Name};
26+
use types::{DNSPattern, RFC822Constraint, RFC822Name};
2727

2828
use crate::certificate::cert_is_self_issued;
2929
use crate::ops::{CryptoOps, VerificationCertificate};
3030
use crate::policy::Policy;
3131
use crate::trust_store::Store;
32-
use crate::types::DNSName;
3332
use crate::types::{DNSConstraint, IPAddress, IPConstraint};
3433
use crate::ApplyNameConstraintStatus::{Applied, Skipped};
3534

@@ -155,45 +154,53 @@ impl<'a, 'chain> NameChain<'a, 'chain> {
155154
budget.name_constraint_check()?;
156155

157156
match (constraint, san) {
158-
(GeneralName::DNSName(pattern), GeneralName::DNSName(name)) => {
159-
match (DNSConstraint::new(pattern.0), DNSName::new(name.0)) {
160-
(Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))),
157+
(GeneralName::DNSName(constraint), GeneralName::DNSName(name)) => {
158+
// NOTE: A DNS SAN can be a wildcard pattern instead of a normal DNS name.
159+
// These are handled by matching unconditionally on the inner name,
160+
// since a NC of `foo.com` will match both `foo.com` and any arbitrarily deep
161+
// subdomain of `foo.com`, where a wildcard SAN like `*.foo.com` will only
162+
// match exactly one subdomain of `foo.com`. Therefore, the NC's matching
163+
// set is a strict superset of any possible wildcard SAN pattern.
164+
match (DNSConstraint::new(constraint.0), DNSPattern::new(name.0)) {
165+
(Some(constraint), Some(name)) => {
166+
Ok(Applied(constraint.matches(name.inner_name())))
167+
}
161168
(_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!(
162169
"unsatisfiable DNS name constraint: malformed SAN {}",
163170
name.0
164171
)))),
165172
(None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!(
166173
"malformed DNS name constraint: {}",
167-
pattern.0
174+
constraint.0
168175
)))),
169176
}
170177
}
171-
(GeneralName::IPAddress(pattern), GeneralName::IPAddress(name)) => {
178+
(GeneralName::IPAddress(constraint), GeneralName::IPAddress(name)) => {
172179
match (
173-
IPConstraint::from_bytes(pattern),
180+
IPConstraint::from_bytes(constraint),
174181
IPAddress::from_bytes(name),
175182
) {
176-
(Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))),
183+
(Some(constraint), Some(name)) => Ok(Applied(constraint.matches(&name))),
177184
(_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!(
178185
"unsatisfiable IP name constraint: malformed SAN {:?}",
179186
name,
180187
)))),
181188
(None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!(
182189
"malformed IP name constraints: {:?}",
183-
pattern
190+
constraint
184191
)))),
185192
}
186193
}
187-
(GeneralName::RFC822Name(pattern), GeneralName::RFC822Name(name)) => {
188-
match (RFC822Constraint::new(pattern.0), RFC822Name::new(name.0)) {
189-
(Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))),
194+
(GeneralName::RFC822Name(constraint), GeneralName::RFC822Name(name)) => {
195+
match (RFC822Constraint::new(constraint.0), RFC822Name::new(name.0)) {
196+
(Some(constraint), Some(name)) => Ok(Applied(constraint.matches(&name))),
190197
(_, None) => Err(ValidationError::new(ValidationErrorKind::Other(format!(
191198
"unsatisfiable RFC822 name constraint: malformed SAN {:?}",
192199
name.0,
193200
)))),
194201
(None, _) => Err(ValidationError::new(ValidationErrorKind::Other(format!(
195202
"malformed RFC822 name constraints: {:?}",
196-
pattern.0
203+
constraint.0
197204
)))),
198205
}
199206
}

src/rust/cryptography-x509-verification/src/types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ impl<'a> DNSPattern<'a> {
138138
},
139139
}
140140
}
141+
142+
/// Returns the inner `DNSName` within this `DNSPattern`, e.g.
143+
/// `foo.com` for `*.foo.com` or `example.com` for `example.com`.
144+
///
145+
/// This API must not be used to bypass pattern matching; it exists
146+
/// solely to enable checks that only require the inner name, such
147+
/// as Name Constraint checks.
148+
pub fn inner_name(&self) -> &DNSName<'a> {
149+
match self {
150+
DNSPattern::Exact(dnsname) => dnsname,
151+
DNSPattern::Wildcard(dnsname) => dnsname,
152+
}
153+
}
141154
}
142155

143156
/// A `DNSConstraint` represents a DNS name constraint as defined in [RFC 5280 4.2.1.10].

0 commit comments

Comments
 (0)