/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This code is made available to you under your choice of the following sets * of licensing terms: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Copyright 2014 Mozilla Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pkixgtest.h" #include "mozpkix/pkixcheck.h" #include "mozpkix/pkixder.h" #include "mozpkix/pkixutil.h" namespace mozilla { namespace pkix { Result MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID, Input referenceDNSID, /*out*/ bool& matches); 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]); } } // namespace mozilla::pkix using namespace mozilla::pkix; using namespace mozilla::pkix::test; struct PresentedMatchesReference { ByteString presentedDNSID; ByteString referenceDNSID; Result expectedResult; bool expectedMatches; // only valid when expectedResult == Success }; ::std::ostream& operator<<(::std::ostream& os, const PresentedMatchesReference&) { return os << "TODO (bug 1318770)"; } #define DNS_ID_MATCH(a, b) \ { \ ByteString(reinterpret_cast(a), sizeof(a) - 1), \ ByteString(reinterpret_cast(b), sizeof(b) - 1), \ Success, \ true \ } #define DNS_ID_MISMATCH(a, b) \ { \ ByteString(reinterpret_cast(a), sizeof(a) - 1), \ ByteString(reinterpret_cast(b), sizeof(b) - 1), \ Success, \ false \ } #define DNS_ID_BAD_DER(a, b) \ { \ ByteString(reinterpret_cast(a), sizeof(a) - 1), \ ByteString(reinterpret_cast(b), sizeof(b) - 1), \ Result::ERROR_BAD_DER, \ false \ } static const PresentedMatchesReference DNSID_MATCH_PARAMS[] = { DNS_ID_BAD_DER("", "a"), DNS_ID_MATCH("a", "a"), DNS_ID_MISMATCH("b", "a"), DNS_ID_MATCH("*.b.a", "c.b.a"), DNS_ID_MISMATCH("*.b.a", "b.a"), DNS_ID_MISMATCH("*.b.a", "b.a."), // We allow underscores for compatibility with existing practices. DNS_ID_MATCH("a_b", "a_b"), DNS_ID_MATCH("*.example.com", "uses_underscore.example.com"), DNS_ID_MATCH("*.uses_underscore.example.com", "a.uses_underscore.example.com"), // See bug 1139039 DNS_ID_MATCH("_.example.com", "_.example.com"), DNS_ID_MATCH("*.example.com", "_.example.com"), DNS_ID_MATCH("_", "_"), DNS_ID_MATCH("___", "___"), DNS_ID_MATCH("example_", "example_"), DNS_ID_MATCH("_example", "_example"), DNS_ID_MATCH("*._._", "x._._"), // See bug 1139039 // A DNS-ID must not end in an all-numeric label. We don't consider // underscores to be numeric. DNS_ID_MATCH("_1", "_1"), DNS_ID_MATCH("example._1", "example._1"), DNS_ID_MATCH("example.1_", "example.1_"), // Wildcard not in leftmost label DNS_ID_MATCH("d.c.b.a", "d.c.b.a"), DNS_ID_BAD_DER("d.*.b.a", "d.c.b.a"), DNS_ID_BAD_DER("d.c*.b.a", "d.c.b.a"), DNS_ID_BAD_DER("d.c*.b.a", "d.cc.b.a"), // case sensitivity DNS_ID_MATCH("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"), DNS_ID_MATCH("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), DNS_ID_MATCH("aBc", "Abc"), // digits DNS_ID_MATCH("a1", "a1"), // A trailing dot indicates an absolute name. Absolute presented names are // not allowed, but absolute reference names are allowed. DNS_ID_MATCH("example", "example"), DNS_ID_BAD_DER("example.", "example."), DNS_ID_MATCH("example", "example."), DNS_ID_BAD_DER("example.", "example"), DNS_ID_MATCH("example.com", "example.com"), DNS_ID_BAD_DER("example.com.", "example.com."), DNS_ID_MATCH("example.com", "example.com."), DNS_ID_BAD_DER("example.com.", "example.com"), DNS_ID_BAD_DER("example.com..", "example.com."), DNS_ID_BAD_DER("example.com..", "example.com"), DNS_ID_BAD_DER("example.com...", "example.com."), // xn-- IDN prefix DNS_ID_BAD_DER("x*.b.a", "xa.b.a"), DNS_ID_BAD_DER("x*.b.a", "xna.b.a"), DNS_ID_BAD_DER("x*.b.a", "xn-a.b.a"), DNS_ID_BAD_DER("x*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"), DNS_ID_BAD_DER("xn---*.b.a", "xn--a.b.a"), // "*" cannot expand to nothing. DNS_ID_BAD_DER("c*.b.a", "c.b.a"), ///////////////////////////////////////////////////////////////////////////// // These are test cases adapted from Chromium's x509_certificate_unittest.cc. // The parameter order is the opposite in Chromium's tests. Also, some tests // were modified to fit into this framework or due to intentional differences // between mozilla::pkix and Chromium. DNS_ID_MATCH("foo.com", "foo.com"), DNS_ID_MATCH("f", "f"), DNS_ID_MISMATCH("i", "h"), DNS_ID_MATCH("*.foo.com", "bar.foo.com"), DNS_ID_MATCH("*.test.fr", "www.test.fr"), DNS_ID_MATCH("*.test.FR", "wwW.tESt.fr"), DNS_ID_BAD_DER(".uk", "f.uk"), DNS_ID_BAD_DER("?.bar.foo.com", "w.bar.foo.com"), DNS_ID_BAD_DER("(www|ftp).foo.com", "www.foo.com"), // regex! DNS_ID_BAD_DER("www.foo.com\0", "www.foo.com"), DNS_ID_BAD_DER("www.foo.com\0*.foo.com", "www.foo.com"), DNS_ID_MISMATCH("ww.house.example", "www.house.example"), DNS_ID_MISMATCH("www.test.org", "test.org"), DNS_ID_MISMATCH("*.test.org", "test.org"), DNS_ID_BAD_DER("*.org", "test.org"), DNS_ID_BAD_DER("w*.bar.foo.com", "w.bar.foo.com"), DNS_ID_BAD_DER("ww*ww.bar.foo.com", "www.bar.foo.com"), DNS_ID_BAD_DER("ww*ww.bar.foo.com", "wwww.bar.foo.com"), // Different than Chromium, matches NSS. DNS_ID_BAD_DER("w*w.bar.foo.com", "wwww.bar.foo.com"), DNS_ID_BAD_DER("w*w.bar.foo.c0m", "wwww.bar.foo.com"), // '*' must be the only character in the wildcard label DNS_ID_BAD_DER("wa*.bar.foo.com", "WALLY.bar.foo.com"), // We require "*" to be the last character in a wildcard label, but // Chromium does not. DNS_ID_BAD_DER("*Ly.bar.foo.com", "wally.bar.foo.com"), // Chromium does URL decoding of the reference ID, but we don't, and we also // require that the reference ID is valid, so we can't test these two. // DNS_ID_MATCH("www.foo.com", "ww%57.foo.com"), // DNS_ID_MATCH("www&.foo.com", "www%26.foo.com"), DNS_ID_MISMATCH("*.test.de", "www.test.co.jp"), DNS_ID_BAD_DER("*.jp", "www.test.co.jp"), DNS_ID_MISMATCH("www.test.co.uk", "www.test.co.jp"), DNS_ID_BAD_DER("www.*.co.jp", "www.test.co.jp"), DNS_ID_MATCH("www.bar.foo.com", "www.bar.foo.com"), DNS_ID_MISMATCH("*.foo.com", "www.bar.foo.com"), DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"), DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"), // Our matcher requires the reference ID to be a valid DNS name, so we cannot // test this case. //DNS_ID_BAD_DER("*.*.bar.foo.com", "*..bar.foo.com"), DNS_ID_MATCH("www.bath.org", "www.bath.org"), // Our matcher requires the reference ID to be a valid DNS name, so we cannot // test these cases. // DNS_ID_BAD_DER("www.bath.org", ""), // DNS_ID_BAD_DER("www.bath.org", "20.30.40.50"), // DNS_ID_BAD_DER("www.bath.org", "66.77.88.99"), // IDN tests DNS_ID_MATCH("xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"), DNS_ID_MISMATCH("*.xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), DNS_ID_BAD_DER("xn--poema-*.com.br", "xn--poema-9qae5a.com.br"), DNS_ID_BAD_DER("xn--*-9qae5a.com.br", "xn--poema-9qae5a.com.br"), DNS_ID_BAD_DER("*--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), // The following are adapted from the examples quoted from // http://tools.ietf.org/html/rfc6125#section-6.4.3 // (e.g., *.example.com would match foo.example.com but // not bar.foo.example.com or example.com). DNS_ID_MATCH("*.example.com", "foo.example.com"), DNS_ID_MISMATCH("*.example.com", "bar.foo.example.com"), DNS_ID_MISMATCH("*.example.com", "example.com"), // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would // be taken to match baz1.example.net and foobaz.example.net and // buzz.example.net, respectively. However, we don't allow any characters // other than '*' in the wildcard label. DNS_ID_BAD_DER("baz*.example.net", "baz1.example.net"), // Both of these are different from Chromium, but match NSS, becaues the // wildcard character "*" is not the last character of the label. DNS_ID_BAD_DER("*baz.example.net", "foobaz.example.net"), DNS_ID_BAD_DER("b*z.example.net", "buzz.example.net"), // Wildcards should not be valid for public registry controlled domains, // and unknown/unrecognized domains, at least three domain components must // be present. For mozilla::pkix and NSS, there must always be at least two // labels after the wildcard label. DNS_ID_MATCH("*.test.example", "www.test.example"), DNS_ID_MATCH("*.example.co.uk", "test.example.co.uk"), DNS_ID_BAD_DER("*.exmaple", "test.example"), // The result is different than Chromium, because Chromium takes into account // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does // not know that. DNS_ID_MATCH("*.co.uk", "example.co.uk"), DNS_ID_BAD_DER("*.com", "foo.com"), DNS_ID_BAD_DER("*.us", "foo.us"), DNS_ID_BAD_DER("*", "foo"), // IDN variants of wildcards and registry controlled domains. DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"), DNS_ID_MATCH("*.example.xn--mgbaam7a8h", "test.example.xn--mgbaam7a8h"), // RFC6126 allows this, and NSS accepts it, but Chromium disallows it. // TODO: File bug against Chromium. DNS_ID_MATCH("*.com.br", "xn--poema-9qae5a.com.br"), DNS_ID_BAD_DER("*.xn--mgbaam7a8h", "example.xn--mgbaam7a8h"), // Wildcards should be permissible for 'private' registry-controlled // domains. (In mozilla::pkix, we do not know if it is a private registry- // controlled domain or not.) DNS_ID_MATCH("*.appspot.com", "www.appspot.com"), DNS_ID_MATCH("*.s3.amazonaws.com", "foo.s3.amazonaws.com"), // Multiple wildcards are not valid. DNS_ID_BAD_DER("*.*.com", "foo.example.com"), DNS_ID_BAD_DER("*.bar.*.com", "foo.bar.example.com"), // Absolute vs relative DNS name tests. Although not explicitly specified // in RFC 6125, absolute reference names (those ending in a .) should // match either absolute or relative presented names. We don't allow // absolute presented names. // TODO: File errata against RFC 6125 about this. DNS_ID_BAD_DER("foo.com.", "foo.com"), DNS_ID_MATCH("foo.com", "foo.com."), DNS_ID_BAD_DER("foo.com.", "foo.com."), DNS_ID_BAD_DER("f.", "f"), DNS_ID_MATCH("f", "f."), DNS_ID_BAD_DER("f.", "f."), DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com"), DNS_ID_MATCH("*.bar.foo.com", "www-3.bar.foo.com."), DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com."), // We require the reference ID to be a valid DNS name, so we cannot test this // case. // DNS_ID_MISMATCH(".", "."), DNS_ID_BAD_DER("*.com.", "example.com"), DNS_ID_BAD_DER("*.com", "example.com."), DNS_ID_BAD_DER("*.com.", "example.com."), DNS_ID_BAD_DER("*.", "foo."), DNS_ID_BAD_DER("*.", "foo"), // The result is different than Chromium because we don't know that co.uk is // a TLD. DNS_ID_MATCH("*.co.uk", "foo.co.uk"), DNS_ID_MATCH("*.co.uk", "foo.co.uk."), DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk"), DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk."), DNS_ID_MISMATCH("*.example.com", "localhost"), DNS_ID_MISMATCH("*.example.com", "localhost."), // Note that we already have the testcase DNS_ID_BAD_DER("*", "foo") above }; struct InputValidity { ByteString input; bool isValidReferenceID; bool isValidPresentedID; }; ::std::ostream& operator<<(::std::ostream& os, const InputValidity&) { return os << "TODO (bug 1318770)"; } // str is null-terminated, which is why we subtract 1. str may contain embedded // nulls (including at the end) preceding the null terminator though. #define I(str, validReferenceID, validPresentedID) \ { \ ByteString(reinterpret_cast(str), sizeof(str) - 1), \ validReferenceID, \ validPresentedID, \ } static const InputValidity DNSNAMES_VALIDITY[] = { I("a", true, true), I("a.b", true, true), I("a.b.c", true, true), I("a.b.c.d", true, true), // empty labels I("", false, false), I(".", false, false), I("a", true, true), I(".a", false, false), I(".a.b", false, false), I("..a", false, false), I("a..b", false, false), I("a...b", false, false), I("a..b.c", false, false), I("a.b..c", false, false), I(".a.b.c.", false, false), // absolute names (only allowed for reference names) I("a.", true, false), I("a.b.", true, false), I("a.b.c.", true, false), // absolute names with empty label at end I("a..", false, false), I("a.b..", false, false), I("a.b.c..", false, false), I("a...", false, false), // Punycode I("xn--", false, false), I("xn--.", false, false), I("xn--.a", false, false), I("a.xn--", false, false), I("a.xn--.", false, false), I("a.xn--.b", false, false), I("a.xn--.b", false, false), I("a.xn--\0.b", false, false), I("a.xn--a.b", true, true), I("xn--a", true, true), I("a.xn--a", true, true), I("a.xn--a.a", true, true), I("\xc4\x95.com", false, false), // UTF-8 ĕ I("xn--jea.com", true, true), // punycode ĕ I("xn--\xc4\x95.com", false, false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup // Surprising punycode I("xn--google.com", true, true), // 䕮䕵䕶䕱.com I("xn--citibank.com", true, true), // 岍岊岊岅岉岎.com I("xn--cnn.com", true, true), // 䁾.com I("a.xn--cnn", true, true), // a.䁾 I("a.xn--cnn.com", true, true), // a.䁾.com I("1.2.3.4", false, false), // IPv4 address I("1::2", false, false), // IPV6 address // whitespace not allowed anywhere. I(" ", false, false), I(" a", false, false), I("a ", false, false), I("a b", false, false), I("a.b 1", false, false), I("a\t", false, false), // Nulls not allowed I("\0", false, false), I("a\0", false, false), I("example.org\0.example.com", false, false), // Hi Moxie! I("\0a", false, false), I("xn--\0", false, false), // Allowed character set I("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true, true), I("A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z", true, true), I("0.1.2.3.4.5.6.7.8.9.a", true, true), // "a" needed to avoid numeric last label I("a-b", true, true), // hyphen (a label cannot start or end with a hyphen) // Underscores I("a_b", true, true), // See bug 1139039 I("_", true, true), I("a_", true, true), I("_a", true, true), I("_1", true, true), I("1_", true, true), I("___", true, true), // An invalid character in various positions I("!", false, false), I("!a", false, false), I("a!", false, false), I("a!b", false, false), I("a.!", false, false), I("a.a!", false, false), I("a.!a", false, false), I("a.a!a", false, false), I("a.!a.a", false, false), I("a.a!.a", false, false), I("a.a!a.a", false, false), // Various other invalid characters I("a!", false, false), I("a@", false, false), I("a#", false, false), I("a$", false, false), I("a%", false, false), I("a^", false, false), I("a&", false, false), I("a*", false, false), I("a(", false, false), I("a)", false, false), // last label can't be fully numeric I("1", false, false), I("a.1", false, false), // other labels can be fully numeric I("1.a", true, true), I("1.2.a", true, true), I("1.2.3.a", true, true), // last label can be *partly* numeric I("1a", true, true), I("1.1a", true, true), I("1-1", true, true), I("a.1-1", true, true), I("a.1-a", true, true), // labels cannot start with a hyphen I("-", false, false), I("-1", false, false), // labels cannot end with a hyphen I("1-", false, false), I("1-.a", false, false), I("a-", false, false), I("a-.a", false, false), I("a.1-.a", false, false), I("a.a-.a", false, false), // labels can contain a hyphen in the middle I("a-b", true, true), I("1-2", true, true), I("a.a-1", true, true), // multiple consecutive hyphens allowed I("a--1", true, true), I("1---a", true, true), I("a-----------------b", true, true), // Wildcard specifications are not valid reference names, but are valid // presented names if there are enough labels and if '*' is the only // character in the wildcard label. I("*.a", false, false), I("a*", false, false), I("a*.", false, false), I("a*.a", false, false), I("a*.a.", false, false), I("*.a.b", false, true), I("*.a.b.", false, false), I("a*.b.c", false, false), I("*.a.b.c", false, true), I("a*.b.c.d", false, false), // Multiple wildcards are not allowed. I("a**.b.c", false, false), I("a*b*.c.d", false, false), I("a*.b*.c", false, false), // Wildcards are only allowed in the first label. I("a.*", false, false), I("a.*.b", false, false), I("a.b.*", false, false), I("a.b*.c", false, false), I("*.b*.c", false, false), I(".*.a.b", false, false), I(".a*.b.c", false, false), // Wildcards must be at the *end* of the first label. I("*a.b.c", false, false), I("a*b.c.d", false, false), // Wildcards not allowed with IDNA prefix I("x*.a.b", false, false), I("xn*.a.b", false, false), I("xn-*.a.b", false, false), I("xn--*.a.b", false, false), I("xn--w*.a.b", false, false), // Redacted labels from RFC6962bis draft 4 // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-04#section-3.2.2 I("(PRIVATE).foo", false, false), // maximum label length is 63 characters I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "abc", true, true), I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "abcd", false, false), // maximum total length is 253 characters I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "12345678" "a", true, true), I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." "1234567890" "1234567890" "1234567890" "1234567890" "123456789" "a", false, false), }; static const InputValidity DNSNAMES_VALIDITY_TURKISH_I[] = { // http://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing // IDN registration rules disallow "latin capital letter i with dot above," // but our checks aren't intended to enforce those rules. I("I", true, true), // ASCII capital I I("i", true, true), // ASCII lowercase i I("\xC4\xB0", false, false), // latin capital letter i with dot above I("\xC4\xB1", false, false), // latin small letter dotless i I("xn--i-9bb", true, true), // latin capital letter i with dot above, in punycode I("xn--cfa", true, true), // latin small letter dotless i, in punycode I("xn--\xC4\xB0", false, false), // latin capital letter i with dot above, mashup I("xn--\xC4\xB1", false, false), // latin small letter dotless i, mashup }; static const uint8_t LOWERCASE_I_VALUE[1] = { 'i' }; static const uint8_t UPPERCASE_I_VALUE[1] = { 'I' }; static const Input LOWERCASE_I(LOWERCASE_I_VALUE); static const Input UPPERCASE_I(UPPERCASE_I_VALUE); template struct IPAddressParams { ByteString input; bool isValid; uint8_t expectedValueIfValid[L]; }; template ::std::ostream& operator<<(::std::ostream& os, const IPAddressParams&) { return os << "TODO (bug 1318770)"; } #define IPV4_VALID(str, a, b, c, d) \ { \ ByteString(reinterpret_cast(str), sizeof(str) - 1), \ true, \ { a, b, c, d } \ } // The value of expectedValueIfValid must be ignored for invalid IP addresses. // The value { 73, 73, 73, 73 } is used because it is unlikely to result in an // accidental match, unlike { 0, 0, 0, 0 }, which is a value we actually test. #define IPV4_INVALID(str) \ { \ ByteString(reinterpret_cast(str), sizeof(str) - 1), \ false, \ { 73, 73, 73, 73 } \ } static const IPAddressParams<4> IPV4_ADDRESSES[] = { IPV4_INVALID(""), IPV4_INVALID("1"), IPV4_INVALID("1.2"), IPV4_INVALID("1.2.3"), IPV4_VALID("1.2.3.4", 1, 2, 3, 4), IPV4_INVALID("1.2.3.4.5"), IPV4_INVALID("1.2.3.4a"), // a DNSName! IPV4_INVALID("a.2.3.4"), // not even a DNSName! IPV4_INVALID("1::2"), // IPv6 address // Whitespace not allowed IPV4_INVALID(" 1.2.3.4"), IPV4_INVALID("1.2.3.4 "), IPV4_INVALID("1 .2.3.4"), IPV4_INVALID("\n1.2.3.4"), IPV4_INVALID("1.2.3.4\n"), // Nulls not allowed IPV4_INVALID("\0"), IPV4_INVALID("\0" "1.2.3.4"), IPV4_INVALID("1.2.3.4\0"), IPV4_INVALID("1.2.3.4\0.5"), // Range IPV4_VALID("0.0.0.0", 0, 0, 0, 0), IPV4_VALID("255.255.255.255", 255, 255, 255, 255), IPV4_INVALID("256.0.0.0"), IPV4_INVALID("0.256.0.0"), IPV4_INVALID("0.0.256.0"), IPV4_INVALID("0.0.0.256"), IPV4_INVALID("999.0.0.0"), IPV4_INVALID("9999999999999999999.0.0.0"), // All digits allowed IPV4_VALID("0.1.2.3", 0, 1, 2, 3), IPV4_VALID("4.5.6.7", 4, 5, 6, 7), IPV4_VALID("8.9.0.1", 8, 9, 0, 1), // Leading zeros not allowed IPV4_INVALID("01.2.3.4"), IPV4_INVALID("001.2.3.4"), IPV4_INVALID("00000000001.2.3.4"), IPV4_INVALID("010.2.3.4"), IPV4_INVALID("1.02.3.4"), IPV4_INVALID("1.2.03.4"), IPV4_INVALID("1.2.3.04"), // Empty components IPV4_INVALID(".2.3.4"), IPV4_INVALID("1..3.4"), IPV4_INVALID("1.2..4"), IPV4_INVALID("1.2.3."), // Too many components IPV4_INVALID("1.2.3.4.5"), IPV4_INVALID("1.2.3.4.5.6"), IPV4_INVALID("0.1.2.3.4"), IPV4_INVALID("1.2.3.4.0"), // Leading/trailing dot IPV4_INVALID(".1.2.3.4"), IPV4_INVALID("1.2.3.4."), // Other common forms of IPv4 address // http://en.wikipedia.org/wiki/IPv4#Address_representations IPV4_VALID("192.0.2.235", 192, 0, 2, 235), // dotted decimal (control value) IPV4_INVALID("0xC0.0x00.0x02.0xEB"), // dotted hex IPV4_INVALID("0301.0000.0002.0353"), // dotted octal IPV4_INVALID("0xC00002EB"), // non-dotted hex IPV4_INVALID("3221226219"), // non-dotted decimal IPV4_INVALID("030000001353"), // non-dotted octal IPV4_INVALID("192.0.0002.0xEB"), // mixed }; #define IPV6_VALID(str, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ { \ ByteString(reinterpret_cast(str), sizeof(str) - 1), \ true, \ { a, b, c, d, \ e, f, g, h, \ i, j, k, l, \ m, n, o, p } \ } #define IPV6_INVALID(str) \ { \ ByteString(reinterpret_cast(str), sizeof(str) - 1), \ false, \ { 73, 73, 73, 73, \ 73, 73, 73, 73, \ 73, 73, 73, 73, \ 73, 73, 73, 73 } \ } static const IPAddressParams<16> IPV6_ADDRESSES[] = { IPV6_INVALID(""), IPV6_INVALID("1234"), IPV6_INVALID("1234:5678"), IPV6_INVALID("1234:5678:9abc"), IPV6_INVALID("1234:5678:9abc:def0"), IPV6_INVALID("1234:5678:9abc:def0:1234:"), IPV6_INVALID("1234:5678:9abc:def0:1234:5678:"), IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:"), IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc:def0", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0), IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:"), IPV6_INVALID(":1234:5678:9abc:def0:1234:5678:9abc:def0"), IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:0000"), // Valid contractions IPV6_VALID("::1", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), IPV6_VALID("::1234", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34), IPV6_VALID("1234::", 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), IPV6_VALID("1234::5678", 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x78), IPV6_VALID("1234:5678::abcd", 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd), IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc::", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0x00), // Contraction in full IPv6 addresses not allowed IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), // start IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), // end IPV6_INVALID("1234:5678::9abc:def0:1234:5678:9abc:def0"), // interior // Multiple contractions not allowed IPV6_INVALID("::1::"), IPV6_INVALID("::1::2"), IPV6_INVALID("1::2::"), // Colon madness! IPV6_INVALID(":"), IPV6_INVALID("::"), IPV6_INVALID(":::"), IPV6_INVALID("::::"), IPV6_INVALID(":::1"), IPV6_INVALID("::::1"), IPV6_INVALID("1:::2"), IPV6_INVALID("1::::2"), IPV6_INVALID("1:2:::"), IPV6_INVALID("1:2::::"), IPV6_INVALID("::1234:"), IPV6_INVALID(":1234::"), IPV6_INVALID("01234::"), // too many digits, even if zero IPV6_INVALID("12345678::"), // too many digits or missing colon // uppercase IPV6_VALID("ABCD:EFAB::", 0xab, 0xcd, 0xef, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), // miXeD CAse IPV6_VALID("aBcd:eFAb::", 0xab, 0xcd, 0xef, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), // IPv4-style IPV6_VALID("::2.3.4.5", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05), IPV6_VALID("1234::2.3.4.5", 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05), IPV6_VALID("::abcd:2.3.4.5", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd, 0x02, 0x03, 0x04, 0x05), IPV6_VALID("1234:5678:9abc:def0:1234:5678:252.253.254.255", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 252, 253, 254, 255), IPV6_VALID("1234:5678:9abc:def0:1234::252.253.254.255", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x00, 0x00, 252, 253, 254, 255), IPV6_INVALID("1234::252.253.254"), IPV6_INVALID("::252.253.254"), IPV6_INVALID("::252.253.254.300"), IPV6_INVALID("1234::252.253.254.255:"), IPV6_INVALID("1234::252.253.254.255:5678"), // Contractions that don't contract IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), IPV6_INVALID("1234:5678:9abc:def0::1234:5678:9abc:def0"), IPV6_INVALID("1234:5678:9abc:def0:1234:5678::252.253.254.255"), // With and without leading zeros IPV6_VALID("::123", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x23), IPV6_VALID("::0123", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x23), IPV6_VALID("::012", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12), IPV6_VALID("::0012", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12), IPV6_VALID("::01", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), IPV6_VALID("::001", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), IPV6_VALID("::0001", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), IPV6_VALID("::0", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), IPV6_VALID("::00", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), IPV6_VALID("::000", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), IPV6_VALID("::0000", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), IPV6_INVALID("::01234"), IPV6_INVALID("::00123"), IPV6_INVALID("::000123"), // Trailing zero IPV6_INVALID("::12340"), // Whitespace IPV6_INVALID(" 1234:5678:9abc:def0:1234:5678:9abc:def0"), IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0"), IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0\n"), IPV6_INVALID("1234 :5678:9abc:def0:1234:5678:9abc:def0"), IPV6_INVALID("1234: 5678:9abc:def0:1234:5678:9abc:def0"), IPV6_INVALID(":: 2.3.4.5"), IPV6_INVALID("1234::252.253.254.255 "), IPV6_INVALID("1234::252.253.254.255\n"), IPV6_INVALID("1234::252.253. 254.255"), // Nulls IPV6_INVALID("\0"), IPV6_INVALID("::1\0:2"), IPV6_INVALID("::1\0"), IPV6_INVALID("::1.2.3.4\0"), IPV6_INVALID("::1.2\02.3.4"), }; class pkixnames_MatchPresentedDNSIDWithReferenceDNSID : public ::testing::Test , public ::testing::WithParamInterface { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID, MatchPresentedDNSIDWithReferenceDNSID) { const PresentedMatchesReference& param(GetParam()); SCOPED_TRACE(param.presentedDNSID.c_str()); SCOPED_TRACE(param.referenceDNSID.c_str()); Input presented; ASSERT_EQ(Success, presented.Init(param.presentedDNSID.data(), param.presentedDNSID.length())); Input reference; ASSERT_EQ(Success, reference.Init(param.referenceDNSID.data(), param.referenceDNSID.length())); // sanity check that test makes sense ASSERT_TRUE(IsValidReferenceDNSID(reference)); bool matches; ASSERT_EQ(param.expectedResult, MatchPresentedDNSIDWithReferenceDNSID(presented, reference, matches)); if (param.expectedResult == Success) { ASSERT_EQ(param.expectedMatches, matches); } } INSTANTIATE_TEST_SUITE_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID, pkixnames_MatchPresentedDNSIDWithReferenceDNSID, testing::ValuesIn(DNSID_MATCH_PARAMS)); class pkixnames_Turkish_I_Comparison : public ::testing::Test , public ::testing::WithParamInterface { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID) { // Make sure we don't have the similar problems that strcasecmp and others // have with the other kinds of "i" and "I" commonly used in Turkish locales. const InputValidity& inputValidity(GetParam()); SCOPED_TRACE(inputValidity.input.c_str()); Input input; ASSERT_EQ(Success, input.Init(inputValidity.input.data(), inputValidity.input.length())); bool isASCII = InputsAreEqual(LOWERCASE_I, input) || InputsAreEqual(UPPERCASE_I, input); { bool matches; ASSERT_EQ(inputValidity.isValidPresentedID ? Success : Result::ERROR_BAD_DER, MatchPresentedDNSIDWithReferenceDNSID(input, LOWERCASE_I, matches)); if (inputValidity.isValidPresentedID) { ASSERT_EQ(isASCII, matches); } } { bool matches; ASSERT_EQ(inputValidity.isValidPresentedID ? Success : Result::ERROR_BAD_DER, MatchPresentedDNSIDWithReferenceDNSID(input, UPPERCASE_I, matches)); if (inputValidity.isValidPresentedID) { ASSERT_EQ(isASCII, matches); } } } INSTANTIATE_TEST_SUITE_P(pkixnames_Turkish_I_Comparison, pkixnames_Turkish_I_Comparison, testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I)); class pkixnames_IsValidReferenceDNSID : public ::testing::Test , public ::testing::WithParamInterface { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID) { const InputValidity& inputValidity(GetParam()); SCOPED_TRACE(inputValidity.input.c_str()); Input input; ASSERT_EQ(Success, input.Init(inputValidity.input.data(), inputValidity.input.length())); ASSERT_EQ(inputValidity.isValidReferenceID, IsValidReferenceDNSID(input)); ASSERT_EQ(inputValidity.isValidPresentedID, IsValidPresentedDNSID(input)); } INSTANTIATE_TEST_SUITE_P(pkixnames_IsValidReferenceDNSID, pkixnames_IsValidReferenceDNSID, testing::ValuesIn(DNSNAMES_VALIDITY)); INSTANTIATE_TEST_SUITE_P(pkixnames_IsValidReferenceDNSID_Turkish_I, pkixnames_IsValidReferenceDNSID, testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I)); class pkixnames_ParseIPv4Address : public ::testing::Test , public ::testing::WithParamInterface> { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address) { const IPAddressParams<4>& param(GetParam()); SCOPED_TRACE(param.input.c_str()); Input input; ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); uint8_t ipAddress[4]; ASSERT_EQ(param.isValid, ParseIPv4Address(input, ipAddress)); if (param.isValid) { for (size_t i = 0; i < sizeof(ipAddress); ++i) { ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]); } } } INSTANTIATE_TEST_SUITE_P(pkixnames_ParseIPv4Address, pkixnames_ParseIPv4Address, testing::ValuesIn(IPV4_ADDRESSES)); class pkixnames_ParseIPv6Address : public ::testing::Test , public ::testing::WithParamInterface> { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address) { const IPAddressParams<16>& param(GetParam()); SCOPED_TRACE(param.input.c_str()); Input input; ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); uint8_t ipAddress[16]; ASSERT_EQ(param.isValid, ParseIPv6Address(input, ipAddress)); if (param.isValid) { for (size_t i = 0; i < sizeof(ipAddress); ++i) { ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]); } } } INSTANTIATE_TEST_SUITE_P(pkixnames_ParseIPv6Address, pkixnames_ParseIPv6Address, testing::ValuesIn(IPV6_ADDRESSES)); // This is an arbitrary string that is used to indicate that no SAN extension // should be put into the generated certificate. It needs to be different from // "" or any other subjectAltName value that we actually want to test, but its // actual value does not matter. Note that this isn't a correctly-encoded SAN // extension value! static const ByteString NO_SAN(reinterpret_cast("I'm a bad, bad, certificate")); struct CheckCertHostnameParams { ByteString hostname; ByteString subject; ByteString subjectAltName; Result result; }; ::std::ostream& operator<<(::std::ostream& os, const CheckCertHostnameParams&) { return os << "TODO (bug 1318770)"; } class pkixnames_CheckCertHostname : public ::testing::Test , public ::testing::WithParamInterface { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; #define WITH_SAN(r, ps, psan, result) \ { \ ByteString(reinterpret_cast(r), sizeof(r) - 1), \ ps, \ psan, \ result \ } #define WITHOUT_SAN(r, ps, result) \ { \ ByteString(reinterpret_cast(r), sizeof(r) - 1), \ ps, \ NO_SAN, \ result \ } static const uint8_t example_com[] = { 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm' }; // Note that We avoid zero-valued bytes in these IP addresses so that we don't // get false negatives from anti-NULL-byte defenses in dNSName decoding. static const uint8_t ipv4_addr_bytes[] = { 1, 2, 3, 4 }; static const char ipv4_addr_bytes_as_str[] = "\x01\x02\x03\x04"; static const char ipv4_addr_str[] = "1.2.3.4"; static const uint8_t ipv4_addr_bytes_FFFFFFFF[8] = { 1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff }; static const uint8_t ipv4_compatible_ipv6_addr_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4 }; static const char ipv4_compatible_ipv6_addr_str[] = "::1.2.3.4"; static const uint8_t ipv4_mapped_ipv6_addr_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 1, 2, 3, 4 }; static const char ipv4_mapped_ipv6_addr_str[] = "::FFFF:1.2.3.4"; static const uint8_t ipv6_addr_bytes[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11 }; static const char ipv6_addr_bytes_as_str[] = "\x11\x22\x33\x44" "\x55\x66\x77\x88" "\x99\xaa\xbb\xcc" "\xdd\xee\xff\x11"; static const char ipv6_addr_str[] = "1122:3344:5566:7788:99aa:bbcc:ddee:ff11"; static const uint8_t ipv6_other_addr_bytes[] = { 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, }; static const uint8_t ipv4_other_addr_bytes[] = { 5, 6, 7, 8 }; static const uint8_t ipv4_other_addr_bytes_FFFFFFFF[] = { 5, 6, 7, 8, 0xff, 0xff, 0xff, 0xff }; static const uint8_t ipv4_addr_00000000_bytes[] = { 0, 0, 0, 0 }; static const uint8_t ipv4_addr_FFFFFFFF_bytes[] = { 0, 0, 0, 0 }; static const uint8_t ipv4_constraint_all_zeros_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; static const uint8_t ipv6_addr_all_zeros_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const uint8_t ipv6_constraint_all_zeros_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const uint8_t ipv4_constraint_CIDR_16_bytes[] = { 1, 2, 0, 0, 0xff, 0xff, 0, 0 }; static const uint8_t ipv4_constraint_CIDR_17_bytes[] = { 1, 2, 0, 0, 0xff, 0xff, 0x80, 0 }; // The subnet is 1.2.0.0/16 but it is specified as 1.2.3.0/16 static const uint8_t ipv4_constraint_CIDR_16_bad_addr_bytes[] = { 1, 2, 3, 0, 0xff, 0xff, 0, 0 }; // Masks are supposed to be of the form , but this one is of the // form . static const uint8_t ipv4_constraint_bad_mask_bytes[] = { 1, 2, 3, 0, 0xff, 0, 0xff, 0 }; static const uint8_t ipv6_constraint_CIDR_16_bytes[] = { 0x11, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // The subnet is 1122::/16 but it is specified as 1122:3344::/16 static const uint8_t ipv6_constraint_CIDR_16_bad_addr_bytes[] = { 0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Masks are supposed to be of the form , but this one is of the // form . static const uint8_t ipv6_constraint_bad_mask_bytes[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const uint8_t ipv4_addr_truncated_bytes[] = { 1, 2, 3 }; static const uint8_t ipv4_addr_overlong_bytes[] = { 1, 2, 3, 4, 5 }; static const uint8_t ipv4_constraint_truncated_bytes[] = { 0, 0, 0, 0, 0, 0, 0, }; static const uint8_t ipv4_constraint_overlong_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const uint8_t ipv6_addr_truncated_bytes[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; static const uint8_t ipv6_addr_overlong_bytes[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x00 }; static const uint8_t ipv6_constraint_truncated_bytes[] = { 0x11, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const uint8_t ipv6_constraint_overlong_bytes[] = { 0x11, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Note that, for DNSNames, these test cases in CHECK_CERT_HOSTNAME_PARAMS are // mostly about testing different scenerios regarding the structure of entries // in the subjectAltName and subject of the certificate, than about the how // specific presented identifier values are matched against the reference // identifier values. This is because we also use the test cases in // DNSNAMES_VALIDITY to test CheckCertHostname. Consequently, tests about // whether specific presented DNSNames (including wildcards, in particular) are // matched against a reference DNSName only need to be added to // DNSNAMES_VALIDITY, and not here. static const CheckCertHostnameParams CHECK_CERT_HOSTNAME_PARAMS[] = { // This is technically illegal. PrintableString is defined in such a way that // '*' is not an allowed character, but there are many real-world certificates // that are encoded this way. WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::PrintableString)), Success), WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::UTF8String)), Success), // Many certificates use TeletexString when encoding wildcards in CN-IDs // because PrintableString is defined as not allowing '*' and UTF8String was, // at one point in history, considered too new to depend on for compatibility. // We accept TeletexString-encoded CN-IDs when they don't contain any escape // sequences. The reference I used for the escape codes was // https://tools.ietf.org/html/rfc1468. The escaping mechanism is actually // pretty complex and these tests don't even come close to testing all the // possibilities. WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::TeletexString)), Success), // "ESC ( B" ({0x1B,0x50,0x42}) is the escape code to switch to ASCII, which // is redundant because it already the default. WITHOUT_SAN("foo.example.com", RDN(CN("\x1B(B*.example.com", der::TeletexString)), Result::ERROR_BAD_CERT_DOMAIN), WITHOUT_SAN("foo.example.com", RDN(CN("*.example\x1B(B.com", der::TeletexString)), Result::ERROR_BAD_CERT_DOMAIN), WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com\x1B(B", der::TeletexString)), Result::ERROR_BAD_CERT_DOMAIN), // "ESC $ B" ({0x1B,0x24,0x42}) is the escape code to switch to // JIS X 0208-1983 (a Japanese character set). WITHOUT_SAN("foo.example.com", RDN(CN("\x1B$B*.example.com", der::TeletexString)), Result::ERROR_BAD_CERT_DOMAIN), WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com\x1B$B", der::TeletexString)), Result::ERROR_BAD_CERT_DOMAIN), // Match a DNSName SAN entry with a redundant (ignored) matching CN-ID. WITH_SAN("a", RDN(CN("a")), DNSName("a"), Success), // Match a DNSName SAN entry when there is an CN-ID that doesn't match. WITH_SAN("b", RDN(CN("a")), DNSName("b"), Success), // Do not match a CN-ID when there is a valid DNSName SAN Entry. WITH_SAN("a", RDN(CN("a")), DNSName("b"), Result::ERROR_BAD_CERT_DOMAIN), // Do not match a CN-ID when there is a malformed DNSName SAN Entry. WITH_SAN("a", RDN(CN("a")), DNSName("!"), Result::ERROR_BAD_DER), // Do not match a matching CN-ID when there is a valid IPAddress SAN entry. WITH_SAN("a", RDN(CN("a")), IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), // Do not match a matching CN-ID when there is a malformed IPAddress SAN entry. WITH_SAN("a", RDN(CN("a")), IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN), // Match a DNSName against a matching CN-ID when there is a SAN, but the SAN // does not contain an DNSName or IPAddress entry. WITH_SAN("a", RDN(CN("a")), RFC822Name("foo@example.com"), Success), // Match a matching CN-ID when there is no SAN. WITHOUT_SAN("a", RDN(CN("a")), Success), // Do not match a mismatching CN-ID when there is no SAN. WITHOUT_SAN("a", RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), // The first DNSName matches. WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success), // The last DNSName matches. WITH_SAN("b", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success), // The middle DNSName matches. WITH_SAN("b", RDN(CN("foo")), DNSName("a") + DNSName("b") + DNSName("c"), Success), // After an IP address. WITH_SAN("b", RDN(CN("foo")), IPAddress(ipv4_addr_bytes) + DNSName("b"), Success), // Before an IP address. WITH_SAN("a", RDN(CN("foo")), DNSName("a") + IPAddress(ipv4_addr_bytes), Success), // Between an RFC822Name and an IP address. WITH_SAN("b", RDN(CN("foo")), RFC822Name("foo@example.com") + DNSName("b") + IPAddress(ipv4_addr_bytes), Success), // Duplicate DNSName. WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("a"), Success), // After an invalid DNSName. WITH_SAN("b", RDN(CN("foo")), DNSName("!") + DNSName("b"), Result::ERROR_BAD_DER), // http://tools.ietf.org/html/rfc5280#section-4.2.1.6: "If the subjectAltName // extension is present, the sequence MUST contain at least one entry." // However, for compatibility reasons, this is not enforced. See bug 1143085. // This case is treated as if the extension is not present (i.e. name // matching falls back to the subject CN). WITH_SAN("a", RDN(CN("a")), ByteString(), Success), WITH_SAN("a", RDN(CN("b")), ByteString(), Result::ERROR_BAD_CERT_DOMAIN), // http://tools.ietf.org/html/rfc5280#section-4.1.2.6 says "If subject naming // information is present only in the subjectAltName extension (e.g., a key // bound only to an email address or URI), then the subject name MUST be an // empty sequence and the subjectAltName extension MUST be critical." So, we // have to support an empty subject. We don't enforce that the SAN must be // critical or even that there is a SAN when the subject is empty, though. WITH_SAN("a", ByteString(), DNSName("a"), Success), // Make sure we return ERROR_BAD_CERT_DOMAIN and not ERROR_BAD_DER. WITHOUT_SAN("a", ByteString(), Result::ERROR_BAD_CERT_DOMAIN), // Two CNs in the same RDN, both match. WITHOUT_SAN("a", RDN(CN("a") + CN("a")), Success), // Two CNs in the same RDN, both DNSNames, first one matches. WITHOUT_SAN("a", RDN(CN("a") + CN("b")), Result::ERROR_BAD_CERT_DOMAIN), // Two CNs in the same RDN, both DNSNames, last one matches. WITHOUT_SAN("b", RDN(CN("a") + CN("b")), Success), // Two CNs in the same RDN, first one matches, second isn't a DNSName. WITHOUT_SAN("a", RDN(CN("a") + CN("Not a DNSName")), Result::ERROR_BAD_CERT_DOMAIN), // Two CNs in the same RDN, first one not a DNSName, second matches. WITHOUT_SAN("b", RDN(CN("Not a DNSName") + CN("b")), Success), // Two CNs in separate RDNs, both match. WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("a")), Success), // Two CNs in separate RDNs, both DNSNames, first one matches. WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), // Two CNs in separate RDNs, both DNSNames, last one matches. WITHOUT_SAN("b", RDN(CN("a")) + RDN(CN("b")), Success), // Two CNs in separate RDNs, first one matches, second isn't a DNSName. WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("Not a DNSName")), Result::ERROR_BAD_CERT_DOMAIN), // Two CNs in separate RDNs, first one not a DNSName, second matches. WITHOUT_SAN("b", RDN(CN("Not a DNSName")) + RDN(CN("b")), Success), // One CN, one RDN, CN is the first AVA in the RDN, CN matches. WITHOUT_SAN("a", RDN(CN("a") + OU("b")), Success), // One CN, one RDN, CN is the first AVA in the RDN, CN does not match. WITHOUT_SAN("b", RDN(CN("a") + OU("b")), Result::ERROR_BAD_CERT_DOMAIN), // One CN, one RDN, CN is not the first AVA in the RDN, CN matches. WITHOUT_SAN("b", RDN(OU("a") + CN("b")), Success), // One CN, one RDN, CN is not the first AVA in the RDN, CN does not match. WITHOUT_SAN("a", RDN(OU("a") + CN("b")), Result::ERROR_BAD_CERT_DOMAIN), // One CN, multiple RDNs, CN is in the first RDN, CN matches. WITHOUT_SAN("a", RDN(CN("a")) + RDN(OU("b")), Success), // One CN, multiple RDNs, CN is in the first RDN, CN does not match. WITHOUT_SAN("b", RDN(CN("a")) + RDN(OU("b")), Result::ERROR_BAD_CERT_DOMAIN), // One CN, multiple RDNs, CN is not in the first RDN, CN matches. WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")), Success), // One CN, multiple RDNs, CN is not in the first RDN, CN does not match. WITHOUT_SAN("a", RDN(OU("a")) + RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), // One CN, one RDN, CN is not in the first or last AVA, CN matches. WITHOUT_SAN("b", RDN(OU("a") + CN("b") + OU("c")), Success), // One CN, multiple RDNs, CN is not in the first or last RDN, CN matches. WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")) + RDN(OU("c")), Success), // Empty CN does not match. WITHOUT_SAN("example.com", RDN(CN("")), Result::ERROR_BAD_CERT_DOMAIN), WITHOUT_SAN("uses_underscore.example.com", RDN(CN("*.example.com")), Success), WITHOUT_SAN("a.uses_underscore.example.com", RDN(CN("*.uses_underscore.example.com")), Success), WITH_SAN("uses_underscore.example.com", RDN(CN("foo")), DNSName("*.example.com"), Success), WITH_SAN("a.uses_underscore.example.com", RDN(CN("foo")), DNSName("*.uses_underscore.example.com"), Success), // Do not match a DNSName that is encoded in a malformed IPAddress. WITH_SAN("example.com", RDN(CN("foo")), IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN), // We skip over the malformed IPAddress and match the DNSName entry because // we've heard reports of real-world certificates that have malformed // IPAddress SANs. WITH_SAN("example.org", RDN(CN("foo")), IPAddress(example_com) + DNSName("example.org"), Success), WITH_SAN("example.com", RDN(CN("foo")), DNSName("!") + DNSName("example.com"), Result::ERROR_BAD_DER), // Match a matching IPv4 address SAN entry. WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes), Success), // Match a matching IPv4 addresses in the CN when there is no SAN WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), Success), // Do not match a matching IPv4 address in the CN when there is a SAN with // a DNSName entry. WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN), // Do not match a matching IPv4 address in the CN when there is a SAN with // a non-matching IPAddress entry. WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), IPAddress(ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), // Match a matching IPv4 address in the CN when there is a SAN with a // non-IPAddress, non-DNSName entry. WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), Success), // Do not match a matching IPv4 address in the CN when there is a SAN with a // malformed IPAddress entry. WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN), // Do not match a matching IPv4 address in the CN when there is a SAN with a // malformed DNSName entry. WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), DNSName("!"), Result::ERROR_BAD_CERT_DOMAIN), // We don't match IPv6 addresses in the CN, regardless of whether there is // a SAN. WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), Result::ERROR_BAD_CERT_DOMAIN), WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN), WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), IPAddress(ipv6_addr_bytes), Success), WITH_SAN(ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv6_addr_bytes), Success), // We don't match the binary encoding of the bytes of IP addresses in the // CN. WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_bytes_as_str)), Result::ERROR_BAD_CERT_DOMAIN), WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_bytes_as_str)), Result::ERROR_BAD_CERT_DOMAIN), // We don't match IP addresses with DNSName SANs. WITH_SAN(ipv4_addr_str, RDN(CN("foo")), DNSName(ipv4_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN), WITH_SAN(ipv4_addr_str, RDN(CN("foo")), DNSName(ipv4_addr_str), Result::ERROR_BAD_CERT_DOMAIN), WITH_SAN(ipv6_addr_str, RDN(CN("foo")), DNSName(ipv6_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN), WITH_SAN(ipv6_addr_str, RDN(CN("foo")), DNSName(ipv6_addr_str), Result::ERROR_BAD_CERT_DOMAIN), // Do not match an IPv4 reference ID against the equivalent IPv4-compatible // IPv6 SAN entry. WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_compatible_ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6 // SAN entry. WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_mapped_ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), // Do not match an IPv4-compatible IPv6 reference ID against the equivalent // IPv4 SAN entry. WITH_SAN(ipv4_compatible_ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6 // SAN entry. WITH_SAN(ipv4_mapped_ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), // Test that the presence of an otherName entry is handled appropriately. // (The actual value of the otherName entry isn't important - that's not what // we're testing here.) WITH_SAN("example.com", ByteString(), // The tag for otherName is CONTEXT_SPECIFIC | CONSTRUCTED | 0 TLV((2 << 6) | (1 << 5) | 0, ByteString()) + DNSName("example.com"), Success), WITH_SAN("example.com", ByteString(), TLV((2 << 6) | (1 << 5) | 0, ByteString()), Result::ERROR_BAD_CERT_DOMAIN), }; ByteString CreateCert(const ByteString& subject, const ByteString& subjectAltName, EndEntityOrCA endEntityOrCA = EndEntityOrCA::MustBeEndEntity) { ByteString serialNumber(CreateEncodedSerialNumber(1)); EXPECT_FALSE(ENCODING_FAILED(serialNumber)); ByteString issuerDER(Name(RDN(CN("issuer")))); EXPECT_FALSE(ENCODING_FAILED(issuerDER)); ByteString extensions[2]; if (subjectAltName != NO_SAN) { extensions[0] = CreateEncodedSubjectAltName(subjectAltName); EXPECT_FALSE(ENCODING_FAILED(extensions[0])); } if (endEntityOrCA == EndEntityOrCA::MustBeCA) { // Currently, these tests assume that if we're creating a CA certificate, it // will not have a subjectAlternativeName extension. If that assumption // changes, this code will have to be updated. Ideally this would be // ASSERT_EQ, but that inserts a 'return;', which doesn't match this // function's return type. EXPECT_EQ(subjectAltName, NO_SAN); extensions[0] = CreateEncodedBasicConstraints(true, nullptr, Critical::Yes); EXPECT_FALSE(ENCODING_FAILED(extensions[0])); } ScopedTestKeyPair keyPair(CloneReusedKeyPair()); return CreateEncodedCertificate( v3, sha256WithRSAEncryption(), serialNumber, issuerDER, oneDayBeforeNow, oneDayAfterNow, Name(subject), *keyPair, extensions, *keyPair, sha256WithRSAEncryption()); } TEST_P(pkixnames_CheckCertHostname, CheckCertHostname) { const CheckCertHostnameParams& param(GetParam()); ByteString cert(CreateCert(param.subject, param.subjectAltName)); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Input hostnameInput; ASSERT_EQ(Success, hostnameInput.Init(param.hostname.data(), param.hostname.length())); ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); } INSTANTIATE_TEST_SUITE_P(pkixnames_CheckCertHostname, pkixnames_CheckCertHostname, testing::ValuesIn(CHECK_CERT_HOSTNAME_PARAMS)); TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence) { // A certificate with a truly empty SAN extension (one that doesn't even // contain a SEQUENCE at all) is malformed. If we didn't treat this as // malformed then we'd have to treat it like the CN_EmptySAN cases. ByteString serialNumber(CreateEncodedSerialNumber(1)); EXPECT_FALSE(ENCODING_FAILED(serialNumber)); ByteString extensions[2]; extensions[0] = CreateEncodedEmptySubjectAltName(); ASSERT_FALSE(ENCODING_FAILED(extensions[0])); ScopedTestKeyPair keyPair(CloneReusedKeyPair()); ByteString certDER(CreateEncodedCertificate( v3, sha256WithRSAEncryption(), serialNumber, Name(RDN(CN("issuer"))), oneDayBeforeNow, oneDayAfterNow, Name(RDN(CN("a"))), *keyPair, extensions, *keyPair, sha256WithRSAEncryption())); ASSERT_FALSE(ENCODING_FAILED(certDER)); Input certInput; ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); static const uint8_t a[] = { 'a' }; ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID, CheckCertHostname(certInput, Input(a), mNameMatchingPolicy)); } class SkipInvalidSubjectAlternativeNamesNameMatchingPolicy : public NameMatchingPolicy { public: virtual Result FallBackToCommonName( Time, /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override { fallBackToCommonName = FallBackToSearchWithinSubject::No; return Success; } virtual HandleInvalidSubjectAlternativeNamesBy HandleInvalidSubjectAlternativeNames() override { return HandleInvalidSubjectAlternativeNamesBy::Skipping; } }; TEST_F(pkixnames_CheckCertHostname, SkipInvalidSubjectAlternativeNames) { ByteString cert(CreateCert(RDN(CN("invalid SAN example")), DNSName("192.0.2.0"))); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); const char* hostname = "example.com"; Input hostnameInput; ASSERT_EQ(Success, hostnameInput.Init(reinterpret_cast(hostname), strlen(hostname))); // The default name matching policy halts on invalid SAN entries. ASSERT_EQ(Result::ERROR_BAD_DER, CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); SkipInvalidSubjectAlternativeNamesNameMatchingPolicy nameMatchingPolicy; // A policy that skips invalid SAN entries should result in a domain mismatch // error. ASSERT_EQ(Result::ERROR_BAD_CERT_DOMAIN, CheckCertHostname(certInput, hostnameInput, nameMatchingPolicy)); } class pkixnames_CheckCertHostname_PresentedMatchesReference : public ::testing::Test , public ::testing::WithParamInterface { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN) { // Since there is no SAN, a valid presented DNS ID in the subject CN field // should result in a match. const PresentedMatchesReference& param(GetParam()); ByteString cert(CreateCert(RDN(CN(param.presentedDNSID)), NO_SAN)); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Input hostnameInput; ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(), param.referenceDNSID.length())); ASSERT_EQ(param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN, CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); } TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, SubjectAltName_CNNotDNSName) { // A DNSName SAN entry should match, regardless of the contents of the // subject CN. const PresentedMatchesReference& param(GetParam()); ByteString cert(CreateCert(RDN(CN("Common Name")), DNSName(param.presentedDNSID))); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Input hostnameInput; ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(), param.referenceDNSID.length())); Result expectedResult = param.expectedResult != Success ? param.expectedResult : param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN; ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); } INSTANTIATE_TEST_SUITE_P(pkixnames_CheckCertHostname_DNSID_MATCH_PARAMS, pkixnames_CheckCertHostname_PresentedMatchesReference, testing::ValuesIn(DNSID_MATCH_PARAMS)); TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN) { // Make sure we don't have the similar problems that strcasecmp and others // have with the other kinds of "i" and "I" commonly used in Turkish locales, // when we're matching a CN due to lack of subjectAltName. const InputValidity& param(GetParam()); SCOPED_TRACE(param.input.c_str()); Input input; ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN)); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Result expectedResult = (InputsAreEqual(LOWERCASE_I, input) || InputsAreEqual(UPPERCASE_I, input)) ? Success : Result::ERROR_BAD_CERT_DOMAIN; ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I, mNameMatchingPolicy)); ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I, mNameMatchingPolicy)); } TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN) { // Make sure we don't have the similar problems that strcasecmp and others // have with the other kinds of "i" and "I" commonly used in Turkish locales, // when we're matching a dNSName in the SAN. const InputValidity& param(GetParam()); SCOPED_TRACE(param.input.c_str()); Input input; ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); ByteString cert(CreateCert(RDN(CN("Common Name")), DNSName(param.input))); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Result expectedResult = (!param.isValidPresentedID) ? Result::ERROR_BAD_DER : (InputsAreEqual(LOWERCASE_I, input) || InputsAreEqual(UPPERCASE_I, input)) ? Success : Result::ERROR_BAD_CERT_DOMAIN; ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I, mNameMatchingPolicy)); ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I, mNameMatchingPolicy)); } class pkixnames_CheckCertHostname_IPV4_Addresses : public ::testing::Test , public ::testing::WithParamInterface> { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses, ValidIPv4AddressInIPAddressSAN) { // When the reference hostname is a valid IPv4 address, a correctly-formed // IPv4 Address SAN matches it. const IPAddressParams<4>& param(GetParam()); ByteString cert(CreateCert(RDN(CN("Common Name")), IPAddress(param.expectedValueIfValid))); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Input hostnameInput; ASSERT_EQ(Success, hostnameInput.Init(param.input.data(), param.input.length())); ASSERT_EQ(param.isValid ? Success : Result::ERROR_BAD_CERT_DOMAIN, CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); } TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses, ValidIPv4AddressInCN_NoSAN) { // When the reference hostname is a valid IPv4 address, a correctly-formed // IPv4 Address in the CN matches it when there is no SAN. const IPAddressParams<4>& param(GetParam()); SCOPED_TRACE(param.input.c_str()); ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN)); ASSERT_FALSE(ENCODING_FAILED(cert)); Input certInput; ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); Input hostnameInput; ASSERT_EQ(Success, hostnameInput.Init(param.input.data(), param.input.length())); // Some of the invalid IPv4 addresses are valid DNS names! Result expectedResult = (param.isValid || IsValidReferenceDNSID(hostnameInput)) ? Success : Result::ERROR_BAD_CERT_DOMAIN; ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); } INSTANTIATE_TEST_SUITE_P(pkixnames_CheckCertHostname_IPV4_ADDRESSES, pkixnames_CheckCertHostname_IPV4_Addresses, testing::ValuesIn(IPV4_ADDRESSES)); struct NameConstraintParams { ByteString subject; ByteString subjectAltName; ByteString subtrees; Result expectedPermittedSubtreesResult; Result expectedExcludedSubtreesResult; }; ::std::ostream& operator<<(::std::ostream& os, const NameConstraintParams&) { return os << "TODO (bug 1318770)"; } static ByteString PermittedSubtrees(const ByteString& generalSubtrees) { return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, generalSubtrees); } static ByteString ExcludedSubtrees(const ByteString& generalSubtrees) { return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, generalSubtrees); } // Does not encode min or max. static ByteString GeneralSubtree(const ByteString& base) { return TLV(der::SEQUENCE, base); } static const NameConstraintParams NAME_CONSTRAINT_PARAMS[] = { ///////////////////////////////////////////////////////////////////////////// // XXX: Malformed name constraints for supported types of names are ignored // when there are no names of that type to constrain. { ByteString(), NO_SAN, GeneralSubtree(DNSName("!")), Success, Success }, { // DirectoryName constraints are an exception, because *every* certificate // has at least one DirectoryName (tbsCertificate.subject). ByteString(), NO_SAN, GeneralSubtree(Name(ByteString(reinterpret_cast("!"), 1))), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(RFC822Name("!")), Success, Success }, ///////////////////////////////////////////////////////////////////////////// // Edge cases of name constraint absolute vs. relative and subdomain matching // that are not clearly explained in RFC 5280. (See the long comment above // MatchPresentedDNSIDWithReferenceDNSID.) // 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? { ByteString(), DNSName("host.example.com"), GeneralSubtree(DNSName("host.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // This test case is an example from RFC 5280. ByteString(), DNSName("host1.example.com"), GeneralSubtree(DNSName("host.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { ByteString(), RFC822Name("a@host.example.com"), GeneralSubtree(RFC822Name("host.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // This test case is an example from RFC 5280. ByteString(), RFC822Name("a@host1.example.com"), GeneralSubtree(RFC822Name("host.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // 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? { // This test case is an example from RFC 5280. ByteString(), DNSName("www.host.example.com"), GeneralSubtree(DNSName( "host.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // The subdomain matching rule for host names that do not start with "." is // different for RFC822Names than for DNSNames! ByteString(), RFC822Name("a@www.host.example.com"), GeneralSubtree(RFC822Name( "host.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // 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"? { ByteString(), DNSName("bigfoo.bar.com"), GeneralSubtree(DNSName( "foo.bar.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { ByteString(), RFC822Name("a@bigfoo.bar.com"), GeneralSubtree(RFC822Name( "foo.bar.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // 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"? { ByteString(), DNSName("www.example.com"), GeneralSubtree(DNSName( ".example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // When there is no Local-part, an RFC822 name constraint's domain may // start with '.', and the semantics are the same as for DNSNames. ByteString(), RFC822Name("a@www.example.com"), GeneralSubtree(RFC822Name( ".example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // When there is a Local-part, an RFC822 name constraint's domain must not // start with '.'. ByteString(), RFC822Name("a@www.example.com"), GeneralSubtree(RFC822Name( "a@.example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // Check that we only allow subdomains to match. ByteString(), DNSName( "example.com"), GeneralSubtree(DNSName(".example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // Check that we only allow subdomains to match. ByteString(), RFC822Name("a@example.com"), GeneralSubtree(RFC822Name(".example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // Check that we don't get confused and consider "b" == "." ByteString(), DNSName("bexample.com"), GeneralSubtree(DNSName(".example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // Check that we don't get confused and consider "b" == "." ByteString(), RFC822Name("a@bexample.com"), GeneralSubtree(RFC822Name( ".example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // Q: Is there a way to prevent subdomain matches? // (This is tested in a different set of tests because it requires a // combination of permittedSubtrees and excludedSubtrees.) // 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? // { // The DNSName in the constraint is not valid because constraint DNS IDs // are not allowed to be absolute. ByteString(), DNSName("example.com"), GeneralSubtree(DNSName("example.com.")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { ByteString(), RFC822Name("a@example.com"), GeneralSubtree(RFC822Name( "example.com.")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { // The DNSName in the SAN is not valid because presented DNS IDs are not // allowed to be absolute. ByteString(), DNSName("example.com."), GeneralSubtree(DNSName("example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { ByteString(), RFC822Name("a@example.com."), GeneralSubtree(RFC822Name( "example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { // The presented DNSName is the same length as the constraint, because the // subdomain is only one character long and because the constraint both // begins and ends with ".". But, it doesn't matter because absolute names // are not allowed for DNSName constraints. ByteString(), DNSName("p.example.com"), GeneralSubtree(DNSName(".example.com.")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { // The presented DNSName is the same length as the constraint, because the // subdomain is only one character long and because the constraint both // begins and ends with ".". ByteString(), RFC822Name("a@p.example.com"), GeneralSubtree(RFC822Name( ".example.com.")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { // Same as previous test case, but using a wildcard presented ID. ByteString(), DNSName("*.example.com"), GeneralSubtree(DNSName(".example.com.")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // Same as previous test case, but using a wildcard presented ID, which is // invalid in an RFC822Name. ByteString(), RFC822Name("a@*.example.com"), GeneralSubtree(RFC822Name( ".example.com.")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, // Q: Are "" and "." valid DNSName constraints? If so, what do they mean? { ByteString(), DNSName("example.com"), GeneralSubtree(DNSName("")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), RFC822Name("a@example.com"), GeneralSubtree(RFC822Name("")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // The malformed (absolute) presented ID does not match. ByteString(), DNSName("example.com."), GeneralSubtree(DNSName("")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("a@example.com."), GeneralSubtree(RFC822Name("")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // Invalid syntax in name constraint ByteString(), DNSName("example.com"), GeneralSubtree(DNSName(".")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { // Invalid syntax in name constraint ByteString(), RFC822Name("a@example.com"), GeneralSubtree(RFC822Name(".")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, }, { ByteString(), DNSName("example.com."), GeneralSubtree(DNSName(".")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("a@example.com."), GeneralSubtree(RFC822Name(".")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, ///////////////////////////////////////////////////////////////////////////// // Basic IP Address constraints (non-CN-ID) // The Mozilla CA Policy says this means "no IPv4 addresses allowed." { ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), IPAddress(ipv4_addr_00000000_bytes), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), IPAddress(ipv4_addr_FFFFFFFF_bytes), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // The Mozilla CA Policy says this means "no IPv6 addresses allowed." { ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), IPAddress(ipv6_addr_all_zeros_bytes), GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // RFC 5280 doesn't partition IP address constraints into separate IPv4 and // IPv6 categories, so a IPv4 permittedSubtrees constraint excludes all IPv6 // addresses, and vice versa. { ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // IPv4 Subnets { ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_CIDR_17_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), IPAddress(ipv4_other_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // XXX(bug 1089430): We don't reject this even though it is weird. ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bad_addr_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // XXX(bug 1089430): We don't reject this even though it is weird. ByteString(), IPAddress(ipv4_other_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_bad_mask_bytes)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // IPv6 Subnets { ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), IPAddress(ipv6_other_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // XXX(bug 1089430): We don't reject this even though it is weird. ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bad_addr_bytes)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // XXX(bug 1089430): We don't reject this even though it is weird. ByteString(), IPAddress(ipv6_other_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_bad_mask_bytes)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, // Malformed presented IP addresses and constraints { // The presented IPv4 address is empty ByteString(), IPAddress(), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv4 address is truncated ByteString(), IPAddress(ipv4_addr_truncated_bytes), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv4 address is too long ByteString(), IPAddress(ipv4_addr_overlong_bytes), GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv4 constraint is empty ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress()), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv4 constraint is truncated ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv4 constraint is too long ByteString(), IPAddress(ipv4_addr_bytes), GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv6 address is empty ByteString(), IPAddress(), GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv6 address is truncated ByteString(), IPAddress(ipv6_addr_truncated_bytes), GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv6 address is too long ByteString(), IPAddress(ipv6_addr_overlong_bytes), GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv6 constraint is empty ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress()), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv6 constraint is truncated ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { // The presented IPv6 constraint is too long ByteString(), IPAddress(ipv6_addr_bytes), GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, ///////////////////////////////////////////////////////////////////////////// // XXX: We don't reject malformed name constraints when there are no names of // that type. { ByteString(), NO_SAN, GeneralSubtree(DNSName("!")), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_addr_overlong_bytes)), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_addr_overlong_bytes)), Success, Success }, { ByteString(), NO_SAN, GeneralSubtree(RFC822Name("\0")), Success, Success }, ///////////////////////////////////////////////////////////////////////////// // Basic CN-ID DNSName constraint tests. { // Empty Name is ignored for DNSName constraints. ByteString(), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { // Empty CN is ignored for DNSName constraints because it isn't a // syntactically-valid DNSName. // // NSS gives different results. RDN(CN("")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { // IP Address is ignored for DNSName constraints. // // NSS gives different results. RDN(CN("1.2.3.4")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { // OU has something that looks like a dNSName that matches. RDN(OU("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { // OU has something that looks like a dNSName that does not match. RDN(OU("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { // NSS gives different results. RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // DNSName CN-ID match is detected when there is a SAN w/o any DNSName or // IPAddress RDN(CN("a.example.com")), RFC822Name("foo@example.com"), GeneralSubtree(DNSName("a.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // DNSName CN-ID mismatch is detected when there is a SAN w/o any DNSName // or IPAddress RDN(CN("a.example.com")), RFC822Name("foo@example.com"), GeneralSubtree(DNSName("b.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // DNSName CN-ID match not reported when there is a DNSName SAN RDN(CN("a.example.com")), DNSName("b.example.com"), GeneralSubtree(DNSName("a.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // DNSName CN-ID mismatch not reported when there is a DNSName SAN RDN(CN("a.example.com")), DNSName("b.example.com"), GeneralSubtree(DNSName("b.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE, }, { // DNSName CN-ID match not reported when there is an IPAddress SAN RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes), GeneralSubtree(DNSName("a.example.com")), Success, Success }, { // DNSName CN-ID mismatch not reported when there is an IPAddress SAN RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes), GeneralSubtree(DNSName("b.example.com")), Success, Success }, { // IPAddress CN-ID match is detected when there is a SAN w/o any DNSName or // IPAddress RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // IPAddress CN-ID mismatch is detected when there is a SAN w/o any DNSName // or IPAddress RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // IPAddress CN-ID match not reported when there is a DNSName SAN RDN(CN(ipv4_addr_str)), DNSName("b.example.com"), GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), Success, Success }, { // IPAddress CN-ID mismatch not reported when there is a DNSName SAN RDN(CN(ipv4_addr_str)), DNSName("b.example.com"), GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), Success, Success }, { // IPAddress CN-ID match not reported when there is an IPAddress SAN RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes), GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // IPAddress CN-ID mismatch not reported when there is an IPAddress SAN RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes), GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, ///////////////////////////////////////////////////////////////////////////// // Test that constraints are applied to the most specific (last) CN, and only // that CN-ID. { // Name constraint only matches a.example.com, but the most specific CN // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.) RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // Name constraint only matches a.example.com, but the most specific CN // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.) RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success }, { // Name constraint only permits b.example.com, and the most specific CN // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.) RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("b.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // Name constraint only permits b.example.com, and the most specific CN // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.) RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("b.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, ///////////////////////////////////////////////////////////////////////////// // Additional RFC822 name constraint tests. There are more tests regarding // the DNSName part of the constraint mixed into the DNSName constraint // tests. { ByteString(), RFC822Name("a@example.com"), GeneralSubtree(RFC822Name("a@example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // Bug 1056773: name constraints that omit Local-part but include '@' are // invalid. { ByteString(), RFC822Name("a@example.com"), GeneralSubtree(RFC822Name("@example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("@example.com"), GeneralSubtree(RFC822Name("@example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("example.com"), GeneralSubtree(RFC822Name("@example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("a@mail.example.com"), GeneralSubtree(RFC822Name("a@*.example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("a@*.example.com"), GeneralSubtree(RFC822Name(".example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("@example.com"), GeneralSubtree(RFC822Name(".example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, { ByteString(), RFC822Name("@a.example.com"), GeneralSubtree(RFC822Name(".example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, ///////////////////////////////////////////////////////////////////////////// // Test name constraints with underscores. // { ByteString(), DNSName("uses_underscore.example.com"), GeneralSubtree(DNSName("uses_underscore.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), DNSName("uses_underscore.example.com"), GeneralSubtree(DNSName("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), DNSName("a.uses_underscore.example.com"), GeneralSubtree(DNSName("uses_underscore.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), RFC822Name("a@uses_underscore.example.com"), GeneralSubtree(RFC822Name("uses_underscore.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), RFC822Name("uses_underscore@example.com"), GeneralSubtree(RFC822Name("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), RFC822Name("a@a.uses_underscore.example.com"), GeneralSubtree(RFC822Name(".uses_underscore.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, ///////////////////////////////////////////////////////////////////////////// // Name constraint tests that relate to having an empty SAN. According to RFC // 5280 this isn't valid, but we allow it for compatibility reasons (see bug // 1143085). { // For DNSNames, we fall back to the subject CN. RDN(CN("a.example.com")), ByteString(), GeneralSubtree(DNSName("a.example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // For RFC822Names, we do not fall back to the subject emailAddress. // This new implementation seems to conform better to the standards for // RFC822 name constraints, by only applying the name constraints to // emailAddress names in the certificate subject if there is no // subjectAltName extension in the cert. // In this case, the presence of the (empty) SAN extension means that RFC822 // name constraints are not enforced on the emailAddress attributes of the // subject. RDN(emailAddress("a@example.com")), ByteString(), GeneralSubtree(RFC822Name("a@example.com")), Success, Success }, { // Compare this to the case where there is no SAN (i.e. the name // constraints are enforced, because the extension is not present at all). RDN(emailAddress("a@example.com")), NO_SAN, GeneralSubtree(RFC822Name("a@example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, ///////////////////////////////////////////////////////////////////////////// // DirectoryName name constraint tests { // One AVA per RDN RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + RDN(CN("example.com"))))), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // RDNs can have multiple AVAs. RDN(OU("Example Organization") + CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + CN("example.com"))))), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // The constraint is a prefix of the subject DN. RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // The name constraint is not a prefix of the subject DN. // Note that for excludedSubtrees, we simply prohibit any non-empty // directoryName constraint to ensure we are not being too lenient. RDN(OU("Other Example Organization")) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + RDN(CN("example.com"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // Same as the previous one, but one RDN with multiple AVAs. RDN(OU("Other Example Organization") + CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + CN("example.com"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // With multiple AVAs per RDN in the subject DN, the constraint is not a // prefix of the subject DN. RDN(OU("Example Organization") + CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // The subject DN RDN has multiple AVAs, but the name constraint has only // one AVA per RDN. RDN(OU("Example Organization") + CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + RDN(CN("example.com"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // The name constraint RDN has multiple AVAs, but the subject DN has only // one AVA per RDN. RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + CN("example.com"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // In this case, the constraint uses a different encoding from the subject. // We consider them to match because we allow UTF8String and // PrintableString to compare equal when their contents are equal. RDN(OU("Example Organization", der::UTF8String)) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com"))))), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // Same as above, but with UTF8String/PrintableString switched. RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", der::UTF8String)) + RDN(CN("example.com"))))), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // If the contents aren't the same, then they shouldn't match. RDN(OU("Other Example Organization", der::UTF8String)) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { // Only UTF8String and PrintableString are considered equivalent. RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", der::TeletexString)) + RDN(CN("example.com"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // Some additional tests for completeness: // Ensure that wildcards are handled: { RDN(CN("*.example.com")), NO_SAN, GeneralSubtree(DNSName("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), DNSName("*.example.com"), GeneralSubtree(DNSName("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), DNSName("www.example.com"), GeneralSubtree(DNSName("*.example.com")), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, // Handle multiple name constraint entries: { RDN(CN("example.com")), NO_SAN, GeneralSubtree(DNSName("example.org")) + GeneralSubtree(DNSName("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { ByteString(), DNSName("example.com"), GeneralSubtree(DNSName("example.org")) + GeneralSubtree(DNSName("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // Handle multiple names in subject alternative name extension: { ByteString(), DNSName("example.com") + DNSName("example.org"), GeneralSubtree(DNSName("example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // Handle a mix of DNSName and DirectoryName: { RDN(OU("Example Organization")), DNSName("example.com"), GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + GeneralSubtree(DNSName("example.com")), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { RDN(OU("Other Example Organization")), DNSName("example.com"), GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + GeneralSubtree(DNSName("example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, { RDN(OU("Example Organization")), DNSName("example.org"), GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + GeneralSubtree(DNSName("example.com")), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // Handle a certificate with no DirectoryName: { ByteString(), DNSName("example.com"), GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, }; class pkixnames_CheckNameConstraints : public ::testing::Test , public ::testing::WithParamInterface { public: DefaultNameMatchingPolicy mNameMatchingPolicy; }; TEST_P(pkixnames_CheckNameConstraints, NameConstraintsEnforcedForDirectlyIssuedEndEntity) { // Test that name constraints are enforced on a certificate directly issued by // a certificate with the given name constraints. const NameConstraintParams& param(GetParam()); ByteString certDER(CreateCert(param.subject, param.subjectAltName)); ASSERT_FALSE(ENCODING_FAILED(certDER)); Input certInput; ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); ASSERT_EQ(Success, cert.Init()); { ByteString nameConstraintsDER(TLV(der::SEQUENCE, PermittedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedPermittedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_serverAuth)); } { ByteString nameConstraintsDER(TLV(der::SEQUENCE, ExcludedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedExcludedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_serverAuth)); } { ByteString nameConstraintsDER(TLV(der::SEQUENCE, PermittedSubtrees(param.subtrees) + ExcludedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ((param.expectedPermittedSubtreesResult == param.expectedExcludedSubtreesResult) ? param.expectedExcludedSubtreesResult : Result::ERROR_CERT_NOT_IN_NAME_SPACE, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_serverAuth)); } } INSTANTIATE_TEST_SUITE_P(pkixnames_CheckNameConstraints, pkixnames_CheckNameConstraints, testing::ValuesIn(NAME_CONSTRAINT_PARAMS)); // The |subjectAltName| param is not used for these test cases (hence the use of // "NO_SAN"). static const NameConstraintParams NO_FALLBACK_NAME_CONSTRAINT_PARAMS[] = { // The only difference between end-entities being verified for serverAuth and // intermediates or end-entities being verified for other uses is that for // the latter cases, there is no fallback matching of DNSName entries to the // subject common name. { RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), Success, Success }, // Sanity-check that name constraints are in fact enforced in these cases. { RDN(CN("Example Name")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))), Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, // (In this implementation, if a DirectoryName is in excludedSubtrees, nothing // is considered to be in the name space.) { RDN(CN("Other Example Name")), NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))), Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE }, }; class pkixnames_CheckNameConstraintsOnIntermediate : public ::testing::Test , public ::testing::WithParamInterface { }; TEST_P(pkixnames_CheckNameConstraintsOnIntermediate, NameConstraintsEnforcedOnIntermediate) { // Test that name constraints are enforced on an intermediate certificate // directly issued by a certificate with the given name constraints. const NameConstraintParams& param(GetParam()); ByteString certDER(CreateCert(param.subject, NO_SAN, EndEntityOrCA::MustBeCA)); ASSERT_FALSE(ENCODING_FAILED(certDER)); Input certInput; ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr); ASSERT_EQ(Success, cert.Init()); { ByteString nameConstraintsDER(TLV(der::SEQUENCE, PermittedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedPermittedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_serverAuth)); } { ByteString nameConstraintsDER(TLV(der::SEQUENCE, ExcludedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedExcludedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_serverAuth)); } { ByteString nameConstraintsDER(TLV(der::SEQUENCE, PermittedSubtrees(param.subtrees) + ExcludedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedExcludedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_serverAuth)); } } INSTANTIATE_TEST_SUITE_P(pkixnames_CheckNameConstraintsOnIntermediate, pkixnames_CheckNameConstraintsOnIntermediate, testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS)); class pkixnames_CheckNameConstraintsForNonServerAuthUsage : public ::testing::Test , public ::testing::WithParamInterface { }; TEST_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage, NameConstraintsEnforcedForNonServerAuthUsage) { // Test that for key purposes other than serverAuth, fallback to the subject // common name does not occur. const NameConstraintParams& param(GetParam()); ByteString certDER(CreateCert(param.subject, NO_SAN)); ASSERT_FALSE(ENCODING_FAILED(certDER)); Input certInput; ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); ASSERT_EQ(Success, cert.Init()); { ByteString nameConstraintsDER(TLV(der::SEQUENCE, PermittedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedPermittedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_clientAuth)); } { ByteString nameConstraintsDER(TLV(der::SEQUENCE, ExcludedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedExcludedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_clientAuth)); } { ByteString nameConstraintsDER(TLV(der::SEQUENCE, PermittedSubtrees(param.subtrees) + ExcludedSubtrees(param.subtrees))); Input nameConstraints; ASSERT_EQ(Success, nameConstraints.Init(nameConstraintsDER.data(), nameConstraintsDER.length())); ASSERT_EQ(param.expectedExcludedSubtreesResult, CheckNameConstraints(nameConstraints, cert, KeyPurposeId::id_kp_clientAuth)); } } INSTANTIATE_TEST_SUITE_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage, pkixnames_CheckNameConstraintsForNonServerAuthUsage, testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS));