Bug 1063281, Part 2: Implement IsValidDNSName, r=keeler

--HG--
extra : rebase_source : 202898df26c7321f543ab7aeb222cdc6db67fe0d
This commit is contained in:
Brian Smith 2014-09-30 14:41:39 -07:00
parent 3b8c2fc2a8
commit 4a2c8b5274
4 changed files with 400 additions and 0 deletions

View File

@ -0,0 +1,137 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2014 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This code attempts to implement RFC6125 name matching. It also attempts to
// give the same results as the Chromium implementation
// (X509Certificate::VerifyHostname) when both are given clean input (no
// leading whitespace, etc.)
//
// On Windows and maybe other platforms, OS-provided IP address parsing
// functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
// can't rely on them.
#include "pkix/Input.h"
namespace mozilla { namespace pkix {
bool
IsValidDNSName(Input hostname)
{
if (hostname.GetLength() > 253) {
return false;
}
Reader input(hostname);
size_t labelLength = 0;
bool labelIsAllNumeric = false;
bool endsWithHyphen = false;
do {
static const size_t MAX_LABEL_LENGTH = 63;
uint8_t b;
if (input.Read(b) != Success) {
return false;
}
switch (b) {
case '-':
if (labelLength == 0) {
return false; // Labels must not start with a hyphen.
}
labelIsAllNumeric = false;
endsWithHyphen = true;
++labelLength;
if (labelLength > MAX_LABEL_LENGTH) {
return false;
}
break;
// We avoid isdigit because it is locale-sensitive. See
// http://pubs.opengroup.org/onlinepubs/009695399/functions/isdigit.html
case '0': case '5':
case '1': case '6':
case '2': case '7':
case '3': case '8':
case '4': case '9':
if (labelLength == 0) {
labelIsAllNumeric = true;
}
endsWithHyphen = false;
++labelLength;
if (labelLength > MAX_LABEL_LENGTH) {
return false;
}
break;
// We avoid using islower/isupper/tolower/toupper or similar things, to
// avoid any possibility of this code being locale-sensitive. See
// http://pubs.opengroup.org/onlinepubs/009695399/functions/isupper.html
case 'a': case 'A': case 'n': case 'N':
case 'b': case 'B': case 'o': case 'O':
case 'c': case 'C': case 'p': case 'P':
case 'd': case 'D': case 'q': case 'Q':
case 'e': case 'E': case 'r': case 'R':
case 'f': case 'F': case 's': case 'S':
case 'g': case 'G': case 't': case 'T':
case 'h': case 'H': case 'u': case 'U':
case 'i': case 'I': case 'v': case 'V':
case 'j': case 'J': case 'w': case 'W':
case 'k': case 'K': case 'x': case 'X':
case 'l': case 'L': case 'y': case 'Y':
case 'm': case 'M': case 'z': case 'Z':
labelIsAllNumeric = false;
endsWithHyphen = false;
++labelLength;
if (labelLength > MAX_LABEL_LENGTH) {
return false;
}
break;
case '.':
if (labelLength == 0) {
return false;
}
if (endsWithHyphen) {
return false; // Labels must not end with a hyphen.
}
labelLength = 0;
break;
default:
return false; // Invalid character.
}
} while (!input.AtEnd());
if (endsWithHyphen) {
return false; // Labels must not end with a hyphen.
}
if (labelIsAllNumeric) {
return false; // Last label must not be all numeric.
}
return true;
}
} } // namespace mozilla::pkix

View File

@ -10,6 +10,7 @@ SOURCES += [
'lib/pkixcert.cpp',
'lib/pkixcheck.cpp',
'lib/pkixder.cpp',
'lib/pkixnames.cpp',
'lib/pkixnss.cpp',
'lib/pkixocsp.cpp',
'lib/pkixresult.cpp',

View File

@ -17,6 +17,7 @@ SOURCES += [
'pkixder_pki_types_tests.cpp',
'pkixder_universal_types_tests.cpp',
'pkixgtest.cpp',
'pkixnames_tests.cpp',
'pkixocsp_CreateEncodedOCSPRequest_tests.cpp',
'pkixocsp_VerifyEncodedOCSPResponse.cpp',
]

View File

@ -0,0 +1,261 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2014 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstring>
#include "pkix/Input.h"
#include "pkixgtest.h"
#include "pkixtestutil.h"
namespace mozilla { namespace pkix {
bool IsValidDNSName(Input hostname);
} } // namespace mozilla::pkix
using namespace mozilla::pkix;
using namespace mozilla::pkix::test;
struct InputValidity
{
ByteString input;
bool isValid;
};
// 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, valid) \
{ \
ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), valid \
}
static const InputValidity DNSNAMES_VALIDITY[] =
{
I("a", true),
I("a.b", true),
I("a.b.c", true),
I("a.b.c.d", true),
// empty labels
I("", false),
I(".", false),
I("a", true),
I(".a", false),
I(".a.b", false),
I("..a", false),
I("a..b", false),
I("a...b", false),
I("a..b.c", false),
I("a.b..c", false),
I(".a.b.c.", false),
// absolute names
I("a.", true),
I("a.b.", true),
I("a.b.c.", true),
// absolute names with empty label at end
I("a..", false),
I("a.b..", false),
I("a.b.c..", false),
I("a...", false),
// Punycode
I("xn--", false),
I("xn--.", false),
I("xn--.a", false),
I("a.xn--", false),
I("a.xn--.", false),
I("a.xn--.b", false),
I("a.xn--.b", false),
I("a.xn--\0.b", false),
I("a.xn--a.b", true),
I("xn--a", true),
I("a.xn--a", true),
I("a.xn--a.a", true),
I("\0xc4\0x95.com", false), // UTF-8 ĕ
I("xn--jea.com", true), // punycode ĕ
I("xn--\0xc4\0x95.com", false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup
// Surprising punycode
I("xn--google.com", true), // 䕮䕵䕶䕱.com
I("xn--citibank.com", true), // 岍岊岊岅岉岎.com
I("xn--cnn.com", true), // 䁾.com
I("a.xn--cnn", true), // a.䁾
I("a.xn--cnn.com", true), // a.䁾.com
I("1.2.3.4", false), // IPv4 address
I("1::2", false), // IPV6 address
// whitespace not allowed anywhere.
I(" ", false),
I(" a", false),
I("a ", false),
I("a b", false),
I("a.b 1", false),
I("a\t", false),
// Nulls not allowed
I("\0", false),
I("a\0", false),
I("example.org\0.example.com", false), // Hi Moxie!
I("\0a", false),
I("xn--\0", 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),
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),
I("0.1.2.3.4.5.6.7.8.9.a", true), // "a" needed to avoid numeric last label
I("a-b", true), // hyphen (a label cannot start or end with a hyphen)
// An invalid character in various positions
I("!", false),
I("!a", false),
I("a!", false),
I("a!b", false),
I("a.!", false),
I("a.a!", false),
I("a.!a", false),
I("a.a!a", false),
I("a.!a.a", false),
I("a.a!.a", false),
I("a.a!a.a", false),
// Various other invalid characters
I("a!", false),
I("a@", false),
I("a#", false),
I("a$", false),
I("a%", false),
I("a^", false),
I("a&", false),
I("a*", false),
I("a(", false),
I("a)", false),
// last label can't be fully numeric
I("1", false),
I("a.1", false),
// other labels can be fully numeric
I("1.a", true),
I("1.2.a", true),
I("1.2.3.a", true),
// last label can be *partly* numeric
I("1a", true),
I("1.1a", true),
I("1-1", true),
I("a.1-1", true),
I("a.1-a", true),
// labels cannot start with a hyphen
I("-", false),
I("-1", false),
// labels cannot end with a hyphen
I("1-", false),
I("1-.a", false),
I("a-", false),
I("a-.a", false),
I("a.1-.a", false),
I("a.a-.a", false),
// labels can contain a hyphen in the middle
I("a-b", true),
I("1-2", true),
I("a.a-1", true),
// multiple consecutive hyphens allowed
I("a--1", true),
I("1---a", true),
I("a-----------------b", true),
// Wildcard specifications are not valid DNS names
I("*.a", false),
I("a*", false),
I("a*.a", 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),
// maximum label length is 63 characters
I("1234567890" "1234567890" "1234567890"
"1234567890" "1234567890" "1234567890" "abc", true),
I("1234567890" "1234567890" "1234567890"
"1234567890" "1234567890" "1234567890" "abcd", 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),
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),
};
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), // ASCII capital I
I("i", true), // ASCII lowercase i
I("\0xC4\0xB0", false), // latin capital letter i with dot above
I("\0xC4\0xB1", false), // latin small letter dotless i
I("xn--i-9bb", true), // latin capital letter i with dot above, in punycode
I("xn--cfa", true), // latin small letter dotless i, in punycode
I("xn--\0xC4\0xB0", false), // latin capital letter i with dot above, mashup
I("xn--\0xC4\0xB1", false), // latin small letter dotless i, mashup
};
class pkixnames_IsValidDNSName
: public ::testing::Test
, public ::testing::WithParamInterface<InputValidity>
{
};
TEST_P(pkixnames_IsValidDNSName, IsValidDNSName)
{
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.isValid, IsValidDNSName(input));
}
INSTANTIATE_TEST_CASE_P(pkixnames_IsValidDNSName,
pkixnames_IsValidDNSName,
testing::ValuesIn(DNSNAMES_VALIDITY));
INSTANTIATE_TEST_CASE_P(pkixnames_IsValidDNSName_Turkish_I,
pkixnames_IsValidDNSName,
testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));