Bug 970542, Part 2: DNSName name constraint matching, r=keeler

--HG--
extra : rebase_source : 50b1a7d5d9da97cc64e09d5e6cdc41b8200c3551
This commit is contained in:
Brian Smith 2014-10-20 22:20:58 -07:00
Родитель 8d8b1cf373
Коммит 8b38009a34
1 изменённых файлов: 229 добавлений и 23 удалений

Просмотреть файл

@ -86,18 +86,21 @@ MOZILLA_PKIX_ENUM_CLASS ValidDNSIDMatchType
{
ReferenceID = 0,
PresentedID = 1,
NameConstraint = 2,
};
bool IsValidDNSID(Input hostname, ValidDNSIDMatchType matchType);
bool PresentedDNSIDMatchesReferenceDNSID(
Input presentedDNSID, ValidDNSIDMatchType referenceDNSIDMatchType,
Input referenceDNSID);
} // unnamed namespace
bool IsValidReferenceDNSID(Input hostname);
bool IsValidPresentedDNSID(Input hostname);
bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]);
bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]);
bool PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID,
Input referenceDNSID);
// Verify that the given end-entity cert, which is assumed to have been already
// validated with BuildCertChain, is valid for the given hostname. hostname is
@ -440,8 +443,9 @@ MatchPresentedIDWithReferenceID(GeneralNameType nameType,
switch (nameType) {
case GeneralNameType::dNSName:
foundMatch = PresentedDNSIDMatchesReferenceDNSID(presentedID,
referenceID);
foundMatch = PresentedDNSIDMatchesReferenceDNSID(
presentedID, ValidDNSIDMatchType::ReferenceID,
referenceID);
break;
case GeneralNameType::iPAddress:
foundMatch = InputsAreEqual(presentedID, referenceID);
@ -453,8 +457,6 @@ MatchPresentedIDWithReferenceID(GeneralNameType nameType,
return Success;
}
} // unnamed namespace
// We do not distinguish between a syntactically-invalid presentedDNSID and one
// that is syntactically valid but does not match referenceDNSID; in both
// cases, the result is false.
@ -468,23 +470,208 @@ MatchPresentedIDWithReferenceID(GeneralNameType nameType,
// <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
// follow NSS's stricter policy by accepting wildcards only of the form
// <x>*.<DNSID>, where <x> may be empty.
//
// An absolute presented DNS ID matches an absolute reference ID and a relative
// reference ID, and vice-versa. For example, all of these are matches:
//
// Presented ID Reference ID
// ---------------------------
// example.com example.com
// example.com. example.com
// example.com example.com.
// example.com. exmaple.com.
//
// There are more subtleties documented inline in the code.
//
// Name constraints ///////////////////////////////////////////////////////////
//
// This is all RFC 5280 has to say about DNSName constraints:
//
// DNS name restrictions are expressed as host.example.com. Any DNS
// name that can be constructed by simply adding zero or more labels to
// the left-hand side of the name satisfies the name constraint. For
// example, www.host.example.com would satisfy the constraint but
// host1.example.com would not.
//
// This lack of specificity has lead to a lot of uncertainty regarding
// subdomain matching. In particular, the following questions have been
// raised and answered:
//
// Q: Does a presented identifier equal (case insensitive) to the name
// constraint match the constraint? For example, does the presented
// ID "host.example.com" match a "host.example.com" constraint?
// A: Yes. RFC5280 says "by simply adding zero or more labels" and this
// is the case of adding zero labels.
//
// Q: When the name constraint does not start with ".", do subdomain
// presented identifiers match it? For example, does the presented
// ID "www.host.example.com" match a "host.example.com" constraint?
// A: Yes. RFC5280 says "by simply adding zero or more labels" and this
// is the case of adding more than zero labels. The example is the
// one from RFC 5280.
//
// Q: When the name constraint does not start with ".", does a
// non-subdomain prefix match it? For example, does "bigfoo.bar.com"
// match "foo.bar.com"? [4]
// A: No. We interpret RFC 5280's language of "adding zero or more labels"
// to mean that whole labels must be prefixed.
//
// (Note that the above three scenarios are the same as the RFC 6265
// domain matching rules [0].)
//
// Q: Is a name constraint that starts with "." valid, and if so, what
// semantics does it have? For example, does a presented ID of
// "www.example.com" match a constraint of ".example.com"? Does a
// presented ID of "example.com" match a constraint of ".example.com"?
// A: This implementation, NSS[1], and SChannel[2] all support a
// leading ".", but OpenSSL[3] does not yet. Amongst the
// implementations that support it, a leading "." is legal and means
// the same thing as when the "." is omitted, EXCEPT that a
// presented identifier equal (case insensitive) to the name
// constraint is not matched; i.e. presented DNSName identifiers
// must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
// have name constraints with the leading "." in their root
// certificates. The name constraints imposed on DCISS by Mozilla also
// have the it, so supporting this is a requirement for backward
// compatibility, even if it is not yet standardized. So, for example, a
// presented ID of "www.example.com" matches a constraint of
// ".example.com" but a presented ID of "example.com" does not.
//
// Q: Is there a way to prevent subdomain matches?
// A: Yes.
//
// Some people have proposed that dNSName constraints that do not
// start with a "." should be restricted to exact (case insensitive)
// matches. However, such a change of semantics from what RFC5280
// specifies would be a non-backward-compatible change in the case of
// permittedSubtrees constraints, and it would be a security issue for
// excludedSubtrees constraints.
//
// However, it can be done with a combination of permittedSubtrees and
// excludedSubtrees, e.g. "example.com" in permittedSubtrees and
// ".example.com" in excudedSubtrees.
//
// Q: Are name constraints allowed to be specified as absolute names?
// For example, does a presented ID of "example.com" match a name
// constraint of "example.com." and vice versa.
// A: Relative DNSNames match relative DNSName constraints but not
// absolute DNSName constraints. Absolute DNSNames match absolute
// DNSName constraints but not relative DNSName constraints (except "";
// see below). This follows from the requirement that matching DNSNames
// are constructed "by simply adding zero or more labels to the
// left-hand side" of the constraint.
//
// Q: Are "" and "." valid DNSName constraints? If so, what do they mean?
// A: Yes, both are valid. All relative and absolute DNSNames match
// a constraint of "" because any DNSName can be formed "by simply
// adding zero or more labels to the left-hand side" of "". In
// particular, an excludedSubtrees DNSName constraint of "" forbids all
// DNSNames. Only absolute names match a DNSName constraint of ".";
// relative DNSNames do not match "." because one cannot form a relative
// DNSName "by simply adding zero or more labels to the left-hand side"
// of "." (all such names would be absolute).
//
// [0] RFC 6265 (Cookies) Domain Matching rules:
// http://tools.ietf.org/html/rfc6265#section-5.1.3
// [1] NSS source code:
// https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
// [2] Description of SChannel's behavior from Microsoft:
// http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
// [3] Proposal to add such support to OpenSSL:
// http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
// https://rt.openssl.org/Ticket/Display.html?id=3562
// [4] Feedback on the lack of clarify in the definition that never got
// incorporated into the spec:
// https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
bool
PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID, Input referenceDNSID)
PresentedDNSIDMatchesReferenceDNSID(
Input presentedDNSID,
ValidDNSIDMatchType referenceDNSIDMatchType,
Input referenceDNSID)
{
if (!IsValidPresentedDNSID(presentedDNSID)) {
return false;
}
if (!IsValidReferenceDNSID(referenceDNSID)) {
if (!IsValidDNSID(referenceDNSID, referenceDNSIDMatchType)) {
return false;
}
Reader presented(presentedDNSID);
Reader reference(referenceDNSID);
switch (referenceDNSIDMatchType)
{
case ValidDNSIDMatchType::ReferenceID:
break;
case ValidDNSIDMatchType::NameConstraint:
{
if (presentedDNSID.GetLength() > referenceDNSID.GetLength()) {
if (referenceDNSID.GetLength() == 0) {
// An empty constraint matches everything.
return true;
}
// If the reference ID starts with a dot then skip the prefix of
// of the presented ID and start the comparison at the position of that
// dot. Examples:
//
// Matches Doesn't Match
// -----------------------------------------------------------
// original presented ID: www.example.com badexample.com
// skipped: www ba
// presented ID w/o prefix: .example.com dexample.com
// reference ID: .example.com .example.com
//
// If the reference ID does not start with a dot then we skip the
// prefix of the presented ID but also verify that the prefix ends with
// a dot. Examples:
//
// Matches Doesn't Match
// -----------------------------------------------------------
// original presented ID: www.example.com badexample.com
// skipped: www ba
// must be '.': . d
// presented ID w/o prefix: example.com example.com
// reference ID: example.com example.com
//
if (reference.Peek('.')) {
if (presented.Skip(static_cast<Input::size_type>(
presentedDNSID.GetLength() -
referenceDNSID.GetLength())) != Success) {
assert(false);
return false;
}
} else {
if (presented.Skip(static_cast<Input::size_type>(
presentedDNSID.GetLength() -
referenceDNSID.GetLength() - 1)) != Success) {
assert(false);
return false;
}
uint8_t b;
if (presented.Read(b) != Success) {
assert(false);
return false;
}
if (b != '.') {
return false;
}
}
}
break;
}
case ValidDNSIDMatchType::PresentedID: // fall through
default:
assert(false);
return false;
}
bool isFirstPresentedByte = true;
do {
uint8_t presentedByte;
Result rv = presented.Read(presentedByte);
if (rv != Success) {
if (presented.Read(presentedByte) != Success) {
return false;
}
if (presentedByte == '*') {
@ -497,8 +684,7 @@ PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID, Input referenceDNSID)
// string.
do {
uint8_t referenceByte;
rv = reference.Read(referenceByte);
if (rv != Success) {
if (reference.Read(referenceByte) != Success) {
return false;
}
} while (!reference.Peek('.'));
@ -521,8 +707,7 @@ PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID, Input referenceDNSID)
}
uint8_t referenceByte;
rv = reference.Read(referenceByte);
if (rv != Success) {
if (reference.Read(referenceByte) != Success) {
return false;
}
if (LocaleInsensitveToLower(presentedByte) !=
@ -533,15 +718,17 @@ PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID, Input referenceDNSID)
isFirstPresentedByte = false;
} while (!presented.AtEnd());
// Allow a relative presented DNS ID to match an absolute reference DNS ID.
// Allow a relative presented DNS ID to match an absolute reference DNS ID,
// unless we're matching a name constraint.
if (!reference.AtEnd()) {
uint8_t referenceByte;
Result rv = reference.Read(referenceByte);
if (rv != Success) {
return false;
}
if (referenceByte != '.') {
return false;
if (referenceDNSIDMatchType != ValidDNSIDMatchType::NameConstraint) {
uint8_t referenceByte;
if (reference.Read(referenceByte) != Success) {
return false;
}
if (referenceByte != '.') {
return false;
}
}
if (!reference.AtEnd()) {
return false;
@ -551,6 +738,16 @@ PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID, Input referenceDNSID)
return true;
}
} // unnamed namespace
bool PresentedDNSIDMatchesReferenceDNSID(Input presentedDNSID,
Input referenceDNSID)
{
return PresentedDNSIDMatchesReferenceDNSID(presentedDNSID,
ValidDNSIDMatchType::ReferenceID,
referenceDNSID);
}
namespace {
// We avoid isdigit because it is locale-sensitive. See
@ -844,6 +1041,10 @@ IsValidDNSID(Input hostname, ValidDNSIDMatchType matchType)
Reader input(hostname);
if (matchType == ValidDNSIDMatchType::NameConstraint && input.AtEnd()) {
return true;
}
bool allowWildcard = matchType == ValidDNSIDMatchType::PresentedID;
bool isWildcard = false;
size_t dotCount = 0;
@ -853,6 +1054,8 @@ IsValidDNSID(Input hostname, ValidDNSIDMatchType matchType)
bool labelIsWildcard = false;
bool labelEndsWithHyphen = false;
bool isFirstByte = true;
do {
static const size_t MAX_LABEL_LENGTH = 63;
@ -938,7 +1141,9 @@ IsValidDNSID(Input hostname, ValidDNSIDMatchType matchType)
case '.':
++dotCount;
if (labelLength == 0) {
if (labelLength == 0 &&
(matchType != ValidDNSIDMatchType::NameConstraint ||
!isFirstByte)) {
return false;
}
if (labelEndsWithHyphen) {
@ -952,6 +1157,7 @@ IsValidDNSID(Input hostname, ValidDNSIDMatchType matchType)
default:
return false; // Invalid character.
}
isFirstByte = false;
} while (!input.AtEnd());
if (labelEndsWithHyphen) {