forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract GeneralNames class out of net/cert/internal/name_constraints.cc
Bug: Change-Id: Ie93d7a3f72cbffabfa0389dc57207c5ce0f47ba0 Reviewed-on: https://chromium-review.googlesource.com/665261 Reviewed-by: Eric Roman <[email protected]> Commit-Queue: Matt Mueller <[email protected]> Cr-Commit-Position: refs/heads/master@{#502013}
- Loading branch information
1 parent
63c0214
commit 9e3ad30
Showing
7 changed files
with
375 additions
and
290 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
// Copyright 2017 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "net/cert/internal/general_names.h" | ||
|
||
#include "base/logging.h" | ||
#include "base/strings/string_util.h" | ||
#include "net/cert/internal/cert_error_params.h" | ||
#include "net/cert/internal/cert_errors.h" | ||
#include "net/der/input.h" | ||
#include "net/der/parser.h" | ||
#include "net/der/tag.h" | ||
|
||
namespace net { | ||
|
||
DEFINE_CERT_ERROR_ID(kFailedParsingGeneralName, "Failed parsing GeneralName"); | ||
|
||
namespace { | ||
|
||
DEFINE_CERT_ERROR_ID(kDnsNameNotAscii, "dNSName is not ASCII"); | ||
DEFINE_CERT_ERROR_ID(kFailedParsingIp, "Failed parsing iPAddress"); | ||
DEFINE_CERT_ERROR_ID(kUnknownGeneralNameType, "Unknown GeneralName type"); | ||
DEFINE_CERT_ERROR_ID(kFailedReadingGeneralNames, | ||
"Failed reading GeneralNames SEQUENCE"); | ||
DEFINE_CERT_ERROR_ID(kGeneralNamesTrailingData, | ||
"GeneralNames contains trailing data after the sequence"); | ||
DEFINE_CERT_ERROR_ID(kGeneralNamesEmpty, | ||
"GeneralNames is a sequence of 0 elements"); | ||
DEFINE_CERT_ERROR_ID(kFailedReadingGeneralName, | ||
"Failed reading GeneralName TLV"); | ||
|
||
// Return true if the bitmask |mask| contains only zeros after the first | ||
// |prefix_length| bits. | ||
bool IsSuffixZero(const IPAddressBytes& mask, unsigned prefix_length) { | ||
size_t zero_bits = mask.size() * CHAR_BIT - prefix_length; | ||
size_t zero_bytes = zero_bits / CHAR_BIT; | ||
std::vector<uint8_t> zeros(zero_bytes, 0); | ||
if (memcmp(zeros.data(), mask.data() + mask.size() - zero_bytes, zero_bytes)) | ||
return false; | ||
size_t leftover_bits = zero_bits % CHAR_BIT; | ||
if (leftover_bits) { | ||
uint8_t b = mask[mask.size() - zero_bytes - 1]; | ||
for (size_t i = 0; i < leftover_bits; ++i) { | ||
if (b & (1 << i)) | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
} // namespace | ||
|
||
GeneralNames::GeneralNames() {} | ||
|
||
GeneralNames::~GeneralNames() {} | ||
|
||
// static | ||
std::unique_ptr<GeneralNames> GeneralNames::Create( | ||
const der::Input& general_names_tlv, | ||
CertErrors* errors) { | ||
DCHECK(errors); | ||
|
||
// RFC 5280 section 4.2.1.6: | ||
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName | ||
std::unique_ptr<GeneralNames> general_names(new GeneralNames()); | ||
der::Parser parser(general_names_tlv); | ||
der::Parser sequence_parser; | ||
if (!parser.ReadSequence(&sequence_parser)) { | ||
errors->AddError(kFailedReadingGeneralNames); | ||
return nullptr; | ||
} | ||
// Should not have trailing data after GeneralNames sequence. | ||
if (parser.HasMore()) { | ||
errors->AddError(kGeneralNamesTrailingData); | ||
return nullptr; | ||
} | ||
// The GeneralNames sequence should have at least 1 element. | ||
if (!sequence_parser.HasMore()) { | ||
errors->AddError(kGeneralNamesEmpty); | ||
return nullptr; | ||
} | ||
|
||
while (sequence_parser.HasMore()) { | ||
der::Input raw_general_name; | ||
if (!sequence_parser.ReadRawTLV(&raw_general_name)) { | ||
errors->AddError(kFailedReadingGeneralName); | ||
return nullptr; | ||
} | ||
|
||
if (!ParseGeneralName(raw_general_name, GENERAL_NAME_ALL_TYPES, | ||
IP_ADDRESS_ONLY, general_names.get(), errors)) { | ||
errors->AddError(kFailedParsingGeneralName); | ||
return nullptr; | ||
} | ||
} | ||
|
||
return general_names; | ||
} | ||
|
||
WARN_UNUSED_RESULT bool ParseGeneralName( | ||
const der::Input& input, | ||
int types_to_record, | ||
GeneralNames::ParseGeneralNameIPAddressType ip_address_type, | ||
GeneralNames* subtrees, | ||
CertErrors* errors) { | ||
DCHECK(errors); | ||
der::Parser parser(input); | ||
der::Tag tag; | ||
der::Input value; | ||
if (!parser.ReadTagAndValue(&tag, &value)) | ||
return false; | ||
GeneralNameTypes name_type = GENERAL_NAME_NONE; | ||
if (tag == der::ContextSpecificConstructed(0)) { | ||
// otherName [0] OtherName, | ||
name_type = GENERAL_NAME_OTHER_NAME; | ||
} else if (tag == der::ContextSpecificPrimitive(1)) { | ||
// rfc822Name [1] IA5String, | ||
name_type = GENERAL_NAME_RFC822_NAME; | ||
} else if (tag == der::ContextSpecificPrimitive(2)) { | ||
// dNSName [2] IA5String, | ||
name_type = GENERAL_NAME_DNS_NAME; | ||
const std::string s = value.AsString(); | ||
if (!base::IsStringASCII(s)) { | ||
errors->AddError(kDnsNameNotAscii); | ||
return false; | ||
} | ||
subtrees->dns_names.push_back(s); | ||
} else if (tag == der::ContextSpecificConstructed(3)) { | ||
// x400Address [3] ORAddress, | ||
name_type = GENERAL_NAME_X400_ADDRESS; | ||
} else if (tag == der::ContextSpecificConstructed(4)) { | ||
// directoryName [4] Name, | ||
name_type = GENERAL_NAME_DIRECTORY_NAME; | ||
// Name is a CHOICE { rdnSequence RDNSequence }, therefore the SEQUENCE | ||
// tag is explicit. Remove it, since the name matching functions expect | ||
// only the value portion. | ||
der::Parser name_parser(value); | ||
der::Input name_value; | ||
if (!name_parser.ReadTag(der::kSequence, &name_value) || parser.HasMore()) | ||
return false; | ||
subtrees->directory_names.push_back( | ||
std::vector<uint8_t>(name_value.UnsafeData(), | ||
name_value.UnsafeData() + name_value.Length())); | ||
} else if (tag == der::ContextSpecificConstructed(5)) { | ||
// ediPartyName [5] EDIPartyName, | ||
name_type = GENERAL_NAME_EDI_PARTY_NAME; | ||
} else if (tag == der::ContextSpecificPrimitive(6)) { | ||
// uniformResourceIdentifier [6] IA5String, | ||
name_type = GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER; | ||
} else if (tag == der::ContextSpecificPrimitive(7)) { | ||
// iPAddress [7] OCTET STRING, | ||
name_type = GENERAL_NAME_IP_ADDRESS; | ||
if (ip_address_type == GeneralNames::IP_ADDRESS_ONLY) { | ||
// RFC 5280 section 4.2.1.6: | ||
// When the subjectAltName extension contains an iPAddress, the address | ||
// MUST be stored in the octet string in "network byte order", as | ||
// specified in [RFC791]. The least significant bit (LSB) of each octet | ||
// is the LSB of the corresponding byte in the network address. For IP | ||
// version 4, as specified in [RFC791], the octet string MUST contain | ||
// exactly four octets. For IP version 6, as specified in [RFC2460], | ||
// the octet string MUST contain exactly sixteen octets. | ||
if ((value.Length() != IPAddress::kIPv4AddressSize && | ||
value.Length() != IPAddress::kIPv6AddressSize)) { | ||
errors->AddError(kFailedParsingIp); | ||
return false; | ||
} | ||
subtrees->ip_addresses.push_back( | ||
IPAddress(value.UnsafeData(), value.Length())); | ||
} else { | ||
DCHECK_EQ(ip_address_type, GeneralNames::IP_ADDRESS_AND_NETMASK); | ||
// RFC 5280 section 4.2.1.10: | ||
// The syntax of iPAddress MUST be as described in Section 4.2.1.6 with | ||
// the following additions specifically for name constraints. For IPv4 | ||
// addresses, the iPAddress field of GeneralName MUST contain eight (8) | ||
// octets, encoded in the style of RFC 4632 (CIDR) to represent an | ||
// address range [RFC4632]. For IPv6 addresses, the iPAddress field | ||
// MUST contain 32 octets similarly encoded. For example, a name | ||
// constraint for "class C" subnet 192.0.2.0 is represented as the | ||
// octets C0 00 02 00 FF FF FF 00, representing the CIDR notation | ||
// 192.0.2.0/24 (mask 255.255.255.0). | ||
if (value.Length() != IPAddress::kIPv4AddressSize * 2 && | ||
value.Length() != IPAddress::kIPv6AddressSize * 2) { | ||
errors->AddError(kFailedParsingIp); | ||
return false; | ||
} | ||
const IPAddress mask(value.UnsafeData() + value.Length() / 2, | ||
value.Length() / 2); | ||
const unsigned mask_prefix_length = MaskPrefixLength(mask); | ||
if (!IsSuffixZero(mask.bytes(), mask_prefix_length)) { | ||
errors->AddError(kFailedParsingIp); | ||
return false; | ||
} | ||
subtrees->ip_address_ranges.push_back( | ||
std::make_pair(IPAddress(value.UnsafeData(), value.Length() / 2), | ||
mask_prefix_length)); | ||
} | ||
} else if (tag == der::ContextSpecificPrimitive(8)) { | ||
// registeredID [8] OBJECT IDENTIFIER } | ||
name_type = GENERAL_NAME_REGISTERED_ID; | ||
} else { | ||
errors->AddError(kUnknownGeneralNameType, | ||
CreateCertErrorParams1SizeT("tag", tag)); | ||
return false; | ||
} | ||
DCHECK_NE(GENERAL_NAME_NONE, name_type); | ||
if (name_type & types_to_record) | ||
subtrees->present_name_types |= name_type; | ||
return true; | ||
} | ||
|
||
} // namespace net |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright 2017 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef NET_CERT_INTERNAL_GENERAL_NAMES_H_ | ||
#define NET_CERT_INTERNAL_GENERAL_NAMES_H_ | ||
|
||
#include <memory> | ||
#include <vector> | ||
|
||
#include "net/base/ip_address.h" | ||
#include "net/base/net_export.h" | ||
#include "net/cert/internal/cert_error_id.h" | ||
|
||
namespace net { | ||
|
||
class CertErrors; | ||
|
||
NET_EXPORT extern const CertErrorId kFailedParsingGeneralName; | ||
|
||
namespace der { | ||
class Input; | ||
} // namespace der | ||
|
||
// Bitfield values for the GeneralName types defined in RFC 5280. The ordering | ||
// and exact values are not important, but match the order from the RFC for | ||
// convenience. | ||
enum GeneralNameTypes { | ||
GENERAL_NAME_NONE = 0, | ||
GENERAL_NAME_OTHER_NAME = 1 << 0, | ||
GENERAL_NAME_RFC822_NAME = 1 << 1, | ||
GENERAL_NAME_DNS_NAME = 1 << 2, | ||
GENERAL_NAME_X400_ADDRESS = 1 << 3, | ||
GENERAL_NAME_DIRECTORY_NAME = 1 << 4, | ||
GENERAL_NAME_EDI_PARTY_NAME = 1 << 5, | ||
GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER = 1 << 6, | ||
GENERAL_NAME_IP_ADDRESS = 1 << 7, | ||
GENERAL_NAME_REGISTERED_ID = 1 << 8, | ||
GENERAL_NAME_ALL_TYPES = (1 << 9) - 1, | ||
}; | ||
|
||
// Represents a GeneralNames structure. When processing GeneralNames, it is | ||
// often necessary to know which types of names were present, and to check | ||
// all the names of a certain type. Therefore, a bitfield of all the name | ||
// types is kept, and the names are split into members for each type. Only | ||
// name types that are handled by this code are stored (though all types are | ||
// recorded in the bitfield.) | ||
struct NET_EXPORT GeneralNames { | ||
// Controls parsing of iPAddress names in ParseGeneralName. | ||
// IP_ADDRESS_ONLY parses the iPAddress names as a 4 or 16 byte IP address. | ||
// IP_ADDRESS_AND_NETMASK parses the iPAddress names as 8 or 32 bytes | ||
// containing an IP address followed by a netmask. | ||
enum ParseGeneralNameIPAddressType { | ||
IP_ADDRESS_ONLY, | ||
IP_ADDRESS_AND_NETMASK, | ||
}; | ||
|
||
GeneralNames(); | ||
~GeneralNames(); | ||
|
||
// Create a GeneralNames object representing the DER-encoded | ||
// |general_names_tlv|. Returns nullptr on failure, and may fill |errors| with | ||
// additional information. |errors| must be non-null. | ||
static std::unique_ptr<GeneralNames> Create( | ||
const der::Input& general_names_tlv, | ||
CertErrors* errors); | ||
|
||
// ASCII hostnames. | ||
std::vector<std::string> dns_names; | ||
|
||
// DER-encoded Name values (not including the Sequence tag). | ||
std::vector<std::vector<uint8_t>> directory_names; | ||
|
||
// iPAddresses as sequences of octets in network byte order. This will be | ||
// populated if the GeneralNames represents a Subject Alternative Name. | ||
std::vector<IPAddress> ip_addresses; | ||
|
||
// iPAddress ranges, as <IP, prefix length> pairs. This will be populated | ||
// if the GeneralNames represents a Name Constraints. | ||
std::vector<std::pair<IPAddress, unsigned>> ip_address_ranges; | ||
|
||
// Which name types were present, as a bitfield of GeneralNameTypes. | ||
// Includes both the supported and unsupported types (although unsupported | ||
// ones may not be recorded depending on the context, like non-critical name | ||
// constraints.) | ||
int present_name_types = GENERAL_NAME_NONE; | ||
}; | ||
|
||
// Parses a GeneralName value and adds it to |subtrees|. | ||
// |types_to_record| is a bitfield of GeneralNameTypes of the types to be | ||
// recorded in |subtrees->present_name_types|. It does not affect the recording | ||
// of the other fields. TODO(mattm): this seems weird. | ||
// |ip_address_type| specifies how to parse iPAddress names. | ||
// Returns false on failure, and may fill |errors| with additional information. | ||
// |errors| must be non-null. | ||
// TODO(mattm): should this be a method on GeneralNames? | ||
WARN_UNUSED_RESULT bool ParseGeneralName( | ||
const der::Input& input, | ||
int types_to_record, | ||
GeneralNames::ParseGeneralNameIPAddressType ip_address_type, | ||
GeneralNames* subtrees, | ||
CertErrors* errors); | ||
|
||
} // namespace net | ||
|
||
#endif // NET_CERT_INTERNAL_GENERAL_NAMES_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright 2017 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "net/cert/internal/general_names.h" | ||
|
||
#include "net/cert/internal/test_helpers.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
|
||
namespace net { | ||
namespace { | ||
|
||
::testing::AssertionResult LoadTestData(const char* token, | ||
const std::string& basename, | ||
std::string* result) { | ||
std::string path = "net/data/name_constraints_unittest/" + basename; | ||
|
||
const PemBlockMapping mappings[] = { | ||
{token, result}, | ||
}; | ||
|
||
return ReadTestDataFromPemFile(path, mappings); | ||
} | ||
|
||
::testing::AssertionResult LoadTestSubjectAltNameData( | ||
const std::string& basename, | ||
std::string* result) { | ||
return LoadTestData("SUBJECT ALTERNATIVE NAME", basename, result); | ||
} | ||
|
||
} // namespace | ||
|
||
TEST(GeneralNames, CreateFailsOnEmptySubjectAltName) { | ||
std::string invalid_san_der; | ||
ASSERT_TRUE( | ||
LoadTestSubjectAltNameData("san-invalid-empty.pem", &invalid_san_der)); | ||
CertErrors errors; | ||
EXPECT_FALSE(GeneralNames::Create(der::Input(&invalid_san_der), &errors)); | ||
} | ||
|
||
TEST(GeneralNames, CreateFailsOnInvalidIpInSubjectAltName) { | ||
std::string invalid_san_der; | ||
ASSERT_TRUE(LoadTestSubjectAltNameData("san-invalid-ipaddress.pem", | ||
&invalid_san_der)); | ||
CertErrors errors; | ||
EXPECT_FALSE(GeneralNames::Create(der::Input(&invalid_san_der), &errors)); | ||
} | ||
|
||
} // namespace net |
Oops, something went wrong.