2887 lines
109 KiB
C++
2887 lines
109 KiB
C++
/* -*- 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 GNU 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<const uint8_t*>(a), sizeof(a) - 1), \
|
|
ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
|
|
Success, \
|
|
true \
|
|
}
|
|
|
|
#define DNS_ID_MISMATCH(a, b) \
|
|
{ \
|
|
ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
|
|
ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
|
|
Success, \
|
|
false \
|
|
}
|
|
|
|
#define DNS_ID_BAD_DER(a, b) \
|
|
{ \
|
|
ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
|
|
ByteString(reinterpret_cast<const uint8_t*>(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._._"),
|
|
|
|
// We allow reference ID labels to start and end with hyphens for
|
|
// compatibility.
|
|
DNS_ID_MATCH("*.example.com", "-.example.com"),
|
|
DNS_ID_MATCH("*.example.com", "-hyphenstart.example.com"),
|
|
DNS_ID_MATCH("*.example.com", "hyphenend-.example.com"),
|
|
// Presented ID labels may not start or end with hyphens.
|
|
DNS_ID_BAD_DER("-.example.com", "-.example.com"),
|
|
DNS_ID_BAD_DER("-hyphenstart.example.com", "-hyphenstart.example.com"),
|
|
DNS_ID_BAD_DER("hyphenend-.example.com", "hyphenend-.example.com"),
|
|
|
|
// 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<const uint8_t*>(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--", true, false),
|
|
I("xn--.", true, false),
|
|
I("xn--.a", true, false),
|
|
I("a.xn--", true, false),
|
|
I("a.xn--.", true, false),
|
|
I("a.xn--.b", true, false),
|
|
I("a.xn--.b", true, 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 (presented ID labels 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),
|
|
|
|
// presented ID labels cannot start with a hyphen
|
|
I("-", true, false),
|
|
I("-1", true, false),
|
|
|
|
// presented ID labels cannot end with a hyphen
|
|
I("1-", true, false),
|
|
I("1-.a", true, false),
|
|
I("a-", true, false),
|
|
I("a-.a", true, false),
|
|
I("a.1-.a", true, false),
|
|
I("a.a-.a", true, 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 <unsigned int L>
|
|
struct IPAddressParams
|
|
{
|
|
ByteString input;
|
|
bool isValid;
|
|
uint8_t expectedValueIfValid[L];
|
|
};
|
|
|
|
template <unsigned int L>
|
|
::std::ostream& operator<<(::std::ostream& os, const IPAddressParams<L>&)
|
|
{
|
|
return os << "TODO (bug 1318770)";
|
|
}
|
|
|
|
#define IPV4_VALID(str, a, b, c, d) \
|
|
{ \
|
|
ByteString(reinterpret_cast<const uint8_t*>(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<const uint8_t*>(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<const uint8_t*>(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<const uint8_t*>(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<PresentedMatchesReference>
|
|
{
|
|
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<InputValidity>
|
|
{
|
|
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<InputValidity>
|
|
{
|
|
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<IPAddressParams<4>>
|
|
{
|
|
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<IPAddressParams<16>>
|
|
{
|
|
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<const uint8_t*>("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<CheckCertHostnameParams>
|
|
{
|
|
public:
|
|
DefaultNameMatchingPolicy mNameMatchingPolicy;
|
|
};
|
|
|
|
#define WITH_SAN(r, ps, psan, result) \
|
|
{ \
|
|
ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \
|
|
ps, \
|
|
psan, \
|
|
result \
|
|
}
|
|
|
|
#define WITHOUT_SAN(r, ps, result) \
|
|
{ \
|
|
ByteString(reinterpret_cast<const uint8_t*>(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 <ones><zeros>, but this one is of the
|
|
// form <ones><zeros><ones><zeros>.
|
|
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 <ones><zeros>, but this one is of the
|
|
// form <ones><zeros><ones><zeros>.
|
|
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<const uint8_t*>(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<PresentedMatchesReference>
|
|
{
|
|
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<IPAddressParams<4>>
|
|
{
|
|
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<const uint8_t*>("!"), 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 GNU 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 GNU 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<NameConstraintParams>
|
|
{
|
|
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<NameConstraintParams>
|
|
{
|
|
};
|
|
|
|
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<NameConstraintParams>
|
|
{
|
|
};
|
|
|
|
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));
|