Bring in the core of chromium certificate verifier as libpki
Initially this leaves the canonical source in chrome, Additions and fillins are committed directly, the chrome files are coverted using the IMPORT script run from the pki directory for the moment. The intention here is to continue frequent automatic conversion (and avoid wholesale cosmetic changes in here for now) until chrome converts to use these files in place of it's versions. At that point these will become the definiative files, and the IMPORT script can be tossed out. A middle step along the way will be to change google3's verify.cc in third_party/chromium_certificate_verifier to use this instead of it's own extracted copy. Status (and what is not done yet) being roughly tracked in README.md Bug: chromium:1322914 Change-Id: Ibdb5479bc68985fa61ce6b10f98f31f6b3a7cbdf Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60285 Commit-Queue: Bob Beck <bbe@google.com> Reviewed-by: Adam Langley <agl@google.com>
This commit is contained in:
parent
ee194c75a6
commit
bc97b7a8e1
@ -139,7 +139,8 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CLANG)
|
||||
# Note clang-cl is odd and sets both CLANG and MSVC. We base our configuration
|
||||
# primarily on our normal Clang one.
|
||||
set(C_CXX_FLAGS "-Werror -Wformat=2 -Wsign-compare -Wmissing-field-initializers -Wwrite-strings -Wvla -Wshadow -Wtype-limits")
|
||||
# TODO(bbe) took out -Wmissing-field-initializers for pki - fix and put back or disable only for pki
|
||||
set(C_CXX_FLAGS "-Werror -Wformat=2 -Wsign-compare -Wwrite-strings -Wvla -Wshadow -Wtype-limits")
|
||||
if(MSVC)
|
||||
# clang-cl sets different default warnings than clang. It also treats -Wall
|
||||
# as -Weverything, to match MSVC. Instead -W3 is the alias for -Wall.
|
||||
@ -518,6 +519,7 @@ add_subdirectory(tool)
|
||||
add_subdirectory(util/fipstools)
|
||||
add_subdirectory(util/fipstools/acvp/modulewrapper)
|
||||
add_subdirectory(decrepit)
|
||||
add_subdirectory(pki)
|
||||
|
||||
if(FUZZ)
|
||||
if(LIBFUZZER_FROM_DEPS)
|
||||
|
103
pki/CMakeLists.txt
Normal file
103
pki/CMakeLists.txt
Normal file
@ -0,0 +1,103 @@
|
||||
project(pki)
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_library(
|
||||
pki
|
||||
|
||||
fillins/ip_address.cc
|
||||
fillins/utf_string_conversions.cc
|
||||
fillins/string_util.cc
|
||||
fillins/base64.cc
|
||||
fillins/openssl_util.cc
|
||||
string_util.cc
|
||||
trust_store.cc
|
||||
trust_store_collection.cc
|
||||
parse_certificate.cc
|
||||
parsed_certificate.cc
|
||||
parser.cc
|
||||
parse_values.cc
|
||||
parse_name.cc
|
||||
parsed_certificate.cc
|
||||
name_constraints.cc
|
||||
input.cc
|
||||
tag.cc
|
||||
cert_errors.cc
|
||||
general_names.cc
|
||||
pem.cc
|
||||
crl.cc
|
||||
revocation_util.cc
|
||||
encode_values.cc
|
||||
verify_name_match.cc
|
||||
cert_errors.cc
|
||||
common_cert_errors.cc
|
||||
parse_certificate.cc
|
||||
parsed_certificate.cc
|
||||
extended_key_usage.cc
|
||||
certificate_policies.cc
|
||||
verify_certificate_chain.cc
|
||||
verify_signed_data.cc
|
||||
signature_algorithm.cc
|
||||
cert_error_id.cc
|
||||
cert_error_params.cc
|
||||
trust_store.cc
|
||||
trust_store_collection.cc
|
||||
trust_store_in_memory.cc
|
||||
simple_path_builder_delegate.cc
|
||||
cert_issuer_source_static.cc
|
||||
path_builder.cc
|
||||
)
|
||||
# Although libpki also provides headers that require an include directory, the
|
||||
# flag is already specified by libcrypto, so we omit target_include_directories
|
||||
# here.
|
||||
install_if_enabled(TARGETS pki EXPORT OpenSSLTargets ${INSTALL_DESTINATION_DEFAULT})
|
||||
set_property(TARGET pki PROPERTY EXPORT_NAME PKI)
|
||||
set_property(TARGET pki PROPERTY CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_BORINGSSL_LIBPKI_")
|
||||
if (APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-aligned-new")
|
||||
endif()
|
||||
target_link_libraries(pki ssl crypto)
|
||||
|
||||
add_executable(
|
||||
pki_test
|
||||
|
||||
fillins/path_service.cc
|
||||
fillins/file_util.cc
|
||||
test_helpers.cc
|
||||
string_util_unittest.cc
|
||||
parser_unittest.cc
|
||||
parse_values_unittest.cc
|
||||
input_unittest.cc
|
||||
signature_algorithm_unittest.cc
|
||||
extended_key_usage_unittest.cc
|
||||
parse_name_unittest.cc
|
||||
verify_name_match_unittest.cc
|
||||
verify_signed_data_unittest.cc
|
||||
parse_certificate_unittest.cc
|
||||
parsed_certificate_unittest.cc
|
||||
simple_path_builder_delegate_unittest.cc
|
||||
trust_store_collection_unittest.cc
|
||||
certificate_policies_unittest.cc
|
||||
verify_certificate_chain_unittest.cc
|
||||
nist_pkits_unittest.cc
|
||||
path_builder_pkits_unittest.cc
|
||||
name_constraints_unittest.cc
|
||||
cert_issuer_source_static_unittest.cc
|
||||
path_builder_unittest.cc
|
||||
mock_signature_verify_cache.cc
|
||||
path_builder_verify_certificate_chain_unittest.cc
|
||||
verify_certificate_chain_pkits_unittest.cc
|
||||
# encode_values_unittest.cc # Currently does a bunch of time goo..
|
||||
# ocsp_unittest.cc # Not sure we will keep this here..
|
||||
)
|
||||
target_link_libraries(pki_test test_support_lib boringssl_gtest_main pki ssl crypto)
|
||||
set_property(TARGET pki_test PROPERTY CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_BORINGSSL_LIBPKI_")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_BORINGSSL_PKI_SRCDIR_=${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
if (APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-aligned-new")
|
||||
endif()
|
||||
add_dependencies(all_tests pki_test)
|
||||
|
||||
|
23
pki/IMPORT
Executable file
23
pki/IMPORT
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Set this to be the location of a chromium checkout, and
|
||||
# apply the patches in ./patches with "git am" first
|
||||
# before running this script.
|
||||
CHROMIUM_SRC=~/chromium/src
|
||||
|
||||
mkdir -p ./testdata
|
||||
cp $CHROMIUM_SRC/net/test/test_certificate_data.h ./testdata
|
||||
|
||||
tar -C $CHROMIUM_SRC/net/third_party -cf - nist-pkits | tar -C ./testdata -xf -
|
||||
tar -C $CHROMIUM_SRC/net/data -cf - cert_issuer_source_static_unittest \
|
||||
ssl/certificates \
|
||||
certificate_policies_unittest \
|
||||
name_constraints_unittest \
|
||||
ocsp_unittest \
|
||||
parse_certificate_unittest \
|
||||
path_builder_unittest \
|
||||
verify_certificate_chain_unittest \
|
||||
verify_name_match_unittest \
|
||||
verify_signed_data_unittest | tar -C ./testdata -xf -
|
||||
|
||||
go run ./import_tool.go -spec import_spec.json --source-base $CHROMIUM_SRC -dest-base .
|
32
pki/README.md
Normal file
32
pki/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# BoringSSL pki - Web PKI Certificate path building and verification library
|
||||
|
||||
This directory and library should be considered experimental and should not be
|
||||
depended upon not to change without notice. You should not use this.
|
||||
|
||||
It contains an extracted and modified copy of chrome's certificate
|
||||
verifier core logic.
|
||||
|
||||
It is for the moment, intended to be synchronized from a checkout of chrome's
|
||||
head with the IMPORT script run in this directory. The eventual goal is to
|
||||
make both chrome and google3 consume this.
|
||||
|
||||
## Current status:
|
||||
* Some of the Path Builder tests depending on chrome testing classes and
|
||||
SavedUserData are disabled. These probably need either a mimicing
|
||||
SaveUserData class here, or be pulled out into chrome only.
|
||||
* This contains a copy of der as bssl:der - a consideration for
|
||||
re-integrating with chromium. the encode_values part of der does not include
|
||||
the base::time or absl::time based stuff as they are not used within the
|
||||
library, this should probably be split out for chrome, or chrome's der could
|
||||
be modified (along with this one and eventually merged together) to not use
|
||||
base::time for encoding GeneralizedTimes, but rather use boringssl posix
|
||||
times as does the rest of this library.
|
||||
* The Name Constraint limitation code is modified to remove clamped_math
|
||||
and mimic BoringSSL's overall limits - Some of the tests that test
|
||||
for specific edge cases for chrome's limits have been disabled. The
|
||||
tests need to be changed to reflect the overall limit, or ignored
|
||||
and we make name constraints subquadratic and stop caring about this.
|
||||
* Fuzzer targets are not yet hooked up.
|
||||
|
||||
|
||||
|
331
pki/asn1_util.cc
Normal file
331
pki/asn1_util.cc
Normal file
@ -0,0 +1,331 @@
|
||||
// Copyright 2012 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "asn1_util.h"
|
||||
|
||||
#include "parse_certificate.h"
|
||||
#include "input.h"
|
||||
#include "parser.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl::asn1 {
|
||||
|
||||
namespace {
|
||||
|
||||
// Parses input |in| which should point to the beginning of a Certificate, and
|
||||
// sets |*tbs_certificate| ready to parse the Subject. If parsing
|
||||
// fails, this function returns false and |*tbs_certificate| is left in an
|
||||
// undefined state.
|
||||
bool SeekToSubject(der::Input in, der::Parser* tbs_certificate) {
|
||||
// From RFC 5280, section 4.1
|
||||
// Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// ... }
|
||||
|
||||
der::Parser parser(in);
|
||||
der::Parser certificate;
|
||||
if (!parser.ReadSequence(&certificate))
|
||||
return false;
|
||||
|
||||
// We don't allow junk after the certificate.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
if (!certificate.ReadSequence(tbs_certificate))
|
||||
return false;
|
||||
|
||||
bool unused;
|
||||
if (!tbs_certificate->SkipOptionalTag(
|
||||
der::kTagConstructed | der::kTagContextSpecific | 0, &unused)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// serialNumber
|
||||
if (!tbs_certificate->SkipTag(der::kInteger))
|
||||
return false;
|
||||
// signature
|
||||
if (!tbs_certificate->SkipTag(der::kSequence))
|
||||
return false;
|
||||
// issuer
|
||||
if (!tbs_certificate->SkipTag(der::kSequence))
|
||||
return false;
|
||||
// validity
|
||||
if (!tbs_certificate->SkipTag(der::kSequence))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses input |in| which should point to the beginning of a Certificate, and
|
||||
// sets |*tbs_certificate| ready to parse the SubjectPublicKeyInfo. If parsing
|
||||
// fails, this function returns false and |*tbs_certificate| is left in an
|
||||
// undefined state.
|
||||
bool SeekToSPKI(der::Input in, der::Parser* tbs_certificate) {
|
||||
return SeekToSubject(in, tbs_certificate) &&
|
||||
// Skip over Subject.
|
||||
tbs_certificate->SkipTag(der::kSequence);
|
||||
}
|
||||
|
||||
// Parses input |in| which should point to the beginning of a
|
||||
// Certificate. If parsing fails, this function returns false, with
|
||||
// |*extensions_present| and |*extensions_parser| left in an undefined
|
||||
// state. If parsing succeeds and extensions are present, this function
|
||||
// sets |*extensions_present| to true and sets |*extensions_parser|
|
||||
// ready to parse the Extensions. If extensions are not present, it sets
|
||||
// |*extensions_present| to false and |*extensions_parser| is left in an
|
||||
// undefined state.
|
||||
bool SeekToExtensions(der::Input in,
|
||||
bool* extensions_present,
|
||||
der::Parser* extensions_parser) {
|
||||
bool present;
|
||||
der::Parser tbs_cert_parser;
|
||||
if (!SeekToSPKI(in, &tbs_cert_parser))
|
||||
return false;
|
||||
|
||||
// From RFC 5280, section 4.1
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// ...
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL }
|
||||
|
||||
// subjectPublicKeyInfo
|
||||
if (!tbs_cert_parser.SkipTag(der::kSequence))
|
||||
return false;
|
||||
// issuerUniqueID
|
||||
if (!tbs_cert_parser.SkipOptionalTag(der::kTagContextSpecific | 1,
|
||||
&present)) {
|
||||
return false;
|
||||
}
|
||||
// subjectUniqueID
|
||||
if (!tbs_cert_parser.SkipOptionalTag(der::kTagContextSpecific | 2,
|
||||
&present)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<der::Input> extensions;
|
||||
if (!tbs_cert_parser.ReadOptionalTag(
|
||||
der::kTagConstructed | der::kTagContextSpecific | 3, &extensions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!extensions) {
|
||||
*extensions_present = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||||
// Extension ::= SEQUENCE {
|
||||
// extnID OBJECT IDENTIFIER,
|
||||
// critical BOOLEAN DEFAULT FALSE,
|
||||
// extnValue OCTET STRING }
|
||||
|
||||
// |extensions| was EXPLICITly tagged, so we still need to remove the
|
||||
// ASN.1 SEQUENCE header.
|
||||
der::Parser explicit_extensions_parser(extensions.value());
|
||||
if (!explicit_extensions_parser.ReadSequence(extensions_parser))
|
||||
return false;
|
||||
|
||||
if (explicit_extensions_parser.HasMore())
|
||||
return false;
|
||||
|
||||
*extensions_present = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse a DER-encoded, X.509 certificate in |cert| and find an extension with
|
||||
// the given OID. Returns false on parse error or true if the parse was
|
||||
// successful. |*out_extension_present| will be true iff the extension was
|
||||
// found. In the case where it was found, |*out_extension| will describe the
|
||||
// extension, or is undefined on parse error or if the extension is missing.
|
||||
bool ExtractExtensionWithOID(std::string_view cert,
|
||||
der::Input extension_oid,
|
||||
bool* out_extension_present,
|
||||
ParsedExtension* out_extension) {
|
||||
der::Parser extensions;
|
||||
bool extensions_present;
|
||||
if (!SeekToExtensions(der::Input(cert), &extensions_present, &extensions))
|
||||
return false;
|
||||
if (!extensions_present) {
|
||||
*out_extension_present = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (extensions.HasMore()) {
|
||||
der::Input extension_tlv;
|
||||
if (!extensions.ReadRawTLV(&extension_tlv) ||
|
||||
!ParseExtension(extension_tlv, out_extension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_extension->oid == extension_oid) {
|
||||
*out_extension_present = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
*out_extension_present = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ExtractSubjectFromDERCert(std::string_view cert,
|
||||
std::string_view* subject_out) {
|
||||
der::Parser parser;
|
||||
if (!SeekToSubject(der::Input(cert), &parser))
|
||||
return false;
|
||||
der::Input subject;
|
||||
if (!parser.ReadRawTLV(&subject))
|
||||
return false;
|
||||
*subject_out = subject.AsStringView();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractSPKIFromDERCert(std::string_view cert,
|
||||
std::string_view* spki_out) {
|
||||
der::Parser parser;
|
||||
if (!SeekToSPKI(der::Input(cert), &parser))
|
||||
return false;
|
||||
der::Input spki;
|
||||
if (!parser.ReadRawTLV(&spki))
|
||||
return false;
|
||||
*spki_out = spki.AsStringView();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractSubjectPublicKeyFromSPKI(std::string_view spki,
|
||||
std::string_view* spk_out) {
|
||||
// From RFC 5280, Section 4.1
|
||||
// SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
// algorithm AlgorithmIdentifier,
|
||||
// subjectPublicKey BIT STRING }
|
||||
//
|
||||
// AlgorithmIdentifier ::= SEQUENCE {
|
||||
// algorithm OBJECT IDENTIFIER,
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
||||
|
||||
// Step into SubjectPublicKeyInfo sequence.
|
||||
der::Parser parser((der::Input(spki)));
|
||||
der::Parser spki_parser;
|
||||
if (!parser.ReadSequence(&spki_parser))
|
||||
return false;
|
||||
|
||||
// Step over algorithm field (a SEQUENCE).
|
||||
if (!spki_parser.SkipTag(der::kSequence))
|
||||
return false;
|
||||
|
||||
// Extract the subjectPublicKey field.
|
||||
der::Input spk;
|
||||
if (!spki_parser.ReadTag(der::kBitString, &spk))
|
||||
return false;
|
||||
*spk_out = spk.AsStringView();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasCanSignHttpExchangesDraftExtension(std::string_view cert) {
|
||||
// kCanSignHttpExchangesDraftOid is the DER encoding of the OID for
|
||||
// canSignHttpExchangesDraft defined in:
|
||||
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html
|
||||
static const uint8_t kCanSignHttpExchangesDraftOid[] = {
|
||||
0x2B, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x16};
|
||||
|
||||
bool extension_present;
|
||||
ParsedExtension extension;
|
||||
if (!ExtractExtensionWithOID(cert, der::Input(kCanSignHttpExchangesDraftOid),
|
||||
&extension_present, &extension) ||
|
||||
!extension_present) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The extension should have contents NULL.
|
||||
static const uint8_t kNull[] = {0x05, 0x00};
|
||||
return extension.value == der::Input(kNull);
|
||||
}
|
||||
|
||||
bool ExtractSignatureAlgorithmsFromDERCert(
|
||||
std::string_view cert,
|
||||
std::string_view* cert_signature_algorithm_sequence,
|
||||
std::string_view* tbs_signature_algorithm_sequence) {
|
||||
// From RFC 5280, section 4.1
|
||||
// Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// ... }
|
||||
|
||||
der::Parser parser((der::Input(cert)));
|
||||
der::Parser certificate;
|
||||
if (!parser.ReadSequence(&certificate))
|
||||
return false;
|
||||
|
||||
der::Parser tbs_certificate;
|
||||
if (!certificate.ReadSequence(&tbs_certificate))
|
||||
return false;
|
||||
|
||||
bool unused;
|
||||
if (!tbs_certificate.SkipOptionalTag(
|
||||
der::kTagConstructed | der::kTagContextSpecific | 0, &unused)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// serialNumber
|
||||
if (!tbs_certificate.SkipTag(der::kInteger))
|
||||
return false;
|
||||
// signature
|
||||
der::Input tbs_algorithm;
|
||||
if (!tbs_certificate.ReadRawTLV(&tbs_algorithm))
|
||||
return false;
|
||||
|
||||
der::Input cert_algorithm;
|
||||
if (!certificate.ReadRawTLV(&cert_algorithm))
|
||||
return false;
|
||||
|
||||
*cert_signature_algorithm_sequence = cert_algorithm.AsStringView();
|
||||
*tbs_signature_algorithm_sequence = tbs_algorithm.AsStringView();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractExtensionFromDERCert(std::string_view cert,
|
||||
std::string_view extension_oid,
|
||||
bool* out_extension_present,
|
||||
bool* out_extension_critical,
|
||||
std::string_view* out_contents) {
|
||||
*out_extension_present = false;
|
||||
*out_extension_critical = false;
|
||||
*out_contents = std::string_view();
|
||||
|
||||
ParsedExtension extension;
|
||||
if (!ExtractExtensionWithOID(cert, der::Input(extension_oid),
|
||||
out_extension_present, &extension))
|
||||
return false;
|
||||
if (!*out_extension_present)
|
||||
return true;
|
||||
|
||||
*out_extension_critical = extension.critical;
|
||||
*out_contents = extension.value.AsStringView();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net::asn1
|
75
pki/asn1_util.h
Normal file
75
pki/asn1_util.h
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2012 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_ASN1_UTIL_H_
|
||||
#define BSSL_PKI_ASN1_UTIL_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <string_view>
|
||||
|
||||
|
||||
|
||||
namespace bssl::asn1 {
|
||||
|
||||
// ExtractSubjectFromDERCert parses the DER encoded certificate in |cert| and
|
||||
// extracts the bytes of the X.501 Subject. On successful return, |subject_out|
|
||||
// is set to contain the Subject, pointing into |cert|.
|
||||
OPENSSL_EXPORT bool ExtractSubjectFromDERCert(
|
||||
std::string_view cert,
|
||||
std::string_view* subject_out);
|
||||
|
||||
// ExtractSPKIFromDERCert parses the DER encoded certificate in |cert| and
|
||||
// extracts the bytes of the SubjectPublicKeyInfo. On successful return,
|
||||
// |spki_out| is set to contain the SPKI, pointing into |cert|.
|
||||
OPENSSL_EXPORT bool ExtractSPKIFromDERCert(std::string_view cert,
|
||||
std::string_view* spki_out);
|
||||
|
||||
// ExtractSubjectPublicKeyFromSPKI parses the DER encoded SubjectPublicKeyInfo
|
||||
// in |spki| and extracts the bytes of the SubjectPublicKey. On successful
|
||||
// return, |spk_out| is set to contain the public key, pointing into |spki|.
|
||||
OPENSSL_EXPORT bool ExtractSubjectPublicKeyFromSPKI(
|
||||
std::string_view spki,
|
||||
std::string_view* spk_out);
|
||||
|
||||
// HasCanSignHttpExchangesDraftExtension parses the DER encoded certificate
|
||||
// in |cert| and extracts the canSignHttpExchangesDraft extension
|
||||
// (https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html)
|
||||
// if present. Returns true if the extension was present, and false if
|
||||
// the extension was not present or if there was a parsing failure.
|
||||
OPENSSL_EXPORT bool HasCanSignHttpExchangesDraftExtension(std::string_view cert);
|
||||
|
||||
// Extracts the two (SEQUENCE) tag-length-values for the signature
|
||||
// AlgorithmIdentifiers in a DER encoded certificate. Does not use strict
|
||||
// parsing or validate the resulting AlgorithmIdentifiers.
|
||||
//
|
||||
// On success returns true, and assigns |cert_signature_algorithm_sequence| and
|
||||
// |tbs_signature_algorithm_sequence| to point into |cert|:
|
||||
//
|
||||
// * |cert_signature_algorithm_sequence| points at the TLV for
|
||||
// Certificate.signatureAlgorithm.
|
||||
//
|
||||
// * |tbs_signature_algorithm_sequence| points at the TLV for
|
||||
// TBSCertificate.algorithm.
|
||||
OPENSSL_EXPORT bool ExtractSignatureAlgorithmsFromDERCert(
|
||||
std::string_view cert,
|
||||
std::string_view* cert_signature_algorithm_sequence,
|
||||
std::string_view* tbs_signature_algorithm_sequence);
|
||||
|
||||
// Extracts the contents of the extension (if any) with OID |extension_oid| from
|
||||
// the DER-encoded, X.509 certificate in |cert|.
|
||||
//
|
||||
// Returns false on parse error or true if the parse was successful. Sets
|
||||
// |*out_extension_present| to whether or not the extension was found. If found,
|
||||
// sets |*out_extension_critical| to match the extension's "critical" flag, and
|
||||
// sets |*out_contents| to the contents of the extension (after unwrapping the
|
||||
// OCTET STRING).
|
||||
OPENSSL_EXPORT bool ExtractExtensionFromDERCert(std::string_view cert,
|
||||
std::string_view extension_oid,
|
||||
bool* out_extension_present,
|
||||
bool* out_extension_critical,
|
||||
std::string_view* out_contents);
|
||||
|
||||
} // namespace net::asn1
|
||||
|
||||
#endif // BSSL_PKI_ASN1_UTIL_H_
|
14
pki/cert_error_id.cc
Normal file
14
pki/cert_error_id.cc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cert_error_id.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
const char* CertErrorIdToDebugString(CertErrorId id) {
|
||||
// The CertErrorId is simply a pointer for a C-string literal.
|
||||
return reinterpret_cast<const char*>(id);
|
||||
}
|
||||
|
||||
} // namespace net
|
38
pki/cert_error_id.h
Normal file
38
pki/cert_error_id.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_ERROR_ID_H_
|
||||
#define BSSL_PKI_CERT_ERROR_ID_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// Each "class" of certificate error/warning has its own unique ID. This is
|
||||
// essentially like an error code, however the value is not stable. Under the
|
||||
// hood these IDs are pointers and use the process's address space to ensure
|
||||
// uniqueness.
|
||||
//
|
||||
// Equality of CertErrorId can be done using the == operator.
|
||||
//
|
||||
// To define new error IDs use the macro DEFINE_CERT_ERROR_ID().
|
||||
using CertErrorId = const void*;
|
||||
|
||||
// DEFINE_CERT_ERROR_ID() creates a CertErrorId given a non-null C-string
|
||||
// literal. The string should be a textual name for the error which will appear
|
||||
// when pretty-printing errors for debugging. It should be ASCII.
|
||||
//
|
||||
// TODO(crbug.com/634443): Implement this -- add magic to ensure that storage
|
||||
// of identical strings isn't pool.
|
||||
#define DEFINE_CERT_ERROR_ID(name, c_str_literal) \
|
||||
const CertErrorId name = c_str_literal
|
||||
|
||||
// Returns a debug string for a CertErrorId. In practice this returns the
|
||||
// string literal given to DEFINE_CERT_ERROR_ID(), which is human-readable.
|
||||
OPENSSL_EXPORT const char* CertErrorIdToDebugString(CertErrorId id);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_ERROR_ID_H_
|
145
pki/cert_error_params.cc
Normal file
145
pki/cert_error_params.cc
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include "cert_error_params.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "fillins/check.h"
|
||||
#include "string_util.h"
|
||||
#include "input.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// Parameters subclass for describing (and pretty-printing) 1 or 2 DER
|
||||
// blobs. It makes a copy of the der::Inputs.
|
||||
class CertErrorParams2Der : public CertErrorParams {
|
||||
public:
|
||||
CertErrorParams2Der(const char* name1,
|
||||
const der::Input& der1,
|
||||
const char* name2,
|
||||
const der::Input& der2)
|
||||
: name1_(name1),
|
||||
der1_(der1.AsString()),
|
||||
name2_(name2),
|
||||
der2_(der2.AsString()) {}
|
||||
|
||||
CertErrorParams2Der(const CertErrorParams2Der&) = delete;
|
||||
CertErrorParams2Der& operator=(const CertErrorParams2Der&) = delete;
|
||||
|
||||
std::string ToDebugString() const override {
|
||||
std::string result;
|
||||
AppendDer(name1_, der1_, &result);
|
||||
if (name2_) {
|
||||
result += "\n";
|
||||
AppendDer(name2_, der2_, &result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static void AppendDer(const char* name,
|
||||
const std::string& der,
|
||||
std::string* out) {
|
||||
*out += name;
|
||||
*out +=
|
||||
": " + bssl::string_util::HexEncode(
|
||||
reinterpret_cast<const uint8_t*>(der.data()), der.size());
|
||||
}
|
||||
|
||||
const char* name1_;
|
||||
std::string der1_;
|
||||
|
||||
const char* name2_;
|
||||
std::string der2_;
|
||||
};
|
||||
|
||||
// Parameters subclass for describing (and pretty-printing) a single size_t.
|
||||
class CertErrorParams1SizeT : public CertErrorParams {
|
||||
public:
|
||||
CertErrorParams1SizeT(const char* name, size_t value)
|
||||
: name_(name), value_(value) {}
|
||||
|
||||
CertErrorParams1SizeT(const CertErrorParams1SizeT&) = delete;
|
||||
CertErrorParams1SizeT& operator=(const CertErrorParams1SizeT&) = delete;
|
||||
|
||||
std::string ToDebugString() const override {
|
||||
return name_ + std::string(": ") +
|
||||
bssl::string_util::NumberToDecimalString(value_);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name_;
|
||||
size_t value_;
|
||||
};
|
||||
|
||||
// Parameters subclass for describing (and pretty-printing) two size_t
|
||||
// values.
|
||||
class CertErrorParams2SizeT : public CertErrorParams {
|
||||
public:
|
||||
CertErrorParams2SizeT(const char* name1,
|
||||
size_t value1,
|
||||
const char* name2,
|
||||
size_t value2)
|
||||
: name1_(name1), value1_(value1), name2_(name2), value2_(value2) {}
|
||||
|
||||
CertErrorParams2SizeT(const CertErrorParams2SizeT&) = delete;
|
||||
CertErrorParams2SizeT& operator=(const CertErrorParams2SizeT&) = delete;
|
||||
|
||||
std::string ToDebugString() const override {
|
||||
return name1_ + std::string(": ") +
|
||||
bssl::string_util::NumberToDecimalString(value1_) + "\n" + name2_ +
|
||||
std::string(": ") + bssl::string_util::NumberToDecimalString(value2_);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name1_;
|
||||
size_t value1_;
|
||||
const char* name2_;
|
||||
size_t value2_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CertErrorParams::CertErrorParams() = default;
|
||||
CertErrorParams::~CertErrorParams() = default;
|
||||
|
||||
std::unique_ptr<CertErrorParams> CreateCertErrorParams1Der(
|
||||
const char* name,
|
||||
const der::Input& der) {
|
||||
DCHECK(name);
|
||||
return std::make_unique<CertErrorParams2Der>(name, der, nullptr,
|
||||
der::Input());
|
||||
}
|
||||
|
||||
std::unique_ptr<CertErrorParams> CreateCertErrorParams2Der(
|
||||
const char* name1,
|
||||
const der::Input& der1,
|
||||
const char* name2,
|
||||
const der::Input& der2) {
|
||||
DCHECK(name1);
|
||||
DCHECK(name2);
|
||||
return std::make_unique<CertErrorParams2Der>(name1, der1, name2, der2);
|
||||
}
|
||||
|
||||
std::unique_ptr<CertErrorParams> CreateCertErrorParams1SizeT(const char* name,
|
||||
size_t value) {
|
||||
DCHECK(name);
|
||||
return std::make_unique<CertErrorParams1SizeT>(name, value);
|
||||
}
|
||||
|
||||
OPENSSL_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams2SizeT(
|
||||
const char* name1,
|
||||
size_t value1,
|
||||
const char* name2,
|
||||
size_t value2) {
|
||||
DCHECK(name1);
|
||||
DCHECK(name2);
|
||||
return std::make_unique<CertErrorParams2SizeT>(name1, value1, name2, value2);
|
||||
}
|
||||
|
||||
} // namespace net
|
68
pki/cert_error_params.h
Normal file
68
pki/cert_error_params.h
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_ERROR_PARAMS_H_
|
||||
#define BSSL_PKI_CERT_ERROR_PARAMS_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace der {
|
||||
class Input;
|
||||
}
|
||||
|
||||
// CertErrorParams is a base class for describing extra parameters attached to
|
||||
// a CertErrorNode.
|
||||
//
|
||||
// An example use for parameters is to identify the OID for an unconsumed
|
||||
// critical extension. This parameter could then be pretty printed when
|
||||
// diagnosing the error.
|
||||
class OPENSSL_EXPORT CertErrorParams {
|
||||
public:
|
||||
CertErrorParams();
|
||||
|
||||
CertErrorParams(const CertErrorParams&) = delete;
|
||||
CertErrorParams& operator=(const CertErrorParams&) = delete;
|
||||
|
||||
virtual ~CertErrorParams();
|
||||
|
||||
// Creates a representation of this parameter as a string, which may be
|
||||
// used for pretty printing the error.
|
||||
virtual std::string ToDebugString() const = 0;
|
||||
};
|
||||
|
||||
// Creates a parameter object that holds a copy of |der|, and names it |name|
|
||||
// in debug string outputs.
|
||||
OPENSSL_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams1Der(
|
||||
const char* name,
|
||||
const der::Input& der);
|
||||
|
||||
// Same as CreateCertErrorParams1Der() but has a second DER blob.
|
||||
OPENSSL_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams2Der(
|
||||
const char* name1,
|
||||
const der::Input& der1,
|
||||
const char* name2,
|
||||
const der::Input& der2);
|
||||
|
||||
// Creates a parameter object that holds a single size_t value. |name| is used
|
||||
// when pretty-printing the parameters.
|
||||
OPENSSL_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams1SizeT(
|
||||
const char* name,
|
||||
size_t value);
|
||||
|
||||
// Same as CreateCertErrorParams1SizeT() but has a second size_t.
|
||||
OPENSSL_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams2SizeT(
|
||||
const char* name1,
|
||||
size_t value1,
|
||||
const char* name2,
|
||||
size_t value2);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_ERROR_PARAMS_H_
|
201
pki/cert_errors.cc
Normal file
201
pki/cert_errors.cc
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cert_errors.h"
|
||||
|
||||
#include "cert_error_params.h"
|
||||
#include "parse_name.h"
|
||||
#include "parsed_certificate.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
void AppendLinesWithIndentation(const std::string& text,
|
||||
const std::string& indentation,
|
||||
std::string* out) {
|
||||
std::istringstream stream(text);
|
||||
for (std::string line; std::getline(stream, line, '\n');) {
|
||||
out->append(indentation);
|
||||
out->append(line);
|
||||
out->append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CertError::CertError() = default;
|
||||
|
||||
CertError::CertError(Severity in_severity,
|
||||
CertErrorId in_id,
|
||||
std::unique_ptr<CertErrorParams> in_params)
|
||||
: severity(in_severity), id(in_id), params(std::move(in_params)) {}
|
||||
|
||||
CertError::CertError(CertError&& other) = default;
|
||||
|
||||
CertError& CertError::operator=(CertError&&) = default;
|
||||
|
||||
CertError::~CertError() = default;
|
||||
|
||||
std::string CertError::ToDebugString() const {
|
||||
std::string result;
|
||||
switch (severity) {
|
||||
case SEVERITY_WARNING:
|
||||
result += "WARNING: ";
|
||||
break;
|
||||
case SEVERITY_HIGH:
|
||||
result += "ERROR: ";
|
||||
break;
|
||||
}
|
||||
result += CertErrorIdToDebugString(id);
|
||||
result += +"\n";
|
||||
|
||||
if (params)
|
||||
AppendLinesWithIndentation(params->ToDebugString(), " ", &result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CertErrors::CertErrors() = default;
|
||||
CertErrors::CertErrors(CertErrors&& other) = default;
|
||||
CertErrors& CertErrors::operator=(CertErrors&&) = default;
|
||||
CertErrors::~CertErrors() = default;
|
||||
|
||||
void CertErrors::Add(CertError::Severity severity,
|
||||
CertErrorId id,
|
||||
std::unique_ptr<CertErrorParams> params) {
|
||||
nodes_.emplace_back(severity, id, std::move(params));
|
||||
}
|
||||
|
||||
void CertErrors::AddError(CertErrorId id,
|
||||
std::unique_ptr<CertErrorParams> params) {
|
||||
Add(CertError::SEVERITY_HIGH, id, std::move(params));
|
||||
}
|
||||
|
||||
void CertErrors::AddError(CertErrorId id) {
|
||||
AddError(id, nullptr);
|
||||
}
|
||||
|
||||
void CertErrors::AddWarning(CertErrorId id,
|
||||
std::unique_ptr<CertErrorParams> params) {
|
||||
Add(CertError::SEVERITY_WARNING, id, std::move(params));
|
||||
}
|
||||
|
||||
void CertErrors::AddWarning(CertErrorId id) {
|
||||
AddWarning(id, nullptr);
|
||||
}
|
||||
|
||||
std::string CertErrors::ToDebugString() const {
|
||||
std::string result;
|
||||
for (const CertError& node : nodes_)
|
||||
result += node.ToDebugString();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CertErrors::ContainsError(CertErrorId id) const {
|
||||
for (const CertError& node : nodes_) {
|
||||
if (node.id == id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CertErrors::ContainsAnyErrorWithSeverity(
|
||||
CertError::Severity severity) const {
|
||||
for (const CertError& node : nodes_) {
|
||||
if (node.severity == severity)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CertPathErrors::CertPathErrors() = default;
|
||||
|
||||
CertPathErrors::CertPathErrors(CertPathErrors&& other) = default;
|
||||
CertPathErrors& CertPathErrors::operator=(CertPathErrors&&) = default;
|
||||
|
||||
CertPathErrors::~CertPathErrors() = default;
|
||||
|
||||
CertErrors* CertPathErrors::GetErrorsForCert(size_t cert_index) {
|
||||
if (cert_index >= cert_errors_.size())
|
||||
cert_errors_.resize(cert_index + 1);
|
||||
return &cert_errors_[cert_index];
|
||||
}
|
||||
|
||||
const CertErrors* CertPathErrors::GetErrorsForCert(size_t cert_index) const {
|
||||
if (cert_index >= cert_errors_.size())
|
||||
return nullptr;
|
||||
return &cert_errors_[cert_index];
|
||||
}
|
||||
|
||||
CertErrors* CertPathErrors::GetOtherErrors() {
|
||||
return &other_errors_;
|
||||
}
|
||||
|
||||
bool CertPathErrors::ContainsError(CertErrorId id) const {
|
||||
for (const CertErrors& errors : cert_errors_) {
|
||||
if (errors.ContainsError(id))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (other_errors_.ContainsError(id))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CertPathErrors::ContainsAnyErrorWithSeverity(
|
||||
CertError::Severity severity) const {
|
||||
for (const CertErrors& errors : cert_errors_) {
|
||||
if (errors.ContainsAnyErrorWithSeverity(severity))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (other_errors_.ContainsAnyErrorWithSeverity(severity))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string CertPathErrors::ToDebugString(
|
||||
const ParsedCertificateList& certs) const {
|
||||
std::ostringstream result;
|
||||
|
||||
for (size_t i = 0; i < cert_errors_.size(); ++i) {
|
||||
// Pretty print the current CertErrors. If there were no errors/warnings,
|
||||
// then continue.
|
||||
const CertErrors& errors = cert_errors_[i];
|
||||
std::string cert_errors_string = errors.ToDebugString();
|
||||
if (cert_errors_string.empty())
|
||||
continue;
|
||||
|
||||
// Add a header that identifies which certificate this CertErrors pertains
|
||||
// to.
|
||||
std::string cert_name_debug_str;
|
||||
if (i < certs.size() && certs[i]) {
|
||||
RDNSequence subject;
|
||||
if (ParseName(certs[i]->tbs().subject_tlv, &subject) &&
|
||||
ConvertToRFC2253(subject, &cert_name_debug_str)) {
|
||||
cert_name_debug_str = " (" + cert_name_debug_str + ")";
|
||||
}
|
||||
}
|
||||
result << "----- Certificate i=" << i << cert_name_debug_str << " -----\n";
|
||||
result << cert_errors_string << "\n";
|
||||
}
|
||||
|
||||
// Print any other errors that aren't associated with a particular certificate
|
||||
// in the chain.
|
||||
std::string other_errors = other_errors_.ToDebugString();
|
||||
if (!other_errors.empty()) {
|
||||
result << "----- Other errors (not certificate specific) -----\n";
|
||||
result << other_errors << "\n";
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
} // namespace net
|
168
pki/cert_errors.h
Normal file
168
pki/cert_errors.h
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ----------------------------
|
||||
// Overview of error design
|
||||
// ----------------------------
|
||||
#include "fillins/openssl_util.h"
|
||||
//
|
||||
// Certificate path building/validation/parsing may emit a sequence of errors
|
||||
// and warnings.
|
||||
//
|
||||
// Each individual error/warning entry (CertError) is comprised of:
|
||||
//
|
||||
// * A unique identifier.
|
||||
//
|
||||
// This serves similarly to an error code, and is used to query if a
|
||||
// particular error/warning occurred.
|
||||
//
|
||||
// * [optional] A parameters object.
|
||||
//
|
||||
// Nodes may attach a heap-allocated subclass of CertErrorParams to carry
|
||||
// extra information that is used when reporting the error. For instance
|
||||
// a parsing error may describe where in the DER the failure happened, or
|
||||
// what the unexpected value was.
|
||||
//
|
||||
// A collection of errors is represented by the CertErrors object. This may be
|
||||
// used to group errors that have a common context, such as all the
|
||||
// errors/warnings that apply to a specific certificate.
|
||||
//
|
||||
// Lastly, CertPathErrors composes multiple CertErrors -- one for each
|
||||
// certificate in the verified chain.
|
||||
//
|
||||
// ----------------------------
|
||||
// Defining new errors
|
||||
// ----------------------------
|
||||
//
|
||||
// The error IDs are extensible and do not need to be centrally defined.
|
||||
//
|
||||
// To define a new error use the macro DEFINE_CERT_ERROR_ID() in a .cc file.
|
||||
// If consumers are to be able to query for this error then the symbol should
|
||||
// also be exposed in a header file.
|
||||
//
|
||||
// Error IDs are in truth string literals, whose pointer value will be unique
|
||||
// per process.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_ERRORS_H_
|
||||
#define BSSL_PKI_CERT_ERRORS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "cert_error_id.h"
|
||||
#include "parsed_certificate.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
class CertErrorParams;
|
||||
|
||||
// CertError represents either an error or a warning.
|
||||
struct OPENSSL_EXPORT CertError {
|
||||
enum Severity {
|
||||
SEVERITY_HIGH,
|
||||
SEVERITY_WARNING,
|
||||
};
|
||||
|
||||
CertError();
|
||||
CertError(Severity severity,
|
||||
CertErrorId id,
|
||||
std::unique_ptr<CertErrorParams> params);
|
||||
CertError(CertError&& other);
|
||||
CertError& operator=(CertError&&);
|
||||
~CertError();
|
||||
|
||||
// Pretty-prints the error and its parameters.
|
||||
std::string ToDebugString() const;
|
||||
|
||||
Severity severity;
|
||||
CertErrorId id;
|
||||
std::unique_ptr<CertErrorParams> params;
|
||||
};
|
||||
|
||||
// CertErrors is a collection of CertError, along with convenience methods to
|
||||
// add and inspect errors.
|
||||
class OPENSSL_EXPORT CertErrors {
|
||||
public:
|
||||
CertErrors();
|
||||
CertErrors(CertErrors&& other);
|
||||
CertErrors& operator=(CertErrors&&);
|
||||
~CertErrors();
|
||||
|
||||
// Adds an error/warning. |params| may be null.
|
||||
void Add(CertError::Severity severity,
|
||||
CertErrorId id,
|
||||
std::unique_ptr<CertErrorParams> params);
|
||||
|
||||
// Adds a high severity error.
|
||||
void AddError(CertErrorId id, std::unique_ptr<CertErrorParams> params);
|
||||
void AddError(CertErrorId id);
|
||||
|
||||
// Adds a low severity error.
|
||||
void AddWarning(CertErrorId id, std::unique_ptr<CertErrorParams> params);
|
||||
void AddWarning(CertErrorId id);
|
||||
|
||||
// Dumps a textual representation of the errors for debugging purposes.
|
||||
std::string ToDebugString() const;
|
||||
|
||||
// Returns true if the error |id| was added to this CertErrors (of any
|
||||
// severity).
|
||||
bool ContainsError(CertErrorId id) const;
|
||||
|
||||
// Returns true if this contains any errors of the given severity level.
|
||||
bool ContainsAnyErrorWithSeverity(CertError::Severity severity) const;
|
||||
|
||||
private:
|
||||
std::vector<CertError> nodes_;
|
||||
};
|
||||
|
||||
// CertPathErrors is a collection of CertErrors, to group errors into different
|
||||
// buckets for different certificates. The "index" should correspond with that
|
||||
// of the certificate relative to its chain.
|
||||
class OPENSSL_EXPORT CertPathErrors {
|
||||
public:
|
||||
CertPathErrors();
|
||||
CertPathErrors(CertPathErrors&& other);
|
||||
CertPathErrors& operator=(CertPathErrors&&);
|
||||
~CertPathErrors();
|
||||
|
||||
// Gets a bucket to put errors in for |cert_index|. This will lookup and
|
||||
// return the existing error bucket if one exists, or create a new one for the
|
||||
// specified index. It is expected that |cert_index| is the corresponding
|
||||
// index in a certificate chain (with 0 being the target).
|
||||
CertErrors* GetErrorsForCert(size_t cert_index);
|
||||
|
||||
// Const version of the above, with the difference that if there is no
|
||||
// existing bucket for |cert_index| returns nullptr rather than lazyily
|
||||
// creating one.
|
||||
const CertErrors* GetErrorsForCert(size_t cert_index) const;
|
||||
|
||||
// Returns a bucket to put errors that are not associated with a particular
|
||||
// certificate.
|
||||
CertErrors* GetOtherErrors();
|
||||
|
||||
// Returns true if CertPathErrors contains the specified error (of any
|
||||
// severity).
|
||||
bool ContainsError(CertErrorId id) const;
|
||||
|
||||
// Returns true if this contains any errors of the given severity level.
|
||||
bool ContainsAnyErrorWithSeverity(CertError::Severity severity) const;
|
||||
|
||||
// Shortcut for ContainsAnyErrorWithSeverity(CertError::SEVERITY_HIGH).
|
||||
bool ContainsHighSeverityErrors() const {
|
||||
return ContainsAnyErrorWithSeverity(CertError::SEVERITY_HIGH);
|
||||
}
|
||||
|
||||
// Pretty-prints all the errors in the CertPathErrors. If there were no
|
||||
// errors/warnings, returns an empty string.
|
||||
std::string ToDebugString(const ParsedCertificateList& certs) const;
|
||||
|
||||
private:
|
||||
std::vector<CertErrors> cert_errors_;
|
||||
CertErrors other_errors_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_ERRORS_H_
|
70
pki/cert_issuer_source.h
Normal file
70
pki/cert_issuer_source.h
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_ISSUER_SOURCE_H_
|
||||
#define BSSL_PKI_CERT_ISSUER_SOURCE_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
#include "parsed_certificate.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// Interface for looking up issuers of a certificate during path building.
|
||||
// Provides a synchronous and asynchronous method for retrieving issuers, so the
|
||||
// path builder can try to complete synchronously first. The caller is expected
|
||||
// to call SyncGetIssuersOf first, see if it can make progress with those
|
||||
// results, and if not, then fall back to calling AsyncGetIssuersOf.
|
||||
// An implementations may choose to return results from either one of the Get
|
||||
// methods, or from both.
|
||||
class OPENSSL_EXPORT CertIssuerSource {
|
||||
public:
|
||||
class OPENSSL_EXPORT Request {
|
||||
public:
|
||||
Request() = default;
|
||||
|
||||
Request(const Request&) = delete;
|
||||
Request& operator=(const Request&) = delete;
|
||||
|
||||
// Destruction of the Request cancels it.
|
||||
virtual ~Request() = default;
|
||||
|
||||
// Retrieves issuers and appends them to |issuers|.
|
||||
//
|
||||
// GetNext should be called again to retrieve any remaining issuers.
|
||||
//
|
||||
// If no issuers are left then |issuers| will not be modified. This
|
||||
// indicates that the issuers have been exhausted and GetNext() should
|
||||
// not be called again.
|
||||
virtual void GetNext(ParsedCertificateList* issuers,
|
||||
void* debug_data) = 0;
|
||||
};
|
||||
|
||||
virtual ~CertIssuerSource() = default;
|
||||
|
||||
// Finds certificates whose Subject matches |cert|'s Issuer.
|
||||
// Matches are appended to |issuers|. Any existing contents of |issuers| will
|
||||
// not be modified. If the implementation does not support synchronous
|
||||
// lookups, or if there are no matches, |issuers| is not modified.
|
||||
virtual void SyncGetIssuersOf(const ParsedCertificate* cert,
|
||||
ParsedCertificateList* issuers) = 0;
|
||||
|
||||
// Finds certificates whose Subject matches |cert|'s Issuer.
|
||||
// If the implementation does not support asynchronous lookups or can
|
||||
// determine synchronously that it would return no results, |*out_req|
|
||||
// will be set to nullptr.
|
||||
//
|
||||
// Otherwise a request is started and saved to |out_req|. The results can be
|
||||
// read through the Request interface.
|
||||
virtual void AsyncGetIssuersOf(const ParsedCertificate* cert,
|
||||
std::unique_ptr<Request>* out_req) = 0;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_ISSUER_SOURCE_H_
|
37
pki/cert_issuer_source_static.cc
Normal file
37
pki/cert_issuer_source_static.cc
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cert_issuer_source_static.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
CertIssuerSourceStatic::CertIssuerSourceStatic() = default;
|
||||
CertIssuerSourceStatic::~CertIssuerSourceStatic() = default;
|
||||
|
||||
void CertIssuerSourceStatic::AddCert(
|
||||
std::shared_ptr<const ParsedCertificate> cert) {
|
||||
intermediates_.insert(std::make_pair(
|
||||
cert->normalized_subject().AsStringView(), std::move(cert)));
|
||||
}
|
||||
|
||||
void CertIssuerSourceStatic::Clear() {
|
||||
intermediates_.clear();
|
||||
}
|
||||
|
||||
void CertIssuerSourceStatic::SyncGetIssuersOf(const ParsedCertificate* cert,
|
||||
ParsedCertificateList* issuers) {
|
||||
auto range =
|
||||
intermediates_.equal_range(cert->normalized_issuer().AsStringView());
|
||||
for (auto it = range.first; it != range.second; ++it)
|
||||
issuers->push_back(it->second);
|
||||
}
|
||||
|
||||
void CertIssuerSourceStatic::AsyncGetIssuersOf(
|
||||
const ParsedCertificate* cert,
|
||||
std::unique_ptr<Request>* out_req) {
|
||||
// CertIssuerSourceStatic never returns asynchronous results.
|
||||
out_req->reset();
|
||||
}
|
||||
|
||||
} // namespace net
|
51
pki/cert_issuer_source_static.h
Normal file
51
pki/cert_issuer_source_static.h
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_ISSUER_SOURCE_STATIC_H_
|
||||
#define BSSL_PKI_CERT_ISSUER_SOURCE_STATIC_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
#include "cert_issuer_source.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// Synchronously returns issuers from a pre-supplied set.
|
||||
class OPENSSL_EXPORT CertIssuerSourceStatic : public CertIssuerSource {
|
||||
public:
|
||||
CertIssuerSourceStatic();
|
||||
|
||||
CertIssuerSourceStatic(const CertIssuerSourceStatic&) = delete;
|
||||
CertIssuerSourceStatic& operator=(const CertIssuerSourceStatic&) = delete;
|
||||
|
||||
~CertIssuerSourceStatic() override;
|
||||
|
||||
// Adds |cert| to the set of certificates that this CertIssuerSource will
|
||||
// provide.
|
||||
void AddCert(std::shared_ptr<const ParsedCertificate> cert);
|
||||
|
||||
// Clears the set of certificates.
|
||||
void Clear();
|
||||
|
||||
size_t size() const { return intermediates_.size(); }
|
||||
|
||||
// CertIssuerSource implementation:
|
||||
void SyncGetIssuersOf(const ParsedCertificate* cert,
|
||||
ParsedCertificateList* issuers) override;
|
||||
void AsyncGetIssuersOf(const ParsedCertificate* cert,
|
||||
std::unique_ptr<Request>* out_req) override;
|
||||
|
||||
private:
|
||||
// The certificates that the CertIssuerSourceStatic can return, keyed on the
|
||||
// normalized subject value.
|
||||
std::unordered_multimap<std::string_view,
|
||||
std::shared_ptr<const ParsedCertificate>>
|
||||
intermediates_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_ISSUER_SOURCE_STATIC_H_
|
40
pki/cert_issuer_source_static_unittest.cc
Normal file
40
pki/cert_issuer_source_static_unittest.cc
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cert_issuer_source_static.h"
|
||||
|
||||
#include "cert_issuer_source_sync_unittest.h"
|
||||
#include "parsed_certificate.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
class CertIssuerSourceStaticTestDelegate {
|
||||
public:
|
||||
void AddCert(std::shared_ptr<const ParsedCertificate> cert) {
|
||||
source_.AddCert(std::move(cert));
|
||||
}
|
||||
|
||||
CertIssuerSource& source() { return source_; }
|
||||
|
||||
protected:
|
||||
CertIssuerSourceStatic source_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(CertIssuerSourceStaticTest,
|
||||
CertIssuerSourceSyncTest,
|
||||
CertIssuerSourceStaticTestDelegate);
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(CertIssuerSourceStaticNormalizationTest,
|
||||
CertIssuerSourceSyncNormalizationTest,
|
||||
CertIssuerSourceStaticTestDelegate);
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
|
||||
CertIssuerSourceSyncNotNormalizedTest);
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace net
|
216
pki/cert_issuer_source_sync_unittest.h
Normal file
216
pki/cert_issuer_source_sync_unittest.h
Normal file
@ -0,0 +1,216 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_ISSUER_SOURCE_SYNC_UNITTEST_H_
|
||||
#define BSSL_PKI_CERT_ISSUER_SOURCE_SYNC_UNITTEST_H_
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "cert_issuer_source.h"
|
||||
#include "test_helpers.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/pool.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
::testing::AssertionResult ReadTestPem(const std::string& file_name,
|
||||
const std::string& block_name,
|
||||
std::string* result) {
|
||||
const PemBlockMapping mappings[] = {
|
||||
{block_name.c_str(), result},
|
||||
};
|
||||
|
||||
return ReadTestDataFromPemFile(file_name, mappings);
|
||||
}
|
||||
|
||||
::testing::AssertionResult ReadTestCert(
|
||||
const std::string& file_name,
|
||||
std::shared_ptr<const ParsedCertificate>* result) {
|
||||
std::string der;
|
||||
::testing::AssertionResult r =
|
||||
ReadTestPem("testdata/cert_issuer_source_static_unittest/" + file_name,
|
||||
"CERTIFICATE", &der);
|
||||
if (!r)
|
||||
return r;
|
||||
CertErrors errors;
|
||||
*result = ParsedCertificate::Create(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
|
||||
reinterpret_cast<const uint8_t*>(der.data()), der.size(), nullptr)),
|
||||
{}, &errors);
|
||||
if (!*result) {
|
||||
return ::testing::AssertionFailure()
|
||||
<< "ParsedCertificate::Create() failed:\n"
|
||||
<< errors.ToDebugString();
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename TestDelegate>
|
||||
class CertIssuerSourceSyncTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(ReadTestCert("root.pem", &root_));
|
||||
ASSERT_TRUE(ReadTestCert("i1_1.pem", &i1_1_));
|
||||
ASSERT_TRUE(ReadTestCert("i1_2.pem", &i1_2_));
|
||||
ASSERT_TRUE(ReadTestCert("i2.pem", &i2_));
|
||||
ASSERT_TRUE(ReadTestCert("i3_1.pem", &i3_1_));
|
||||
ASSERT_TRUE(ReadTestCert("i3_2.pem", &i3_2_));
|
||||
ASSERT_TRUE(ReadTestCert("c1.pem", &c1_));
|
||||
ASSERT_TRUE(ReadTestCert("c2.pem", &c2_));
|
||||
ASSERT_TRUE(ReadTestCert("d.pem", &d_));
|
||||
ASSERT_TRUE(ReadTestCert("e1.pem", &e1_));
|
||||
ASSERT_TRUE(ReadTestCert("e2.pem", &e2_));
|
||||
}
|
||||
|
||||
void AddCert(std::shared_ptr<const ParsedCertificate> cert) {
|
||||
delegate_.AddCert(std::move(cert));
|
||||
}
|
||||
|
||||
void AddAllCerts() {
|
||||
AddCert(root_);
|
||||
AddCert(i1_1_);
|
||||
AddCert(i1_2_);
|
||||
AddCert(i2_);
|
||||
AddCert(i3_1_);
|
||||
AddCert(i3_2_);
|
||||
AddCert(c1_);
|
||||
AddCert(c2_);
|
||||
AddCert(d_);
|
||||
AddCert(e1_);
|
||||
AddCert(e2_);
|
||||
}
|
||||
|
||||
CertIssuerSource& source() { return delegate_.source(); }
|
||||
|
||||
protected:
|
||||
bool IssuersMatch(std::shared_ptr<const ParsedCertificate> cert,
|
||||
ParsedCertificateList expected_matches) {
|
||||
ParsedCertificateList matches;
|
||||
source().SyncGetIssuersOf(cert.get(), &matches);
|
||||
|
||||
std::vector<der::Input> der_result_matches;
|
||||
for (const auto& it : matches)
|
||||
der_result_matches.push_back(it->der_cert());
|
||||
std::sort(der_result_matches.begin(), der_result_matches.end());
|
||||
|
||||
std::vector<der::Input> der_expected_matches;
|
||||
for (const auto& it : expected_matches)
|
||||
der_expected_matches.push_back(it->der_cert());
|
||||
std::sort(der_expected_matches.begin(), der_expected_matches.end());
|
||||
|
||||
if (der_expected_matches == der_result_matches)
|
||||
return true;
|
||||
|
||||
// Print some extra information for debugging.
|
||||
EXPECT_EQ(der_expected_matches, der_result_matches);
|
||||
return false;
|
||||
}
|
||||
|
||||
TestDelegate delegate_;
|
||||
std::shared_ptr<const ParsedCertificate> root_;
|
||||
std::shared_ptr<const ParsedCertificate> i1_1_;
|
||||
std::shared_ptr<const ParsedCertificate> i1_2_;
|
||||
std::shared_ptr<const ParsedCertificate> i2_;
|
||||
std::shared_ptr<const ParsedCertificate> i3_1_;
|
||||
std::shared_ptr<const ParsedCertificate> i3_2_;
|
||||
std::shared_ptr<const ParsedCertificate> c1_;
|
||||
std::shared_ptr<const ParsedCertificate> c2_;
|
||||
std::shared_ptr<const ParsedCertificate> d_;
|
||||
std::shared_ptr<const ParsedCertificate> e1_;
|
||||
std::shared_ptr<const ParsedCertificate> e2_;
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE_P(CertIssuerSourceSyncTest);
|
||||
|
||||
TYPED_TEST_P(CertIssuerSourceSyncTest, NoMatch) {
|
||||
this->AddCert(this->root_);
|
||||
|
||||
EXPECT_TRUE(this->IssuersMatch(this->c1_, ParsedCertificateList()));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CertIssuerSourceSyncTest, OneMatch) {
|
||||
this->AddAllCerts();
|
||||
|
||||
EXPECT_TRUE(this->IssuersMatch(this->i1_1_, {this->root_}));
|
||||
EXPECT_TRUE(this->IssuersMatch(this->d_, {this->i2_}));
|
||||
}
|
||||
|
||||
TYPED_TEST_P(CertIssuerSourceSyncTest, MultipleMatches) {
|
||||
this->AddAllCerts();
|
||||
|
||||
EXPECT_TRUE(this->IssuersMatch(this->e1_, {this->i3_1_, this->i3_2_}));
|
||||
EXPECT_TRUE(this->IssuersMatch(this->e2_, {this->i3_1_, this->i3_2_}));
|
||||
}
|
||||
|
||||
// Searching for the issuer of a self-issued cert returns the same cert if it
|
||||
// happens to be in the CertIssuerSourceStatic.
|
||||
// Conceptually this makes sense, though probably not very useful in practice.
|
||||
// Doesn't hurt anything though.
|
||||
TYPED_TEST_P(CertIssuerSourceSyncTest, SelfIssued) {
|
||||
this->AddAllCerts();
|
||||
|
||||
EXPECT_TRUE(this->IssuersMatch(this->root_, {this->root_}));
|
||||
}
|
||||
|
||||
// CertIssuerSourceStatic never returns results asynchronously.
|
||||
TYPED_TEST_P(CertIssuerSourceSyncTest, IsNotAsync) {
|
||||
this->AddCert(this->i1_1_);
|
||||
std::unique_ptr<CertIssuerSource::Request> request;
|
||||
this->source().AsyncGetIssuersOf(this->c1_.get(), &request);
|
||||
EXPECT_EQ(nullptr, request);
|
||||
}
|
||||
|
||||
// These are all the tests that should have the same result with or without
|
||||
// normalization.
|
||||
REGISTER_TYPED_TEST_SUITE_P(CertIssuerSourceSyncTest,
|
||||
NoMatch,
|
||||
OneMatch,
|
||||
MultipleMatches,
|
||||
SelfIssued,
|
||||
IsNotAsync);
|
||||
|
||||
template <typename TestDelegate>
|
||||
class CertIssuerSourceSyncNormalizationTest
|
||||
: public CertIssuerSourceSyncTest<TestDelegate> {};
|
||||
TYPED_TEST_SUITE_P(CertIssuerSourceSyncNormalizationTest);
|
||||
|
||||
TYPED_TEST_P(CertIssuerSourceSyncNormalizationTest,
|
||||
MultipleMatchesAfterNormalization) {
|
||||
this->AddAllCerts();
|
||||
|
||||
EXPECT_TRUE(this->IssuersMatch(this->c1_, {this->i1_1_, this->i1_2_}));
|
||||
EXPECT_TRUE(this->IssuersMatch(this->c2_, {this->i1_1_, this->i1_2_}));
|
||||
}
|
||||
|
||||
// These tests require (utf8) normalization.
|
||||
REGISTER_TYPED_TEST_SUITE_P(CertIssuerSourceSyncNormalizationTest,
|
||||
MultipleMatchesAfterNormalization);
|
||||
|
||||
template <typename TestDelegate>
|
||||
class CertIssuerSourceSyncNotNormalizedTest
|
||||
: public CertIssuerSourceSyncTest<TestDelegate> {};
|
||||
TYPED_TEST_SUITE_P(CertIssuerSourceSyncNotNormalizedTest);
|
||||
|
||||
TYPED_TEST_P(CertIssuerSourceSyncNotNormalizedTest,
|
||||
OneMatchWithoutNormalization) {
|
||||
this->AddAllCerts();
|
||||
|
||||
// Without normalization c1 and c2 should at least be able to find their
|
||||
// exact matching issuer. (c1 should match i1_1, and c2 should match i1_2.)
|
||||
EXPECT_TRUE(this->IssuersMatch(this->c1_, {this->i1_1_}));
|
||||
EXPECT_TRUE(this->IssuersMatch(this->c2_, {this->i1_2_}));
|
||||
}
|
||||
|
||||
// These tests are for implementations which do not do utf8 normalization.
|
||||
REGISTER_TYPED_TEST_SUITE_P(CertIssuerSourceSyncNotNormalizedTest,
|
||||
OneMatchWithoutNormalization);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_ISSUER_SOURCE_SYNC_UNITTEST_H_
|
97
pki/cert_net_fetcher.h
Normal file
97
pki/cert_net_fetcher.h
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_NET_FETCHER_H_
|
||||
#define BSSL_PKI_CERT_NET_FETCHER_H_
|
||||
|
||||
#include "webutil/url/url.h"
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <memory>
|
||||
#include "fillins/net_errors.h"
|
||||
|
||||
|
||||
|
||||
class URL;
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// CertNetFetcher is a synchronous interface for fetching AIA URLs and CRL
|
||||
// URLs. It is shared between a caller thread (which starts and waits for
|
||||
// fetches), and a network thread (which does the actual fetches). It can be
|
||||
// shutdown from the network thread to cancel outstanding requests.
|
||||
//
|
||||
// A Request object is returned when starting a fetch. The consumer can
|
||||
// use this as a handle for aborting the request (by freeing it), or reading
|
||||
// the result of the request (WaitForResult)
|
||||
class OPENSSL_EXPORT CertNetFetcher
|
||||
{
|
||||
public:
|
||||
class Request {
|
||||
public:
|
||||
virtual ~Request() = default;
|
||||
|
||||
// WaitForResult() can be called at most once.
|
||||
//
|
||||
// It will block and wait for the (network) request to complete, and
|
||||
// then write the result into the provided out-parameters.
|
||||
virtual void WaitForResult(Error* error, std::vector<uint8_t>* bytes) = 0;
|
||||
};
|
||||
|
||||
// This value can be used in place of timeout or max size limits.
|
||||
enum { DEFAULT = -1 };
|
||||
|
||||
CertNetFetcher() = default;
|
||||
|
||||
CertNetFetcher(const CertNetFetcher&) = delete;
|
||||
CertNetFetcher& operator=(const CertNetFetcher&) = delete;
|
||||
|
||||
// Shuts down the CertNetFetcher and cancels outstanding network requests. It
|
||||
// is not guaranteed that any outstanding or subsequent
|
||||
// Request::WaitForResult() calls will be completed. Shutdown() must be called
|
||||
// from the network thread. It can be called more than once, but must be
|
||||
// called before the CertNetFetcher is destroyed.
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
// The Fetch*() methods start a request which can be cancelled by
|
||||
// deleting the returned Request. Here is the meaning of the common
|
||||
// parameters:
|
||||
//
|
||||
// * url -- The http:// URL to fetch.
|
||||
// * timeout_seconds -- The maximum allowed duration for the fetch job. If
|
||||
// this delay is exceeded then the request will fail. To use a default
|
||||
// timeout pass DEFAULT.
|
||||
// * max_response_bytes -- The maximum size of the response body. If this
|
||||
// size is exceeded then the request will fail. To use a default timeout
|
||||
// pass DEFAULT.
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<Request> FetchCaIssuers(
|
||||
const URL& url,
|
||||
int timeout_milliseconds,
|
||||
int max_response_bytes) = 0;
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<Request> FetchCrl(
|
||||
const URL& url,
|
||||
int timeout_milliseconds,
|
||||
int max_response_bytes) = 0;
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<Request> FetchOcsp(
|
||||
const URL& url,
|
||||
int timeout_milliseconds,
|
||||
int max_response_bytes) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~CertNetFetcher() = default;
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_NET_FETCHER_H_
|
49
pki/cert_status_flags.h
Normal file
49
pki/cert_status_flags.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2012 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERT_STATUS_FLAGS_H_
|
||||
#define BSSL_PKI_CERT_STATUS_FLAGS_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// Bitmask of status flags of a certificate, representing any errors, as well as
|
||||
// other non-error status information such as whether the certificate is EV.
|
||||
typedef uint32_t CertStatus;
|
||||
|
||||
// NOTE: Because these names have appeared in bug reports, we preserve them as
|
||||
// MACRO_STYLE for continuity, instead of renaming them to kConstantStyle as
|
||||
// befits most static consts.
|
||||
#define CERT_STATUS_FLAG(label, value) \
|
||||
CertStatus static const CERT_STATUS_##label = value;
|
||||
#include "cert_status_flags_list.h"
|
||||
#undef CERT_STATUS_FLAG
|
||||
|
||||
static const CertStatus CERT_STATUS_ALL_ERRORS = 0xFF00FFFF;
|
||||
|
||||
// Returns true if the specified cert status has an error set.
|
||||
inline bool IsCertStatusError(CertStatus status) {
|
||||
return (CERT_STATUS_ALL_ERRORS & status) != 0;
|
||||
}
|
||||
|
||||
// Maps a network error code to the equivalent certificate status flag. If
|
||||
// the error code is not a certificate error, it is mapped to 0.
|
||||
// Note: It is not safe to go net::CertStatus -> net::Error -> net::CertStatus,
|
||||
// as the CertStatus contains more information. Conversely, going from
|
||||
// net::Error -> net::CertStatus -> net::Error is not a lossy function, for the
|
||||
// same reason.
|
||||
// To avoid incorrect use, this is only exported for unittest helpers.
|
||||
OPENSSL_EXPORT CertStatus MapNetErrorToCertStatus(int error);
|
||||
|
||||
// Maps the most serious certificate error in the certificate status flags
|
||||
// to the equivalent network error code.
|
||||
OPENSSL_EXPORT int MapCertStatusToNetError(CertStatus cert_status);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERT_STATUS_FLAGS_H_
|
47
pki/cert_status_flags_list.h
Normal file
47
pki/cert_status_flags_list.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2014 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file intentionally does not have header guards, it's included
|
||||
// inside a macro to generate enum values. The following line silences a
|
||||
// presubmit warning that would otherwise be triggered by this:
|
||||
// no-include-guard-because-multiply-included
|
||||
// NOLINT(build/header_guard)
|
||||
|
||||
// This is the list of CertStatus flags and their values.
|
||||
//
|
||||
// Defines the values using a macro CERT_STATUS_FLAG,
|
||||
// so it can be expanded differently in some places
|
||||
|
||||
// The possible status bits for CertStatus.
|
||||
// Bits 0 to 15 are for errors.
|
||||
CERT_STATUS_FLAG(COMMON_NAME_INVALID, 1 << 0)
|
||||
CERT_STATUS_FLAG(DATE_INVALID, 1 << 1)
|
||||
CERT_STATUS_FLAG(AUTHORITY_INVALID, 1 << 2)
|
||||
// 1 << 3 is reserved for ERR_CERT_CONTAINS_ERRORS (not useful with WinHTTP).
|
||||
CERT_STATUS_FLAG(NO_REVOCATION_MECHANISM, 1 << 4)
|
||||
CERT_STATUS_FLAG(UNABLE_TO_CHECK_REVOCATION, 1 << 5)
|
||||
CERT_STATUS_FLAG(REVOKED, 1 << 6)
|
||||
CERT_STATUS_FLAG(INVALID, 1 << 7)
|
||||
CERT_STATUS_FLAG(WEAK_SIGNATURE_ALGORITHM, 1 << 8)
|
||||
// 1 << 9 was used for CERT_STATUS_NOT_IN_DNS
|
||||
CERT_STATUS_FLAG(NON_UNIQUE_NAME, 1 << 10)
|
||||
CERT_STATUS_FLAG(WEAK_KEY, 1 << 11)
|
||||
// 1 << 12 was used for CERT_STATUS_WEAK_DH_KEY
|
||||
CERT_STATUS_FLAG(PINNED_KEY_MISSING, 1 << 13)
|
||||
CERT_STATUS_FLAG(NAME_CONSTRAINT_VIOLATION, 1 << 14)
|
||||
CERT_STATUS_FLAG(VALIDITY_TOO_LONG, 1 << 15)
|
||||
|
||||
// Bits 16 to 23 are for non-error statuses.
|
||||
CERT_STATUS_FLAG(IS_EV, 1 << 16)
|
||||
CERT_STATUS_FLAG(REV_CHECKING_ENABLED, 1 << 17)
|
||||
// Bit 18 was CERT_STATUS_IS_DNSSEC
|
||||
CERT_STATUS_FLAG(SHA1_SIGNATURE_PRESENT, 1 << 19)
|
||||
CERT_STATUS_FLAG(CT_COMPLIANCE_FAILED, 1 << 20)
|
||||
CERT_STATUS_FLAG(KNOWN_INTERCEPTION_DETECTED, 1 << 21)
|
||||
|
||||
// Bits 24 - 31 are for errors.
|
||||
CERT_STATUS_FLAG(CERTIFICATE_TRANSPARENCY_REQUIRED, 1 << 24)
|
||||
CERT_STATUS_FLAG(SYMANTEC_LEGACY, 1 << 25)
|
||||
CERT_STATUS_FLAG(KNOWN_INTERCEPTION_BLOCKED, 1 << 26)
|
||||
// Bit 27 was CERT_STATUS_LEGACY_TLS.
|
427
pki/cert_verify_proc_blocklist.inc
Normal file
427
pki/cert_verify_proc_blocklist.inc
Normal file
@ -0,0 +1,427 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// The certificate(s) that were misissued, and which represent these SPKIs,
|
||||
// are stored within net/data/ssl/blocklist. Further details about the
|
||||
// rationale is documented in net/data/ssl/blocklist/README.md
|
||||
static constexpr uint8_t
|
||||
kSPKIBlockList[][SHA256_DIGEST_LENGTH] = {
|
||||
// 2740d956b1127b791aa1b3cc644a4dbedba76186a23638b95102351a834ea861.pem
|
||||
{0x04, 0xdd, 0xe9, 0xaa, 0x9a, 0x79, 0xf6, 0x14, 0x98, 0x68, 0x23,
|
||||
0x25, 0xfa, 0x08, 0x70, 0x27, 0x67, 0x07, 0xfb, 0x9c, 0xa9, 0x53,
|
||||
0x84, 0x12, 0x0b, 0x46, 0x89, 0x32, 0x68, 0x49, 0x4f, 0xc9},
|
||||
// 91e5cc32910686c5cac25c18cc805696c7b33868c280caf0c72844a2a8eb91e2.pem
|
||||
{0x0c, 0x43, 0xea, 0x8b, 0xcd, 0xe9, 0xfc, 0x3b, 0xca, 0x16, 0x56,
|
||||
0x64, 0xac, 0x82, 0x15, 0x56, 0x7e, 0x34, 0x89, 0xd5, 0x39, 0x3a,
|
||||
0x0c, 0x81, 0xe1, 0xa7, 0x91, 0x41, 0x99, 0x2e, 0x19, 0x53},
|
||||
// ead610e6e90b439f2ecb51628b0932620f6ef340bd843fca38d3181b8f4ba197.pem
|
||||
{0x12, 0x13, 0x23, 0x60, 0xa3, 0x3b, 0xfd, 0xc6, 0xc3, 0xbf, 0x7b,
|
||||
0x7f, 0xab, 0x26, 0xa1, 0x68, 0x48, 0x74, 0xe7, 0x2c, 0x12, 0x63,
|
||||
0xc1, 0xf5, 0xde, 0x56, 0x5b, 0xb4, 0x9e, 0xf0, 0x37, 0x53},
|
||||
// 4bf6bb839b03b72839329b4ea70bb1b2f0d07e014d9d24aa9cc596114702bee3.pem
|
||||
{0x12, 0x7d, 0xa2, 0x7a, 0x9e, 0x45, 0xf0, 0x82, 0x28, 0x0b, 0x31,
|
||||
0xbf, 0x1e, 0x56, 0x15, 0x20, 0x38, 0x9f, 0x96, 0x65, 0x90, 0x93,
|
||||
0xb2, 0x69, 0x7c, 0x40, 0xfe, 0x86, 0x00, 0x23, 0x6c, 0x8c},
|
||||
// 0f912fd7be760be25afbc56bdc09cd9e5dcc9c6f6a55a778aefcb6aa30e31554.pem
|
||||
{0x13, 0x0a, 0xd4, 0xe0, 0x63, 0x35, 0x21, 0x29, 0x05, 0x31, 0xb6,
|
||||
0x65, 0x1f, 0x57, 0x59, 0xb0, 0xbc, 0x7b, 0xc6, 0x56, 0x70, 0x9f,
|
||||
0xf8, 0xf3, 0x65, 0xc2, 0x14, 0x3b, 0x03, 0x89, 0xb6, 0xf6},
|
||||
// 91018fcd3e0dc73f48d011a123f604d846d66821c58304474f949d7449dd600a.pem
|
||||
{0x15, 0xe7, 0xae, 0x40, 0xcc, 0x4b, 0x3f, 0x72, 0x22, 0xa5, 0xa6,
|
||||
0xfe, 0x3e, 0x7d, 0xc4, 0x7f, 0x6e, 0x46, 0xee, 0x9a, 0x22, 0x51,
|
||||
0x83, 0x9d, 0xb2, 0x96, 0xd6, 0x2a, 0xda, 0x2a, 0x0d, 0xf7},
|
||||
// c7ba6567de93a798ae1faa791e712d378fae1f93c4397fea441bb7cbe6fd5995.pem
|
||||
{0x15, 0x28, 0x39, 0x7d, 0xa2, 0x12, 0x89, 0x0a, 0x83, 0x0b, 0x0b,
|
||||
0x95, 0xa5, 0x99, 0x68, 0xce, 0xf2, 0x34, 0x77, 0x37, 0x79, 0xdf,
|
||||
0x51, 0x81, 0xcf, 0x10, 0xfa, 0x64, 0x75, 0x34, 0xbb, 0x65},
|
||||
// 1af56c98ff043ef92bebff54cebb4dd67a25ba956c817f3e6dd3c1e52eb584c1.key
|
||||
{0x1a, 0xf5, 0x6c, 0x98, 0xff, 0x04, 0x3e, 0xf9, 0x2b, 0xeb, 0xff,
|
||||
0x54, 0xce, 0xbb, 0x4d, 0xd6, 0x7a, 0x25, 0xba, 0x95, 0x6c, 0x81,
|
||||
0x7f, 0x3e, 0x6d, 0xd3, 0xc1, 0xe5, 0x2e, 0xb5, 0x84, 0xc1},
|
||||
// e28393773da845a679f2080cc7fb44a3b7a1c3792cb7eb7729fdcb6a8d99aea7.pem
|
||||
{0x1f, 0x42, 0x24, 0xce, 0xc8, 0x4f, 0xc9, 0x9c, 0xed, 0x88, 0x1f,
|
||||
0xf6, 0xfc, 0xfd, 0x3e, 0x21, 0xf8, 0xc5, 0x19, 0xc5, 0x47, 0xaa,
|
||||
0x6a, 0x5d, 0xd3, 0xde, 0x24, 0x73, 0x02, 0xce, 0x50, 0xd1},
|
||||
// e54e9fc27e7350ff63a77764a40267b7e95ae5df3ed7df5336e8f8541356c845.pem
|
||||
{0x25, 0xda, 0x1a, 0xd5, 0x8b, 0xbf, 0xcf, 0xb2, 0x27, 0xd8, 0x72,
|
||||
0x3b, 0x18, 0x57, 0xd4, 0xc1, 0x8e, 0x7b, 0xaa, 0x74, 0x17, 0xb4,
|
||||
0xf9, 0xef, 0xf9, 0x36, 0x6b, 0x5e, 0x86, 0x9f, 0x8b, 0x39},
|
||||
// 159ca03a88897c8f13817a212629df84ce824709492b8c9adb8e5437d2fc72be.pem
|
||||
{0x2c, 0x99, 0x8e, 0x76, 0x11, 0x60, 0xc3, 0xb0, 0x6d, 0x82, 0xfa,
|
||||
0xa9, 0xfd, 0xc7, 0x54, 0x5d, 0x9b, 0xda, 0x9e, 0xb6, 0x03, 0x10,
|
||||
0xf9, 0x92, 0xaa, 0x51, 0x0a, 0x62, 0x80, 0xb7, 0x42, 0x45},
|
||||
// 82a4cedbc7f61ce5cb04482aa27ea3145bb0cea58ab63ba1931a1654bfbdbb4f.pem
|
||||
{0x2d, 0xc4, 0xcb, 0x59, 0x1f, 0x7e, 0xf0, 0x66, 0x34, 0x41, 0x64,
|
||||
0x6b, 0xcf, 0x5c, 0x0e, 0x9d, 0xbc, 0xde, 0xd7, 0x7c, 0xa0, 0x29,
|
||||
0x45, 0x19, 0x3c, 0xef, 0xc6, 0xed, 0xb1, 0x74, 0x06, 0x14},
|
||||
// d0d672c2547d574ae055d9e78a993ddbcc74044c4253fbfaca573a67d368e1db.pem
|
||||
{0x30, 0xef, 0xe4, 0x13, 0x82, 0x47, 0x6c, 0x33, 0x80, 0xf0, 0x2f,
|
||||
0x7e, 0x23, 0xe6, 0x6b, 0xa2, 0xf8, 0x67, 0xb0, 0x59, 0xee, 0x1e,
|
||||
0xa6, 0x87, 0x96, 0xb4, 0x41, 0xb8, 0x5b, 0x5d, 0x12, 0x56},
|
||||
// 32ecc96f912f96d889e73088cd031c7ded2c651c805016157a23b6f32f798a3b.key
|
||||
{0x32, 0xec, 0xc9, 0x6f, 0x91, 0x2f, 0x96, 0xd8, 0x89, 0xe7, 0x30,
|
||||
0x88, 0xcd, 0x03, 0x1c, 0x7d, 0xed, 0x2c, 0x65, 0x1c, 0x80, 0x50,
|
||||
0x16, 0x15, 0x7a, 0x23, 0xb6, 0xf3, 0x2f, 0x79, 0x8a, 0x3b},
|
||||
// 4aefc3d39ef59e4d4b0304b20f53a8af2efb69edece66def74494abfc10a2d66.pem
|
||||
{0x36, 0xea, 0x96, 0x12, 0x8c, 0x89, 0x83, 0x9f, 0xb6, 0x21, 0xf8,
|
||||
0xad, 0x0e, 0x1e, 0xe0, 0xb9, 0xc2, 0x20, 0x6f, 0x62, 0xab, 0x7b,
|
||||
0x4d, 0xa2, 0xc6, 0x76, 0x58, 0x93, 0xc9, 0xb7, 0xce, 0xd2},
|
||||
// d487a56f83b07482e85e963394c1ecc2c9e51d0903ee946b02c301581ed99e16.pem
|
||||
{0x38, 0x1a, 0x3f, 0xc7, 0xa8, 0xb0, 0x82, 0xfa, 0x28, 0x61, 0x3a,
|
||||
0x4d, 0x07, 0xf2, 0xc7, 0x55, 0x3f, 0x4e, 0x19, 0x18, 0xee, 0x07,
|
||||
0xca, 0xa9, 0xe8, 0xb7, 0xce, 0xde, 0x5a, 0x9c, 0xa0, 0x6a},
|
||||
// 0ef7c54a3af101a2cfedb0c9f36fe8214d51a504fdc2ad1e243019cefd7d03c2.pem
|
||||
{0x38, 0x3e, 0x0e, 0x13, 0x7c, 0x37, 0xbf, 0xb9, 0xdb, 0x29, 0xf9,
|
||||
0xa8, 0xe4, 0x5e, 0x9f, 0xf8, 0xdd, 0x4c, 0x30, 0xe4, 0x40, 0xfe,
|
||||
0xc2, 0xac, 0xd3, 0xdb, 0xa7, 0xb6, 0xc7, 0x20, 0xb9, 0x93},
|
||||
// cb954e9d80a3e520ac71f1a84511657f2f309d172d0bb55e0ec2c236e74ff4b4.pem
|
||||
{0x39, 0x4c, 0xff, 0x58, 0x9e, 0x68, 0x93, 0x12, 0xcf, 0xc0, 0x71,
|
||||
0xee, 0x0b, 0xc1, 0x9f, 0xe4, 0xc6, 0x06, 0x21, 0x6c, 0xe5, 0x43,
|
||||
0x42, 0x9d, 0xe6, 0xdb, 0x62, 0xe4, 0x2d, 0xbb, 0x3b, 0xc1},
|
||||
// 42187727be39faf667aeb92bf0cc4e268f6e2ead2cefbec575bdc90430024f69.pem
|
||||
{0x3e, 0xdb, 0xd9, 0xac, 0xe6, 0x39, 0xba, 0x1a, 0x2d, 0x4a, 0xd0,
|
||||
0x47, 0x18, 0x71, 0x1f, 0xda, 0x23, 0xe8, 0x59, 0xb2, 0xfb, 0xf5,
|
||||
0xd1, 0x37, 0xd4, 0x24, 0x04, 0x5e, 0x79, 0x19, 0xdf, 0xb9},
|
||||
// 294f55ef3bd7244c6ff8a68ab797e9186ec27582751a791515e3292e48372d61.pem
|
||||
{0x45, 0x5b, 0x87, 0xe9, 0x6f, 0x1c, 0xea, 0x2f, 0x8b, 0x6d, 0xae,
|
||||
0x08, 0x08, 0xec, 0x24, 0x73, 0x8f, 0xd9, 0x2b, 0x7f, 0xd3, 0x06,
|
||||
0x75, 0x71, 0x98, 0xbf, 0x38, 0x9d, 0x75, 0x5c, 0x0b, 0x6c},
|
||||
// 3ab0fcc7287454c405863e3aa204fea8eb0c50a524d2a7e15524a830cd4ab0fe.pem
|
||||
{0x49, 0x0b, 0x6e, 0xc6, 0xbe, 0xb2, 0xd6, 0x03, 0x47, 0x20, 0xb5,
|
||||
0x14, 0x9b, 0x6b, 0x29, 0xcd, 0x35, 0x51, 0x59, 0x88, 0xcc, 0x16,
|
||||
0xaf, 0x85, 0x41, 0x48, 0xb0, 0x7b, 0x9b, 0x1f, 0x8a, 0x11},
|
||||
// b6fe9151402bad1c06d7e66db67a26aa7356f2e6c644dbcf9f98968ff632e1b7.pem
|
||||
{0x4b, 0xb8, 0xf3, 0x5b, 0xa1, 0xe1, 0x26, 0xf8, 0xdd, 0xe1, 0xb0,
|
||||
0xc4, 0x20, 0x62, 0x5e, 0xd8, 0x6d, 0xce, 0x61, 0xa7, 0xbd, 0xda,
|
||||
0xdb, 0xde, 0xa9, 0xab, 0xa5, 0x78, 0xff, 0x13, 0x14, 0x5e},
|
||||
// fa5a828c9a7e732692682e60b14c634309cbb2bb79eb12aef44318d853ee97e3.pem
|
||||
{0x4c, 0xdb, 0x06, 0x0f, 0x3c, 0xfe, 0x4c, 0x3d, 0x3f, 0x5e, 0x31,
|
||||
0xc3, 0x00, 0xfd, 0x68, 0xa9, 0x1e, 0x0d, 0x1e, 0x5f, 0x46, 0xb6,
|
||||
0x4e, 0x48, 0x95, 0xf2, 0x0e, 0x1b, 0x5c, 0xf8, 0x26, 0x9f},
|
||||
// ef3cb417fc8ebf6f97876c9e4ece39de1ea5fe649141d1028b7d11c0b2298ced.pem
|
||||
{0x4e, 0xad, 0xa9, 0xb5, 0x31, 0x1e, 0x71, 0x81, 0x99, 0xd9, 0x8e,
|
||||
0xa8, 0x2b, 0x95, 0x00, 0x5c, 0xba, 0x93, 0x19, 0x8a, 0xb1, 0xf9,
|
||||
0x7e, 0xfc, 0xbe, 0x8d, 0xc6, 0x20, 0x16, 0x28, 0xf8, 0xaf},
|
||||
// c1d80ce474a51128b77e794a98aa2d62a0225da3f419e5c7ed73dfbf660e7109.pem
|
||||
{0x4f, 0x71, 0x62, 0xb9, 0x74, 0x49, 0x1c, 0x98, 0x58, 0x5e, 0xc2,
|
||||
0x8f, 0xe7, 0x59, 0xaa, 0x00, 0xc3, 0x30, 0xd0, 0xb4, 0x65, 0x19,
|
||||
0x0a, 0x89, 0x6c, 0xc4, 0xb6, 0x16, 0x23, 0x18, 0x31, 0xfc},
|
||||
// 7abd72a323c9d179c722564f4e27a51dd4afd24006b38a40ce918b94960bcf18.pem
|
||||
{0x57, 0x80, 0x94, 0x46, 0xea, 0xf1, 0x14, 0x84, 0x38, 0x54, 0xfe,
|
||||
0x63, 0x6e, 0xd9, 0xbc, 0xb5, 0x52, 0xe3, 0xc6, 0x16, 0x66, 0x3b,
|
||||
0xc4, 0x4c, 0xc9, 0x5a, 0xcf, 0x56, 0x50, 0x01, 0x6d, 0x3e},
|
||||
// 817d4e05063d5942869c47d8504dc56a5208f7569c3d6d67f3457cfe921b3e29.pem
|
||||
{0x5c, 0x72, 0x2c, 0xb7, 0x0f, 0xb3, 0x11, 0xf2, 0x1e, 0x0d, 0xa0,
|
||||
0xe7, 0xd1, 0x2e, 0xbc, 0x8e, 0x05, 0xf6, 0x07, 0x96, 0xbc, 0x49,
|
||||
0xcf, 0x51, 0x18, 0x49, 0xd5, 0xbc, 0x62, 0x03, 0x03, 0x82},
|
||||
// 79f69a47cfd6c4b4ceae8030d04b49f6171d3b5d6c812f58d040e586f1cb3f14.pem
|
||||
// 933f7d8cda9f0d7c8bfd3c22bf4653f4161fd38ccdcf66b22e95a2f49c2650f8.pem
|
||||
// f8a5ff189fedbfe34e21103389a68340174439ad12974a4e8d4d784d1f3a0faa.pem
|
||||
{0x5e, 0x53, 0xf2, 0x64, 0x67, 0xf8, 0x94, 0xfd, 0xe5, 0x3b, 0x3f,
|
||||
0xa4, 0x06, 0xa4, 0x40, 0xcb, 0xb3, 0xb0, 0x76, 0xbb, 0x5b, 0x75,
|
||||
0x8f, 0xe4, 0x83, 0x4a, 0xd6, 0x65, 0x00, 0x20, 0x89, 0x07},
|
||||
// 2d11e736f0427fd6ba4b372755d34a0edd8d83f7e9e7f6c01b388c9b7afa850d.pem
|
||||
{0x6a, 0xdb, 0x8e, 0x3e, 0x05, 0x54, 0x60, 0x92, 0x2d, 0x15, 0x01,
|
||||
0xcb, 0x97, 0xf9, 0x4c, 0x6a, 0x02, 0xe3, 0x9c, 0x8f, 0x27, 0x74,
|
||||
0xca, 0x40, 0x88, 0x25, 0xb7, 0xb5, 0x83, 0x79, 0xdc, 0x14},
|
||||
// 2a33f5b48176523fd3c0d854f20093417175bfd498ef354cc7f38b54adabaf1a.pem
|
||||
{0x70, 0x7d, 0x36, 0x4e, 0x72, 0xae, 0x52, 0x14, 0x31, 0xdd, 0x95,
|
||||
0x38, 0x97, 0xf9, 0xc4, 0x84, 0x6d, 0x5b, 0x8c, 0x32, 0x42, 0x98,
|
||||
0xfe, 0x53, 0xfb, 0xd4, 0xad, 0xa1, 0xf2, 0xd1, 0x15, 0x7f},
|
||||
// f4a5984324de98bd979ef181a100cf940f2166173319a86a0d9d7c8fac3b0a8f.pem
|
||||
{0x71, 0x65, 0xe9, 0x91, 0xad, 0xe7, 0x91, 0x6d, 0x86, 0xb4, 0x66,
|
||||
0xab, 0xeb, 0xb6, 0xe4, 0x57, 0xca, 0x93, 0x1c, 0x80, 0x4e, 0x58,
|
||||
0xce, 0x1f, 0xba, 0xba, 0xe5, 0x09, 0x15, 0x6f, 0xfb, 0x43},
|
||||
// 3ae699d94e8febdacb86d4f90d40903333478e65e0655c432451197e33fa07f2.pem
|
||||
{0x78, 0x1a, 0x4c, 0xf2, 0xe9, 0x24, 0x52, 0xf3, 0xee, 0x01, 0xd0,
|
||||
0xc3, 0x81, 0xa4, 0x21, 0x4f, 0x39, 0x04, 0x16, 0x5c, 0x39, 0x0a,
|
||||
0xdb, 0xd6, 0x1f, 0xcd, 0x11, 0x24, 0x4e, 0x09, 0xb2, 0xdc},
|
||||
// 8b45da1c06f791eb0cabf26be588f5fb23165c2e614bf885562d0dce50b29b02.pem
|
||||
{0x7a, 0xed, 0xdd, 0xf3, 0x6b, 0x18, 0xf8, 0xac, 0xb7, 0x37, 0x9f,
|
||||
0xe1, 0xce, 0x18, 0x32, 0x12, 0xb2, 0x35, 0x0d, 0x07, 0x88, 0xab,
|
||||
0xe0, 0xe8, 0x24, 0x57, 0xbe, 0x9b, 0xad, 0xad, 0x6d, 0x54},
|
||||
// 5a885db19c01d912c5759388938cafbbdf031ab2d48e91ee15589b42971d039c.pem
|
||||
{0x7a, 0xfe, 0x4b, 0x07, 0x1a, 0x2f, 0x1f, 0x46, 0xf8, 0xba, 0x94,
|
||||
0x4a, 0x26, 0xd5, 0x84, 0xd5, 0x96, 0x0b, 0x92, 0xfb, 0x48, 0xc3,
|
||||
0xba, 0x1b, 0x7c, 0xab, 0x84, 0x90, 0x5f, 0x32, 0xaa, 0xcd},
|
||||
// c43807a64c51a3fbde5421011698013d8b46f4e315c46186dc23aea2670cd34f.pem
|
||||
{0x7c, 0xd2, 0x95, 0xb7, 0x55, 0x44, 0x80, 0x8a, 0xbd, 0x94, 0x09,
|
||||
0x46, 0x6f, 0x08, 0x37, 0xc5, 0xaa, 0xdc, 0x02, 0xe3, 0x3b, 0x61,
|
||||
0x50, 0xc6, 0x64, 0x4d, 0xe0, 0xa0, 0x96, 0x59, 0xf2, 0x3c},
|
||||
// f3bae5e9c0adbfbfb6dbf7e04e74be6ead3ca98a5604ffe591cea86c241848ec.pem
|
||||
{0x7d, 0x5e, 0x3f, 0x50, 0x50, 0x81, 0x97, 0xb9, 0xa4, 0x78, 0xb1,
|
||||
0x13, 0x40, 0xb7, 0xdc, 0xe2, 0x0a, 0x3c, 0x4d, 0xe4, 0x9c, 0x48,
|
||||
0xc9, 0xa2, 0x94, 0x15, 0x8a, 0x89, 0x5c, 0x44, 0xa2, 0x1b},
|
||||
// b8686723e415534bc0dbd16326f9486f85b0b0799bf6639334e61daae67f36cd.pem
|
||||
{0x7e, 0x70, 0x58, 0xea, 0x35, 0xad, 0x43, 0x59, 0x65, 0x41, 0x59,
|
||||
0x97, 0x3f, 0x56, 0x01, 0x87, 0xf1, 0x6d, 0x19, 0xc5, 0x14, 0xb9,
|
||||
0x39, 0xc5, 0x05, 0x56, 0x72, 0xd1, 0xd2, 0xa5, 0x18, 0xac},
|
||||
// 5e8e77aafdda2ba5ce442f27d8246650bbd6508befbeda35966a4dc7e6174edc.pem
|
||||
{0x87, 0xbf, 0xd8, 0xaf, 0xa3, 0xaf, 0x5b, 0x42, 0x9d, 0x09, 0xa9,
|
||||
0xaa, 0x54, 0xee, 0x61, 0x36, 0x4f, 0x5a, 0xe1, 0x11, 0x31, 0xe4,
|
||||
0x38, 0xfc, 0x41, 0x09, 0x53, 0x43, 0xcd, 0x16, 0xb1, 0x35},
|
||||
// 0c258a12a5674aef25f28ba7dcfaeceea348e541e6f5cc4ee63b71b361606ac3.pem
|
||||
{0x8a, 0x2a, 0xff, 0xbd, 0x1a, 0x1c, 0x5d, 0x1b, 0xdc, 0xcb, 0xb7,
|
||||
0xf5, 0x48, 0xba, 0x99, 0x5f, 0x96, 0x68, 0x06, 0xb3, 0xfd, 0x0c,
|
||||
0x3a, 0x00, 0xfa, 0xe2, 0xe5, 0x2f, 0x3c, 0x85, 0x39, 0x89},
|
||||
// 61c0fc2e38b5b6f9071b42cee54a9013d858b6697c68b460948551b3249576a1.pem
|
||||
{0x8e, 0x12, 0xd0, 0xcb, 0x3b, 0x7d, 0xf3, 0xea, 0x22, 0x57, 0x57,
|
||||
0x94, 0x89, 0xfd, 0x86, 0x58, 0xc9, 0x56, 0x03, 0xea, 0x6c, 0xf4,
|
||||
0xb7, 0x31, 0x63, 0xa4, 0x1e, 0xb7, 0xb7, 0xe9, 0x3f, 0xee},
|
||||
// ddd8ab9178c99cbd9685ea4ae66dc28bfdc9a5a8a166f7f69ad0b5042ad6eb28.pem
|
||||
{0x8f, 0x59, 0x1f, 0x7a, 0xa4, 0xdc, 0x3e, 0xfe, 0x94, 0x90, 0xc3,
|
||||
0x8a, 0x46, 0x92, 0xc9, 0x01, 0x1e, 0xd1, 0x28, 0xf1, 0xde, 0x59,
|
||||
0x55, 0x69, 0x40, 0x6d, 0x77, 0xb6, 0xfa, 0x1f, 0x6b, 0x4c},
|
||||
// 136335439334a7698016a0d324de72284e079d7b5220bb8fbd747816eebebaca.pem
|
||||
{0x92, 0x7a, 0x1b, 0x85, 0x62, 0x28, 0x05, 0x76, 0xd0, 0x48, 0xc5,
|
||||
0x03, 0x21, 0xad, 0xa4, 0x3d, 0x87, 0x03, 0xd2, 0xd9, 0x52, 0x1a,
|
||||
0x18, 0xc2, 0x8b, 0x8c, 0x46, 0xcc, 0x6a, 0xae, 0x4e, 0xfd},
|
||||
// 450f1b421bb05c8609854884559c323319619e8b06b001ea2dcbb74a23aa3be2.pem
|
||||
{0x93, 0xca, 0x2d, 0x43, 0x6c, 0xae, 0x7f, 0x68, 0xd2, 0xb4, 0x25,
|
||||
0x6c, 0xa1, 0x75, 0xc9, 0x85, 0xce, 0x39, 0x92, 0x6d, 0xc9, 0xf7,
|
||||
0xee, 0xae, 0xec, 0xf2, 0xf8, 0x97, 0x0f, 0xb9, 0x78, 0x02},
|
||||
// e757fd60d8dd4c26f77aca6a87f63ea4d38d0b736c7f79b56cad932d4c400fb5.pem
|
||||
{0x96, 0x2e, 0x4b, 0x54, 0xbb, 0x98, 0xa7, 0xee, 0x5d, 0x5f, 0xeb,
|
||||
0x96, 0x33, 0xf9, 0x91, 0xd3, 0xc3, 0x30, 0x0e, 0x95, 0x14, 0xda,
|
||||
0xde, 0x7b, 0x0d, 0x4f, 0x82, 0x8c, 0x79, 0x4f, 0x8e, 0x87},
|
||||
// 3d3d823fad13dfeef32da580166d4a4992bed5a22d695d12c8b08cc3463c67a2.pem
|
||||
{0x96, 0x8d, 0xba, 0x69, 0xfb, 0xff, 0x15, 0xbf, 0x37, 0x62, 0x08,
|
||||
0x94, 0x31, 0xad, 0xe5, 0xa7, 0xea, 0xd4, 0xb7, 0xea, 0xf1, 0xbe,
|
||||
0x70, 0x02, 0x68, 0x10, 0xbc, 0x57, 0xd1, 0xc6, 0x4f, 0x6e},
|
||||
// 1f17f2cbb109f01c885c94d9e74a48625ae9659665d6d7e7bc5a10332976370f.pem
|
||||
{0x99, 0xba, 0x47, 0x84, 0xf9, 0xb0, 0x85, 0x12, 0x90, 0x2e, 0xb0,
|
||||
0xc3, 0xc8, 0x6d, 0xf0, 0xec, 0x04, 0x9e, 0xac, 0x9b, 0x65, 0xf7,
|
||||
0x7a, 0x9b, 0xa4, 0x2b, 0xe9, 0xd6, 0xeb, 0xce, 0x32, 0x0f},
|
||||
// a8e1dfd9cd8e470aa2f443914f931cfd61c323e94d75827affee985241c35ce5.pem
|
||||
{0x9b, 0x8a, 0x93, 0xde, 0xcc, 0xcf, 0xba, 0xfc, 0xf4, 0xd0, 0x4d,
|
||||
0x34, 0x42, 0x12, 0x8f, 0xb3, 0x52, 0x18, 0xcf, 0xe4, 0x37, 0xa3,
|
||||
0xd8, 0xd0, 0x32, 0x8c, 0x99, 0xf8, 0x90, 0x89, 0xe4, 0x50},
|
||||
// 8253da6738b60c5c0bb139c78e045428a0c841272abdcb952f95ff05ed1ab476.pem
|
||||
{0x9c, 0x59, 0xa3, 0xcc, 0xae, 0xa4, 0x69, 0x98, 0x42, 0xb0, 0x68,
|
||||
0xcf, 0xc5, 0x2c, 0xf9, 0x45, 0xdb, 0x51, 0x98, 0x69, 0x57, 0xc8,
|
||||
0x32, 0xcd, 0xb1, 0x8c, 0xa7, 0x38, 0x49, 0xfb, 0xb9, 0xee},
|
||||
// 7d8ce822222b90c0b14342c7a8145d1f24351f4d1a1fe0edfd312ee73fb00149.pem
|
||||
{0x9d, 0x98, 0xa1, 0xfb, 0x60, 0x53, 0x8c, 0x4c, 0xc4, 0x85, 0x7f,
|
||||
0xf1, 0xa8, 0xc8, 0x03, 0x4f, 0xaf, 0x6f, 0xc5, 0x92, 0x09, 0x3f,
|
||||
0x61, 0x99, 0x94, 0xb2, 0xc8, 0x13, 0xd2, 0x50, 0xb8, 0x64},
|
||||
// 1c01c6f4dbb2fefc22558b2bca32563f49844acfc32b7be4b0ff599f9e8c7af7.pem
|
||||
{0x9d, 0xd5, 0x5f, 0xc5, 0x73, 0xf5, 0x46, 0xcb, 0x6a, 0x38, 0x31,
|
||||
0xd1, 0x11, 0x2d, 0x87, 0x10, 0xa6, 0xf4, 0xf8, 0x2d, 0xc8, 0x7f,
|
||||
0x5f, 0xae, 0x9d, 0x3a, 0x1a, 0x02, 0x8d, 0xd3, 0x6e, 0x4b},
|
||||
// 487afc8d0d411b2a05561a2a6f35918f4040e5570c4c73ee323cc50583bcfbb7.pem
|
||||
{0xa0, 0xcf, 0x53, 0xf4, 0x22, 0x65, 0x1e, 0x39, 0x31, 0x7a, 0xe3,
|
||||
0x1a, 0xf6, 0x45, 0x77, 0xbe, 0x45, 0x0f, 0xa3, 0x76, 0xe2, 0x89,
|
||||
0xed, 0x83, 0x42, 0xb7, 0xfc, 0x13, 0x3c, 0x69, 0x74, 0x19},
|
||||
// 0d136e439f0ab6e97f3a02a540da9f0641aa554e1d66ea51ae2920d51b2f7217.pem
|
||||
// 4fee0163686ecbd65db968e7494f55d84b25486d438e9de558d629d28cd4d176.pem
|
||||
// 8a1bd21661c60015065212cc98b1abb50dfd14c872a208e66bae890f25c448af.pem
|
||||
{0xa9, 0x03, 0xaf, 0x8c, 0x07, 0xbb, 0x91, 0xb0, 0xd9, 0xe3, 0xf3,
|
||||
0xa3, 0x0c, 0x6d, 0x53, 0x33, 0x9f, 0xc5, 0xbd, 0x47, 0xe5, 0xd6,
|
||||
0xbd, 0xb4, 0x76, 0x59, 0x88, 0x60, 0xc0, 0x68, 0xa0, 0x24},
|
||||
// a2e3bdaacaaf2d2e8204b3bc7eddc805d54d3ab8bdfe7bf102c035f67d8f898a.pem
|
||||
{0xa9, 0xb5, 0x5a, 0x9b, 0x55, 0x31, 0xbb, 0xf7, 0xc7, 0x1a, 0x1e,
|
||||
0x49, 0x20, 0xef, 0xe7, 0x96, 0xc2, 0xb6, 0x79, 0x68, 0xf5, 0x5a,
|
||||
0x6c, 0xe5, 0xcb, 0x62, 0x17, 0x2e, 0xd9, 0x94, 0x5b, 0xca},
|
||||
// 5472692abe5d02cd22eae3e0a0077f17802721d6576cde1cba2263ee803410c5.pem
|
||||
{0xaf, 0x59, 0x15, 0x18, 0xe2, 0xe6, 0xc6, 0x0e, 0xbb, 0xfc, 0x09,
|
||||
0x07, 0xaf, 0xaa, 0x49, 0xbc, 0x40, 0x51, 0xd4, 0x5e, 0x7f, 0x21,
|
||||
0x4a, 0xbf, 0xee, 0x75, 0x12, 0xee, 0x00, 0xf6, 0x61, 0xed},
|
||||
// 1df696f021ab1c3ace9a376b07ed7256a40214cd3396d7934087614924e2d7ef.pem
|
||||
{0xb1, 0x3f, 0xa2, 0xe6, 0x13, 0x1a, 0x88, 0x8a, 0x01, 0xf3, 0xd6,
|
||||
0x20, 0x56, 0xfb, 0x0e, 0xfb, 0xe9, 0x99, 0xeb, 0x6b, 0x6e, 0x14,
|
||||
0x92, 0x76, 0x13, 0xe0, 0x2b, 0xa8, 0xb8, 0xfb, 0x04, 0x6e},
|
||||
// b8c1b957c077ea76e00b0f45bff5ae3acb696f221d2e062164fe37125e5a8d25.pem
|
||||
{0xb3, 0x18, 0x2e, 0x28, 0x9a, 0xe3, 0x4d, 0xdf, 0x2b, 0xe6, 0x43,
|
||||
0xab, 0x79, 0xc2, 0x44, 0x30, 0x16, 0x05, 0xfa, 0x0f, 0x1e, 0xaa,
|
||||
0xe6, 0xd1, 0x0f, 0xb9, 0x29, 0x60, 0x0a, 0xf8, 0x4d, 0xf0},
|
||||
// be144b56fb1163c49c9a0e6b5a458df6b29f7e6449985960c178a4744624b7bc.pem
|
||||
{0xb4, 0xd5, 0xc9, 0x20, 0x41, 0x5e, 0xd0, 0xcc, 0x4f, 0x5d, 0xbc,
|
||||
0x7f, 0x54, 0x26, 0x36, 0x76, 0x2e, 0x80, 0xda, 0x66, 0x25, 0xf3,
|
||||
0x3f, 0x2b, 0x6a, 0xd6, 0xdb, 0x68, 0xbd, 0xba, 0xb2, 0x9a},
|
||||
// 00309c736dd661da6f1eb24173aa849944c168a43a15bffd192eecfdb6f8dbd2.pem
|
||||
{0xb5, 0xba, 0x8d, 0xd7, 0xf8, 0x95, 0x64, 0xc2, 0x88, 0x9d, 0x3d,
|
||||
0x64, 0x53, 0xc8, 0x49, 0x98, 0xc7, 0x78, 0x24, 0x91, 0x9b, 0x64,
|
||||
0xea, 0x08, 0x35, 0xaa, 0x62, 0x98, 0x65, 0x91, 0xbe, 0x50},
|
||||
// 04f1bec36951bc1454a904ce32890c5da3cde1356b7900f6e62dfa2041ebad51.pem
|
||||
{0xb8, 0x9b, 0xcb, 0xb8, 0xac, 0xd4, 0x74, 0xc1, 0xbe, 0xa7, 0xda,
|
||||
0xd6, 0x50, 0x37, 0xf4, 0x8d, 0xce, 0xcc, 0x9d, 0xfa, 0xa0, 0x61,
|
||||
0x2c, 0x3c, 0x24, 0x45, 0x95, 0x64, 0x19, 0xdf, 0x32, 0xfe},
|
||||
// d8888f4a84f74c974dffb573a1bf5bbbacd1713b905096f8eb015062bf396c4d.pem
|
||||
{0xc0, 0xed, 0x20, 0x53, 0x46, 0xbb, 0xbd, 0xe0, 0x6e, 0xb5, 0x60,
|
||||
0xf5, 0xce, 0xe0, 0x2a, 0x36, 0x34, 0xe2, 0x47, 0x4a, 0x7e, 0x76,
|
||||
0xcf, 0x8f, 0xbe, 0xf5, 0x63, 0xbb, 0x11, 0x7d, 0xd0, 0xe3},
|
||||
// 372447c43185c38edd2ce0e9c853f9ac1576ddd1704c2f54d96076c089cb4227.pem
|
||||
{0xc1, 0x73, 0xf0, 0x62, 0x64, 0x56, 0xca, 0x85, 0x4f, 0xf2, 0xa7,
|
||||
0xf0, 0xb1, 0x33, 0xa7, 0xcf, 0x4d, 0x02, 0x11, 0xe5, 0x52, 0xf2,
|
||||
0x4b, 0x3e, 0x33, 0xad, 0xe8, 0xc5, 0x9f, 0x0a, 0x42, 0x4c},
|
||||
// c4387d45364a313fbfe79812b35b815d42852ab03b06f11589638021c8f2cb44.key
|
||||
{0xc4, 0x38, 0x7d, 0x45, 0x36, 0x4a, 0x31, 0x3f, 0xbf, 0xe7, 0x98,
|
||||
0x12, 0xb3, 0x5b, 0x81, 0x5d, 0x42, 0x85, 0x2a, 0xb0, 0x3b, 0x06,
|
||||
0xf1, 0x15, 0x89, 0x63, 0x80, 0x21, 0xc8, 0xf2, 0xcb, 0x44},
|
||||
// 8290cc3fc1c3aac3239782c141ace8f88aeef4e9576a43d01867cf19d025be66.pem
|
||||
// 9532e8b504964331c271f3f5f10070131a08bf8ba438978ce394c34feeae246f.pem
|
||||
{0xc6, 0x01, 0x23, 0x4e, 0x2b, 0x93, 0x25, 0xdc, 0x92, 0xe3, 0xea,
|
||||
0xba, 0xc1, 0x96, 0x00, 0xb0, 0xb4, 0x99, 0x47, 0xd4, 0xd0, 0x4d,
|
||||
0x8c, 0x99, 0xd3, 0x21, 0x27, 0x49, 0x3e, 0xa0, 0x28, 0xf8},
|
||||
// 0753e940378c1bd5e3836e395daea5cb839e5046f1bd0eae1951cf10fec7c965.pem
|
||||
{0xc6, 0x3d, 0x68, 0xc6, 0x48, 0xa1, 0x8b, 0x77, 0x64, 0x1c, 0x42,
|
||||
0x7a, 0x66, 0x9d, 0x61, 0xc9, 0x76, 0x8a, 0x55, 0xf4, 0xfc, 0xd0,
|
||||
0x32, 0x2e, 0xac, 0x96, 0xc5, 0x77, 0x00, 0x29, 0x9c, 0xf1},
|
||||
// 53d48e7b8869a3314f213fd2e0178219ca09022dbe50053bf6f76fccd61e8112.pem
|
||||
{0xc8, 0xfd, 0xdc, 0x75, 0xcb, 0x1b, 0xdb, 0xb5, 0x8c, 0x07, 0xb4,
|
||||
0xea, 0x84, 0x72, 0x87, 0xf6, 0x26, 0x65, 0x9d, 0xd6, 0x6b, 0xc1,
|
||||
0x0a, 0x26, 0xad, 0xd9, 0xb5, 0x75, 0xb3, 0xa0, 0xa3, 0x8d},
|
||||
// ec30c9c3065a06bb07dc5b1c6b497f370c1ca65c0f30c08e042ba6bcecc78f2c.pem
|
||||
{0xcd, 0xee, 0x9f, 0x33, 0x05, 0x57, 0x2a, 0x67, 0x7e, 0x1a, 0x6c,
|
||||
0x82, 0xdc, 0x1e, 0x02, 0xa3, 0x5b, 0x11, 0xca, 0xe6, 0xa6, 0x84,
|
||||
0x33, 0x8c, 0x9f, 0x37, 0xfe, 0x1a, 0xc8, 0xda, 0xec, 0x23},
|
||||
// 063e4afac491dfd332f3089b8542e94617d893d7fe944e10a7937ee29d9693c0.pem
|
||||
{0xce, 0xd4, 0x39, 0x02, 0xab, 0x5f, 0xb5, 0x7b, 0x44, 0x23, 0x22,
|
||||
0xdc, 0x0e, 0x17, 0x2a, 0x4f, 0xb5, 0x5f, 0x71, 0x78, 0xb8, 0x08,
|
||||
0xf9, 0x4e, 0x78, 0x0a, 0x6f, 0xd6, 0xcc, 0x6b, 0xd8, 0x18},
|
||||
// c71f33c36d8efeefbed9d44e85e21cfe96b36fb0e132c52dca2415868492bf8a.pem
|
||||
{0xd3, 0x1e, 0xc3, 0x92, 0x85, 0xb7, 0xa5, 0x31, 0x9d, 0x01, 0x57,
|
||||
0xdb, 0x42, 0x0e, 0xd8, 0x7c, 0x74, 0x3e, 0x33, 0x3b, 0xbc, 0x77,
|
||||
0xf8, 0x77, 0x1f, 0x70, 0x46, 0x4f, 0x43, 0x6a, 0x60, 0x49},
|
||||
// 9ed8f9b0e8e42a1656b8e1dd18f42ba42dc06fe52686173ba2fc70e756f207dc.pem
|
||||
// a686fee577c88ab664d0787ecdfff035f4806f3de418dc9e4d516324fff02083.pem
|
||||
// fdedb5bdfcb67411513a61aee5cb5b5d7c52af06028efc996cc1b05b1d6cea2b.pem
|
||||
{0xd3, 0x4b, 0x25, 0x5b, 0x2f, 0xe7, 0xd1, 0xa0, 0x96, 0x56, 0xcb,
|
||||
0xab, 0x64, 0x09, 0xf7, 0x3c, 0x79, 0x6e, 0xc7, 0xd6, 0x6a, 0xf7,
|
||||
0x36, 0x53, 0xec, 0xc3, 0x9a, 0xf9, 0x78, 0x29, 0x73, 0x10},
|
||||
// 4b22d5a6aec99f3cdb79aa5ec06838479cd5ecba7164f7f22dc1d65f63d85708.pem
|
||||
{0xd6, 0xa1, 0x84, 0x43, 0xd3, 0x48, 0xdb, 0x99, 0x4f, 0x93, 0x4c,
|
||||
0xcd, 0x8e, 0x63, 0x5d, 0x83, 0x3a, 0x27, 0xac, 0x1e, 0x56, 0xf8,
|
||||
0xaf, 0xaf, 0x7c, 0x97, 0xcb, 0x4f, 0x43, 0xea, 0xb6, 0x8b},
|
||||
// d6f034bd94aa233f0297eca4245b283973e447aa590f310c77f48fdf83112254.pem
|
||||
{0xdb, 0x15, 0xc0, 0x06, 0x2b, 0x52, 0x0f, 0x31, 0x8a, 0x19, 0xda,
|
||||
0xcf, 0xec, 0xd6, 0x4f, 0x9e, 0x7a, 0x3f, 0xbe, 0x60, 0x9f, 0xd5,
|
||||
0x86, 0x79, 0x6f, 0x20, 0xae, 0x02, 0x8e, 0x8e, 0x30, 0x58},
|
||||
// 2a4397aafa6227fa11f9f9d76ecbb022b0a4494852c2b93fb2085c8afb19b62a.pem
|
||||
{0xdb, 0x1d, 0x13, 0xec, 0x42, 0xa2, 0xcb, 0xa3, 0x67, 0x3b, 0xa6,
|
||||
0x7a, 0xf2, 0xde, 0xf8, 0x12, 0xe9, 0xc3, 0x55, 0x66, 0x61, 0x75,
|
||||
0x76, 0xd9, 0x5b, 0x4d, 0x6f, 0xac, 0xe3, 0xef, 0x0a, 0xe8},
|
||||
// 3946901f46b0071e90d78279e82fababca177231a704be72c5b0e8918566ea66.pem
|
||||
{0xdd, 0x3e, 0xeb, 0x77, 0x9b, 0xee, 0x07, 0xf9, 0xef, 0xda, 0xc3,
|
||||
0x82, 0x40, 0x8b, 0x28, 0xd1, 0x42, 0xfa, 0x84, 0x2c, 0x78, 0xe8,
|
||||
0xbc, 0x0e, 0x33, 0x34, 0x8d, 0x57, 0xb9, 0x2f, 0x05, 0x83},
|
||||
// c67d722c1495be02cbf9ef1159f5ca4aa782dc832dc6aa60c9aa076a0ad1e69d.pem
|
||||
{0xde, 0x8f, 0x05, 0x07, 0x4e, 0xc0, 0x31, 0x8e, 0x7e, 0x7e, 0x8d,
|
||||
0x31, 0x90, 0xda, 0xe8, 0xb0, 0x08, 0x94, 0xf0, 0xe8, 0xdd, 0xdf,
|
||||
0xd3, 0x91, 0x3d, 0x01, 0x75, 0x9b, 0x4f, 0x79, 0xb0, 0x5d},
|
||||
// c766a9bef2d4071c863a31aa4920e813b2d198608cb7b7cfe21143b836df09ea.pem
|
||||
// e17890ee09a3fbf4f48b9c414a17d637b7a50647e9bc752322727fcc1742a911.pem
|
||||
{0xe4, 0x2f, 0x24, 0xbd, 0x4d, 0x37, 0xf4, 0xaa, 0x2e, 0x56, 0xb9,
|
||||
0x79, 0xd8, 0x3d, 0x1e, 0x65, 0x21, 0x9f, 0xe0, 0xe9, 0xe3, 0xa3,
|
||||
0x82, 0xa1, 0xb3, 0xcb, 0x66, 0xc9, 0x39, 0x55, 0xde, 0x75},
|
||||
// e4f9a3235df7330255f36412bc849fb630f8519961ec3538301deb896c953da5.pem
|
||||
{0xe6, 0xe1, 0x36, 0xc8, 0x61, 0x54, 0xf3, 0x2c, 0x3e, 0x49, 0xf4,
|
||||
0x7c, 0xfc, 0x6b, 0x33, 0x8f, 0xf2, 0xdc, 0x61, 0xce, 0x14, 0xfc,
|
||||
0x75, 0x89, 0xb3, 0xb5, 0x6a, 0x14, 0x50, 0x13, 0x27, 0x01},
|
||||
// 3e26492e20b52de79e15766e6cb4251a1d566b0dbfb225aa7d08dda1dcebbf0a.pem
|
||||
{0xe7, 0xb9, 0x32, 0xae, 0x7e, 0x9b, 0xdc, 0x70, 0x1d, 0x77, 0x1d,
|
||||
0x6f, 0x39, 0xe8, 0xa6, 0x53, 0x44, 0x9e, 0xea, 0x43, 0xbd, 0xb4,
|
||||
0x7b, 0xd9, 0x10, 0x22, 0x95, 0x0d, 0x91, 0x79, 0xd8, 0x7e},
|
||||
// 5ccaf9f8f2bb3a0d215922eca383354b6ee3c62407ed32e30f6fb2618edeea10.pem
|
||||
{0xe8, 0x49, 0xc7, 0x17, 0x6c, 0x93, 0xdf, 0x65, 0xf6, 0x4b, 0x61,
|
||||
0x69, 0x82, 0x36, 0x6e, 0x56, 0x63, 0x11, 0x78, 0x12, 0xb6, 0xfa,
|
||||
0x2b, 0xc0, 0xc8, 0xfa, 0x8a, 0xea, 0xee, 0x41, 0x81, 0xcc},
|
||||
// ea08c8d45d52ca593de524f0513ca6418da9859f7b08ef13ff9dd7bf612d6a37.key
|
||||
{0xea, 0x08, 0xc8, 0xd4, 0x5d, 0x52, 0xca, 0x59, 0x3d, 0xe5, 0x24,
|
||||
0xf0, 0x51, 0x3c, 0xa6, 0x41, 0x8d, 0xa9, 0x85, 0x9f, 0x7b, 0x08,
|
||||
0xef, 0x13, 0xff, 0x9d, 0xd7, 0xbf, 0x61, 0x2d, 0x6a, 0x37},
|
||||
// d40e9c86cd8fe468c1776959f49ea774fa548684b6c406f3909261f4dce2575c.pem
|
||||
{0xea, 0x87, 0xf4, 0x62, 0xde, 0xef, 0xff, 0xbd, 0x77, 0x75, 0xaa,
|
||||
0x2a, 0x4b, 0x7e, 0x0f, 0xcb, 0x91, 0xc2, 0x2e, 0xee, 0x6d, 0xf6,
|
||||
0x9e, 0xd9, 0x01, 0x00, 0xcc, 0xc7, 0x3b, 0x31, 0x14, 0x76},
|
||||
// 60911c79835c3739432d08c45df64311e06985c5889dc5420ce3d142c8c7ef58.pem
|
||||
{0xef, 0x55, 0x12, 0x84, 0x71, 0x52, 0x32, 0xde, 0x92, 0xe2, 0x46,
|
||||
0xc3, 0x23, 0x32, 0x93, 0x62, 0xb1, 0x32, 0x49, 0x3b, 0xb1, 0x6b,
|
||||
0x58, 0x9e, 0x47, 0x75, 0x52, 0x0b, 0xeb, 0x87, 0x1a, 0x56},
|
||||
// 31c8fd37db9b56e708b03d1f01848b068c6da66f36fb5d82c008c6040fa3e133.pem
|
||||
{0xf0, 0x34, 0xf6, 0x42, 0xca, 0x1d, 0x9e, 0x88, 0xe9, 0xef, 0xea,
|
||||
0xfc, 0xb1, 0x5c, 0x7c, 0x93, 0x7a, 0xa1, 0x9e, 0x04, 0xb0, 0x80,
|
||||
0xf2, 0x73, 0x35, 0xe1, 0xda, 0x70, 0xd1, 0xca, 0x12, 0x01},
|
||||
// 83618f932d6947744d5ecca299d4b2820c01483947bd16be814e683f7436be24.pem
|
||||
{0xf2, 0xbb, 0xe0, 0x4c, 0x5d, 0xc7, 0x0d, 0x76, 0x3e, 0x89, 0xc5,
|
||||
0xa0, 0x52, 0x70, 0x48, 0xcd, 0x9e, 0xcd, 0x39, 0xeb, 0x62, 0x1e,
|
||||
0x20, 0x72, 0xff, 0x9a, 0x5f, 0x84, 0x32, 0x57, 0x1a, 0xa0},
|
||||
// 2a3699deca1e9fd099ba45de8489e205977c9f2a5e29d5dd747381eec0744d71.pem
|
||||
{0xf3, 0x0e, 0x8f, 0x61, 0x01, 0x1d, 0x65, 0x87, 0x3c, 0xcb, 0x81,
|
||||
0xb4, 0x0f, 0xa6, 0x21, 0x97, 0x49, 0xb9, 0x94, 0xf0, 0x1f, 0xa2,
|
||||
0x4d, 0x02, 0x01, 0xd5, 0x21, 0xc2, 0x43, 0x56, 0x03, 0xca},
|
||||
// 0d90cd8e35209b4cefebdd62b644bed8eb55c74dddff26e75caf8ae70491f0bd.pem
|
||||
{0xf5, 0x29, 0x3d, 0x47, 0xed, 0x38, 0xd4, 0xc3, 0x1b, 0x2d, 0x42,
|
||||
0xde, 0xe3, 0xb5, 0xb3, 0xac, 0xe9, 0x7c, 0xa2, 0x6c, 0xa2, 0xac,
|
||||
0x03, 0x65, 0xe3, 0x62, 0x2e, 0xe8, 0x02, 0x13, 0x1f, 0xbb},
|
||||
// 67ed4b703d15dc555f8c444b3a05a32579cb7599bd19c9babe10c584ea327ae0.pem
|
||||
{0xfa, 0x00, 0xbe, 0xc7, 0x3d, 0xd9, 0x97, 0x95, 0xdf, 0x11, 0x62,
|
||||
0xc7, 0x89, 0x98, 0x70, 0x04, 0xc2, 0x6c, 0xbf, 0x90, 0xaf, 0x4d,
|
||||
0xb4, 0x42, 0xf6, 0x62, 0x20, 0xde, 0x41, 0x35, 0x4a, 0xc9},
|
||||
// a25a19546819d048000ef9c6577c4bcd8d2155b1e4346a4599d6c8b79799d4a1.pem
|
||||
{0xfc, 0xd7, 0x6c, 0xca, 0x23, 0x47, 0xe5, 0xcd, 0x5b, 0x39, 0x34,
|
||||
0x7f, 0x51, 0xcf, 0x43, 0x65, 0x4b, 0x69, 0xa2, 0xbf, 0xc9, 0x07,
|
||||
0x36, 0x70, 0xa6, 0xbe, 0x47, 0xd8, 0x70, 0x1e, 0x6e, 0x0e},
|
||||
// 44a244105569a730791f509b24c3d7838a462216bb0f560ef87fbe76c2e6005a.pem
|
||||
{0xb0, 0xfc, 0xce, 0x78, 0xc1, 0x66, 0x4e, 0x29, 0x35, 0x44, 0xc1,
|
||||
0x43, 0xe3, 0xd2, 0x68, 0x9f, 0x72, 0x3f, 0x5b, 0x6e, 0x63, 0x17,
|
||||
0x10, 0x7e, 0x16, 0x3d, 0x22, 0xba, 0x80, 0x69, 0x79, 0x4a},
|
||||
// 0230a604d99220e5612ee7862ab9f7a6e18e4f1ac4c9e27075788cc5220169ab.pem
|
||||
{0xc5, 0x62, 0x17, 0xb7, 0xa8, 0x28, 0xc7, 0x34, 0x1c, 0x0a, 0xe7,
|
||||
0xa5, 0x90, 0xd8, 0x79, 0x0d, 0x4d, 0xef, 0x53, 0x66, 0x52, 0xe6,
|
||||
0x0a, 0xe5, 0xb8, 0xbd, 0xfa, 0x26, 0x97, 0x8f, 0xe0, 0x9c},
|
||||
// 06fd20629c143b9eab28d2799caefc5d23fde267d16c631e3f5b8b4bab3f68e6.pem
|
||||
{0xe4, 0x7c, 0x5c, 0xd2, 0xdc, 0x8b, 0xab, 0xb4, 0xe5, 0x3f, 0x8a,
|
||||
0x49, 0x83, 0x92, 0x02, 0x75, 0xef, 0x6f, 0xfa, 0xac, 0xb0, 0x89,
|
||||
0xe8, 0x7a, 0x2c, 0x1f, 0xbe, 0x5a, 0x58, 0x5f, 0x05, 0xed},
|
||||
// 0bd39de4793cdc117138f47708aa4d583acf67adb059a0d91f668d1803bf6489.pem
|
||||
{0x39, 0x73, 0x65, 0x88, 0xb9, 0x4a, 0x4c, 0xe7, 0x67, 0xf7, 0x31,
|
||||
0xca, 0xd5, 0x3f, 0x4c, 0xbe, 0x44, 0x13, 0x7e, 0x32, 0x1e, 0xad,
|
||||
0xca, 0xef, 0x8c, 0xe7, 0x9a, 0x22, 0x9b, 0xbc, 0xa9, 0x89},
|
||||
// c95c133b68319ee516b5f41e377f589878af1556567cc2834ef03b1d10830fd3.pem
|
||||
{0xea, 0x12, 0x70, 0x5d, 0xe7, 0xc4, 0x8f, 0x6f, 0xcc, 0xe2, 0xcb,
|
||||
0x8d, 0xbc, 0x54, 0x2e, 0x0f, 0xc3, 0x8a, 0xc3, 0x8e, 0x08, 0x88,
|
||||
0x0d, 0xd0, 0x4a, 0x02, 0xef, 0x67, 0xc9, 0x3a, 0xe1, 0x35},
|
||||
// 29abf614b2870ed70df11225e9ae2068e3074eb9845ae252c2064e31ce9fe8a1.pem
|
||||
{0xa6, 0xac, 0xa1, 0xec, 0x98, 0x09, 0xcc, 0x5b, 0x48, 0x21, 0xff,
|
||||
0x9d, 0x29, 0xc5, 0xeb, 0xe6, 0x51, 0x96, 0x0b, 0x91, 0xb1, 0xf1,
|
||||
0x9c, 0xc8, 0x9b, 0x55, 0xef, 0x87, 0x81, 0x8a, 0x95, 0x09},
|
||||
};
|
||||
|
||||
// Hashes of SubjectPublicKeyInfos known to be used for interception by a
|
||||
// party other than the device or machine owner.
|
||||
static constexpr uint8_t kKnownInterceptionList[][SHA256_DIGEST_LENGTH] = {
|
||||
// 1df696f021ab1c3ace9a376b07ed7256a40214cd3396d7934087614924e2d7ef.pem
|
||||
{0xb1, 0x3f, 0xa2, 0xe6, 0x13, 0x1a, 0x88, 0x8a, 0x01, 0xf3, 0xd6, 0x20,
|
||||
0x56, 0xfb, 0x0e, 0xfb, 0xe9, 0x99, 0xeb, 0x6b, 0x6e, 0x14, 0x92, 0x76,
|
||||
0x13, 0xe0, 0x2b, 0xa8, 0xb8, 0xfb, 0x04, 0x6e},
|
||||
// 61c0fc2e38b5b6f9071b42cee54a9013d858b6697c68b460948551b3249576a1.pem
|
||||
{0x8e, 0x12, 0xd0, 0xcb, 0x3b, 0x7d, 0xf3, 0xea, 0x22, 0x57, 0x57, 0x94,
|
||||
0x89, 0xfd, 0x86, 0x58, 0xc9, 0x56, 0x03, 0xea, 0x6c, 0xf4, 0xb7, 0x31,
|
||||
0x63, 0xa4, 0x1e, 0xb7, 0xb7, 0xe9, 0x3f, 0xee},
|
||||
// 143315c857a9386973ed16840899c3f96b894a7a612c444efb691f14b0dedd87.pem
|
||||
{0xa4, 0xe9, 0xaf, 0x01, 0x41, 0x6e, 0x3a, 0x02, 0x9b, 0x5d, 0x35, 0xe5,
|
||||
0xb1, 0x19, 0xde, 0x00, 0xcf, 0xe1, 0x56, 0xc5, 0xcf, 0x95, 0xfc, 0x82,
|
||||
0x3c, 0xf6, 0xd0, 0x5e, 0x3c, 0x1a, 0x82, 0x37},
|
||||
// 44a244105569a730791f509b24c3d7838a462216bb0f560ef87fbe76c2e6005a.pem
|
||||
{0xb0, 0xfc, 0xce, 0x78, 0xc1, 0x66, 0x4e, 0x29, 0x35, 0x44, 0xc1, 0x43,
|
||||
0xe3, 0xd2, 0x68, 0x9f, 0x72, 0x3f, 0x5b, 0x6e, 0x63, 0x17, 0x10, 0x7e,
|
||||
0x16, 0x3d, 0x22, 0xba, 0x80, 0x69, 0x79, 0x4a},
|
||||
// 0230a604d99220e5612ee7862ab9f7a6e18e4f1ac4c9e27075788cc5220169ab.pem
|
||||
{0xc5, 0x62, 0x17, 0xb7, 0xa8, 0x28, 0xc7, 0x34, 0x1c, 0x0a, 0xe7, 0xa5,
|
||||
0x90, 0xd8, 0x79, 0x0d, 0x4d, 0xef, 0x53, 0x66, 0x52, 0xe6, 0x0a, 0xe5,
|
||||
0xb8, 0xbd, 0xfa, 0x26, 0x97, 0x8f, 0xe0, 0x9c},
|
||||
// 06fd20629c143b9eab28d2799caefc5d23fde267d16c631e3f5b8b4bab3f68e6.pem
|
||||
{0xe4, 0x7c, 0x5c, 0xd2, 0xdc, 0x8b, 0xab, 0xb4, 0xe5, 0x3f, 0x8a, 0x49,
|
||||
0x83, 0x92, 0x02, 0x75, 0xef, 0x6f, 0xfa, 0xac, 0xb0, 0x89, 0xe8, 0x7a,
|
||||
0x2c, 0x1f, 0xbe, 0x5a, 0x58, 0x5f, 0x05, 0xed},
|
||||
// 0bd39de4793cdc117138f47708aa4d583acf67adb059a0d91f668d1803bf6489.pem
|
||||
{0x39, 0x73, 0x65, 0x88, 0xb9, 0x4a, 0x4c, 0xe7, 0x67, 0xf7, 0x31,
|
||||
0xca, 0xd5, 0x3f, 0x4c, 0xbe, 0x44, 0x13, 0x7e, 0x32, 0x1e, 0xad,
|
||||
0xca, 0xef, 0x8c, 0xe7, 0x9a, 0x22, 0x9b, 0xbc, 0xa9, 0x89},
|
||||
// c95c133b68319ee516b5f41e377f589878af1556567cc2834ef03b1d10830fd3.pem
|
||||
{0xea, 0x12, 0x70, 0x5d, 0xe7, 0xc4, 0x8f, 0x6f, 0xcc, 0xe2, 0xcb, 0x8d,
|
||||
0xbc, 0x54, 0x2e, 0x0f, 0xc3, 0x8a, 0xc3, 0x8e, 0x08, 0x88, 0x0d, 0xd0,
|
||||
0x4a, 0x02, 0xef, 0x67, 0xc9, 0x3a, 0xe1, 0x35},
|
||||
};
|
372
pki/certificate_policies.cc
Normal file
372
pki/certificate_policies.cc
Normal file
@ -0,0 +1,372 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "certificate_policies.h"
|
||||
|
||||
#include "cert_error_params.h"
|
||||
#include "cert_errors.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Errors
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kPolicyQualifiersEmptySequence,
|
||||
"The policy qualifiers SEQUENCE is empty");
|
||||
DEFINE_CERT_ERROR_ID(kUnknownPolicyQualifierOid,
|
||||
"Unknown policy qualifier OID (not CPS or User Notice)");
|
||||
DEFINE_CERT_ERROR_ID(kPoliciesEmptySequence, "Policies is an empty SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kPoliciesDuplicateOid, "Policies contains duplicate OIDs");
|
||||
DEFINE_CERT_ERROR_ID(kPolicyInformationTrailingData,
|
||||
"PolicyInformation has trailing data");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingPolicyQualifiers,
|
||||
"Failed parsing policy qualifiers");
|
||||
DEFINE_CERT_ERROR_ID(kMissingQualifier,
|
||||
"PolicyQualifierInfo is missing qualifier");
|
||||
DEFINE_CERT_ERROR_ID(kPolicyQualifierInfoTrailingData,
|
||||
"PolicyQualifierInfo has trailing data");
|
||||
|
||||
// Minimally parse policyQualifiers, storing in |policy_qualifiers| if non-null.
|
||||
// If a policy qualifier other than User Notice/CPS is present, parsing
|
||||
// will fail if |restrict_to_known_qualifiers| was set to true.
|
||||
bool ParsePolicyQualifiers(bool restrict_to_known_qualifiers,
|
||||
der::Parser* policy_qualifiers_sequence_parser,
|
||||
std::vector<PolicyQualifierInfo>* policy_qualifiers,
|
||||
CertErrors* errors) {
|
||||
DCHECK(errors);
|
||||
|
||||
// If it is present, the policyQualifiers sequence should have at least 1
|
||||
// element.
|
||||
//
|
||||
// policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
||||
// PolicyQualifierInfo OPTIONAL }
|
||||
if (!policy_qualifiers_sequence_parser->HasMore()) {
|
||||
errors->AddError(kPolicyQualifiersEmptySequence);
|
||||
return false;
|
||||
}
|
||||
while (policy_qualifiers_sequence_parser->HasMore()) {
|
||||
// PolicyQualifierInfo ::= SEQUENCE {
|
||||
der::Parser policy_information_parser;
|
||||
if (!policy_qualifiers_sequence_parser->ReadSequence(
|
||||
&policy_information_parser)) {
|
||||
return false;
|
||||
}
|
||||
// policyQualifierId PolicyQualifierId,
|
||||
der::Input qualifier_oid;
|
||||
if (!policy_information_parser.ReadTag(der::kOid, &qualifier_oid))
|
||||
return false;
|
||||
if (restrict_to_known_qualifiers &&
|
||||
qualifier_oid != der::Input(kCpsPointerId) &&
|
||||
qualifier_oid != der::Input(kUserNoticeId)) {
|
||||
errors->AddError(kUnknownPolicyQualifierOid,
|
||||
CreateCertErrorParams1Der("oid", qualifier_oid));
|
||||
return false;
|
||||
}
|
||||
// qualifier ANY DEFINED BY policyQualifierId }
|
||||
der::Input qualifier_tlv;
|
||||
if (!policy_information_parser.ReadRawTLV(&qualifier_tlv)) {
|
||||
errors->AddError(kMissingQualifier);
|
||||
return false;
|
||||
}
|
||||
// Should not have trailing data after qualifier.
|
||||
if (policy_information_parser.HasMore()) {
|
||||
errors->AddError(kPolicyQualifierInfoTrailingData);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (policy_qualifiers)
|
||||
policy_qualifiers->push_back({qualifier_oid, qualifier_tlv});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.4. Certificate Policies:
|
||||
//
|
||||
// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
||||
//
|
||||
// PolicyInformation ::= SEQUENCE {
|
||||
// policyIdentifier CertPolicyId,
|
||||
// policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
||||
// PolicyQualifierInfo OPTIONAL }
|
||||
//
|
||||
// CertPolicyId ::= OBJECT IDENTIFIER
|
||||
//
|
||||
// PolicyQualifierInfo ::= SEQUENCE {
|
||||
// policyQualifierId PolicyQualifierId,
|
||||
// qualifier ANY DEFINED BY policyQualifierId }
|
||||
//
|
||||
// PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
|
||||
//
|
||||
// Qualifier ::= CHOICE {
|
||||
// cPSuri CPSuri,
|
||||
// userNotice UserNotice }
|
||||
//
|
||||
// CPSuri ::= IA5String
|
||||
//
|
||||
// UserNotice ::= SEQUENCE {
|
||||
// noticeRef NoticeReference OPTIONAL,
|
||||
// explicitText DisplayText OPTIONAL }
|
||||
//
|
||||
// NoticeReference ::= SEQUENCE {
|
||||
// organization DisplayText,
|
||||
// noticeNumbers SEQUENCE OF INTEGER }
|
||||
//
|
||||
// DisplayText ::= CHOICE {
|
||||
// ia5String IA5String (SIZE (1..200)),
|
||||
// visibleString VisibleString (SIZE (1..200)),
|
||||
// bmpString BMPString (SIZE (1..200)),
|
||||
// utf8String UTF8String (SIZE (1..200)) }
|
||||
bool ParseCertificatePoliciesExtensionImpl(
|
||||
const der::Input& extension_value,
|
||||
bool fail_parsing_unknown_qualifier_oids,
|
||||
std::vector<der::Input>* policy_oids,
|
||||
std::vector<PolicyInformation>* policy_informations,
|
||||
CertErrors* errors) {
|
||||
DCHECK(policy_oids);
|
||||
DCHECK(errors);
|
||||
// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
||||
der::Parser extension_parser(extension_value);
|
||||
der::Parser policies_sequence_parser;
|
||||
if (!extension_parser.ReadSequence(&policies_sequence_parser))
|
||||
return false;
|
||||
// Should not have trailing data after certificatePolicies sequence.
|
||||
if (extension_parser.HasMore())
|
||||
return false;
|
||||
// The certificatePolicies sequence should have at least 1 element.
|
||||
if (!policies_sequence_parser.HasMore()) {
|
||||
errors->AddError(kPoliciesEmptySequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
policy_oids->clear();
|
||||
if (policy_informations)
|
||||
policy_informations->clear();
|
||||
|
||||
while (policies_sequence_parser.HasMore()) {
|
||||
// PolicyInformation ::= SEQUENCE {
|
||||
der::Parser policy_information_parser;
|
||||
if (!policies_sequence_parser.ReadSequence(&policy_information_parser))
|
||||
return false;
|
||||
// policyIdentifier CertPolicyId,
|
||||
der::Input policy_oid;
|
||||
if (!policy_information_parser.ReadTag(der::kOid, &policy_oid))
|
||||
return false;
|
||||
|
||||
policy_oids->push_back(policy_oid);
|
||||
|
||||
std::vector<PolicyQualifierInfo>* policy_qualifiers = nullptr;
|
||||
if (policy_informations) {
|
||||
policy_informations->emplace_back();
|
||||
policy_informations->back().policy_oid = policy_oid;
|
||||
policy_qualifiers = &policy_informations->back().policy_qualifiers;
|
||||
}
|
||||
|
||||
if (!policy_information_parser.HasMore())
|
||||
continue;
|
||||
|
||||
// policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
||||
// PolicyQualifierInfo OPTIONAL }
|
||||
der::Parser policy_qualifiers_sequence_parser;
|
||||
if (!policy_information_parser.ReadSequence(
|
||||
&policy_qualifiers_sequence_parser)) {
|
||||
return false;
|
||||
}
|
||||
// Should not have trailing data after policyQualifiers sequence.
|
||||
if (policy_information_parser.HasMore()) {
|
||||
errors->AddError(kPolicyInformationTrailingData);
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.4: When qualifiers are used with the special
|
||||
// policy anyPolicy, they MUST be limited to the qualifiers identified in
|
||||
// this section.
|
||||
if (!ParsePolicyQualifiers(fail_parsing_unknown_qualifier_oids ||
|
||||
policy_oid == der::Input(kAnyPolicyOid),
|
||||
&policy_qualifiers_sequence_parser,
|
||||
policy_qualifiers, errors)) {
|
||||
errors->AddError(kFailedParsingPolicyQualifiers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.4: A certificate policy OID MUST NOT appear more
|
||||
// than once in a certificate policies extension.
|
||||
std::sort(policy_oids->begin(), policy_oids->end());
|
||||
auto dupe_policy_iter =
|
||||
std::adjacent_find(policy_oids->begin(), policy_oids->end());
|
||||
if (dupe_policy_iter != policy_oids->end()) {
|
||||
errors->AddError(kPoliciesDuplicateOid,
|
||||
CreateCertErrorParams1Der("oid", *dupe_policy_iter));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PolicyInformation::PolicyInformation() = default;
|
||||
PolicyInformation::~PolicyInformation() = default;
|
||||
PolicyInformation::PolicyInformation(const PolicyInformation&) = default;
|
||||
PolicyInformation::PolicyInformation(PolicyInformation&&) = default;
|
||||
|
||||
bool ParseCertificatePoliciesExtension(const der::Input& extension_value,
|
||||
std::vector<PolicyInformation>* policies,
|
||||
CertErrors* errors) {
|
||||
std::vector<der::Input> unused_policy_oids;
|
||||
return ParseCertificatePoliciesExtensionImpl(
|
||||
extension_value, /*fail_parsing_unknown_qualifier_oids=*/false,
|
||||
&unused_policy_oids, policies, errors);
|
||||
}
|
||||
|
||||
bool ParseCertificatePoliciesExtensionOids(
|
||||
const der::Input& extension_value,
|
||||
bool fail_parsing_unknown_qualifier_oids,
|
||||
std::vector<der::Input>* policy_oids,
|
||||
CertErrors* errors) {
|
||||
return ParseCertificatePoliciesExtensionImpl(
|
||||
extension_value, fail_parsing_unknown_qualifier_oids, policy_oids,
|
||||
nullptr, errors);
|
||||
}
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// PolicyConstraints ::= SEQUENCE {
|
||||
// requireExplicitPolicy [0] SkipCerts OPTIONAL,
|
||||
// inhibitPolicyMapping [1] SkipCerts OPTIONAL }
|
||||
//
|
||||
// SkipCerts ::= INTEGER (0..MAX)
|
||||
bool ParsePolicyConstraints(const der::Input& policy_constraints_tlv,
|
||||
ParsedPolicyConstraints* out) {
|
||||
der::Parser parser(policy_constraints_tlv);
|
||||
|
||||
// PolicyConstraints ::= SEQUENCE {
|
||||
der::Parser sequence_parser;
|
||||
if (!parser.ReadSequence(&sequence_parser))
|
||||
return false;
|
||||
|
||||
// RFC 5280 prohibits CAs from issuing PolicyConstraints as an empty sequence:
|
||||
//
|
||||
// Conforming CAs MUST NOT issue certificates where policy constraints
|
||||
// is an empty sequence. That is, either the inhibitPolicyMapping field
|
||||
// or the requireExplicitPolicy field MUST be present. The behavior of
|
||||
// clients that encounter an empty policy constraints field is not
|
||||
// addressed in this profile.
|
||||
if (!sequence_parser.HasMore())
|
||||
return false;
|
||||
|
||||
std::optional<der::Input> require_value;
|
||||
if (!sequence_parser.ReadOptionalTag(der::ContextSpecificPrimitive(0),
|
||||
&require_value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (require_value) {
|
||||
uint8_t require_explicit_policy;
|
||||
if (!ParseUint8(require_value.value(), &require_explicit_policy)) {
|
||||
// TODO(eroman): Surface reason for failure if length was longer than
|
||||
// uint8.
|
||||
return false;
|
||||
}
|
||||
out->require_explicit_policy = require_explicit_policy;
|
||||
}
|
||||
|
||||
std::optional<der::Input> inhibit_value;
|
||||
if (!sequence_parser.ReadOptionalTag(der::ContextSpecificPrimitive(1),
|
||||
&inhibit_value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inhibit_value) {
|
||||
uint8_t inhibit_policy_mapping;
|
||||
if (!ParseUint8(inhibit_value.value(), &inhibit_policy_mapping)) {
|
||||
// TODO(eroman): Surface reason for failure if length was longer than
|
||||
// uint8.
|
||||
return false;
|
||||
}
|
||||
out->inhibit_policy_mapping = inhibit_policy_mapping;
|
||||
}
|
||||
|
||||
// There should be no remaining data.
|
||||
if (sequence_parser.HasMore() || parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// InhibitAnyPolicy ::= SkipCerts
|
||||
//
|
||||
// SkipCerts ::= INTEGER (0..MAX)
|
||||
bool ParseInhibitAnyPolicy(const der::Input& inhibit_any_policy_tlv,
|
||||
uint8_t* num_certs) {
|
||||
der::Parser parser(inhibit_any_policy_tlv);
|
||||
|
||||
// TODO(eroman): Surface reason for failure if length was longer than uint8.
|
||||
if (!parser.ReadUint8(num_certs))
|
||||
return false;
|
||||
|
||||
// There should be no remaining data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
|
||||
// issuerDomainPolicy CertPolicyId,
|
||||
// subjectDomainPolicy CertPolicyId }
|
||||
bool ParsePolicyMappings(const der::Input& policy_mappings_tlv,
|
||||
std::vector<ParsedPolicyMapping>* mappings) {
|
||||
mappings->clear();
|
||||
|
||||
der::Parser parser(policy_mappings_tlv);
|
||||
|
||||
// PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
|
||||
der::Parser sequence_parser;
|
||||
if (!parser.ReadSequence(&sequence_parser))
|
||||
return false;
|
||||
|
||||
// Must be at least 1 mapping.
|
||||
if (!sequence_parser.HasMore())
|
||||
return false;
|
||||
|
||||
while (sequence_parser.HasMore()) {
|
||||
der::Parser mapping_parser;
|
||||
if (!sequence_parser.ReadSequence(&mapping_parser))
|
||||
return false;
|
||||
|
||||
ParsedPolicyMapping mapping;
|
||||
if (!mapping_parser.ReadTag(der::kOid, &mapping.issuer_domain_policy))
|
||||
return false;
|
||||
if (!mapping_parser.ReadTag(der::kOid, &mapping.subject_domain_policy))
|
||||
return false;
|
||||
|
||||
// There shouldn't be extra unconsumed data.
|
||||
if (mapping_parser.HasMore())
|
||||
return false;
|
||||
|
||||
mappings->push_back(mapping);
|
||||
}
|
||||
|
||||
// There shouldn't be extra unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
136
pki/certificate_policies.h
Normal file
136
pki/certificate_policies.h
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CERTIFICATE_POLICIES_H_
|
||||
#define BSSL_PKI_CERTIFICATE_POLICIES_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "input.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
class CertErrors;
|
||||
|
||||
// Returns the DER-encoded OID, without tag or length, of the anyPolicy
|
||||
// certificate policy defined in RFC 5280 section 4.2.1.4.
|
||||
inline constexpr uint8_t kAnyPolicyOid[] = {0x55, 0x1D, 0x20, 0x00};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.54
|
||||
inline constexpr uint8_t kInhibitAnyPolicyOid[] = {0x55, 0x1d, 0x36};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-policyMappings OBJECT IDENTIFIER ::= { id-ce 33 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.33
|
||||
inline constexpr uint8_t kPolicyMappingsOid[] = {0x55, 0x1d, 0x21};
|
||||
|
||||
// -- policyQualifierIds for Internet policy qualifiers
|
||||
//
|
||||
// id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
|
||||
// id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
|
||||
//
|
||||
// In dotted decimal form: 1.3.6.1.5.5.7.2.1
|
||||
inline constexpr uint8_t kCpsPointerId[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x02, 0x01};
|
||||
|
||||
// id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
|
||||
//
|
||||
// In dotted decimal form: 1.3.6.1.5.5.7.2.2
|
||||
inline constexpr uint8_t kUserNoticeId[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x02, 0x02};
|
||||
|
||||
struct PolicyQualifierInfo {
|
||||
der::Input qualifier_oid;
|
||||
der::Input qualifier;
|
||||
};
|
||||
|
||||
struct OPENSSL_EXPORT PolicyInformation {
|
||||
PolicyInformation();
|
||||
~PolicyInformation();
|
||||
PolicyInformation(const PolicyInformation&);
|
||||
PolicyInformation(PolicyInformation&&);
|
||||
|
||||
der::Input policy_oid;
|
||||
std::vector<PolicyQualifierInfo> policy_qualifiers;
|
||||
};
|
||||
|
||||
// Parses a certificatePolicies extension and stores the policy information
|
||||
// |*policies|, in the order presented in |extension_value|.
|
||||
//
|
||||
// Returns true on success. On failure returns false and may add errors to
|
||||
// |errors|, which must be non-null.
|
||||
//
|
||||
// The values in |policies| are only valid as long as |extension_value| is (as
|
||||
// it references data).
|
||||
OPENSSL_EXPORT bool ParseCertificatePoliciesExtension(
|
||||
const der::Input& extension_value,
|
||||
std::vector<PolicyInformation>* policies,
|
||||
CertErrors* errors);
|
||||
|
||||
// Parses a certificatePolicies extension and stores the policy OIDs in
|
||||
// |*policy_oids|, in sorted order.
|
||||
//
|
||||
// If policyQualifiers for User Notice or CPS are present then they are
|
||||
// ignored (RFC 5280 section 4.2.1.4 says "optional qualifiers, which MAY
|
||||
// be present, are not expected to change the definition of the policy."
|
||||
//
|
||||
// If a policy qualifier other than User Notice/CPS is present, parsing
|
||||
// will fail if |fail_parsing_unknown_qualifier_oids| was set to true,
|
||||
// otherwise the unrecognized qualifiers wil be skipped and not parsed
|
||||
// any further.
|
||||
//
|
||||
// Returns true on success. On failure returns false and may add errors to
|
||||
// |errors|, which must be non-null.
|
||||
//
|
||||
// The values in |policy_oids| are only valid as long as |extension_value| is
|
||||
// (as it references data).
|
||||
OPENSSL_EXPORT bool ParseCertificatePoliciesExtensionOids(
|
||||
const der::Input& extension_value,
|
||||
bool fail_parsing_unknown_qualifier_oids,
|
||||
std::vector<der::Input>* policy_oids,
|
||||
CertErrors* errors);
|
||||
|
||||
struct ParsedPolicyConstraints {
|
||||
std::optional<uint8_t> require_explicit_policy;
|
||||
|
||||
std::optional<uint8_t> inhibit_policy_mapping;
|
||||
};
|
||||
|
||||
// Parses a PolicyConstraints SEQUENCE as defined by RFC 5280. Returns true on
|
||||
// success, and sets |out|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParsePolicyConstraints(
|
||||
const der::Input& policy_constraints_tlv,
|
||||
ParsedPolicyConstraints* out);
|
||||
|
||||
// Parses an InhibitAnyPolicy as defined by RFC 5280. Returns true on success,
|
||||
// and sets |num_certs|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseInhibitAnyPolicy(
|
||||
const der::Input& inhibit_any_policy_tlv,
|
||||
uint8_t* num_certs);
|
||||
|
||||
struct ParsedPolicyMapping {
|
||||
der::Input issuer_domain_policy;
|
||||
der::Input subject_domain_policy;
|
||||
};
|
||||
|
||||
// Parses a PolicyMappings SEQUENCE as defined by RFC 5280. Returns true on
|
||||
// success, and sets |mappings|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParsePolicyMappings(
|
||||
const der::Input& policy_mappings_tlv,
|
||||
std::vector<ParsedPolicyMapping>* mappings);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CERTIFICATE_POLICIES_H_
|
313
pki/certificate_policies_unittest.cc
Normal file
313
pki/certificate_policies_unittest.cc
Normal file
@ -0,0 +1,313 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "certificate_policies.h"
|
||||
|
||||
#include "test_helpers.h"
|
||||
#include "input.h"
|
||||
#include "parser.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl {
|
||||
namespace {
|
||||
|
||||
::testing::AssertionResult LoadTestData(const std::string& name,
|
||||
std::string* result) {
|
||||
std::string path = "testdata/certificate_policies_unittest/" + name;
|
||||
|
||||
const PemBlockMapping mappings[] = {
|
||||
{"CERTIFICATE POLICIES", result},
|
||||
};
|
||||
|
||||
return ReadTestDataFromPemFile(path, mappings);
|
||||
}
|
||||
|
||||
const uint8_t policy_1_2_3_der[] = {0x2A, 0x03};
|
||||
const uint8_t policy_1_2_4_der[] = {0x2A, 0x04};
|
||||
|
||||
class ParseCertificatePoliciesExtensionOidsTest
|
||||
: public testing::TestWithParam<bool> {
|
||||
protected:
|
||||
bool fail_parsing_unknown_qualifier_oids() const { return GetParam(); }
|
||||
};
|
||||
|
||||
// Run the tests with all possible values for
|
||||
// |fail_parsing_unknown_qualifier_oids|.
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
ParseCertificatePoliciesExtensionOidsTest,
|
||||
testing::Bool());
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, InvalidEmpty) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("invalid-empty.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, InvalidIdentifierNotOid) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("invalid-policy_identifier_not_oid.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, AnyPolicy) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("anypolicy.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
ASSERT_EQ(1U, policies.size());
|
||||
EXPECT_EQ(der::Input(kAnyPolicyOid), policies[0]);
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, AnyPolicyWithQualifier) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("anypolicy_with_qualifier.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
ASSERT_EQ(1U, policies.size());
|
||||
EXPECT_EQ(der::Input(kAnyPolicyOid), policies[0]);
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest,
|
||||
InvalidAnyPolicyWithCustomQualifier) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(
|
||||
LoadTestData("invalid-anypolicy_with_custom_qualifier.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, OnePolicy) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
ASSERT_EQ(1U, policies.size());
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, OnePolicyWithQualifier) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_with_qualifier.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
ASSERT_EQ(1U, policies.size());
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest,
|
||||
OnePolicyWithCustomQualifier) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_with_custom_qualifier.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
bool result = ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors);
|
||||
|
||||
if (fail_parsing_unknown_qualifier_oids()) {
|
||||
EXPECT_FALSE(result);
|
||||
} else {
|
||||
EXPECT_TRUE(result);
|
||||
ASSERT_EQ(1U, policies.size());
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest,
|
||||
InvalidPolicyWithDuplicatePolicyOid) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("invalid-policy_1_2_3_dupe.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest,
|
||||
InvalidPolicyWithEmptyQualifiersSequence) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData(
|
||||
"invalid-policy_1_2_3_with_empty_qualifiers_sequence.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest,
|
||||
InvalidPolicyInformationHasUnconsumedData) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData(
|
||||
"invalid-policy_1_2_3_policyinformation_unconsumed_data.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest,
|
||||
InvalidPolicyQualifierInfoHasUnconsumedData) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData(
|
||||
"invalid-policy_1_2_3_policyqualifierinfo_unconsumed_data.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, TwoPolicies) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_and_1_2_4.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
ASSERT_EQ(2U, policies.size());
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
|
||||
EXPECT_EQ(der::Input(policy_1_2_4_der), policies[1]);
|
||||
}
|
||||
|
||||
TEST_P(ParseCertificatePoliciesExtensionOidsTest, TwoPoliciesWithQualifiers) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_and_1_2_4_with_qualifiers.pem", &der));
|
||||
std::vector<der::Input> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(ParseCertificatePoliciesExtensionOids(
|
||||
der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies,
|
||||
&errors));
|
||||
ASSERT_EQ(2U, policies.size());
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
|
||||
EXPECT_EQ(der::Input(policy_1_2_4_der), policies[1]);
|
||||
}
|
||||
|
||||
TEST(ParseCertificatePoliciesExtensionTest, InvalidEmpty) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("invalid-empty.pem", &der));
|
||||
std::vector<PolicyInformation> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(
|
||||
ParseCertificatePoliciesExtension(der::Input(&der), &policies, &errors));
|
||||
}
|
||||
|
||||
TEST(ParseCertificatePoliciesExtensionTest,
|
||||
InvalidPolicyWithDuplicatePolicyOid) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("invalid-policy_1_2_3_dupe.pem", &der));
|
||||
std::vector<PolicyInformation> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_FALSE(
|
||||
ParseCertificatePoliciesExtension(der::Input(&der), &policies, &errors));
|
||||
}
|
||||
|
||||
TEST(ParseCertificatePoliciesExtensionTest, OnePolicyWithCustomQualifier) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_with_custom_qualifier.pem", &der));
|
||||
std::vector<PolicyInformation> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(
|
||||
ParseCertificatePoliciesExtension(der::Input(&der), &policies, &errors));
|
||||
ASSERT_EQ(1U, policies.size());
|
||||
PolicyInformation& policy = policies[0];
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policy.policy_oid);
|
||||
|
||||
ASSERT_EQ(1U, policy.policy_qualifiers.size());
|
||||
PolicyQualifierInfo& qualifier = policy.policy_qualifiers[0];
|
||||
// 1.2.3.4
|
||||
const uint8_t kExpectedQualifierOid[] = {0x2a, 0x03, 0x04};
|
||||
EXPECT_EQ(der::Input(kExpectedQualifierOid), qualifier.qualifier_oid);
|
||||
// UTF8String { "hi" }
|
||||
const uint8_t kExpectedQualifier[] = {0x0c, 0x02, 0x68, 0x69};
|
||||
EXPECT_EQ(der::Input(kExpectedQualifier), qualifier.qualifier);
|
||||
}
|
||||
|
||||
TEST(ParseCertificatePoliciesExtensionTest, TwoPolicies) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_and_1_2_4.pem", &der));
|
||||
std::vector<PolicyInformation> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(
|
||||
ParseCertificatePoliciesExtension(der::Input(&der), &policies, &errors));
|
||||
ASSERT_EQ(2U, policies.size());
|
||||
{
|
||||
PolicyInformation& policy = policies[0];
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policy.policy_oid);
|
||||
EXPECT_EQ(0U, policy.policy_qualifiers.size());
|
||||
}
|
||||
{
|
||||
PolicyInformation& policy = policies[1];
|
||||
EXPECT_EQ(der::Input(policy_1_2_4_der), policy.policy_oid);
|
||||
EXPECT_EQ(0U, policy.policy_qualifiers.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ParseCertificatePoliciesExtensionTest, TwoPoliciesWithQualifiers) {
|
||||
std::string der;
|
||||
ASSERT_TRUE(LoadTestData("policy_1_2_3_and_1_2_4_with_qualifiers.pem", &der));
|
||||
std::vector<PolicyInformation> policies;
|
||||
CertErrors errors;
|
||||
EXPECT_TRUE(
|
||||
ParseCertificatePoliciesExtension(der::Input(&der), &policies, &errors));
|
||||
ASSERT_EQ(2U, policies.size());
|
||||
{
|
||||
PolicyInformation& policy = policies[0];
|
||||
EXPECT_EQ(der::Input(policy_1_2_3_der), policy.policy_oid);
|
||||
ASSERT_EQ(1U, policy.policy_qualifiers.size());
|
||||
PolicyQualifierInfo& qualifier = policy.policy_qualifiers[0];
|
||||
EXPECT_EQ(der::Input(kCpsPointerId), qualifier.qualifier_oid);
|
||||
// IA5String { "https://example.com/1_2_3" }
|
||||
const uint8_t kExpectedQualifier[] = {
|
||||
0x16, 0x19, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
|
||||
0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x5f, 0x32, 0x5f, 0x33};
|
||||
EXPECT_EQ(der::Input(kExpectedQualifier), qualifier.qualifier);
|
||||
}
|
||||
{
|
||||
PolicyInformation& policy = policies[1];
|
||||
EXPECT_EQ(der::Input(policy_1_2_4_der), policy.policy_oid);
|
||||
ASSERT_EQ(1U, policy.policy_qualifiers.size());
|
||||
PolicyQualifierInfo& qualifier = policy.policy_qualifiers[0];
|
||||
EXPECT_EQ(der::Input(kCpsPointerId), qualifier.qualifier_oid);
|
||||
// IA5String { "http://example.com/1_2_4" }
|
||||
const uint8_t kExpectedQualifier[] = {
|
||||
0x16, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
|
||||
0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x31, 0x5f, 0x32, 0x5f, 0x34};
|
||||
EXPECT_EQ(der::Input(kExpectedQualifier), qualifier.qualifier);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: The tests for ParseInhibitAnyPolicy() are part of
|
||||
// parsed_certificate_unittest.cc
|
||||
|
||||
} // namespace
|
||||
} // namespace net
|
85
pki/common_cert_errors.cc
Normal file
85
pki/common_cert_errors.cc
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "common_cert_errors.h"
|
||||
|
||||
namespace bssl::cert_errors {
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kInternalError, "Internal error");
|
||||
DEFINE_CERT_ERROR_ID(kValidityFailedNotAfter, "Time is after notAfter");
|
||||
DEFINE_CERT_ERROR_ID(kValidityFailedNotBefore, "Time is before notBefore");
|
||||
DEFINE_CERT_ERROR_ID(kDistrustedByTrustStore, "Distrusted by trust store");
|
||||
|
||||
DEFINE_CERT_ERROR_ID(
|
||||
kSignatureAlgorithmMismatch,
|
||||
"Certificate.signatureAlgorithm != TBSCertificate.signature");
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kChainIsEmpty, "Chain is empty");
|
||||
DEFINE_CERT_ERROR_ID(kUnconsumedCriticalExtension,
|
||||
"Unconsumed critical extension");
|
||||
DEFINE_CERT_ERROR_ID(kKeyCertSignBitNotSet, "keyCertSign bit is not set");
|
||||
DEFINE_CERT_ERROR_ID(kMaxPathLengthViolated, "max_path_length reached");
|
||||
DEFINE_CERT_ERROR_ID(kBasicConstraintsIndicatesNotCa,
|
||||
"Basic Constraints indicates not a CA");
|
||||
DEFINE_CERT_ERROR_ID(kTargetCertShouldNotBeCa,
|
||||
"Certificate has Basic Constraints indicating it is a CA "
|
||||
"when it should not be a CA");
|
||||
DEFINE_CERT_ERROR_ID(kMissingBasicConstraints,
|
||||
"Does not have Basic Constraints");
|
||||
DEFINE_CERT_ERROR_ID(kNotPermittedByNameConstraints,
|
||||
"Not permitted by name constraints");
|
||||
DEFINE_CERT_ERROR_ID(kTooManyNameConstraintChecks,
|
||||
"Too many name constraints checks");
|
||||
DEFINE_CERT_ERROR_ID(kSubjectDoesNotMatchIssuer,
|
||||
"subject does not match issuer");
|
||||
DEFINE_CERT_ERROR_ID(kVerifySignedDataFailed, "VerifySignedData failed");
|
||||
DEFINE_CERT_ERROR_ID(kSignatureAlgorithmsDifferentEncoding,
|
||||
"Certificate.signatureAlgorithm is encoded differently "
|
||||
"than TBSCertificate.signature");
|
||||
DEFINE_CERT_ERROR_ID(kEkuLacksServerAuth,
|
||||
"The extended key usage does not include server auth");
|
||||
DEFINE_CERT_ERROR_ID(kEkuLacksServerAuthButHasGatedCrypto,
|
||||
"The extended key usage does not include server auth but "
|
||||
"instead includes Netscape Server Gated Crypto");
|
||||
DEFINE_CERT_ERROR_ID(kEkuLacksServerAuthButHasAnyEKU,
|
||||
"The extended key usage does not include server auth but "
|
||||
"instead includes anyExtendeKeyUsage");
|
||||
DEFINE_CERT_ERROR_ID(kEkuLacksClientAuth,
|
||||
"The extended key usage does not include client auth");
|
||||
DEFINE_CERT_ERROR_ID(kEkuLacksClientAuthButHasAnyEKU,
|
||||
"The extended key usage does not include client auth but "
|
||||
"instead includes anyExtendedKeyUsage");
|
||||
DEFINE_CERT_ERROR_ID(kEkuLacksClientAuthOrServerAuth,
|
||||
"The extended key usage does not include client auth "
|
||||
"or server auth");
|
||||
DEFINE_CERT_ERROR_ID(kEkuHasProhibitedOCSPSigning,
|
||||
"The extended key usage includes OCSP signing which "
|
||||
"is not permitted for this use");
|
||||
DEFINE_CERT_ERROR_ID(kEkuHasProhibitedTimeStamping,
|
||||
"The extended key usage includes time stamping which "
|
||||
"is not permitted for this use");
|
||||
DEFINE_CERT_ERROR_ID(kEkuHasProhibitedCodeSigning,
|
||||
"The extended key usage includes code signing which "
|
||||
"is not permitted for this use");
|
||||
DEFINE_CERT_ERROR_ID(kEkuNotPresent,
|
||||
"Certificate does not have extended key usage");
|
||||
DEFINE_CERT_ERROR_ID(kCertIsNotTrustAnchor,
|
||||
"Certificate is not a trust anchor");
|
||||
DEFINE_CERT_ERROR_ID(kNoValidPolicy, "No valid policy");
|
||||
DEFINE_CERT_ERROR_ID(kPolicyMappingAnyPolicy,
|
||||
"PolicyMappings must not map anyPolicy");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingSpki, "Couldn't parse SubjectPublicKeyInfo");
|
||||
DEFINE_CERT_ERROR_ID(kUnacceptableSignatureAlgorithm,
|
||||
"Unacceptable signature algorithm");
|
||||
DEFINE_CERT_ERROR_ID(kUnacceptablePublicKey, "Unacceptable public key");
|
||||
DEFINE_CERT_ERROR_ID(kCertificateRevoked, "Certificate is revoked");
|
||||
DEFINE_CERT_ERROR_ID(kNoRevocationMechanism,
|
||||
"Certificate lacks a revocation mechanism");
|
||||
DEFINE_CERT_ERROR_ID(kUnableToCheckRevocation, "Unable to check revocation");
|
||||
DEFINE_CERT_ERROR_ID(kNoIssuersFound, "No matching issuer found");
|
||||
DEFINE_CERT_ERROR_ID(kDeadlineExceeded, "Deadline exceeded");
|
||||
DEFINE_CERT_ERROR_ID(kIterationLimitExceeded, "Iteration limit exceeded");
|
||||
DEFINE_CERT_ERROR_ID(kDepthLimitExceeded, "Depth limit exceeded");
|
||||
|
||||
} // namespace net::cert_errors
|
164
pki/common_cert_errors.h
Normal file
164
pki/common_cert_errors.h
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_COMMON_CERT_ERRORS_H_
|
||||
#define BSSL_PKI_COMMON_CERT_ERRORS_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
|
||||
#include "cert_errors.h"
|
||||
|
||||
// This file contains the set of "default" certificate errors (those
|
||||
// defined by the core verification/path building code).
|
||||
//
|
||||
// Errors may be defined for other domains.
|
||||
namespace bssl::cert_errors {
|
||||
|
||||
// An internal error occurred which prevented path building or verification
|
||||
// from finishing.
|
||||
OPENSSL_EXPORT extern const CertErrorId kInternalError;
|
||||
|
||||
// The verification time is after the certificate's notAfter time.
|
||||
OPENSSL_EXPORT extern const CertErrorId kValidityFailedNotAfter;
|
||||
|
||||
// The verification time is before the certificate's notBefore time.
|
||||
OPENSSL_EXPORT extern const CertErrorId kValidityFailedNotBefore;
|
||||
|
||||
// The certificate is actively distrusted by the trust store (this is separate
|
||||
// from other revocation mechanisms).
|
||||
OPENSSL_EXPORT extern const CertErrorId kDistrustedByTrustStore;
|
||||
|
||||
// The certificate disagrees on what the signature algorithm was
|
||||
// (Certificate.signatureAlgorithm != TBSCertificate.signature).
|
||||
OPENSSL_EXPORT extern const CertErrorId kSignatureAlgorithmMismatch;
|
||||
|
||||
// Certificate verification was called with an empty chain.
|
||||
OPENSSL_EXPORT extern const CertErrorId kChainIsEmpty;
|
||||
|
||||
// The certificate contains an unknown extension which is marked as critical.
|
||||
OPENSSL_EXPORT extern const CertErrorId kUnconsumedCriticalExtension;
|
||||
|
||||
// The target certificate appears to be a CA (has Basic Constraints CA=true)
|
||||
// but is being used for TLS client or server authentication.
|
||||
OPENSSL_EXPORT extern const CertErrorId kTargetCertShouldNotBeCa;
|
||||
|
||||
// The certificate is being used to sign other certificates, however the
|
||||
// keyCertSign KeyUsage was not set.
|
||||
OPENSSL_EXPORT extern const CertErrorId kKeyCertSignBitNotSet;
|
||||
|
||||
// The chain violates the max_path_length from BasicConstraints.
|
||||
OPENSSL_EXPORT extern const CertErrorId kMaxPathLengthViolated;
|
||||
|
||||
// The certificate being used to sign other certificates has a
|
||||
// BasicConstraints extension, however it sets CA=false
|
||||
OPENSSL_EXPORT extern const CertErrorId kBasicConstraintsIndicatesNotCa;
|
||||
|
||||
// The certificate being used to sign other certificates does not include a
|
||||
// BasicConstraints extension.
|
||||
OPENSSL_EXPORT extern const CertErrorId kMissingBasicConstraints;
|
||||
|
||||
// The certificate has a subject or subjectAltName that violates an issuer's
|
||||
// name constraints.
|
||||
OPENSSL_EXPORT extern const CertErrorId kNotPermittedByNameConstraints;
|
||||
|
||||
// The chain has an excessive number of names and/or name constraints.
|
||||
OPENSSL_EXPORT extern const CertErrorId kTooManyNameConstraintChecks;
|
||||
|
||||
// The certificate's issuer field does not match the subject of its alleged
|
||||
// issuer.
|
||||
OPENSSL_EXPORT extern const CertErrorId kSubjectDoesNotMatchIssuer;
|
||||
|
||||
// Failed to verify the certificate's signature using its issuer's public key.
|
||||
OPENSSL_EXPORT extern const CertErrorId kVerifySignedDataFailed;
|
||||
|
||||
// The certificate encodes its signature differently between
|
||||
// Certificate.algorithm and TBSCertificate.signature, but it appears
|
||||
// to be the same algorithm.
|
||||
OPENSSL_EXPORT extern const CertErrorId kSignatureAlgorithmsDifferentEncoding;
|
||||
|
||||
// The certificate verification is being done for serverAuth, however the
|
||||
// certificate lacks serverAuth in its ExtendedKeyUsages.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuLacksServerAuth;
|
||||
|
||||
// The certificate verification is being done for clientAuth, however the
|
||||
// certificate lacks clientAuth in its ExtendedKeyUsages.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuLacksClientAuth;
|
||||
|
||||
// The root certificate in a chain is not trusted.
|
||||
OPENSSL_EXPORT extern const CertErrorId kCertIsNotTrustAnchor;
|
||||
|
||||
// The chain is not valid for any policy, and an explicit policy was required.
|
||||
// (Either because the relying party requested it during verificaiton, or it was
|
||||
// requrested by a PolicyConstraints extension).
|
||||
OPENSSL_EXPORT extern const CertErrorId kNoValidPolicy;
|
||||
|
||||
// The certificate is trying to map to, or from, anyPolicy.
|
||||
OPENSSL_EXPORT extern const CertErrorId kPolicyMappingAnyPolicy;
|
||||
|
||||
// The public key in this certificate could not be parsed.
|
||||
OPENSSL_EXPORT extern const CertErrorId kFailedParsingSpki;
|
||||
|
||||
// The certificate's signature algorithm (used to verify its
|
||||
// signature) is not acceptable by the consumer. What constitutes as
|
||||
// "acceptable" is determined by the verification delegate.
|
||||
OPENSSL_EXPORT extern const CertErrorId kUnacceptableSignatureAlgorithm;
|
||||
|
||||
// The certificate's public key is not acceptable by the consumer.
|
||||
// What constitutes as "acceptable" is determined by the verification delegate.
|
||||
OPENSSL_EXPORT extern const CertErrorId kUnacceptablePublicKey;
|
||||
|
||||
// The certificate's EKU is missing serverAuth. However Netscape Server Gated
|
||||
// Crypto is present instead.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuLacksServerAuthButHasGatedCrypto;
|
||||
|
||||
// The certificate's EKU is missing serverAuth. However EKU ANY is present
|
||||
// instead.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuLacksServerAuthButHasAnyEKU;
|
||||
|
||||
// The certificate's EKU is missing clientAuth. However EKU ANY is present
|
||||
// instead.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuLacksClientAuthButHasAnyEKU;
|
||||
|
||||
// The certificate's EKU is missing both clientAuth and serverAuth.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuLacksClientAuthOrServerAuth;
|
||||
|
||||
// The certificate's EKU has OSCP Signing when it should not.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuHasProhibitedOCSPSigning;
|
||||
|
||||
// The certificate's EKU has Time Stamping when it should not.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuHasProhibitedTimeStamping;
|
||||
|
||||
// The certificate's EKU has Code Signing when it should not.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuHasProhibitedCodeSigning;
|
||||
|
||||
// The certificate does not have EKU.
|
||||
OPENSSL_EXPORT extern const CertErrorId kEkuNotPresent;
|
||||
|
||||
// The certificate has been revoked.
|
||||
OPENSSL_EXPORT extern const CertErrorId kCertificateRevoked;
|
||||
|
||||
// The certificate lacks a recognized revocation mechanism (i.e. OCSP/CRL).
|
||||
// Emitted as an error when revocation checking expects certificates to have
|
||||
// such info.
|
||||
OPENSSL_EXPORT extern const CertErrorId kNoRevocationMechanism;
|
||||
|
||||
// The certificate had a revocation mechanism, but when used it was unable to
|
||||
// affirmatively say whether the certificate was unrevoked.
|
||||
OPENSSL_EXPORT extern const CertErrorId kUnableToCheckRevocation;
|
||||
|
||||
// Path building was unable to find any issuers for the certificate.
|
||||
OPENSSL_EXPORT extern const CertErrorId kNoIssuersFound;
|
||||
|
||||
// Deadline was reached during path building.
|
||||
OPENSSL_EXPORT extern const CertErrorId kDeadlineExceeded;
|
||||
|
||||
// Iteration limit was reached during path building.
|
||||
OPENSSL_EXPORT extern const CertErrorId kIterationLimitExceeded;
|
||||
|
||||
// Depth limit was reached during path building.
|
||||
OPENSSL_EXPORT extern const CertErrorId kDepthLimitExceeded;
|
||||
|
||||
} // namespace net::cert_errors
|
||||
|
||||
#endif // BSSL_PKI_COMMON_CERT_ERRORS_H_
|
626
pki/crl.cc
Normal file
626
pki/crl.cc
Normal file
@ -0,0 +1,626 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "crl.h"
|
||||
#include "revocation_util.h"
|
||||
#include "signature_algorithm.h"
|
||||
#include "verify_name_match.h"
|
||||
#include "verify_signed_data.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
|
||||
// In dotted notation: 2.5.29.28
|
||||
inline constexpr uint8_t kIssuingDistributionPointOid[] = {0x55, 0x1d, 0x1c};
|
||||
|
||||
[[nodiscard]] bool NormalizeNameTLV(const der::Input& name_tlv,
|
||||
std::string* out_normalized_name) {
|
||||
der::Parser parser(name_tlv);
|
||||
der::Input name_rdn;
|
||||
bssl::CertErrors unused_errors;
|
||||
return parser.ReadTag(der::kSequence, &name_rdn) &&
|
||||
NormalizeName(name_rdn, out_normalized_name, &unused_errors) &&
|
||||
!parser.HasMore();
|
||||
}
|
||||
|
||||
bool ContainsExactMatchingName(std::vector<std::string_view> a,
|
||||
std::vector<std::string_view> b) {
|
||||
std::sort(a.begin(), a.end());
|
||||
std::sort(b.begin(), b.end());
|
||||
std::vector<std::string_view> names_in_common;
|
||||
std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
|
||||
std::back_inserter(names_in_common));
|
||||
return !names_in_common.empty();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ParseCrlCertificateList(const der::Input& crl_tlv,
|
||||
der::Input* out_tbs_cert_list_tlv,
|
||||
der::Input* out_signature_algorithm_tlv,
|
||||
der::BitString* out_signature_value) {
|
||||
der::Parser parser(crl_tlv);
|
||||
|
||||
// CertificateList ::= SEQUENCE {
|
||||
der::Parser certificate_list_parser;
|
||||
if (!parser.ReadSequence(&certificate_list_parser))
|
||||
return false;
|
||||
|
||||
// tbsCertList TBSCertList
|
||||
if (!certificate_list_parser.ReadRawTLV(out_tbs_cert_list_tlv))
|
||||
return false;
|
||||
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
if (!certificate_list_parser.ReadRawTLV(out_signature_algorithm_tlv))
|
||||
return false;
|
||||
|
||||
// signatureValue BIT STRING }
|
||||
std::optional<der::BitString> signature_value =
|
||||
certificate_list_parser.ReadBitString();
|
||||
if (!signature_value)
|
||||
return false;
|
||||
*out_signature_value = signature_value.value();
|
||||
|
||||
// There isn't an extension point at the end of CertificateList.
|
||||
if (certificate_list_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// By definition the input was a single CertificateList, so there shouldn't be
|
||||
// unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseCrlTbsCertList(const der::Input& tbs_tlv, ParsedCrlTbsCertList* out) {
|
||||
der::Parser parser(tbs_tlv);
|
||||
|
||||
// TBSCertList ::= SEQUENCE {
|
||||
der::Parser tbs_parser;
|
||||
if (!parser.ReadSequence(&tbs_parser))
|
||||
return false;
|
||||
|
||||
// version Version OPTIONAL,
|
||||
// -- if present, MUST be v2
|
||||
std::optional<der::Input> version_der;
|
||||
if (!tbs_parser.ReadOptionalTag(der::kInteger, &version_der))
|
||||
return false;
|
||||
if (version_der.has_value()) {
|
||||
uint64_t version64;
|
||||
if (!der::ParseUint64(*version_der, &version64))
|
||||
return false;
|
||||
// If version is present, it MUST be v2(1).
|
||||
if (version64 != 1)
|
||||
return false;
|
||||
out->version = CrlVersion::V2;
|
||||
} else {
|
||||
// Uh, RFC 5280 doesn't actually say it anywhere, but presumably if version
|
||||
// is not specified, it is V1.
|
||||
out->version = CrlVersion::V1;
|
||||
}
|
||||
|
||||
// signature AlgorithmIdentifier,
|
||||
if (!tbs_parser.ReadRawTLV(&out->signature_algorithm_tlv))
|
||||
return false;
|
||||
|
||||
// issuer Name,
|
||||
if (!tbs_parser.ReadRawTLV(&out->issuer_tlv))
|
||||
return false;
|
||||
|
||||
// thisUpdate Time,
|
||||
if (!ReadUTCOrGeneralizedTime(&tbs_parser, &out->this_update))
|
||||
return false;
|
||||
|
||||
// nextUpdate Time OPTIONAL,
|
||||
der::Tag maybe_next_update_tag;
|
||||
der::Input unused_next_update_input;
|
||||
if (tbs_parser.PeekTagAndValue(&maybe_next_update_tag,
|
||||
&unused_next_update_input) &&
|
||||
(maybe_next_update_tag == der::kUtcTime ||
|
||||
maybe_next_update_tag == der::kGeneralizedTime)) {
|
||||
der::GeneralizedTime next_update_time;
|
||||
if (!ReadUTCOrGeneralizedTime(&tbs_parser, &next_update_time))
|
||||
return false;
|
||||
out->next_update = next_update_time;
|
||||
} else {
|
||||
out->next_update = std::nullopt;
|
||||
}
|
||||
|
||||
// revokedCertificates SEQUENCE OF SEQUENCE { ... } OPTIONAL,
|
||||
der::Input unused_revoked_certificates;
|
||||
der::Tag maybe_revoked_certifigates_tag;
|
||||
if (tbs_parser.PeekTagAndValue(&maybe_revoked_certifigates_tag,
|
||||
&unused_revoked_certificates) &&
|
||||
maybe_revoked_certifigates_tag == der::kSequence) {
|
||||
der::Input revoked_certificates_tlv;
|
||||
if (!tbs_parser.ReadRawTLV(&revoked_certificates_tlv))
|
||||
return false;
|
||||
out->revoked_certificates_tlv = revoked_certificates_tlv;
|
||||
} else {
|
||||
out->revoked_certificates_tlv = std::nullopt;
|
||||
}
|
||||
|
||||
// crlExtensions [0] EXPLICIT Extensions OPTIONAL
|
||||
// -- if present, version MUST be v2
|
||||
if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
|
||||
&out->crl_extensions_tlv)) {
|
||||
return false;
|
||||
}
|
||||
if (out->crl_extensions_tlv.has_value()) {
|
||||
if (out->version != CrlVersion::V2)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tbs_parser.HasMore()) {
|
||||
// Invalid or extraneous elements.
|
||||
return false;
|
||||
}
|
||||
|
||||
// By definition the input was a single sequence, so there shouldn't be
|
||||
// unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseIssuingDistributionPoint(
|
||||
const der::Input& extension_value,
|
||||
std::unique_ptr<GeneralNames>* out_distribution_point_names,
|
||||
ContainedCertsType* out_only_contains_cert_type) {
|
||||
der::Parser idp_extension_value_parser(extension_value);
|
||||
// IssuingDistributionPoint ::= SEQUENCE {
|
||||
der::Parser idp_parser;
|
||||
if (!idp_extension_value_parser.ReadSequence(&idp_parser))
|
||||
return false;
|
||||
|
||||
// 5.2.5. Conforming CRLs issuers MUST NOT issue CRLs where the DER
|
||||
// encoding of the issuing distribution point extension is an empty
|
||||
// sequence.
|
||||
if (!idp_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||
std::optional<der::Input> distribution_point;
|
||||
if (!idp_parser.ReadOptionalTag(
|
||||
der::kTagContextSpecific | der::kTagConstructed | 0,
|
||||
&distribution_point)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (distribution_point.has_value()) {
|
||||
// DistributionPointName ::= CHOICE {
|
||||
der::Parser dp_name_parser(*distribution_point);
|
||||
// fullName [0] GeneralNames,
|
||||
// nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
|
||||
std::optional<der::Input> der_full_name;
|
||||
if (!dp_name_parser.ReadOptionalTag(
|
||||
der::kTagContextSpecific | der::kTagConstructed | 0,
|
||||
&der_full_name)) {
|
||||
return false;
|
||||
}
|
||||
if (!der_full_name) {
|
||||
// Only fullName is supported.
|
||||
return false;
|
||||
}
|
||||
CertErrors errors;
|
||||
*out_distribution_point_names =
|
||||
GeneralNames::CreateFromValue(*der_full_name, &errors);
|
||||
if (!*out_distribution_point_names)
|
||||
return false;
|
||||
|
||||
if (dp_name_parser.HasMore()) {
|
||||
// CHOICE represents a single value.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*out_only_contains_cert_type = ContainedCertsType::ANY_CERTS;
|
||||
|
||||
// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
|
||||
std::optional<der::Input> only_contains_user_certs;
|
||||
if (!idp_parser.ReadOptionalTag(der::kTagContextSpecific | 1,
|
||||
&only_contains_user_certs)) {
|
||||
return false;
|
||||
}
|
||||
if (only_contains_user_certs.has_value()) {
|
||||
bool bool_value;
|
||||
if (!der::ParseBool(*only_contains_user_certs, &bool_value))
|
||||
return false;
|
||||
if (!bool_value)
|
||||
return false; // DER-encoding requires DEFAULT values be omitted.
|
||||
*out_only_contains_cert_type = ContainedCertsType::USER_CERTS;
|
||||
}
|
||||
|
||||
// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
|
||||
std::optional<der::Input> only_contains_ca_certs;
|
||||
if (!idp_parser.ReadOptionalTag(der::kTagContextSpecific | 2,
|
||||
&only_contains_ca_certs)) {
|
||||
return false;
|
||||
}
|
||||
if (only_contains_ca_certs.has_value()) {
|
||||
bool bool_value;
|
||||
if (!der::ParseBool(*only_contains_ca_certs, &bool_value))
|
||||
return false;
|
||||
if (!bool_value)
|
||||
return false; // DER-encoding requires DEFAULT values be omitted.
|
||||
if (*out_only_contains_cert_type != ContainedCertsType::ANY_CERTS) {
|
||||
// 5.2.5. at most one of onlyContainsUserCerts, onlyContainsCACerts,
|
||||
// and onlyContainsAttributeCerts may be set to TRUE.
|
||||
return false;
|
||||
}
|
||||
*out_only_contains_cert_type = ContainedCertsType::CA_CERTS;
|
||||
}
|
||||
|
||||
// onlySomeReasons [3] ReasonFlags OPTIONAL,
|
||||
// indirectCRL [4] BOOLEAN DEFAULT FALSE,
|
||||
// onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
|
||||
// onlySomeReasons, indirectCRL, and onlyContainsAttributeCerts are not
|
||||
// supported, fail parsing if they are present.
|
||||
if (idp_parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CRLRevocationStatus GetCRLStatusForCert(
|
||||
const der::Input& cert_serial,
|
||||
CrlVersion crl_version,
|
||||
const std::optional<der::Input>& revoked_certificates_tlv) {
|
||||
if (!revoked_certificates_tlv.has_value()) {
|
||||
// RFC 5280 Section 5.1.2.6: "When there are no revoked certificates, the
|
||||
// revoked certificates list MUST be absent."
|
||||
// No covered certificates are revoked, therefore the cert is good.
|
||||
return CRLRevocationStatus::GOOD;
|
||||
}
|
||||
|
||||
der::Parser parser(*revoked_certificates_tlv);
|
||||
|
||||
// revokedCertificates SEQUENCE OF SEQUENCE {
|
||||
der::Parser revoked_certificates_parser;
|
||||
if (!parser.ReadSequence(&revoked_certificates_parser))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// RFC 5280 Section 5.1.2.6: "When there are no revoked certificates, the
|
||||
// revoked certificates list MUST be absent."
|
||||
if (!revoked_certificates_parser.HasMore())
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// By definition the input was a single Extensions sequence, so there
|
||||
// shouldn't be unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
bool found_matching_serial = false;
|
||||
|
||||
while (revoked_certificates_parser.HasMore()) {
|
||||
// revokedCertificates SEQUENCE OF SEQUENCE {
|
||||
der::Parser crl_entry_parser;
|
||||
if (!revoked_certificates_parser.ReadSequence(&crl_entry_parser))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
der::Input revoked_cert_serial_number;
|
||||
// userCertificate CertificateSerialNumber,
|
||||
if (!crl_entry_parser.ReadTag(der::kInteger, &revoked_cert_serial_number))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// revocationDate Time,
|
||||
der::GeneralizedTime unused_revocation_date;
|
||||
if (!ReadUTCOrGeneralizedTime(&crl_entry_parser, &unused_revocation_date))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// crlEntryExtensions Extensions OPTIONAL
|
||||
if (crl_entry_parser.HasMore()) {
|
||||
// -- if present, version MUST be v2
|
||||
if (crl_version != CrlVersion::V2)
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
der::Input crl_entry_extensions_tlv;
|
||||
if (!crl_entry_parser.ReadRawTLV(&crl_entry_extensions_tlv))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
std::map<der::Input, ParsedExtension> extensions;
|
||||
if (!ParseExtensions(crl_entry_extensions_tlv, &extensions))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// RFC 5280 Section 5.3: "If a CRL contains a critical CRL entry
|
||||
// extension that the application cannot process, then the application
|
||||
// MUST NOT use that CRL to determine the status of any certificates."
|
||||
for (const auto& ext : extensions) {
|
||||
if (ext.second.critical)
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if (crl_entry_parser.HasMore())
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
if (revoked_cert_serial_number == cert_serial) {
|
||||
// Cert is revoked, but can't return yet since there might be critical
|
||||
// extensions on later entries that would prevent use of this CRL.
|
||||
found_matching_serial = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_matching_serial)
|
||||
return CRLRevocationStatus::REVOKED;
|
||||
|
||||
// |cert| is not present in the revokedCertificates list.
|
||||
return CRLRevocationStatus::GOOD;
|
||||
}
|
||||
|
||||
ParsedCrlTbsCertList::ParsedCrlTbsCertList() = default;
|
||||
ParsedCrlTbsCertList::~ParsedCrlTbsCertList() = default;
|
||||
|
||||
CRLRevocationStatus CheckCRL(std::string_view raw_crl,
|
||||
const ParsedCertificateList& valid_chain,
|
||||
size_t target_cert_index,
|
||||
const ParsedDistributionPoint& cert_dp,
|
||||
int64_t verify_time_epoch_seconds,
|
||||
std::optional<int64_t> max_age_seconds) {
|
||||
DCHECK_LT(target_cert_index, valid_chain.size());
|
||||
|
||||
if (cert_dp.reasons) {
|
||||
// Reason codes are not supported. If the distribution point contains a
|
||||
// subset of reasons then skip it. We aren't interested in subsets of CRLs
|
||||
// and the RFC states that there MUST be a CRL that covers all reasons.
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
if (cert_dp.crl_issuer) {
|
||||
// Indirect CRLs are not supported.
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
const ParsedCertificate* target_cert = valid_chain[target_cert_index].get();
|
||||
|
||||
// 6.3.3 (a) Update the local CRL cache by obtaining a complete CRL, a
|
||||
// delta CRL, or both, as required.
|
||||
//
|
||||
// This implementation only supports complete CRLs and takes the CRL as
|
||||
// input, it is up to the caller to provide an up to date CRL.
|
||||
|
||||
der::Input tbs_cert_list_tlv;
|
||||
der::Input signature_algorithm_tlv;
|
||||
der::BitString signature_value;
|
||||
if (!ParseCrlCertificateList(der::Input(raw_crl), &tbs_cert_list_tlv,
|
||||
&signature_algorithm_tlv, &signature_value)) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
ParsedCrlTbsCertList tbs_cert_list;
|
||||
if (!ParseCrlTbsCertList(tbs_cert_list_tlv, &tbs_cert_list))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// 5.1.1.2 signatureAlgorithm
|
||||
//
|
||||
// TODO(https://crbug.com/749276): Check the signature algorithm against
|
||||
// policy.
|
||||
std::optional<SignatureAlgorithm> signature_algorithm =
|
||||
ParseSignatureAlgorithm(signature_algorithm_tlv);
|
||||
if (!signature_algorithm) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
// This field MUST contain the same algorithm identifier as the
|
||||
// signature field in the sequence tbsCertList (Section 5.1.2.2).
|
||||
std::optional<SignatureAlgorithm> tbs_alg =
|
||||
ParseSignatureAlgorithm(tbs_cert_list.signature_algorithm_tlv);
|
||||
if (!tbs_alg || *signature_algorithm != *tbs_alg) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
// Check CRL dates. Roughly corresponds to 6.3.3 (a) (1) but does not attempt
|
||||
// to update the CRL if it is out of date.
|
||||
if (!CheckRevocationDateValid(tbs_cert_list.this_update,
|
||||
tbs_cert_list.next_update.has_value()
|
||||
? &tbs_cert_list.next_update.value()
|
||||
: nullptr,
|
||||
verify_time_epoch_seconds, max_age_seconds)) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
// 6.3.3 (a) (2) is skipped: This implementation does not support delta CRLs.
|
||||
|
||||
// 6.3.3 (b) Verify the issuer and scope of the complete CRL as follows:
|
||||
// 6.3.3 (b) (1) If the DP includes cRLIssuer, then verify that the issuer
|
||||
// field in the complete CRL matches cRLIssuer in the DP and
|
||||
// that the complete CRL contains an issuing distribution
|
||||
// point extension with the indirectCRL boolean asserted.
|
||||
//
|
||||
// Nothing is done here since distribution points with crlIssuer were skipped
|
||||
// above.
|
||||
|
||||
// 6.3.3 (b) (1) Otherwise, verify that the CRL issuer matches the
|
||||
// certificate issuer.
|
||||
//
|
||||
// Normalization for the name comparison is used although the RFC is not
|
||||
// clear on this. There are several places that explicitly are called out as
|
||||
// requiring identical encodings:
|
||||
//
|
||||
// 4.2.1.13. CRL Distribution Points (cert extension) says the DP cRLIssuer
|
||||
// field MUST be exactly the same as the encoding in issuer field of the
|
||||
// CRL.
|
||||
//
|
||||
// 5.2.5. Issuing Distribution Point (crl extension)
|
||||
// The identical encoding MUST be used in the distributionPoint fields
|
||||
// of the certificate and the CRL.
|
||||
//
|
||||
// 5.3.3. Certificate Issuer (crl entry extension) also says "The encoding of
|
||||
// the DN MUST be identical to the encoding used in the certificate"
|
||||
//
|
||||
// But 6.3.3 (b) (1) just says "matches". Also NIST PKITS includes at least
|
||||
// one test that requires normalization here.
|
||||
// TODO(https://crbug.com/749276): could do exact comparison first and only
|
||||
// fall back to normalizing if that fails.
|
||||
std::string normalized_crl_issuer;
|
||||
if (!NormalizeNameTLV(tbs_cert_list.issuer_tlv, &normalized_crl_issuer))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
if (der::Input(&normalized_crl_issuer) != target_cert->normalized_issuer())
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
if (tbs_cert_list.crl_extensions_tlv.has_value()) {
|
||||
std::map<der::Input, ParsedExtension> extensions;
|
||||
if (!ParseExtensions(*tbs_cert_list.crl_extensions_tlv, &extensions))
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
|
||||
// 6.3.3 (b) (2) If the complete CRL includes an issuing distribution point
|
||||
// (IDP) CRL extension, check the following:
|
||||
ParsedExtension idp_extension;
|
||||
if (ConsumeExtension(der::Input(kIssuingDistributionPointOid), &extensions,
|
||||
&idp_extension)) {
|
||||
std::unique_ptr<GeneralNames> distribution_point_names;
|
||||
ContainedCertsType only_contains_cert_type;
|
||||
if (!ParseIssuingDistributionPoint(idp_extension.value,
|
||||
&distribution_point_names,
|
||||
&only_contains_cert_type)) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
if (distribution_point_names) {
|
||||
// 6.3.3. (b) (2) (i) If the distribution point name is present in the
|
||||
// IDP CRL extension and the distribution field is
|
||||
// present in the DP, then verify that one of the
|
||||
// names in the IDP matches one of the names in the
|
||||
// DP.
|
||||
// 5.2.5. The identical encoding MUST be used in the distributionPoint
|
||||
// fields of the certificate and the CRL.
|
||||
// TODO(https://crbug.com/749276): Check other name types?
|
||||
if (!cert_dp.distribution_point_fullname ||
|
||||
!ContainsExactMatchingName(
|
||||
cert_dp.distribution_point_fullname
|
||||
->uniform_resource_identifiers,
|
||||
distribution_point_names->uniform_resource_identifiers)) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
// 6.3.3. (b) (2) (i) If the distribution point name is present in the
|
||||
// IDP CRL extension and the distribution field is
|
||||
// omitted from the DP, then verify that one of the
|
||||
// names in the IDP matches one of the names in the
|
||||
// cRLIssuer field of the DP.
|
||||
// Indirect CRLs are not supported, if indirectCRL was specified,
|
||||
// ParseIssuingDistributionPoint would already have failed.
|
||||
}
|
||||
|
||||
switch (only_contains_cert_type) {
|
||||
case ContainedCertsType::USER_CERTS:
|
||||
// 6.3.3. (b) (2) (ii) If the onlyContainsUserCerts boolean is
|
||||
// asserted in the IDP CRL extension, verify
|
||||
// that the certificate does not include the
|
||||
// basic constraints extension with the cA
|
||||
// boolean asserted.
|
||||
// 5.2.5. If either onlyContainsUserCerts or onlyContainsCACerts is
|
||||
// set to TRUE, then the scope of the CRL MUST NOT include any
|
||||
// version 1 or version 2 certificates.
|
||||
if ((target_cert->has_basic_constraints() &&
|
||||
target_cert->basic_constraints().is_ca) ||
|
||||
target_cert->tbs().version == CertificateVersion::V1 ||
|
||||
target_cert->tbs().version == CertificateVersion::V2) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
break;
|
||||
|
||||
case ContainedCertsType::CA_CERTS:
|
||||
// 6.3.3. (b) (2) (iii) If the onlyContainsCACerts boolean is asserted
|
||||
// in the IDP CRL extension, verify that the
|
||||
// certificate includes the basic constraints
|
||||
// extension with the cA boolean asserted.
|
||||
// The version check is not done here, as the basicConstraints
|
||||
// extension is required, and could not be present unless it is a V3
|
||||
// certificate.
|
||||
if (!target_cert->has_basic_constraints() ||
|
||||
!target_cert->basic_constraints().is_ca) {
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
break;
|
||||
|
||||
case ContainedCertsType::ANY_CERTS:
|
||||
// (iv) Verify that the onlyContainsAttributeCerts
|
||||
// boolean is not asserted.
|
||||
// If onlyContainsAttributeCerts was present,
|
||||
// ParseIssuingDistributionPoint would already have failed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& ext : extensions) {
|
||||
// Fail if any unhandled critical CRL extensions are present.
|
||||
if (ext.second.critical)
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// 6.3.3 (c-e) skipped: delta CRLs and reason codes are not supported.
|
||||
|
||||
// This implementation only supports direct CRLs where the CRL was signed by
|
||||
// one of the certs in its validated issuer chain. This allows handling some
|
||||
// cases of key rollover without requiring additional CRL issuer cert
|
||||
// discovery & path building.
|
||||
// TODO(https://crbug.com/749276): should this loop start at
|
||||
// |target_cert_index|? There doesn't seem to be anything in the specs that
|
||||
// precludes a CRL signed by a self-issued cert from covering itself. On the
|
||||
// other hand it seems like a pretty weird thing to allow and causes NIST
|
||||
// PKITS 4.5.3 to pass when it seems like it would not be intended to (since
|
||||
// issuingDistributionPoint CRL extension is not handled).
|
||||
for (size_t i = target_cert_index + 1; i < valid_chain.size(); ++i) {
|
||||
const ParsedCertificate* issuer_cert = valid_chain[i].get();
|
||||
|
||||
// 6.3.3 (f) Obtain and validate the certification path for the issuer of
|
||||
// the complete CRL. The trust anchor for the certification
|
||||
// path MUST be the same as the trust anchor used to validate
|
||||
// the target certificate.
|
||||
//
|
||||
// As the |issuer_cert| is from the already validated chain, it is already
|
||||
// known to chain to the same trust anchor as the target certificate.
|
||||
if (der::Input(&normalized_crl_issuer) != issuer_cert->normalized_subject())
|
||||
continue;
|
||||
|
||||
// 6.3.3 (f) If a key usage extension is present in the CRL issuer's
|
||||
// certificate, verify that the cRLSign bit is set.
|
||||
if (issuer_cert->has_key_usage() &&
|
||||
!issuer_cert->key_usage().AssertsBit(KEY_USAGE_BIT_CRL_SIGN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 6.3.3 (g) Validate the signature on the complete CRL using the public
|
||||
// key validated in step (f).
|
||||
if (!VerifySignedData(*signature_algorithm, tbs_cert_list_tlv,
|
||||
signature_value, issuer_cert->tbs().spki_tlv,
|
||||
/*cache=*/nullptr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 6.3.3 (h,i) skipped. This implementation does not support delta CRLs.
|
||||
|
||||
// 6.3.3 (j) If (cert_status is UNREVOKED), then search for the
|
||||
// certificate on the complete CRL. If an entry is found that
|
||||
// matches the certificate issuer and serial number as described
|
||||
// in Section 5.3.3, then set the cert_status variable to the
|
||||
// indicated reason as described in step (i).
|
||||
//
|
||||
// CRL is valid and covers |target_cert|, check if |target_cert| is present
|
||||
// in the revokedCertificates sequence.
|
||||
return GetCRLStatusForCert(target_cert->tbs().serial_number,
|
||||
tbs_cert_list.version,
|
||||
tbs_cert_list.revoked_certificates_tlv);
|
||||
|
||||
// 6.3.3 (k,l) skipped. This implementation does not support reason codes.
|
||||
}
|
||||
|
||||
// Did not find the issuer & signer of |raw_crl| in |valid_chain|.
|
||||
return CRLRevocationStatus::UNKNOWN;
|
||||
}
|
||||
|
||||
} // namespace net
|
225
pki/crl.h
Normal file
225
pki/crl.h
Normal file
@ -0,0 +1,225 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_CRL_H_
|
||||
#define BSSL_PKI_CRL_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
|
||||
#include "general_names.h"
|
||||
#include "parsed_certificate.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
struct ParsedCrlTbsCertList;
|
||||
struct ParsedDistributionPoint;
|
||||
|
||||
// TODO(https://crbug.com/749276): This is the same enum with the same meaning
|
||||
// as OCSPRevocationStatus, maybe they should be merged?
|
||||
enum class CRLRevocationStatus {
|
||||
GOOD = 0,
|
||||
REVOKED = 1,
|
||||
UNKNOWN = 2,
|
||||
MAX_VALUE = UNKNOWN
|
||||
};
|
||||
|
||||
// Parses a DER-encoded CRL "CertificateList" as specified by RFC 5280 Section
|
||||
// 5.1. Returns true on success and sets the results in the |out_*| parameters.
|
||||
// The contents of the output data is not validated.
|
||||
//
|
||||
// Note that on success the out parameters alias data from the input |crl_tlv|.
|
||||
// Hence the output values are only valid as long as |crl_tlv| remains valid.
|
||||
//
|
||||
// On failure the out parameters have an undefined state. Some of them may have
|
||||
// been updated during parsing, whereas others may not have been changed.
|
||||
//
|
||||
// CertificateList ::= SEQUENCE {
|
||||
// tbsCertList TBSCertList,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseCrlCertificateList(
|
||||
const der::Input& crl_tlv,
|
||||
der::Input* out_tbs_cert_list_tlv,
|
||||
der::Input* out_signature_algorithm_tlv,
|
||||
der::BitString* out_signature_value);
|
||||
|
||||
// Parses a DER-encoded "TBSCertList" as specified by RFC 5280 Section 5.1.
|
||||
// Returns true on success and sets the results in |out|.
|
||||
//
|
||||
// Note that on success |out| aliases data from the input |tbs_tlv|.
|
||||
// Hence the fields of the ParsedCrlTbsCertList are only valid as long as
|
||||
// |tbs_tlv| remains valid.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
//
|
||||
// Refer to the per-field documentation of ParsedCrlTbsCertList for details on
|
||||
// what validity checks parsing performs.
|
||||
//
|
||||
// TBSCertList ::= SEQUENCE {
|
||||
// version Version OPTIONAL,
|
||||
// -- if present, MUST be v2
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// thisUpdate Time,
|
||||
// nextUpdate Time OPTIONAL,
|
||||
// revokedCertificates SEQUENCE OF SEQUENCE {
|
||||
// userCertificate CertificateSerialNumber,
|
||||
// revocationDate Time,
|
||||
// crlEntryExtensions Extensions OPTIONAL
|
||||
// -- if present, version MUST be v2
|
||||
// } OPTIONAL,
|
||||
// crlExtensions [0] EXPLICIT Extensions OPTIONAL
|
||||
// -- if present, version MUST be v2
|
||||
// }
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseCrlTbsCertList(
|
||||
const der::Input& tbs_tlv,
|
||||
ParsedCrlTbsCertList* out);
|
||||
|
||||
// Represents a CRL "Version" from RFC 5280. TBSCertList reuses the same
|
||||
// Version definition from TBSCertificate, however only v1(not present) and
|
||||
// v2(1) are valid values, so a unique enum is used to avoid confusion.
|
||||
enum class CrlVersion {
|
||||
V1,
|
||||
V2,
|
||||
};
|
||||
|
||||
// Corresponds with "TBSCertList" from RFC 5280 Section 5.1:
|
||||
struct OPENSSL_EXPORT ParsedCrlTbsCertList {
|
||||
ParsedCrlTbsCertList();
|
||||
~ParsedCrlTbsCertList();
|
||||
|
||||
// version Version OPTIONAL,
|
||||
// -- if present, MUST be v2
|
||||
//
|
||||
// Parsing guarantees that the version is one of v1 or v2.
|
||||
CrlVersion version = CrlVersion::V1;
|
||||
|
||||
// signature AlgorithmIdentifier,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
//
|
||||
// This can be further parsed using SignatureValue::Create().
|
||||
der::Input signature_algorithm_tlv;
|
||||
|
||||
// issuer Name,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
der::Input issuer_tlv;
|
||||
|
||||
// thisUpdate Time,
|
||||
// nextUpdate Time OPTIONAL,
|
||||
//
|
||||
// Parsing guarantees that thisUpdate and nextUpdate(if present) are valid
|
||||
// DER-encoded dates, however it DOES NOT guarantee anything about their
|
||||
// values. For instance notAfter could be before notBefore, or the dates
|
||||
// could indicate an expired CRL.
|
||||
der::GeneralizedTime this_update;
|
||||
std::optional<der::GeneralizedTime> next_update;
|
||||
|
||||
// revokedCertificates SEQUENCE OF SEQUENCE {
|
||||
// userCertificate CertificateSerialNumber,
|
||||
// revocationDate Time,
|
||||
// crlEntryExtensions Extensions OPTIONAL
|
||||
// -- if present, version MUST be v2
|
||||
// } OPTIONAL,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
std::optional<der::Input> revoked_certificates_tlv;
|
||||
|
||||
// crlExtensions [0] EXPLICIT Extensions OPTIONAL
|
||||
// -- if present, version MUST be v2
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE. (Note that the
|
||||
// EXPLICIT outer tag is stripped.)
|
||||
//
|
||||
// Parsing guarantees that if extensions is present the version is v2.
|
||||
std::optional<der::Input> crl_extensions_tlv;
|
||||
};
|
||||
|
||||
// Represents the IssuingDistributionPoint certificate type constraints:
|
||||
enum class ContainedCertsType {
|
||||
// Neither onlyContainsUserCerts or onlyContainsCACerts was present.
|
||||
ANY_CERTS,
|
||||
// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
|
||||
USER_CERTS,
|
||||
// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
|
||||
CA_CERTS,
|
||||
};
|
||||
|
||||
// Parses a DER-encoded IssuingDistributionPoint extension value.
|
||||
// Returns true on success and sets the results in the |out_*| parameters.
|
||||
//
|
||||
// If the IssuingDistributionPoint contains a distributionPoint fullName field,
|
||||
// |out_distribution_point_names| will contain the parsed representation.
|
||||
// If the distributionPoint type is nameRelativeToCRLIssuer, parsing will fail.
|
||||
//
|
||||
// |out_only_contains_cert_type| will contain the logical representation of the
|
||||
// onlyContainsUserCerts and onlyContainsCACerts fields (or their absence).
|
||||
//
|
||||
// indirectCRL and onlyContainsAttributeCerts are not supported and parsing will
|
||||
// fail if they are present.
|
||||
//
|
||||
// Note that on success |out_distribution_point_names| aliases data from the
|
||||
// input |extension_value|.
|
||||
//
|
||||
// On failure the |out_*| parameters have undefined state.
|
||||
//
|
||||
// IssuingDistributionPoint ::= SEQUENCE {
|
||||
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||
// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
|
||||
// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
|
||||
// onlySomeReasons [3] ReasonFlags OPTIONAL,
|
||||
// indirectCRL [4] BOOLEAN DEFAULT FALSE,
|
||||
// onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseIssuingDistributionPoint(
|
||||
const der::Input& extension_value,
|
||||
std::unique_ptr<GeneralNames>* out_distribution_point_names,
|
||||
ContainedCertsType* out_only_contains_cert_type);
|
||||
|
||||
OPENSSL_EXPORT CRLRevocationStatus
|
||||
GetCRLStatusForCert(const der::Input& cert_serial,
|
||||
CrlVersion crl_version,
|
||||
const std::optional<der::Input>& revoked_certificates_tlv);
|
||||
|
||||
// Checks the revocation status of the certificate |cert| by using the
|
||||
// DER-encoded |raw_crl|. |cert| must already have passed certificate path
|
||||
// validation.
|
||||
//
|
||||
// Returns GOOD if the CRL indicates the certificate is not revoked,
|
||||
// REVOKED if it indicates it is revoked, or UNKNOWN for all other cases.
|
||||
//
|
||||
// * |raw_crl|: A DER encoded CRL CertificateList.
|
||||
// * |valid_chain|: The validated certificate chain containing the target cert.
|
||||
// * |target_cert_index|: The index into |valid_chain| of the certificate being
|
||||
// checked for revocation.
|
||||
// * |cert_dp|: The distribution point from the target certificate's CRL
|
||||
// distribution points extension that |raw_crl| corresponds to. If
|
||||
// |raw_crl| was not specified in a distribution point, the caller must
|
||||
// synthesize a ParsedDistributionPoint object as specified by RFC 5280
|
||||
// 6.3.3.
|
||||
// * |verify_time_epoch_seconds|: The time as the difference in seconds from
|
||||
// the POSIX epoch to use when checking revocation status.
|
||||
// * |max_age_seconds|: If present, the maximum age in seconds for a CRL,
|
||||
// implemented as time since the |thisUpdate| field in the CRL
|
||||
// TBSCertList. Responses older than |max_age_seconds| will be
|
||||
// considered invalid.
|
||||
[[nodiscard]] OPENSSL_EXPORT CRLRevocationStatus
|
||||
CheckCRL(std::string_view raw_crl,
|
||||
const ParsedCertificateList& valid_chain,
|
||||
size_t target_cert_index,
|
||||
const ParsedDistributionPoint& cert_dp,
|
||||
int64_t verify_time_epoch_seconds,
|
||||
std::optional<int64_t> max_age_seconds);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_CRL_H_
|
103
pki/encode_values.cc
Normal file
103
pki/encode_values.cc
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "encode_values.h"
|
||||
|
||||
#include "parse_values.h"
|
||||
|
||||
#include <openssl/time.h>
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
namespace {
|
||||
|
||||
bool WriteFourDigit(uint16_t value, uint8_t out[4]) {
|
||||
if (value >= 10000)
|
||||
return false;
|
||||
out[3] = '0' + (value % 10);
|
||||
value /= 10;
|
||||
out[2] = '0' + (value % 10);
|
||||
value /= 10;
|
||||
out[1] = '0' + (value % 10);
|
||||
value /= 10;
|
||||
out[0] = '0' + value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteTwoDigit(uint8_t value, uint8_t out[2]) {
|
||||
if (value >= 100)
|
||||
return false;
|
||||
out[0] = '0' + (value / 10);
|
||||
out[1] = '0' + (value % 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EncodePosixTimeAsGeneralizedTime(int64_t posix_time,
|
||||
GeneralizedTime* generalized_time) {
|
||||
struct tm tmp_tm;
|
||||
if (!OPENSSL_posix_to_tm(posix_time, &tmp_tm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
generalized_time->year = tmp_tm.tm_year + 1900;
|
||||
generalized_time->month = tmp_tm.tm_mon + 1;
|
||||
generalized_time->day = tmp_tm.tm_mday;
|
||||
generalized_time->hours = tmp_tm.tm_hour;
|
||||
generalized_time->minutes = tmp_tm.tm_min;
|
||||
generalized_time->seconds = tmp_tm.tm_sec;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeneralizedTimeToPosixTime(const der::GeneralizedTime& generalized,
|
||||
int64_t* result) {
|
||||
struct tm tmp_tm;
|
||||
tmp_tm.tm_year = generalized.year - 1900;
|
||||
tmp_tm.tm_mon = generalized.month - 1;
|
||||
tmp_tm.tm_mday = generalized.day;
|
||||
tmp_tm.tm_hour = generalized.hours;
|
||||
tmp_tm.tm_min = generalized.minutes;
|
||||
tmp_tm.tm_sec = generalized.seconds;
|
||||
// BoringSSL POSIX time, like POSIX itself, does not support leap seconds.
|
||||
// Collapse to previous second.
|
||||
if (tmp_tm.tm_sec == 60) {
|
||||
tmp_tm.tm_sec = 59;
|
||||
}
|
||||
return OPENSSL_tm_to_posix(&tmp_tm, result);
|
||||
}
|
||||
|
||||
bool EncodeGeneralizedTime(const GeneralizedTime& time,
|
||||
uint8_t out[kGeneralizedTimeLength]) {
|
||||
if (!WriteFourDigit(time.year, out) || !WriteTwoDigit(time.month, out + 4) ||
|
||||
!WriteTwoDigit(time.day, out + 6) ||
|
||||
!WriteTwoDigit(time.hours, out + 8) ||
|
||||
!WriteTwoDigit(time.minutes, out + 10) ||
|
||||
!WriteTwoDigit(time.seconds, out + 12)) {
|
||||
return false;
|
||||
}
|
||||
out[14] = 'Z';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EncodeUTCTime(const GeneralizedTime& time, uint8_t out[kUTCTimeLength]) {
|
||||
if (!time.InUTCTimeRange())
|
||||
return false;
|
||||
|
||||
uint16_t year = time.year - 1900;
|
||||
if (year >= 100)
|
||||
year -= 100;
|
||||
|
||||
if (!WriteTwoDigit(year, out) || !WriteTwoDigit(time.month, out + 2) ||
|
||||
!WriteTwoDigit(time.day, out + 4) ||
|
||||
!WriteTwoDigit(time.hours, out + 6) ||
|
||||
!WriteTwoDigit(time.minutes, out + 8) ||
|
||||
!WriteTwoDigit(time.seconds, out + 10)) {
|
||||
return false;
|
||||
}
|
||||
out[12] = 'Z';
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace bssl::der
|
48
pki/encode_values.h
Normal file
48
pki/encode_values.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_DER_ENCODE_VALUES_H_
|
||||
#define BSSL_DER_ENCODE_VALUES_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
struct GeneralizedTime;
|
||||
|
||||
// Encodes |posix_time|, a posix time in seconds, to DER |generalized_time|, for
|
||||
// comparing against other GeneralizedTime objects, returning true on success or
|
||||
// false if |posix_time| is outside of the range from year 0000 to 9999.
|
||||
OPENSSL_EXPORT bool EncodePosixTimeAsGeneralizedTime(
|
||||
int64_t posix_time,
|
||||
GeneralizedTime* generalized_time);
|
||||
|
||||
// Converts a GeneralizedTime struct to a posix time in seconds in |result|,
|
||||
// returning true on success or false if |generalized| was invalid or cannot be
|
||||
// represented as a posix time in the range from the year 0000 to 9999.
|
||||
OPENSSL_EXPORT bool GeneralizedTimeToPosixTime(
|
||||
const der::GeneralizedTime& generalized,
|
||||
int64_t* result);
|
||||
|
||||
static const size_t kGeneralizedTimeLength = 15;
|
||||
|
||||
// Encodes |time| to |out| as a DER GeneralizedTime value. Returns true on
|
||||
// success and false on error.
|
||||
OPENSSL_EXPORT bool EncodeGeneralizedTime(const GeneralizedTime& time,
|
||||
uint8_t out[kGeneralizedTimeLength]);
|
||||
|
||||
static const size_t kUTCTimeLength = 13;
|
||||
|
||||
// Encodes |time| to |out| as a DER UTCTime value. Returns true on success and
|
||||
// false on error.
|
||||
OPENSSL_EXPORT bool EncodeUTCTime(const GeneralizedTime& time,
|
||||
uint8_t out[kUTCTimeLength]);
|
||||
|
||||
} // namespace bssl::der
|
||||
|
||||
#endif // BSSL_DER_ENCODE_VALUES_H_
|
169
pki/encode_values_unittest.cc
Normal file
169
pki/encode_values_unittest.cc
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "encode_values.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "parse_values.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl::der::test {
|
||||
|
||||
namespace {
|
||||
|
||||
template <size_t N>
|
||||
std::string_view ToStringPiece(const uint8_t (&data)[N]) {
|
||||
return std::string_view(reinterpret_cast<const char*>(data), N);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(EncodeValuesTest, EncodePosixTimeAsGeneralizedTime) {
|
||||
// Fri, 24 Jun 2016 17:04:54 GMT
|
||||
int64_t time = 1466787894;
|
||||
GeneralizedTime generalized_time;
|
||||
ASSERT_TRUE(EncodePosixTimeAsGeneralizedTime(time, &generalized_time));
|
||||
EXPECT_EQ(2016, generalized_time.year);
|
||||
EXPECT_EQ(6, generalized_time.month);
|
||||
EXPECT_EQ(24, generalized_time.day);
|
||||
EXPECT_EQ(17, generalized_time.hours);
|
||||
EXPECT_EQ(4, generalized_time.minutes);
|
||||
EXPECT_EQ(54, generalized_time.seconds);
|
||||
}
|
||||
|
||||
TEST(EncodeValuesTest, GeneralizedTimeToPosixTime) {
|
||||
GeneralizedTime generalized_time;
|
||||
generalized_time.year = 2016;
|
||||
generalized_time.month = 6;
|
||||
generalized_time.day = 24;
|
||||
generalized_time.hours = 17;
|
||||
generalized_time.minutes = 4;
|
||||
generalized_time.seconds = 54;
|
||||
int64_t time;
|
||||
ASSERT_TRUE(GeneralizedTimeToPosixTime(generalized_time, &time));
|
||||
EXPECT_EQ(1466787894, time);
|
||||
}
|
||||
|
||||
// As Posix times use BoringSSL's POSIX time routines underneath the covers
|
||||
// these should not have any issues on 32 bit platforms.
|
||||
TEST(EncodeValuesTest, GeneralizedTimeToPosixTimeAfter32BitPosixMaxYear) {
|
||||
GeneralizedTime generalized_time;
|
||||
generalized_time.year = 2039;
|
||||
generalized_time.month = 1;
|
||||
generalized_time.day = 1;
|
||||
generalized_time.hours = 0;
|
||||
generalized_time.minutes = 0;
|
||||
generalized_time.seconds = 0;
|
||||
int64_t time;
|
||||
ASSERT_TRUE(GeneralizedTimeToPosixTime(generalized_time, &time));
|
||||
|
||||
generalized_time.day = 0; // Invalid day of month should fail.
|
||||
EXPECT_FALSE(GeneralizedTimeToPosixTime(generalized_time, &time));
|
||||
}
|
||||
|
||||
TEST(EncodeValuesTest, EncodeGeneralizedTime) {
|
||||
GeneralizedTime time;
|
||||
time.year = 2014;
|
||||
time.month = 12;
|
||||
time.day = 18;
|
||||
time.hours = 16;
|
||||
time.minutes = 12;
|
||||
time.seconds = 59;
|
||||
|
||||
// Encode a time where no components have leading zeros.
|
||||
uint8_t out[kGeneralizedTimeLength];
|
||||
ASSERT_TRUE(EncodeGeneralizedTime(time, out));
|
||||
EXPECT_EQ("20141218161259Z", ToStringPiece(out));
|
||||
|
||||
// Test bounds on all components. Note the encoding function does not validate
|
||||
// the input is a valid time, only that it is encodable.
|
||||
time.year = 0;
|
||||
time.month = 0;
|
||||
time.day = 0;
|
||||
time.hours = 0;
|
||||
time.minutes = 0;
|
||||
time.seconds = 0;
|
||||
ASSERT_TRUE(EncodeGeneralizedTime(time, out));
|
||||
EXPECT_EQ("00000000000000Z", ToStringPiece(out));
|
||||
|
||||
time.year = 9999;
|
||||
time.month = 99;
|
||||
time.day = 99;
|
||||
time.hours = 99;
|
||||
time.minutes = 99;
|
||||
time.seconds = 99;
|
||||
ASSERT_TRUE(EncodeGeneralizedTime(time, out));
|
||||
EXPECT_EQ("99999999999999Z", ToStringPiece(out));
|
||||
|
||||
time.year = 10000;
|
||||
EXPECT_FALSE(EncodeGeneralizedTime(time, out));
|
||||
|
||||
time.year = 2000;
|
||||
time.month = 100;
|
||||
EXPECT_FALSE(EncodeGeneralizedTime(time, out));
|
||||
}
|
||||
|
||||
TEST(EncodeValuesTest, EncodeUTCTime) {
|
||||
GeneralizedTime time;
|
||||
time.year = 2014;
|
||||
time.month = 12;
|
||||
time.day = 18;
|
||||
time.hours = 16;
|
||||
time.minutes = 12;
|
||||
time.seconds = 59;
|
||||
|
||||
// Encode a time where no components have leading zeros.
|
||||
uint8_t out[kUTCTimeLength];
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("141218161259Z", ToStringPiece(out));
|
||||
|
||||
time.year = 2049;
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("491218161259Z", ToStringPiece(out));
|
||||
|
||||
time.year = 2000;
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("001218161259Z", ToStringPiece(out));
|
||||
|
||||
time.year = 1999;
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("991218161259Z", ToStringPiece(out));
|
||||
|
||||
time.year = 1950;
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("501218161259Z", ToStringPiece(out));
|
||||
|
||||
time.year = 2050;
|
||||
EXPECT_FALSE(EncodeUTCTime(time, out));
|
||||
|
||||
time.year = 1949;
|
||||
EXPECT_FALSE(EncodeUTCTime(time, out));
|
||||
|
||||
// Test bounds on all components. Note the encoding function does not validate
|
||||
// the input is a valid time, only that it is encodable.
|
||||
time.year = 2000;
|
||||
time.month = 0;
|
||||
time.day = 0;
|
||||
time.hours = 0;
|
||||
time.minutes = 0;
|
||||
time.seconds = 0;
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("000000000000Z", ToStringPiece(out));
|
||||
|
||||
time.year = 1999;
|
||||
time.month = 99;
|
||||
time.day = 99;
|
||||
time.hours = 99;
|
||||
time.minutes = 99;
|
||||
time.seconds = 99;
|
||||
ASSERT_TRUE(EncodeUTCTime(time, out));
|
||||
EXPECT_EQ("999999999999Z", ToStringPiece(out));
|
||||
|
||||
time.year = 2000;
|
||||
time.month = 100;
|
||||
EXPECT_FALSE(EncodeUTCTime(time, out));
|
||||
}
|
||||
|
||||
} // namespace bssl::der::test
|
40
pki/extended_key_usage.cc
Normal file
40
pki/extended_key_usage.cc
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "extended_key_usage.h"
|
||||
|
||||
#include "input.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
bool ParseEKUExtension(const der::Input& extension_value,
|
||||
std::vector<der::Input>* eku_oids) {
|
||||
der::Parser extension_parser(extension_value);
|
||||
der::Parser sequence_parser;
|
||||
if (!extension_parser.ReadSequence(&sequence_parser))
|
||||
return false;
|
||||
|
||||
// Section 4.2.1.12 of RFC 5280 defines ExtKeyUsageSyntax as:
|
||||
// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
|
||||
//
|
||||
// Therefore, the sequence must contain at least one KeyPurposeId.
|
||||
if (!sequence_parser.HasMore())
|
||||
return false;
|
||||
while (sequence_parser.HasMore()) {
|
||||
der::Input eku_oid;
|
||||
if (!sequence_parser.ReadTag(der::kOid, &eku_oid))
|
||||
// The SEQUENCE OF must contain only KeyPurposeIds (OIDs).
|
||||
return false;
|
||||
eku_oids->push_back(eku_oid);
|
||||
}
|
||||
if (extension_parser.HasMore())
|
||||
// The extension value must follow ExtKeyUsageSyntax - there is no way that
|
||||
// it could be extended to allow for something after the SEQUENCE OF.
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
89
pki/extended_key_usage.h
Normal file
89
pki/extended_key_usage.h
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_EXTENDED_KEY_USAGE_H_
|
||||
#define BSSL_PKI_EXTENDED_KEY_USAGE_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "input.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// The arc for the anyExtendedKeyUsage OID is found under the id-ce arc,
|
||||
// defined in section 4.2.1 of RFC 5280:
|
||||
// id-ce OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 29 }
|
||||
//
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
|
||||
// anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 }
|
||||
// In dotted notation: 2.5.29.37.0
|
||||
inline constexpr uint8_t kAnyEKU[] = {0x55, 0x1d, 0x25, 0x00};
|
||||
|
||||
// All other key usage purposes defined in RFC 5280 are found in the id-kp
|
||||
// arc, defined in section 4.2.1.12 as:
|
||||
// id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
|
||||
//
|
||||
// With id-pkix defined in RFC 5280 section 4.2.2 as:
|
||||
// id-pkix OBJECT IDENTIFIER ::=
|
||||
// { iso(1) identified-organization(3) dod(6) internet(1)
|
||||
// security(5) mechanisms(5) pkix(7) }
|
||||
//
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
|
||||
// In dotted notation: 1.3.6.1.5.5.7.3.1
|
||||
inline constexpr uint8_t kServerAuth[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x03, 0x01};
|
||||
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
|
||||
// In dotted notation: 1.3.6.1.5.5.7.3.2
|
||||
inline constexpr uint8_t kClientAuth[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x03, 0x02};
|
||||
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
|
||||
// In dotted notation: 1.3.6.1.5.5.7.3.3
|
||||
inline constexpr uint8_t kCodeSigning[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x03, 0x03};
|
||||
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
|
||||
// In dotted notation: 1.3.6.1.5.5.7.3.4
|
||||
inline constexpr uint8_t kEmailProtection[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x03, 0x04};
|
||||
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 }
|
||||
// In dotted notation: 1.3.6.1.5.5.7.3.8
|
||||
inline constexpr uint8_t kTimeStamping[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x03, 0x08};
|
||||
|
||||
// From RFC 5280 section 4.2.1.12:
|
||||
// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
|
||||
// In dotted notation: 1.3.6.1.5.5.7.3.9
|
||||
inline constexpr uint8_t kOCSPSigning[] = {0x2b, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x03, 0x09};
|
||||
|
||||
// Netscape Server Gated Crypto (2.16.840.1.113730.4.1) is a deprecated OID
|
||||
// which in some situations is considered equivalent to the serverAuth key
|
||||
// purpose.
|
||||
inline constexpr uint8_t kNetscapeServerGatedCrypto[] = {
|
||||
0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01};
|
||||
|
||||
// Parses |extension_value|, which contains the extnValue field of an X.509v3
|
||||
// Extended Key Usage extension, and populates |eku_oids| with the list of
|
||||
// DER-encoded OID values (that is, without tag and length). Returns false if
|
||||
// |extension_value| is improperly encoded.
|
||||
//
|
||||
// Note: The returned OIDs are only as valid as long as the data pointed to by
|
||||
// |extension_value| is valid.
|
||||
OPENSSL_EXPORT bool ParseEKUExtension(const der::Input& extension_value,
|
||||
std::vector<der::Input>* eku_oids);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_EXTENDED_KEY_USAGE_H_
|
166
pki/extended_key_usage_unittest.cc
Normal file
166
pki/extended_key_usage_unittest.cc
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "extended_key_usage.h"
|
||||
#include "input.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper method to check if an EKU is present in a std::vector of EKUs.
|
||||
bool HasEKU(const std::vector<der::Input>& list, const der::Input& eku) {
|
||||
for (const auto& oid : list) {
|
||||
if (oid == eku)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that we can read multiple EKUs from an extension.
|
||||
TEST(ExtendedKeyUsageTest, ParseEKUExtension) {
|
||||
// clang-format off
|
||||
const uint8_t raw_extension_value[] = {
|
||||
0x30, 0x14, // SEQUENCE (20 bytes)
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, // 1.3.6.1.5.5.7.3.1
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02 // 1.3.6.1.5.5.7.3.2
|
||||
// end of SEQUENCE
|
||||
};
|
||||
// clang-format on
|
||||
der::Input extension_value(raw_extension_value);
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_TRUE(ParseEKUExtension(extension_value, &ekus));
|
||||
|
||||
EXPECT_EQ(2u, ekus.size());
|
||||
EXPECT_TRUE(HasEKU(ekus, der::Input(kServerAuth)));
|
||||
EXPECT_TRUE(HasEKU(ekus, der::Input(kClientAuth)));
|
||||
}
|
||||
|
||||
// Check that an extension with the same OID present multiple times doesn't
|
||||
// cause an error.
|
||||
TEST(ExtendedKeyUsageTest, RepeatedOid) {
|
||||
// clang-format off
|
||||
const uint8_t extension_bytes[] = {
|
||||
0x30, 0x14, // SEQUENCE (20 bytes)
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, // 1.3.6.1.5.5.7.3.1
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 // 1.3.6.1.5.5.7.3.1
|
||||
};
|
||||
// clang-format on
|
||||
der::Input extension(extension_bytes);
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_TRUE(ParseEKUExtension(extension, &ekus));
|
||||
EXPECT_EQ(2u, ekus.size());
|
||||
for (const auto& eku : ekus) {
|
||||
EXPECT_EQ(der::Input(kServerAuth), eku);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that parsing an EKU extension which contains a private OID doesn't
|
||||
// cause an error.
|
||||
TEST(ExtendedKeyUsageTest, ParseEKUExtensionGracefullyHandlesPrivateOids) {
|
||||
// clang-format off
|
||||
const uint8_t extension_bytes[] = {
|
||||
0x30, 0x13, // SEQUENCE (19 bytes)
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, // 1.3.6.1.5.5.7.3.1
|
||||
0x06, 0x07, // OBJECT IDENTIFIER (7 bytes)
|
||||
0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79 // 1.3.6.1.4.1.11129
|
||||
};
|
||||
// clang-format on
|
||||
der::Input extension(extension_bytes);
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_TRUE(ParseEKUExtension(extension, &ekus));
|
||||
EXPECT_EQ(2u, ekus.size());
|
||||
EXPECT_TRUE(HasEKU(ekus, der::Input(kServerAuth)));
|
||||
|
||||
const uint8_t google_oid[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79};
|
||||
der::Input google(google_oid);
|
||||
EXPECT_TRUE(HasEKU(ekus, google));
|
||||
}
|
||||
|
||||
// Test a variety of bad inputs.
|
||||
|
||||
// If the extension value has data following the sequence of oids, parsing it
|
||||
// should fail.
|
||||
TEST(ExtendedKeyUsageTest, ExtraData) {
|
||||
// clang-format off
|
||||
const uint8_t extra_data[] = {
|
||||
0x30, 0x14, // SEQUENCE (20 bytes)
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, // 1.3.6.1.5.5.7.3.1
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, // 1.3.6.1.5.5.7.3.2
|
||||
// end of SEQUENCE
|
||||
0x02, 0x01, // INTEGER (1 byte)
|
||||
0x01 // 1
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_FALSE(ParseEKUExtension(der::Input(extra_data), &ekus));
|
||||
}
|
||||
|
||||
// Check that ParseEKUExtension only accepts a sequence containing only oids.
|
||||
// This test case has an integer in the sequence (which should fail). A key
|
||||
// difference between this test case and ExtendedKeyUsageTest.ExtraData is where
|
||||
// the sequence ends - in this test case the integer is still part of the
|
||||
// sequence, while in ExtendedKeyUsageTest.ExtraData the integer is after the
|
||||
// sequence.
|
||||
TEST(ExtendedKeyUsageTest, NotAnOid) {
|
||||
// clang-format off
|
||||
const uint8_t not_an_oid[] = {
|
||||
0x30, 0x0d, // SEQUENCE (13 bytes)
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, // 1.3.6.1.5.5.7.3.1
|
||||
0x02, 0x01, // INTEGER (1 byte)
|
||||
0x01 // 1
|
||||
// end of SEQUENCE
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_FALSE(ParseEKUExtension(der::Input(not_an_oid), &ekus));
|
||||
}
|
||||
|
||||
// Checks that the list of oids passed to ParseEKUExtension are in a sequence,
|
||||
// instead of one or more oid tag-length-values concatenated together.
|
||||
TEST(ExtendedKeyUsageTest, NotASequence) {
|
||||
// clang-format off
|
||||
const uint8_t not_a_sequence[] = {
|
||||
0x06, 0x08, // OBJECT IDENTIFIER (8 bytes)
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 // 1.3.6.1.5.5.7.3.1
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_FALSE(ParseEKUExtension(der::Input(not_a_sequence), &ekus));
|
||||
}
|
||||
|
||||
// A sequence passed into ParseEKUExtension must have at least one oid in it.
|
||||
TEST(ExtendedKeyUsageTest, EmptySequence) {
|
||||
const uint8_t empty_sequence[] = {0x30, 0x00}; // SEQUENCE (0 bytes)
|
||||
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_FALSE(ParseEKUExtension(der::Input(empty_sequence), &ekus));
|
||||
}
|
||||
|
||||
// The extension value must not be empty.
|
||||
TEST(ExtendedKeyUsageTest, EmptyExtension) {
|
||||
std::vector<der::Input> ekus;
|
||||
EXPECT_FALSE(ParseEKUExtension(der::Input(), &ekus));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace net
|
48
pki/fillins/base64.cc
Normal file
48
pki/fillins/base64.cc
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <openssl/base64.h>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
bool Base64Encode(const std::string_view &input, std::string *output) {
|
||||
size_t len;
|
||||
if (!EVP_EncodedLength(&len, input.size())) {
|
||||
return false;
|
||||
}
|
||||
std::vector<char> encoded(len);
|
||||
len = EVP_EncodeBlock(reinterpret_cast<uint8_t *>(encoded.data()),
|
||||
reinterpret_cast<const uint8_t *>(input.data()),
|
||||
input.size());
|
||||
if (!len) {
|
||||
return false;
|
||||
}
|
||||
output->assign(encoded.data(), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Base64Decode(const std::string_view &input, std::string *output) {
|
||||
size_t len;
|
||||
if (!EVP_DecodedLength(&len, input.size())) {
|
||||
return false;
|
||||
}
|
||||
std::vector<char> decoded(len);
|
||||
if (!EVP_DecodeBase64(reinterpret_cast<uint8_t *>(decoded.data()), &len, len,
|
||||
reinterpret_cast<const uint8_t *>(input.data()),
|
||||
input.size())) {
|
||||
return false;
|
||||
}
|
||||
output->assign(decoded.data(), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
27
pki/fillins/base64.h
Normal file
27
pki/fillins/base64.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_BASE64_H
|
||||
#define BSSL_FILLINS_BASE64_H
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
OPENSSL_EXPORT bool Base64Encode(const std::string_view &input,
|
||||
std::string *output);
|
||||
|
||||
OPENSSL_EXPORT bool Base64Decode(const std::string_view &input,
|
||||
std::string *output);
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_BASE64_H
|
53
pki/fillins/check.h
Normal file
53
pki/fillins/check.h
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef PKI_CHECK_H_
|
||||
#define PKI_CHECK_H_
|
||||
|
||||
// This header defines the CHECK, DCHECK, macros, inherited from chrome.
|
||||
|
||||
// This file is not used in chrome, so check here to make sure we are
|
||||
// only compiling inside boringssl.
|
||||
#if !defined(_BORINGSSL_LIBPKI_)
|
||||
#error "_BORINGSSL_LIBPKI_ is not defined when compiling BoringSSL libpki"
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// This is not used in this include file, but is here temporarily to avoid
|
||||
// an intrusive change in chrome's files until we are fully extracted.
|
||||
// TODO(bbe) move this include to the relevant .cc files once we are no
|
||||
// longer using chrome as the canonical source.
|
||||
#include <openssl/base.h>
|
||||
|
||||
// In chrome DCHECK is used like assert() but often erroneously. to be
|
||||
// safe we make DCHECK the same as CHECK, and if we truly wish to have
|
||||
// this be an assert, we convert to assert() as in the rest of boringssl.
|
||||
// TODO(bbe) scan all DCHECK's in here once we are no longer using chrome
|
||||
// as the canonical source and convert to CHECK unless certain they
|
||||
// can be an assert().
|
||||
#define DCHECK CHECK
|
||||
|
||||
// CHECK aborts if a condition is not true.
|
||||
#define CHECK(A) \
|
||||
do { \
|
||||
if (!(A)) \
|
||||
abort(); \
|
||||
} while (0);
|
||||
|
||||
#define DCHECK_EQ CHECK_EQ
|
||||
#define DCHECK_NE CHECK_NE
|
||||
#define DCHECK_LE CHECK_LE
|
||||
#define DCHECK_LT CHECK_LT
|
||||
#define DCHECK_GE CHECK_GE
|
||||
#define DCHECK_GT CHECK_GT
|
||||
|
||||
#define CHECK_EQ(val1, val2) CHECK((val1) == (val2))
|
||||
#define CHECK_NE(val1, val2) CHECK((val1) != (val2))
|
||||
#define CHECK_LE(val1, val2) CHECK((val1) <= (val2))
|
||||
#define CHECK_LT(val1, val2) CHECK((val1) < (val2))
|
||||
#define CHECK_GE(val1, val2) CHECK((val1) >= (val2))
|
||||
#define CHECK_GT(val1, val2) CHECK((val1) > (val2))
|
||||
|
||||
#endif // PKI_CHECK_H_
|
35
pki/fillins/file_util.cc
Normal file
35
pki/fillins/file_util.cc
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "file_util.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <string>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
bool ReadFileToString(const FilePath &path, std::string *out) {
|
||||
std::ifstream file(path.value(), std::ios::binary);
|
||||
file.unsetf(std::ios::skipws);
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
if (file.tellg() <= 0) {
|
||||
return false;
|
||||
}
|
||||
out->reserve(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
out->assign(std::istreambuf_iterator<char>(file),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
24
pki/fillins/file_util.h
Normal file
24
pki/fillins/file_util.h
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_FILE_UTIL_H
|
||||
#define BSSL_FILLINS_FILE_UTIL_H
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include "path_service.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
OPENSSL_EXPORT bool ReadFileToString(const FilePath &path, std::string *out);
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_FILE_UTIL_H
|
16
pki/fillins/inet.h
Normal file
16
pki/fillins/inet.h
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef PKI_FILLINS_INET_H_
|
||||
#define PKI_FILLINS_INET_H_
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#if defined(OPENSSL_WINDOWS)
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif // OPENSSL_WINDOWS
|
||||
|
||||
#endif // PKI_FILLINS_INET_H_
|
171
pki/fillins/ip_address.cc
Normal file
171
pki/fillins/ip_address.cc
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ip_address.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <climits>
|
||||
|
||||
#include "check.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
IPAddress::IPAddress() {}
|
||||
|
||||
IPAddress::IPAddress(const uint8_t *address, size_t address_len)
|
||||
: addr_(reinterpret_cast<const char *>(address), address_len) {}
|
||||
|
||||
IPAddress::IPAddress(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
|
||||
addr_.reserve(4);
|
||||
addr_.push_back(b0);
|
||||
addr_.push_back(b1);
|
||||
addr_.push_back(b2);
|
||||
addr_.push_back(b3);
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4,
|
||||
uint8_t b5, uint8_t b6, uint8_t b7, uint8_t b8, uint8_t b9,
|
||||
uint8_t b10, uint8_t b11, uint8_t b12, uint8_t b13,
|
||||
uint8_t b14, uint8_t b15) {
|
||||
addr_.reserve(16);
|
||||
addr_.push_back(b0);
|
||||
addr_.push_back(b1);
|
||||
addr_.push_back(b2);
|
||||
addr_.push_back(b3);
|
||||
addr_.push_back(b4);
|
||||
addr_.push_back(b5);
|
||||
addr_.push_back(b6);
|
||||
addr_.push_back(b7);
|
||||
addr_.push_back(b8);
|
||||
addr_.push_back(b9);
|
||||
addr_.push_back(b10);
|
||||
addr_.push_back(b11);
|
||||
addr_.push_back(b12);
|
||||
addr_.push_back(b13);
|
||||
addr_.push_back(b14);
|
||||
addr_.push_back(b15);
|
||||
}
|
||||
|
||||
// static
|
||||
IPAddress IPAddress::AllZeros(size_t num_zero_bytes) {
|
||||
CHECK_LE(num_zero_bytes, 16u);
|
||||
IPAddress result;
|
||||
result.addr_.reserve(num_zero_bytes);
|
||||
for (size_t i = 0; i < num_zero_bytes; ++i) {
|
||||
result.addr_.push_back(0u);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
IPAddress IPAddress::IPv4AllZeros() { return AllZeros(kIPv4AddressSize); }
|
||||
|
||||
bool IPAddress::IsIPv4() const { return addr_.size() == kIPv4AddressSize; }
|
||||
|
||||
bool IPAddress::IsIPv6() const { return addr_.size() == kIPv6AddressSize; }
|
||||
|
||||
bool IPAddress::IsValid() const { return IsIPv4() || IsIPv6(); }
|
||||
|
||||
const uint8_t *IPAddress::data() const {
|
||||
return reinterpret_cast<const uint8_t *>(addr_.data());
|
||||
}
|
||||
|
||||
size_t IPAddress::size() const { return addr_.size(); }
|
||||
|
||||
const IPAddressBytes &IPAddress::bytes() const { return addr_; }
|
||||
|
||||
static IPAddress ConvertIPv4ToIPv4MappedIPv6(const IPAddress &address) {
|
||||
CHECK(address.IsIPv4());
|
||||
// IPv4-mapped addresses are formed by:
|
||||
// <80 bits of zeros> + <16 bits of ones> + <32-bit IPv4 address>.
|
||||
uint8_t bytes[16];
|
||||
memset(bytes, 0, 10);
|
||||
memset(bytes + 10, 0xff, 2);
|
||||
memcpy(bytes + 12, address.data(), address.size());
|
||||
return IPAddress(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
// Note that this function assumes:
|
||||
// * |ip_address| is at least |prefix_length_in_bits| (bits) long;
|
||||
// * |ip_prefix| is at least |prefix_length_in_bits| (bits) long.
|
||||
static bool IPAddressPrefixCheck(const uint8_t *ip_address,
|
||||
const uint8_t *ip_prefix,
|
||||
size_t prefix_length_in_bits) {
|
||||
// Compare all the bytes that fall entirely within the prefix.
|
||||
size_t num_entire_bytes_in_prefix = prefix_length_in_bits / 8;
|
||||
for (size_t i = 0; i < num_entire_bytes_in_prefix; ++i) {
|
||||
if (ip_address[i] != ip_prefix[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In case the prefix was not a multiple of 8, there will be 1 byte
|
||||
// which is only partially masked.
|
||||
size_t remaining_bits = prefix_length_in_bits % 8;
|
||||
if (remaining_bits != 0) {
|
||||
uint8_t mask = 0xFF << (8 - remaining_bits);
|
||||
size_t i = num_entire_bytes_in_prefix;
|
||||
if ((ip_address[i] & mask) != (ip_prefix[i] & mask)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPAddressMatchesPrefix(const IPAddress &ip_address,
|
||||
const IPAddress &ip_prefix,
|
||||
size_t prefix_length_in_bits) {
|
||||
// Both the input IP address and the prefix IP address should be either IPv4
|
||||
// or IPv6.
|
||||
DCHECK(ip_address.IsValid());
|
||||
DCHECK(ip_prefix.IsValid());
|
||||
|
||||
DCHECK_LE(prefix_length_in_bits, ip_prefix.size() * 8);
|
||||
|
||||
// In case we have an IPv6 / IPv4 mismatch, convert the IPv4 addresses to
|
||||
// IPv6 addresses in order to do the comparison.
|
||||
if (ip_address.size() != ip_prefix.size()) {
|
||||
if (ip_address.IsIPv4()) {
|
||||
return IPAddressMatchesPrefix(ConvertIPv4ToIPv4MappedIPv6(ip_address),
|
||||
ip_prefix, prefix_length_in_bits);
|
||||
}
|
||||
return IPAddressMatchesPrefix(ip_address,
|
||||
ConvertIPv4ToIPv4MappedIPv6(ip_prefix),
|
||||
96 + prefix_length_in_bits);
|
||||
}
|
||||
|
||||
return IPAddressPrefixCheck(ip_address.data(), ip_prefix.data(),
|
||||
prefix_length_in_bits);
|
||||
}
|
||||
|
||||
static unsigned CommonPrefixLength(const IPAddress &a1, const IPAddress &a2) {
|
||||
DCHECK_EQ(a1.size(), a2.size());
|
||||
for (size_t i = 0; i < a1.size(); ++i) {
|
||||
uint8_t diff = a1.bytes()[i] ^ a2.bytes()[i];
|
||||
if (!diff)
|
||||
continue;
|
||||
for (unsigned j = 0; j < CHAR_BIT; ++j) {
|
||||
if (diff & (1 << (CHAR_BIT - 1)))
|
||||
return i * CHAR_BIT + j;
|
||||
diff <<= 1;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
return a1.size() * CHAR_BIT;
|
||||
}
|
||||
|
||||
unsigned MaskPrefixLength(const IPAddress &mask) {
|
||||
uint8_t all_ones[16];
|
||||
const size_t mask_len = std::min(mask.size(), sizeof(all_ones));
|
||||
memset(all_ones, 0xff, mask_len);
|
||||
return CommonPrefixLength(mask, IPAddress(all_ones, mask_len));
|
||||
}
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
62
pki/fillins/ip_address.h
Normal file
62
pki/fillins/ip_address.h
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_IP_ADDRESS
|
||||
#define BSSL_FILLINS_IP_ADDRESS
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
typedef std::string IPAddressBytes;
|
||||
|
||||
class OPENSSL_EXPORT IPAddress {
|
||||
public:
|
||||
enum : size_t { kIPv4AddressSize = 4, kIPv6AddressSize = 16 };
|
||||
|
||||
OPENSSL_EXPORT IPAddress();
|
||||
OPENSSL_EXPORT IPAddress(const uint8_t *address, size_t address_len);
|
||||
OPENSSL_EXPORT IPAddress(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3);
|
||||
OPENSSL_EXPORT IPAddress(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3,
|
||||
uint8_t b4, uint8_t b5, uint8_t b6, uint8_t b7,
|
||||
uint8_t b8, uint8_t b9, uint8_t b10, uint8_t b11,
|
||||
uint8_t b12, uint8_t b13, uint8_t b14, uint8_t b15);
|
||||
|
||||
static IPAddress IPv4AllZeros();
|
||||
|
||||
OPENSSL_EXPORT bool IsIPv4() const;
|
||||
OPENSSL_EXPORT bool IsIPv6() const;
|
||||
OPENSSL_EXPORT bool IsValid() const;
|
||||
|
||||
OPENSSL_EXPORT const uint8_t *data() const;
|
||||
OPENSSL_EXPORT size_t size() const;
|
||||
OPENSSL_EXPORT const IPAddressBytes &bytes() const;
|
||||
|
||||
OPENSSL_EXPORT bool operator==(const IPAddress &other) const {
|
||||
return addr_ == other.addr_;
|
||||
}
|
||||
|
||||
private:
|
||||
static IPAddress AllZeros(size_t num_zero_bytes);
|
||||
std::string addr_;
|
||||
};
|
||||
|
||||
OPENSSL_EXPORT bool IPAddressMatchesPrefix(const IPAddress &ip_address,
|
||||
const IPAddress &ip_prefix,
|
||||
size_t prefix_length_in_bits);
|
||||
|
||||
OPENSSL_EXPORT unsigned MaskPrefixLength(const IPAddress &mask);
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_IP_ADDRESS
|
18
pki/fillins/net_errors.h
Normal file
18
pki/fillins/net_errors.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_NET_ERRORS_H
|
||||
#define BSSL_FILLINS_NET_ERRORS_H
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
enum OPENSSL_EXPORT Error {
|
||||
OK = 0,
|
||||
};
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_NET_ERRORS_H
|
19
pki/fillins/openssl_util.cc
Normal file
19
pki/fillins/openssl_util.cc
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "openssl_util.h"
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
OpenSSLErrStackTracer::OpenSSLErrStackTracer() {}
|
||||
|
||||
OpenSSLErrStackTracer::~OpenSSLErrStackTracer() { ERR_clear_error(); }
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
28
pki/fillins/openssl_util.h
Normal file
28
pki/fillins/openssl_util.h
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_OPENSSL_UTIL_H
|
||||
#define BSSL_FILLINS_OPENSSL_UTIL_H
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
// Place an instance of this class on the call stack to automatically clear
|
||||
// the OpenSSL error stack on function exit.
|
||||
class OPENSSL_EXPORT OpenSSLErrStackTracer {
|
||||
public:
|
||||
OPENSSL_EXPORT OpenSSLErrStackTracer();
|
||||
OPENSSL_EXPORT ~OpenSSLErrStackTracer();
|
||||
};
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_OPENSSL_UTIL_H
|
44
pki/fillins/path_service.cc
Normal file
44
pki/fillins/path_service.cc
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "path_service.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
FilePath::FilePath() {}
|
||||
|
||||
FilePath::FilePath(const std::string &path) : path_(path) {}
|
||||
|
||||
const std::string &FilePath::value() const { return path_; }
|
||||
|
||||
FilePath FilePath::AppendASCII(const std::string &ascii_path_element) const {
|
||||
// Append a path element to a path. Use the \ separator if this appears to
|
||||
// be a Windows path, otherwise the Unix one.
|
||||
if (path_.find(":\\") != std::string::npos) {
|
||||
return FilePath(path_ + "\\" + ascii_path_element);
|
||||
}
|
||||
return FilePath(path_ + "/" + ascii_path_element);
|
||||
}
|
||||
|
||||
// static
|
||||
void PathService::Get(PathKey key, FilePath *out) {
|
||||
#if defined(_BORINGSSL_PKI_SRCDIR_)
|
||||
// We stringify the compile parameter because cmake. sigh.
|
||||
#define _boringssl_xstr(s) _boringssl_str(s)
|
||||
#define _boringssl_str(s) #s
|
||||
const char pki_srcdir[] = _boringssl_xstr(_BORINGSSL_PKI_SRCDIR_);
|
||||
#else
|
||||
#error "No _BORINGSSL_PKI_SRCDIR"
|
||||
#endif // defined(BORINGSSL_PKI_SRCDIR
|
||||
*out = FilePath(pki_srcdir);
|
||||
}
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
43
pki/fillins/path_service.h
Normal file
43
pki/fillins/path_service.h
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_PATH_SERVICE_H
|
||||
#define BSSL_FILLINS_PATH_SERVICE_H
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
class OPENSSL_EXPORT FilePath {
|
||||
public:
|
||||
OPENSSL_EXPORT FilePath();
|
||||
OPENSSL_EXPORT FilePath(const std::string &path);
|
||||
|
||||
OPENSSL_EXPORT const std::string &value() const;
|
||||
|
||||
OPENSSL_EXPORT FilePath
|
||||
AppendASCII(const std::string &ascii_path_element) const;
|
||||
|
||||
private:
|
||||
std::string path_;
|
||||
};
|
||||
|
||||
enum OPENSSL_EXPORT PathKey {
|
||||
DIR_SOURCE_ROOT = 0,
|
||||
};
|
||||
|
||||
class OPENSSL_EXPORT PathService {
|
||||
public:
|
||||
static void Get(PathKey key, FilePath *out);
|
||||
};
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_PATH_SERVICE_H
|
89
pki/fillins/string_util.cc
Normal file
89
pki/fillins/string_util.cc
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "../string_util.h"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "string_util.h"
|
||||
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
|
||||
// TODO(bbe): get rid of this
|
||||
std::string HexEncode(const void *bytes, size_t size) {
|
||||
return bssl::string_util::HexEncode((const uint8_t *)bytes, size);
|
||||
}
|
||||
|
||||
static bool IsUnicodeWhitespace(char c) {
|
||||
return c == 9 || c == 10 || c == 11 || c == 12 || c == 13 || c == ' ';
|
||||
}
|
||||
|
||||
std::string CollapseWhitespaceASCII(std::string_view text,
|
||||
bool trim_sequences_with_line_breaks) {
|
||||
std::string result;
|
||||
result.resize(text.size());
|
||||
|
||||
// Set flags to pretend we're already in a trimmed whitespace sequence, so we
|
||||
// will trim any leading whitespace.
|
||||
bool in_whitespace = true;
|
||||
bool already_trimmed = true;
|
||||
|
||||
int chars_written = 0;
|
||||
for (auto i = text.begin(); i != text.end(); ++i) {
|
||||
if (IsUnicodeWhitespace(*i)) {
|
||||
if (!in_whitespace) {
|
||||
// Reduce all whitespace sequences to a single space.
|
||||
in_whitespace = true;
|
||||
result[chars_written++] = L' ';
|
||||
}
|
||||
if (trim_sequences_with_line_breaks && !already_trimmed &&
|
||||
((*i == '\n') || (*i == '\r'))) {
|
||||
// Whitespace sequences containing CR or LF are eliminated entirely.
|
||||
already_trimmed = true;
|
||||
--chars_written;
|
||||
}
|
||||
} else {
|
||||
// Non-whitespace chracters are copied straight across.
|
||||
in_whitespace = false;
|
||||
already_trimmed = false;
|
||||
result[chars_written++] = *i;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_whitespace && !already_trimmed) {
|
||||
// Any trailing whitespace is eliminated.
|
||||
--chars_written;
|
||||
}
|
||||
|
||||
result.resize(chars_written);
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(bbe): get rid of this (used to be strcasecmp in google3, which
|
||||
// causes windows pain because msvc and strings.h)
|
||||
bool EqualsCaseInsensitiveASCII(std::string_view a, std::string_view b) {
|
||||
return bssl::string_util::IsEqualNoCase(a, b);
|
||||
}
|
||||
|
||||
bool IsAsciiAlpha(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
bool IsAsciiDigit(char c) { return c >= '0' && c <= '9'; }
|
||||
|
||||
void ReplaceSubstringsAfterOffset(std::string *s, size_t offset,
|
||||
std::string_view find,
|
||||
std::string_view replace) {
|
||||
std::string_view prefix(s->data(), offset);
|
||||
std::string suffix =
|
||||
bssl::string_util::FindAndReplace(s->substr(offset), find, replace);
|
||||
*s = std::string(prefix) + suffix;
|
||||
};
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
41
pki/fillins/string_util.h
Normal file
41
pki/fillins/string_util.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_STRING_UTIL_H
|
||||
#define BSSL_FILLINS_STRING_UTIL_H
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
OPENSSL_EXPORT std::string HexEncode(const void *bytes, size_t size);
|
||||
|
||||
OPENSSL_EXPORT std::string CollapseWhitespaceASCII(
|
||||
std::string_view text, bool trim_sequences_with_line_breaks);
|
||||
|
||||
OPENSSL_EXPORT bool EqualsCaseInsensitiveASCII(std::string_view a,
|
||||
std::string_view b);
|
||||
|
||||
OPENSSL_EXPORT bool IsAsciiAlpha(char c);
|
||||
|
||||
OPENSSL_EXPORT bool IsAsciiDigit(char c);
|
||||
|
||||
OPENSSL_EXPORT void ReplaceSubstringsAfterOffset(std::string *s, size_t offset,
|
||||
std::string_view find,
|
||||
std::string_view replace);
|
||||
|
||||
OPENSSL_EXPORT std::string HexDecode(std::string_view hex);
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_STRING_UTIL_H
|
47
pki/fillins/utf_string_conversions.cc
Normal file
47
pki/fillins/utf_string_conversions.cc
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "utf_string_conversions.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
static const size_t kMaxUTF8Bytes = 4;
|
||||
|
||||
static size_t EncodeUTF8(uint32_t codepoint, char *out_buf) {
|
||||
if (codepoint < 0x7f) {
|
||||
out_buf[0] = codepoint;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (codepoint <= 0x7ff) {
|
||||
out_buf[0] = 0xc0 | (codepoint >> 6);
|
||||
out_buf[1] = 0x80 | (codepoint & 0x3f);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (codepoint <= 0xffff) {
|
||||
out_buf[0] = 0xe0 | (codepoint >> 12);
|
||||
out_buf[1] = 0x80 | ((codepoint >> 6) & 0x3f);
|
||||
out_buf[2] = 0x80 | (codepoint & 0x3f);
|
||||
return 3;
|
||||
}
|
||||
|
||||
out_buf[0] = 0xf0 | (codepoint >> 18);
|
||||
out_buf[1] = 0x80 | ((codepoint >> 12) & 0x3f);
|
||||
out_buf[2] = 0x80 | ((codepoint >> 6) & 0x3f);
|
||||
out_buf[3] = 0x80 | (codepoint & 0x3f);
|
||||
return 4;
|
||||
}
|
||||
|
||||
void WriteUnicodeCharacter(uint32_t codepoint, std::string *append_to) {
|
||||
char buf[kMaxUTF8Bytes];
|
||||
const size_t num_bytes = EncodeUTF8(codepoint, buf);
|
||||
append_to->append(buf, num_bytes);
|
||||
}
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
34
pki/fillins/utf_string_conversions.h
Normal file
34
pki/fillins/utf_string_conversions.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_FILLINS_UTF_STRING_CONVERSIONS
|
||||
#define BSSL_FILLINS_UTF_STRING_CONVERSIONS
|
||||
|
||||
#include <openssl/base.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define CBU_IS_SURROGATE(c) (((c)&0xfffff800) == 0xd800)
|
||||
|
||||
#define CBU_IS_UNICODE_NONCHAR(c) \
|
||||
((c) >= 0xfdd0 && ((uint32_t)(c) <= 0xfdef || ((c)&0xfffe) == 0xfffe) && \
|
||||
(uint32_t)(c) <= 0x10ffff)
|
||||
|
||||
#define CBU_IS_UNICODE_CHAR(c) \
|
||||
((uint32_t)(c) < 0xd800 || \
|
||||
((uint32_t)(c) > 0xdfff && (uint32_t)(c) <= 0x10ffff && \
|
||||
!CBU_IS_UNICODE_NONCHAR(c)))
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace fillins {
|
||||
|
||||
OPENSSL_EXPORT void WriteUnicodeCharacter(uint32_t codepoint,
|
||||
std::string *append_to);
|
||||
|
||||
} // namespace fillins
|
||||
|
||||
} // namespace bssl
|
||||
|
||||
#endif // BSSL_FILLINS_UTF_STRING_CONVERSIONS
|
242
pki/general_names.cc
Normal file
242
pki/general_names.cc
Normal file
@ -0,0 +1,242 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "general_names.h"
|
||||
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
|
||||
#include "cert_error_params.h"
|
||||
#include "cert_errors.h"
|
||||
#include "string_util.h"
|
||||
#include "input.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingGeneralName, "Failed parsing GeneralName");
|
||||
|
||||
namespace {
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kRFC822NameNotAscii, "rfc822Name is not ASCII");
|
||||
DEFINE_CERT_ERROR_ID(kDnsNameNotAscii, "dNSName is not ASCII");
|
||||
DEFINE_CERT_ERROR_ID(kURINotAscii, "uniformResourceIdentifier 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 fillins::IPAddressBytes& mask, unsigned prefix_length) {
|
||||
size_t zero_bits = mask.size() * CHAR_BIT - prefix_length;
|
||||
size_t zero_bytes = zero_bits / CHAR_BIT;
|
||||
// We allocate the vector one byte bigger than needed to ensure there is a
|
||||
// valid pointer to pass to memcmp for a zero length comparison.
|
||||
std::vector<uint8_t> zeros(zero_bytes + 1, 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() = default;
|
||||
|
||||
GeneralNames::~GeneralNames() = default;
|
||||
|
||||
// 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
|
||||
der::Parser parser(general_names_tlv);
|
||||
der::Input sequence_value;
|
||||
if (!parser.ReadTag(der::kSequence, &sequence_value)) {
|
||||
errors->AddError(kFailedReadingGeneralNames);
|
||||
return nullptr;
|
||||
}
|
||||
// Should not have trailing data after GeneralNames sequence.
|
||||
if (parser.HasMore()) {
|
||||
errors->AddError(kGeneralNamesTrailingData);
|
||||
return nullptr;
|
||||
}
|
||||
return CreateFromValue(sequence_value, errors);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<GeneralNames> GeneralNames::CreateFromValue(
|
||||
const der::Input& general_names_value,
|
||||
CertErrors* errors) {
|
||||
DCHECK(errors);
|
||||
|
||||
auto general_names = std::make_unique<GeneralNames>();
|
||||
|
||||
der::Parser sequence_parser(general_names_value);
|
||||
// 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, IP_ADDRESS_ONLY,
|
||||
general_names.get(), errors)) {
|
||||
errors->AddError(kFailedParsingGeneralName);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return general_names;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ParseGeneralName(
|
||||
const der::Input& input,
|
||||
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;
|
||||
subtrees->other_names.push_back(value);
|
||||
} else if (tag == der::ContextSpecificPrimitive(1)) {
|
||||
// rfc822Name [1] IA5String,
|
||||
name_type = GENERAL_NAME_RFC822_NAME;
|
||||
const std::string_view s = value.AsStringView();
|
||||
if (!bssl::string_util::IsAscii(s)) {
|
||||
errors->AddError(kRFC822NameNotAscii);
|
||||
return false;
|
||||
}
|
||||
subtrees->rfc822_names.push_back(s);
|
||||
} else if (tag == der::ContextSpecificPrimitive(2)) {
|
||||
// dNSName [2] IA5String,
|
||||
name_type = GENERAL_NAME_DNS_NAME;
|
||||
const std::string_view s = value.AsStringView();
|
||||
if (!bssl::string_util::IsAscii(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;
|
||||
subtrees->x400_addresses.push_back(value);
|
||||
} 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(name_value);
|
||||
} else if (tag == der::ContextSpecificConstructed(5)) {
|
||||
// ediPartyName [5] EDIPartyName,
|
||||
name_type = GENERAL_NAME_EDI_PARTY_NAME;
|
||||
subtrees->edi_party_names.push_back(value);
|
||||
} else if (tag == der::ContextSpecificPrimitive(6)) {
|
||||
// uniformResourceIdentifier [6] IA5String,
|
||||
name_type = GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER;
|
||||
const std::string_view s = value.AsStringView();
|
||||
if (!bssl::string_util::IsAscii(s)) {
|
||||
errors->AddError(kURINotAscii);
|
||||
return false;
|
||||
}
|
||||
subtrees->uniform_resource_identifiers.push_back(s);
|
||||
} 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() != fillins::IPAddress::kIPv4AddressSize &&
|
||||
value.Length() != fillins::IPAddress::kIPv6AddressSize)) {
|
||||
errors->AddError(kFailedParsingIp);
|
||||
return false;
|
||||
}
|
||||
subtrees->ip_addresses.emplace_back(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() != fillins::IPAddress::kIPv4AddressSize * 2 &&
|
||||
value.Length() != fillins::IPAddress::kIPv6AddressSize * 2) {
|
||||
errors->AddError(kFailedParsingIp);
|
||||
return false;
|
||||
}
|
||||
const fillins::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.emplace_back(
|
||||
fillins::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;
|
||||
subtrees->registered_ids.push_back(value);
|
||||
} else {
|
||||
errors->AddError(kUnknownGeneralNameType,
|
||||
CreateCertErrorParams1SizeT("tag", tag));
|
||||
return false;
|
||||
}
|
||||
DCHECK_NE(GENERAL_NAME_NONE, name_type);
|
||||
subtrees->present_name_types |= name_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
124
pki/general_names.h
Normal file
124
pki/general_names.h
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_GENERAL_NAMES_H_
|
||||
#define BSSL_PKI_GENERAL_NAMES_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "fillins/ip_address.h"
|
||||
|
||||
#include "cert_error_id.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
class CertErrors;
|
||||
|
||||
OPENSSL_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.
|
||||
struct OPENSSL_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|. The returned object may reference data from
|
||||
// |general_names_tlv|, so is only valid as long as |general_names_tlv| is.
|
||||
// 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);
|
||||
|
||||
// As above, but takes the GeneralNames sequence value, without the tag and
|
||||
// length.
|
||||
static std::unique_ptr<GeneralNames> CreateFromValue(
|
||||
const der::Input& general_names_value,
|
||||
CertErrors* errors);
|
||||
|
||||
// DER-encoded OtherName values.
|
||||
std::vector<der::Input> other_names;
|
||||
|
||||
// ASCII rfc822names.
|
||||
std::vector<std::string_view> rfc822_names;
|
||||
|
||||
// ASCII hostnames.
|
||||
std::vector<std::string_view> dns_names;
|
||||
|
||||
// DER-encoded ORAddress values.
|
||||
std::vector<der::Input> x400_addresses;
|
||||
|
||||
// DER-encoded Name values (not including the Sequence tag).
|
||||
std::vector<der::Input> directory_names;
|
||||
|
||||
// DER-encoded EDIPartyName values.
|
||||
std::vector<der::Input> edi_party_names;
|
||||
|
||||
// ASCII URIs.
|
||||
std::vector<std::string_view> uniform_resource_identifiers;
|
||||
|
||||
// iPAddresses as sequences of octets in network byte order. This will be
|
||||
// populated if the GeneralNames represents a Subject Alternative Name.
|
||||
std::vector<fillins::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<fillins::IPAddress, unsigned>> ip_address_ranges;
|
||||
|
||||
// DER-encoded OBJECT IDENTIFIERs.
|
||||
std::vector<der::Input> registered_ids;
|
||||
|
||||
// Which name types were present, as a bitfield of GeneralNameTypes.
|
||||
int present_name_types = GENERAL_NAME_NONE;
|
||||
};
|
||||
|
||||
// Parses a GeneralName value and adds it to |subtrees|.
|
||||
// |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?
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseGeneralName(
|
||||
const der::Input& input,
|
||||
GeneralNames::ParseGeneralNameIPAddressType ip_address_type,
|
||||
GeneralNames* subtrees,
|
||||
CertErrors* errors);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_GENERAL_NAMES_H_
|
359
pki/import_spec.json
Normal file
359
pki/import_spec.json
Normal file
@ -0,0 +1,359 @@
|
||||
{
|
||||
"replacements": [
|
||||
{"match": "^#include \"base/supports_user_data.h\"",
|
||||
"replace": ""},
|
||||
{"match": ": public base::SupportsUserData",
|
||||
"replace": ""},
|
||||
{"match": "~Result\\(\\) override;",
|
||||
"replace": "~Result();"},
|
||||
{"match": "base::SupportsUserData",
|
||||
"replace": "void"},
|
||||
{"match": "^#include \"net/dns/dns_util.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/gtest_prod_util.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/pickle.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/check.h\"",
|
||||
"replace": "#include \"fillins/check.h\""},
|
||||
{"match": "^#include \"base/notreached.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/check_op.h\"",
|
||||
"replace": "#include \"fillins/check.h\""},
|
||||
{"match": "^#include \"net/base/hash_value.h\"",
|
||||
"replace": "#include \"fillins/hash_value.h\""},
|
||||
{"match": "^#include \"net/cert/x509_util.h\"",
|
||||
"replace": "#include \"fillins/x509_util.h\""},
|
||||
{"match": "^#include \"url/gurl.h\"",
|
||||
"replace": "#include \"webutil/url/url.h\""},
|
||||
{"match": "^#include \"build/build_config.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/numerics/clamped_math.h\"",
|
||||
"replace": "#include \"fillins/clamped_math.h\""},
|
||||
{"match": "^#include \"base/numerics/safe_conversions.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"net/base/net_export.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/strings/string_util.h\"",
|
||||
"replace": "#include \"fillins/string_util.h\""},
|
||||
{"match": "^#include \"base/base_paths.h\"",
|
||||
"replace": "#include \"fillins/path_service.h\"",
|
||||
"using": ["bssl::fillins::PathService"]},
|
||||
{"match": "base::PathService",
|
||||
"replace": "bssl::fillins::PathService"},
|
||||
{"match": "base::ClampAdd",
|
||||
"replace": "bssl::fillins::ClampAdd"},
|
||||
{"match": "base::ClampMul",
|
||||
"replace": "bssl::fillins::ClampAdd"},
|
||||
{"match": "^#include \"base/files/file_util.h\"",
|
||||
"replace": "#include \"fillins/file_util.h\""},
|
||||
{"match": "^#include \"base/path_service.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"crypto/openssl_util.h\"",
|
||||
"replace": "#include \"fillins/openssl_util.h\""},
|
||||
{"match": "^#include \"net/base/ip_address.h\"",
|
||||
"replace": "#include \"fillins/ip_address.h\"",
|
||||
"using": ["bssl::fillins::IPAddress",
|
||||
"bssl::fillins::IPAddressBytes"]},
|
||||
{"match": " IPAddress",
|
||||
"replace": " fillins::IPAddress"},
|
||||
{"match": "<IPAddress",
|
||||
"replace": "<fillins::IPAddress"},
|
||||
{"match": "\\(IPAddress",
|
||||
"replace": "(fillins::IPAddress"},
|
||||
{"match": "\"net/data/",
|
||||
"replace": "\"testdata/"},
|
||||
{"match": "\"net/third_party/nist-pkits",
|
||||
"replace": "\"testdata/nist-pkits"},
|
||||
{"match": "^#include \"net/base/net_errors.h\"",
|
||||
"replace": "#include \"fillins/net_errors.h\"\n"},
|
||||
{"match": "^#include \"net/test/test_certificate_data.h\"",
|
||||
"replace": "#include \"testdata/test_certificate_data.h\""},
|
||||
{"match": "^#include \"net/third_party/nist-pkits/pkits_testcases-inl.h\"",
|
||||
"replace": "#include \"testdata/nist-pkits/pkits_testcases-inl.h\""},
|
||||
{"match": "^#include \"base/sys_byteorder.h\"",
|
||||
"replace": "#include \"fillins/inet.h\""},
|
||||
{"match": "^#include \"base/third_party/icu/icu_utf.h\"",
|
||||
"replace": "#include \"fillins/utf_string_conversions.h\""},
|
||||
{"match": "^#include \"base/strings/utf_string_conversions.h\"",
|
||||
"replace": "#include \"fillins/utf_string_conversions.h\""},
|
||||
{"match": "^#include \"base/strings/utf_string_conversion_utils.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/memory/ref_counted.h\"",
|
||||
"replace": "#include <memory>"},
|
||||
{"match": "^#include \"base/base64.h\"",
|
||||
"replace": "#include \"fillins/base64.h\""},
|
||||
{"match": "^#include \"base/strings/stringprintf.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"third_party/boringssl/src/include/openssl/(.*).h\"",
|
||||
"replace": "#include <openssl/$1.h>"},
|
||||
{"match": "^#include \"net/cert/pki/",
|
||||
"replace": "#include \""},
|
||||
{"match": "^#include \"net/cert/",
|
||||
"replace": "#include \""},
|
||||
{"match": "^#include \"net/der/",
|
||||
"replace": "#include \""},
|
||||
{"match": "^#include \"net/",
|
||||
"replace": "#include \""},
|
||||
{"match": "^#include \"net_buildflags.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/time/time.h\"",
|
||||
"replace": ""},
|
||||
{"match": "^#include \"base/strings/string_piece.h\"",
|
||||
"replace": "#include <string_view>\n"},
|
||||
{"match": "^#include \"testing/gtest/include/gtest/gtest.h\"",
|
||||
"replace": "#include <gtest/gtest.h>"},
|
||||
{"match": "^#include \"testing/gmock/include/gmock/gmock.h\"",
|
||||
"replace": "#include <gtest/gtest.h>"},
|
||||
{"match": "^#include \"base/containers/span.h\"",
|
||||
"replace": "#include <openssl/span.h>"},
|
||||
{"match": "^#include \"third_party/abseil-cpp/absl/types/optional.h\"",
|
||||
"replace": "#include <optional>"},
|
||||
{"match": "^#include \"base/containers/contains.h\"",
|
||||
"replace": ""},
|
||||
{"match": "GURL",
|
||||
"replace": "URL",
|
||||
"include": "webutil/url/url.h"},
|
||||
{"match": "absl::nullopt",
|
||||
"replace": "std::nullopt" },
|
||||
{"match": "absl::optional",
|
||||
"replace": "std::optional" },
|
||||
{"match": "absl::make_optional",
|
||||
"replace": "std::make_optional" },
|
||||
{"match": "base::span",
|
||||
"replace": "bssl::Span" },
|
||||
{"match": "base::make_span",
|
||||
"replace": "bssl::MakeSpan" },
|
||||
{"match": "base::as_bytes",
|
||||
"replace": "fillins::as_bytes",
|
||||
"include": "fillins/bits.h"},
|
||||
{"match": "^namespace net {",
|
||||
"replace": "namespace bssl {"},
|
||||
{"match": "namespace net::([^ ]+) {",
|
||||
"replace": "namespace bssl::$1 {"},
|
||||
{"match": "NET_EXPORT_PRIVATE ",
|
||||
"replace": "OPENSSL_EXPORT ",
|
||||
"include": "fillins/openssl_util.h"},
|
||||
{"match": "NET_EXPORT ",
|
||||
"replace": "OPENSSL_EXPORT ",
|
||||
"include": "fillins/openssl_util.h"},
|
||||
{"match": "NOTREACHED\\(\\)",
|
||||
"replace": "abort(); //NOTREACHED" },
|
||||
{"match": "NOTREACHED_NORETURN\\(\\)",
|
||||
"replace": "abort(); //NOTREACHED_NORETURN" },
|
||||
{"match": "FRIEND_TEST_ALL_PREFIXES\\(.+;",
|
||||
"replace": ""},
|
||||
{"match": " NET_DER",
|
||||
"replace": " BSSL_DER"},
|
||||
{"match": " NET_CERT_PKI",
|
||||
"replace": " BSSL_PKI"},
|
||||
{"match": " NET_CERT",
|
||||
"replace": " BSSL_PKI"},
|
||||
{"match": "^using base::StringPiece;",
|
||||
"replace": ""},
|
||||
{"match": "base::StringPiece",
|
||||
"replace": "std::string_view"},
|
||||
{"match": "base::StartsWith\\(",
|
||||
"replace": "bssl::string_util::StartsWith(",
|
||||
"include": "string_util.h"},
|
||||
{"match": "base::StringPrintf",
|
||||
"replace": "absl::StrFormat",
|
||||
"include": "third_party/absl/strings/str_format.h"},
|
||||
{"match": "base::Base64Encode",
|
||||
"replace": "fillins::Base64Encode"},
|
||||
{"match": "base::Base64Decode",
|
||||
"replace": "fillins::Base64Decode"},
|
||||
{"match": "base::ReadFileToString",
|
||||
"replace": "fillins::ReadFileToString"},
|
||||
{"match": "base::CollapseWhitespaceASCII",
|
||||
"replace": "fillins::CollapseWhitespaceASCII"},
|
||||
{"match": "base::FilePath",
|
||||
"replace": "fillins::FilePath"},
|
||||
{"match": "base::DIR_SOURCE_ROOT",
|
||||
"replace": "fillins::DIR_SOURCE_ROOT"},
|
||||
{"match": "base::NetToHost16\\(",
|
||||
"replace": "ntohs("},
|
||||
{"match": "base::NetToHost32\\(",
|
||||
"replace": "ntohl("},
|
||||
{ "match": "base_icu::UChar32",
|
||||
"replace": "uint32_t"},
|
||||
{"match": "base::WriteUnicodeCharacter\\(",
|
||||
"replace": "fillins::WriteUnicodeCharacter("},
|
||||
{"match": "base::IsAsciiAlpha\\(",
|
||||
"replace": "fillins::IsAsciiAlpha("},
|
||||
{"match": "scoped_refptr<",
|
||||
"replace": "std::shared_ptr<"},
|
||||
{"match": ": public base::RefCountedThreadSafe<.+>",
|
||||
"replace": ""},
|
||||
{"match": "friend class base::RefCountedThreadSafe<.+>;",
|
||||
"replace": ""},
|
||||
{"match": "net::string_util::",
|
||||
"replace": "bssl::string_util::"},
|
||||
{"match": "net::x509_util::",
|
||||
"replace": "x509_util::"},
|
||||
{"match": " net::der",
|
||||
"replace": " bssl::der"},
|
||||
{"match": "net::ParsedCertificate",
|
||||
"replace": "ParsedCertificate"},
|
||||
{"match": "net::CertPathBuilderResultPath",
|
||||
"replace": "CertPathBuilderResultPath"},
|
||||
{"match": "net::CertErrors",
|
||||
"replace": "bssl::CertErrors"},
|
||||
{"match": "base::Time::Exploded",
|
||||
"replace": "fillins::Exploded",
|
||||
"include": "fillins/time.h"},
|
||||
{"match": "([a-zA-Z_0-9]+)\\.UTCExplode\\(&([^)]*)\\)",
|
||||
"replace": "fillins::UTCExplode($1, &$2)"},
|
||||
{"match": "net::ReadTestFileToString\\(",
|
||||
"replace": "ReadTestFileToString("},
|
||||
{"match": "base::Seconds\\(",
|
||||
"replace": "absl::Seconds("},
|
||||
{"match": "base::Time::UnixEpoch\\(",
|
||||
"replace": "absl::UnixEpoch("},
|
||||
{"match": "base::Time::FromUTCExploded\\(",
|
||||
"replace": "fillins::FromUTCExploded(",
|
||||
"include": "fillins/time.h"},
|
||||
{"match": "base::Time::Now\\(\\)",
|
||||
"replace": "absl::Now()"},
|
||||
{"match": "base::Time::Min\\(\\)",
|
||||
"replace": "absl::InfinitePast()"},
|
||||
{"match": "base::Time::Max\\(\\)",
|
||||
"replace": "absl::InfiniteFuture()"},
|
||||
{"match": "base::Time",
|
||||
"replace": "absl::Time",
|
||||
"include": "fillins/time.h"},
|
||||
{"match": "constexpr absl::Time",
|
||||
"replace": "const absl::Time"},
|
||||
{"match": "^ // Map from OID to ParsedExtension.$",
|
||||
"replace": "~ParsedCertificate();\n$0"},
|
||||
{"match": "^ ~ParsedCertificate\\(\\);$",
|
||||
"replace": " "},
|
||||
{"match": "crypto::OpenSSLErrStackTracer",
|
||||
"replace": "fillins::OpenSSLErrStackTracer"},
|
||||
{"match": "\\(FROM_HERE\\)",
|
||||
"replace": ""},
|
||||
{"match": "([^a-zA-Z])StringPiece([^a-zA-Z])",
|
||||
"replace": "${1}std::string_view$2"},
|
||||
{"match": "crypto::kSHA256Length",
|
||||
"replace": "SHA256_DIGEST_LENGTH"},
|
||||
{"match": "raw_ptr<([^>]*)>",
|
||||
"replace": "$1 *"}
|
||||
],
|
||||
"files": [
|
||||
"net/cert/asn1_util.h",
|
||||
"net/cert/asn1_util.cc",
|
||||
"net/cert/cert_net_fetcher.h",
|
||||
"net/cert/cert_status_flags.h",
|
||||
"net/cert/cert_status_flags_list.h",
|
||||
"net/cert/cert_verify_proc_blocklist.inc",
|
||||
"net/cert/pki/cert_error_id.cc",
|
||||
"net/cert/pki/cert_error_id.h",
|
||||
"net/cert/pki/cert_error_params.cc",
|
||||
"net/cert/pki/cert_error_params.h",
|
||||
"net/cert/pki/cert_errors.cc",
|
||||
"net/cert/pki/cert_errors.h",
|
||||
"net/cert/pki/certificate_policies.cc",
|
||||
"net/cert/pki/certificate_policies.h",
|
||||
"net/cert/pki/certificate_policies_unittest.cc",
|
||||
"net/cert/pki/cert_issuer_source.h",
|
||||
"net/cert/pki/cert_issuer_source_static.cc",
|
||||
"net/cert/pki/cert_issuer_source_static.h",
|
||||
"net/cert/pki/cert_issuer_source_static_unittest.cc",
|
||||
"net/cert/pki/cert_issuer_source_sync_unittest.h",
|
||||
"net/cert/pki/common_cert_errors.cc",
|
||||
"net/cert/pki/common_cert_errors.h",
|
||||
"net/cert/pki/crl.h",
|
||||
"net/cert/pki/crl.cc",
|
||||
"net/cert/pki/extended_key_usage.cc",
|
||||
"net/cert/pki/extended_key_usage.h",
|
||||
"net/cert/pki/extended_key_usage_unittest.cc",
|
||||
"net/cert/pki/general_names.h",
|
||||
"net/cert/pki/general_names.cc",
|
||||
"net/cert/pki/mock_signature_verify_cache.h",
|
||||
"net/cert/pki/mock_signature_verify_cache.cc",
|
||||
"net/cert/pki/name_constraints.cc",
|
||||
"net/cert/pki/name_constraints.h",
|
||||
"net/cert/pki/name_constraints_unittest.cc",
|
||||
"net/cert/pki/nist_pkits_unittest.cc",
|
||||
"net/cert/pki/nist_pkits_unittest.h",
|
||||
"net/cert/pki/ocsp.cc",
|
||||
"net/cert/pki/ocsp.h",
|
||||
"net/cert/pki/ocsp_parse_ocsp_cert_id_fuzzer.cc",
|
||||
"net/cert/pki/ocsp_parse_ocsp_response_data_fuzzer.cc",
|
||||
"net/cert/pki/ocsp_parse_ocsp_response_fuzzer.cc",
|
||||
"net/cert/pki/ocsp_parse_ocsp_single_response_fuzzer.cc",
|
||||
"net/cert/pki/ocsp_unittest.cc",
|
||||
"net/cert/pki/parse_certificate.cc",
|
||||
"net/cert/pki/parse_certificate.h",
|
||||
"net/cert/pki/parse_certificate_unittest.cc",
|
||||
"net/cert/pki/parsed_certificate.cc",
|
||||
"net/cert/pki/parsed_certificate.h",
|
||||
"net/cert/pki/parse_certificate_fuzzer.cc",
|
||||
"net/cert/pki/parsed_certificate_unittest.cc",
|
||||
"net/cert/pki/parse_name.cc",
|
||||
"net/cert/pki/parse_name.h",
|
||||
"net/cert/pki/parse_name_unittest.cc",
|
||||
"net/cert/pki/path_builder.cc",
|
||||
"net/cert/pki/path_builder.h",
|
||||
"net/cert/pki/path_builder_pkits_unittest.cc",
|
||||
"net/cert/pki/path_builder_unittest.cc",
|
||||
"net/cert/pki/path_builder_verify_certificate_chain_unittest.cc",
|
||||
"net/cert/pki/revocation_util.h",
|
||||
"net/cert/pki/revocation_util.cc",
|
||||
"net/cert/pki/signature_algorithm.cc",
|
||||
"net/cert/pki/signature_algorithm.h",
|
||||
"net/cert/pki/signature_algorithm_unittest.cc",
|
||||
"net/cert/pki/simple_path_builder_delegate.cc",
|
||||
"net/cert/pki/simple_path_builder_delegate.h",
|
||||
"net/cert/pki/simple_path_builder_delegate_unittest.cc",
|
||||
"net/cert/pki/string_util.cc",
|
||||
"net/cert/pki/string_util_unittest.cc",
|
||||
"net/cert/pki/string_util.h",
|
||||
"net/cert/pki/signature_verify_cache.h",
|
||||
"net/cert/pki/test_helpers.cc",
|
||||
"net/cert/pki/test_helpers.h",
|
||||
"net/cert/pki/trust_store.cc",
|
||||
"net/cert/pki/trust_store_collection.cc",
|
||||
"net/cert/pki/trust_store_collection.h",
|
||||
"net/cert/pki/trust_store_collection_unittest.cc",
|
||||
"net/cert/pki/trust_store.h",
|
||||
"net/cert/pki/trust_store_in_memory.cc",
|
||||
"net/cert/pki/trust_store_in_memory.h",
|
||||
"net/cert/pki/verify_certificate_chain.cc",
|
||||
"net/cert/pki/verify_certificate_chain.h",
|
||||
"net/cert/pki/verify_certificate_chain_pkits_unittest.cc",
|
||||
"net/cert/pki/verify_certificate_chain_typed_unittest.h",
|
||||
"net/cert/pki/verify_certificate_chain_unittest.cc",
|
||||
"net/cert/pki/verify_name_match.cc",
|
||||
"net/cert/pki/verify_name_match.h",
|
||||
"net/cert/pki/verify_name_match_unittest.cc",
|
||||
"net/cert/pki/verify_name_match_fuzzer.cc",
|
||||
"net/cert/pki/verify_name_match_normalizename_fuzzer.cc",
|
||||
"net/cert/pki/verify_name_match_verifynameinsubtree_fuzzer.cc",
|
||||
"net/cert/pki/verify_signed_data.cc",
|
||||
"net/cert/pki/verify_signed_data.h",
|
||||
"net/cert/pki/verify_signed_data_unittest.cc",
|
||||
"net/cert/ocsp_revocation_status.h",
|
||||
"net/cert/ocsp_verify_result.h",
|
||||
"net/cert/ocsp_verify_result.cc",
|
||||
"net/cert/pem.cc",
|
||||
"net/cert/pem.h",
|
||||
"net/cert/x509_certificate.cc",
|
||||
"net/cert/x509_certificate.h",
|
||||
"net/cert/x509_cert_types.h",
|
||||
"net/der/encode_values.cc",
|
||||
"net/der/encode_values.h",
|
||||
"net/der/encode_values_unittest.cc",
|
||||
"net/der/input.cc",
|
||||
"net/der/input.h",
|
||||
"net/der/input_unittest.cc",
|
||||
"net/der/parser.cc",
|
||||
"net/der/parser.h",
|
||||
"net/der/parser_unittest.cc",
|
||||
"net/der/parse_values.cc",
|
||||
"net/der/parse_values.h",
|
||||
"net/der/parse_values_unittest.cc",
|
||||
"net/der/tag.cc",
|
||||
"net/der/tag.h"
|
||||
]
|
||||
}
|
187
pki/import_tool.go
Normal file
187
pki/import_tool.go
Normal file
@ -0,0 +1,187 @@
|
||||
// import_tool is a quick tool for importing Chromium's certificate verifier
|
||||
// code into google3. In time it might be replaced by Copybara, but this is a
|
||||
// lighter-weight solution while we're quickly iterating and only going in one
|
||||
// direction.
|
||||
//
|
||||
// Usage: ./import_tool -spec import_spec.json\
|
||||
// -source-base ~/src/chromium/src/net\
|
||||
// -dest-base .
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type specification struct {
|
||||
Replacements []replacement `json:"replacements"`
|
||||
Files []string `json:"files"`
|
||||
}
|
||||
|
||||
type replacement struct {
|
||||
Match string `json:"match"`
|
||||
matchRE *regexp.Regexp `json:"-"`
|
||||
Replace string `json:"replace"`
|
||||
Include string `json:"include"`
|
||||
Using []string `json:"using"`
|
||||
used uint32
|
||||
}
|
||||
|
||||
var (
|
||||
specFile *string = flag.String("spec", "", "Location of spec JSON")
|
||||
sourceBase *string = flag.String("source-base", "", "Path of the source files")
|
||||
destBase *string = flag.String("dest-base", "", "Path of the destination files")
|
||||
)
|
||||
|
||||
func transformFile(spec *specification, filename string) error {
|
||||
const newLine = "\n"
|
||||
|
||||
sourcePath := filepath.Join(*sourceBase, filename)
|
||||
destPath := filename
|
||||
destPath = strings.TrimPrefix(destPath, "net/")
|
||||
destPath = strings.TrimPrefix(destPath, "cert/")
|
||||
destPath = strings.TrimPrefix(destPath, "der/")
|
||||
destPath = strings.TrimPrefix(destPath, "pki/")
|
||||
destPath = filepath.Join(*destBase, destPath)
|
||||
destDir := filepath.Dir(destPath)
|
||||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
dest, err := os.OpenFile(destPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dest.Close()
|
||||
|
||||
var using []string
|
||||
var includeInsertionPoint int
|
||||
includes := make(map[string]struct{})
|
||||
scanner := bufio.NewScanner(source)
|
||||
out := ""
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if includeInsertionPoint == 0 && len(line) > 0 &&
|
||||
!strings.HasPrefix(line, "// ") &&
|
||||
!strings.HasPrefix(line, "#if") &&
|
||||
!strings.HasPrefix(line, "#define ") {
|
||||
includeInsertionPoint = len(out)
|
||||
}
|
||||
|
||||
for i, repl := range spec.Replacements {
|
||||
if !repl.matchRE.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
line = repl.matchRE.ReplaceAllString(line, repl.Replace)
|
||||
atomic.StoreUint32(&spec.Replacements[i].used, 1)
|
||||
using = append(using, repl.Using...)
|
||||
if repl.Include != "" {
|
||||
includes[repl.Include] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range using {
|
||||
line = strings.Replace(
|
||||
line, "namespace chromium_certificate_verifier {",
|
||||
"namespace chromium_certificate_verifier {\nusing "+u+";", 1)
|
||||
}
|
||||
|
||||
out += line
|
||||
out += newLine
|
||||
}
|
||||
|
||||
if len(includes) > 0 {
|
||||
if includeInsertionPoint == 0 {
|
||||
panic("failed to find include insertion point for " + filename)
|
||||
}
|
||||
|
||||
var s string
|
||||
for include := range includes {
|
||||
s = s + "#include \"" + include + "\"\n"
|
||||
}
|
||||
|
||||
out = out[:includeInsertionPoint] + s + out[includeInsertionPoint:]
|
||||
}
|
||||
|
||||
dest.WriteString(out)
|
||||
fmt.Printf("%s\n", filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func do() error {
|
||||
flag.Parse()
|
||||
|
||||
specBytes, err := ioutil.ReadFile(*specFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var spec specification
|
||||
if err := json.Unmarshal(specBytes, &spec); err != nil {
|
||||
if jsonError, ok := err.(*json.SyntaxError); ok {
|
||||
return fmt.Errorf("JSON parse error at offset %v: %v", jsonError.Offset, err.Error())
|
||||
}
|
||||
return errors.New("JSON parse error: " + err.Error())
|
||||
}
|
||||
|
||||
for i, repl := range spec.Replacements {
|
||||
var err error
|
||||
spec.Replacements[i].matchRE, err = regexp.Compile(repl.Match)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse %q: %s", repl.Match, err)
|
||||
}
|
||||
}
|
||||
|
||||
errors := make(chan error, len(spec.Files))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, filename := range spec.Files {
|
||||
wg.Add(1)
|
||||
|
||||
go func(filename string) {
|
||||
if err := transformFile(&spec, filename); err != nil {
|
||||
errors <- err
|
||||
}
|
||||
wg.Done()
|
||||
}(filename)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
select {
|
||||
case err := <-errors:
|
||||
return err
|
||||
default:
|
||||
break
|
||||
}
|
||||
for _, repl := range spec.Replacements {
|
||||
if repl.used == 0 {
|
||||
fmt.Fprintf(os.Stderr, "replacement for \"%s\" not used\n", repl.Match)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := do(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
71
pki/input.cc
Normal file
71
pki/input.cc
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "input.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "fillins/check.h"
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
Input::Input(std::string_view in)
|
||||
: data_(reinterpret_cast<const uint8_t*>(in.data())), len_(in.length()) {}
|
||||
|
||||
Input::Input(const std::string* s) : Input(std::string_view(*s)) {}
|
||||
|
||||
std::string Input::AsString() const {
|
||||
return std::string(reinterpret_cast<const char*>(data_), len_);
|
||||
}
|
||||
|
||||
std::string_view Input::AsStringView() const {
|
||||
return std::string_view(reinterpret_cast<const char*>(data_), len_);
|
||||
}
|
||||
|
||||
bssl::Span<const uint8_t> Input::AsSpan() const {
|
||||
return bssl::MakeSpan(data_, len_);
|
||||
}
|
||||
|
||||
bool operator==(const Input& lhs, const Input& rhs) {
|
||||
return lhs.Length() == rhs.Length() &&
|
||||
std::equal(lhs.UnsafeData(), lhs.UnsafeData() + lhs.Length(),
|
||||
rhs.UnsafeData());
|
||||
}
|
||||
|
||||
bool operator!=(const Input& lhs, const Input& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
ByteReader::ByteReader(const Input& in)
|
||||
: data_(in.UnsafeData()), len_(in.Length()) {
|
||||
}
|
||||
|
||||
bool ByteReader::ReadByte(uint8_t* byte_p) {
|
||||
if (!HasMore())
|
||||
return false;
|
||||
*byte_p = *data_;
|
||||
Advance(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ByteReader::ReadBytes(size_t len, Input* out) {
|
||||
if (len > len_)
|
||||
return false;
|
||||
*out = Input(data_, len);
|
||||
Advance(len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns whether there is any more data to be read.
|
||||
bool ByteReader::HasMore() {
|
||||
return len_ > 0;
|
||||
}
|
||||
|
||||
void ByteReader::Advance(size_t len) {
|
||||
CHECK_LE(len, len_);
|
||||
data_ += len;
|
||||
len_ -= len;
|
||||
}
|
||||
|
||||
} // namespace bssl::der
|
156
pki/input.h
Normal file
156
pki/input.h
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_DER_INPUT_H_
|
||||
#define BSSL_DER_INPUT_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openssl/span.h>
|
||||
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
// An opaque class that represents a fixed buffer of data of a fixed length,
|
||||
// to be used as an input to other operations. An Input object does not own
|
||||
// the data it references, so callers are responsible for making sure that
|
||||
// the data outlives the Input object and any other associated objects.
|
||||
//
|
||||
// All data access for an Input should be done through the ByteReader class.
|
||||
// This class and associated classes are designed with safety in mind to make it
|
||||
// difficult to read memory outside of an Input. ByteReader provides a simple
|
||||
// API for reading through the Input sequentially. For more complicated uses,
|
||||
// multiple instances of a ByteReader for a particular Input can be created.
|
||||
class OPENSSL_EXPORT Input {
|
||||
public:
|
||||
// Creates an empty Input, one from which no data can be read.
|
||||
constexpr Input() = default;
|
||||
|
||||
// Creates an Input from a constant array |data|.
|
||||
template <size_t N>
|
||||
constexpr explicit Input(const uint8_t (&data)[N]) : data_(data), len_(N) {}
|
||||
|
||||
// Creates an Input from the given |data| and |len|.
|
||||
constexpr explicit Input(const uint8_t* data, size_t len)
|
||||
: data_(data), len_(len) {}
|
||||
|
||||
// Creates an Input from a std::string_view
|
||||
explicit Input(std::string_view sp);
|
||||
|
||||
// Creates an Input from a std::string. The lifetimes are a bit subtle when
|
||||
// using this function: The constructed Input is only valid so long as |s| is
|
||||
// still alive and not mutated.
|
||||
explicit Input(const std::string* s);
|
||||
|
||||
// Returns the length in bytes of an Input's data.
|
||||
constexpr size_t Length() const { return len_; }
|
||||
|
||||
// Returns a pointer to the Input's data. This method is marked as "unsafe"
|
||||
// because access to the Input's data should be done through ByteReader
|
||||
// instead. This method should only be used where using a ByteReader truly
|
||||
// is not an option.
|
||||
constexpr const uint8_t* UnsafeData() const { return data_; }
|
||||
|
||||
// Returns a copy of the data represented by this object as a std::string.
|
||||
std::string AsString() const;
|
||||
|
||||
// Returns a std::string_view pointing to the same data as the Input. The
|
||||
// resulting string_view must not outlive the data that was used to construct
|
||||
// this Input.
|
||||
std::string_view AsStringView() const;
|
||||
|
||||
// Returns a bssl::Span pointing to the same data as the Input. The resulting
|
||||
// bssl::Span must not outlive the data that was used to construct this
|
||||
// Input.
|
||||
bssl::Span<const uint8_t> AsSpan() const;
|
||||
|
||||
private:
|
||||
// This constructor is deleted to prevent constructing an Input from a
|
||||
// std::string r-value. Since the Input points to memory owned by another
|
||||
// object, such an Input would point to invalid memory. Without this deleted
|
||||
// constructor, a std::string could be passed in to the std::string_view
|
||||
// constructor because of std::string_view's implicit constructor.
|
||||
Input(std::string) = delete;
|
||||
|
||||
const uint8_t* data_ = nullptr;
|
||||
size_t len_ = 0;
|
||||
};
|
||||
|
||||
// Return true if |lhs|'s data and |rhs|'s data are byte-wise equal.
|
||||
OPENSSL_EXPORT bool operator==(const Input& lhs, const Input& rhs);
|
||||
|
||||
// Return true if |lhs|'s data and |rhs|'s data are not byte-wise equal.
|
||||
OPENSSL_EXPORT bool operator!=(const Input& lhs, const Input& rhs);
|
||||
|
||||
// Returns true if |lhs|'s data is lexicographically less than |rhs|'s data.
|
||||
OPENSSL_EXPORT constexpr bool operator<(const Input& lhs,
|
||||
const Input& rhs) {
|
||||
// This is `std::lexicographical_compare`, but that's not `constexpr` until
|
||||
// C++-20.
|
||||
auto* it1 = lhs.UnsafeData();
|
||||
auto* it2 = rhs.UnsafeData();
|
||||
const auto* end1 = lhs.UnsafeData() + lhs.Length();
|
||||
const auto* end2 = rhs.UnsafeData() + rhs.Length();
|
||||
for (; it1 != end1 && it2 != end2; ++it1, ++it2) {
|
||||
if (*it1 < *it2) {
|
||||
return true;
|
||||
} else if (*it2 < *it1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return it2 != end2;
|
||||
}
|
||||
|
||||
// This class provides ways to read data from an Input in a bounds-checked way.
|
||||
// The ByteReader is designed to read through the input sequentially. Once a
|
||||
// byte has been read with a ByteReader, the caller can't go back and re-read
|
||||
// that byte with the same reader. Of course, the caller can create multiple
|
||||
// ByteReaders for the same input (or copy an existing ByteReader).
|
||||
//
|
||||
// For something simple like a single byte lookahead, the easiest way to do
|
||||
// that is to copy the ByteReader and call ReadByte() on the copy - the original
|
||||
// ByteReader will be unaffected and the peeked byte will be read through
|
||||
// ReadByte(). For other read patterns, it can be useful to mark where one is
|
||||
// in a ByteReader to be able to return to that spot.
|
||||
//
|
||||
// Some operations using Mark can also be done by creating a copy of the
|
||||
// ByteReader. By using a Mark instead, you use less memory, but more
|
||||
// importantly, you end up with an immutable object that matches the semantics
|
||||
// of what is intended.
|
||||
class OPENSSL_EXPORT ByteReader {
|
||||
public:
|
||||
// Creates a ByteReader to read the data represented by an Input.
|
||||
explicit ByteReader(const Input& in);
|
||||
|
||||
// Reads a single byte from the input source, putting the byte read in
|
||||
// |*byte_p|. If a byte cannot be read from the input (because there is
|
||||
// no input left), then this method returns false.
|
||||
[[nodiscard]] bool ReadByte(uint8_t* out);
|
||||
|
||||
// Reads |len| bytes from the input source, and initializes an Input to
|
||||
// point to that data. If there aren't enough bytes left in the input source,
|
||||
// then this method returns false.
|
||||
[[nodiscard]] bool ReadBytes(size_t len, Input* out);
|
||||
|
||||
// Returns how many bytes are left to read.
|
||||
size_t BytesLeft() const { return len_; }
|
||||
|
||||
// Returns whether there is any more data to be read.
|
||||
bool HasMore();
|
||||
|
||||
private:
|
||||
void Advance(size_t len);
|
||||
|
||||
const uint8_t* data_;
|
||||
size_t len_;
|
||||
};
|
||||
|
||||
} // namespace bssl::der
|
||||
|
||||
#endif // BSSL_DER_INPUT_H_
|
107
pki/input_unittest.cc
Normal file
107
pki/input_unittest.cc
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "input.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl::der::test {
|
||||
|
||||
constexpr uint8_t kInput[] = {'t', 'e', 's', 't'};
|
||||
const uint8_t kInput2[] = {'t', 'e', 'a', 'l'};
|
||||
|
||||
TEST(InputTest, Equals) {
|
||||
Input test(kInput);
|
||||
Input test2(kInput);
|
||||
EXPECT_EQ(test, test2);
|
||||
|
||||
uint8_t input_copy[std::size(kInput)] = {0};
|
||||
memcpy(input_copy, kInput, std::size(kInput));
|
||||
Input test_copy(input_copy);
|
||||
EXPECT_EQ(test, test_copy);
|
||||
|
||||
Input test_truncated(kInput, std::size(kInput) - 1);
|
||||
EXPECT_NE(test, test_truncated);
|
||||
EXPECT_NE(test_truncated, test);
|
||||
}
|
||||
|
||||
TEST(InputTest, LessThan) {
|
||||
Input test(kInput);
|
||||
EXPECT_FALSE(test < test);
|
||||
|
||||
Input test2(kInput2);
|
||||
EXPECT_FALSE(test < test2);
|
||||
EXPECT_TRUE(test2 < test);
|
||||
|
||||
Input test_truncated(kInput, std::size(kInput) - 1);
|
||||
EXPECT_FALSE(test < test_truncated);
|
||||
EXPECT_TRUE(test_truncated < test);
|
||||
}
|
||||
|
||||
TEST(InputTest, AsString) {
|
||||
Input input(kInput);
|
||||
std::string expected_string(reinterpret_cast<const char*>(kInput),
|
||||
std::size(kInput));
|
||||
EXPECT_EQ(expected_string, input.AsString());
|
||||
}
|
||||
|
||||
TEST(InputTest, StaticArray) {
|
||||
Input input(kInput);
|
||||
EXPECT_EQ(std::size(kInput), input.Length());
|
||||
|
||||
Input input2(kInput);
|
||||
EXPECT_EQ(input, input2);
|
||||
}
|
||||
|
||||
TEST(InputTest, ConstExpr) {
|
||||
constexpr Input default_input;
|
||||
static_assert(default_input.Length() == 0);
|
||||
static_assert(default_input.UnsafeData() == nullptr);
|
||||
|
||||
constexpr Input const_array_input(kInput);
|
||||
static_assert(const_array_input.Length() == 4);
|
||||
static_assert(const_array_input.UnsafeData() == kInput);
|
||||
static_assert(default_input < const_array_input);
|
||||
|
||||
constexpr Input ptr_len_input(kInput, 2);
|
||||
static_assert(ptr_len_input.Length() == 2);
|
||||
static_assert(ptr_len_input.UnsafeData() == kInput);
|
||||
static_assert(ptr_len_input < const_array_input);
|
||||
|
||||
Input runtime_input(kInput2, 2);
|
||||
EXPECT_EQ(runtime_input, ptr_len_input);
|
||||
}
|
||||
|
||||
TEST(ByteReaderTest, NoReadPastEnd) {
|
||||
ByteReader reader(Input(nullptr, 0));
|
||||
uint8_t data;
|
||||
EXPECT_FALSE(reader.ReadByte(&data));
|
||||
}
|
||||
|
||||
TEST(ByteReaderTest, ReadToEnd) {
|
||||
uint8_t out;
|
||||
ByteReader reader((Input(kInput)));
|
||||
for (uint8_t input : kInput) {
|
||||
ASSERT_TRUE(reader.ReadByte(&out));
|
||||
ASSERT_EQ(input, out);
|
||||
}
|
||||
EXPECT_FALSE(reader.ReadByte(&out));
|
||||
}
|
||||
|
||||
TEST(ByteReaderTest, PartialReadFails) {
|
||||
Input out;
|
||||
ByteReader reader((Input(kInput)));
|
||||
EXPECT_FALSE(reader.ReadBytes(5, &out));
|
||||
}
|
||||
|
||||
TEST(ByteReaderTest, HasMore) {
|
||||
Input out;
|
||||
ByteReader reader((Input(kInput)));
|
||||
|
||||
ASSERT_TRUE(reader.HasMore());
|
||||
ASSERT_TRUE(reader.ReadBytes(std::size(kInput), &out));
|
||||
ASSERT_FALSE(reader.HasMore());
|
||||
}
|
||||
|
||||
} // namespace bssl::der::test
|
32
pki/mock_signature_verify_cache.cc
Normal file
32
pki/mock_signature_verify_cache.cc
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "mock_signature_verify_cache.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
MockSignatureVerifyCache::MockSignatureVerifyCache() = default;
|
||||
|
||||
MockSignatureVerifyCache::~MockSignatureVerifyCache() = default;
|
||||
|
||||
void MockSignatureVerifyCache::Store(const std::string& key,
|
||||
SignatureVerifyCache::Value value) {
|
||||
cache_.insert_or_assign(key, value);
|
||||
stores_++;
|
||||
}
|
||||
|
||||
SignatureVerifyCache::Value MockSignatureVerifyCache::Check(
|
||||
const std::string& key) {
|
||||
auto iter = cache_.find(key);
|
||||
if (iter == cache_.end()) {
|
||||
misses_++;
|
||||
return SignatureVerifyCache::Value::kUnknown;
|
||||
}
|
||||
hits_++;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
} // namespace net
|
48
pki/mock_signature_verify_cache.h
Normal file
48
pki/mock_signature_verify_cache.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_MOCK_SIGNATURE_VERIFY_CACHE_H_
|
||||
#define BSSL_PKI_MOCK_SIGNATURE_VERIFY_CACHE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
#include "signature_verify_cache.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// MockSignatureVerifyCache is an implementation of SignatureVerifyCache. It is
|
||||
// intended only for testing of cache functionality.
|
||||
|
||||
class MockSignatureVerifyCache : public SignatureVerifyCache {
|
||||
public:
|
||||
MockSignatureVerifyCache();
|
||||
|
||||
~MockSignatureVerifyCache() override;
|
||||
|
||||
void Store(const std::string& key,
|
||||
SignatureVerifyCache::Value value) override;
|
||||
|
||||
SignatureVerifyCache::Value Check(const std::string& key) override;
|
||||
|
||||
size_t CacheHits() { return hits_; }
|
||||
|
||||
size_t CacheMisses() { return misses_; }
|
||||
|
||||
size_t CacheStores() { return stores_; }
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, SignatureVerifyCache::Value> cache_;
|
||||
size_t hits_ = 0;
|
||||
size_t misses_ = 0;
|
||||
size_t stores_ = 0;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_MOCK_PATH_BUILDER_DELEGATE_H_
|
674
pki/name_constraints.cc
Normal file
674
pki/name_constraints.cc
Normal file
@ -0,0 +1,674 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "name_constraints.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "common_cert_errors.h"
|
||||
#include "general_names.h"
|
||||
#include "string_util.h"
|
||||
#include "verify_name_match.h"
|
||||
#include "input.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// The name types of GeneralName that are fully supported in name constraints.
|
||||
//
|
||||
// (The other types will have the minimal checking described by RFC 5280
|
||||
// section 4.2.1.10: If a name constraints extension that is marked as critical
|
||||
// imposes constraints on a particular name form, and an instance of
|
||||
// that name form appears in the subject field or subjectAltName
|
||||
// extension of a subsequent certificate, then the application MUST
|
||||
// either process the constraint or reject the certificate.)
|
||||
const int kSupportedNameTypes =
|
||||
GENERAL_NAME_RFC822_NAME | GENERAL_NAME_DNS_NAME |
|
||||
GENERAL_NAME_DIRECTORY_NAME | GENERAL_NAME_IP_ADDRESS;
|
||||
|
||||
// Controls wildcard handling of DNSNameMatches.
|
||||
// If WildcardMatchType is WILDCARD_PARTIAL_MATCH "*.bar.com" is considered to
|
||||
// match the constraint "foo.bar.com". If it is WILDCARD_FULL_MATCH, "*.bar.com"
|
||||
// will match "bar.com" but not "foo.bar.com".
|
||||
enum WildcardMatchType { WILDCARD_PARTIAL_MATCH, WILDCARD_FULL_MATCH };
|
||||
|
||||
// Returns true if |name| falls in the subtree defined by |dns_constraint|.
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// DNS name restrictions are expressed as host.example.com. Any DNS
|
||||
// name that can be constructed by simply adding zero or more labels
|
||||
// to the left-hand side of the name satisfies the name constraint. For
|
||||
// example, www.host.example.com would satisfy the constraint but
|
||||
// host1.example.com would not.
|
||||
//
|
||||
// |wildcard_matching| controls handling of wildcard names (|name| starts with
|
||||
// "*."). Wildcard handling is not specified by RFC 5280, but certificate
|
||||
// verification allows it, name constraints must check it similarly.
|
||||
bool DNSNameMatches(std::string_view name,
|
||||
std::string_view dns_constraint,
|
||||
WildcardMatchType wildcard_matching) {
|
||||
// Everything matches the empty DNS name constraint.
|
||||
if (dns_constraint.empty())
|
||||
return true;
|
||||
|
||||
// Normalize absolute DNS names by removing the trailing dot, if any.
|
||||
if (!name.empty() && *name.rbegin() == '.')
|
||||
name.remove_suffix(1);
|
||||
if (!dns_constraint.empty() && *dns_constraint.rbegin() == '.')
|
||||
dns_constraint.remove_suffix(1);
|
||||
|
||||
// Wildcard partial-match handling ("*.bar.com" matching name constraint
|
||||
// "foo.bar.com"). This only handles the case where the the dnsname and the
|
||||
// constraint match after removing the leftmost label, otherwise it is handled
|
||||
// by falling through to the check of whether the dnsname is fully within or
|
||||
// fully outside of the constraint.
|
||||
if (wildcard_matching == WILDCARD_PARTIAL_MATCH && name.size() > 2 &&
|
||||
name[0] == '*' && name[1] == '.') {
|
||||
size_t dns_constraint_dot_pos = dns_constraint.find('.');
|
||||
if (dns_constraint_dot_pos != std::string::npos) {
|
||||
std::string_view dns_constraint_domain =
|
||||
dns_constraint.substr(dns_constraint_dot_pos + 1);
|
||||
std::string_view wildcard_domain = name.substr(2);
|
||||
if (bssl::string_util::IsEqualNoCase(wildcard_domain,
|
||||
dns_constraint_domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bssl::string_util::EndsWithNoCase(name, dns_constraint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exact match.
|
||||
if (name.size() == dns_constraint.size())
|
||||
return true;
|
||||
// If dNSName constraint starts with a dot, only subdomains should match.
|
||||
// (e.g., "foo.bar.com" matches constraint ".bar.com", but "bar.com" doesn't.)
|
||||
// RFC 5280 is ambiguous, but this matches the behavior of other platforms.
|
||||
if (!dns_constraint.empty() && dns_constraint[0] == '.')
|
||||
dns_constraint.remove_prefix(1);
|
||||
// Subtree match.
|
||||
if (name.size() > dns_constraint.size() &&
|
||||
name[name.size() - dns_constraint.size() - 1] == '.') {
|
||||
return true;
|
||||
}
|
||||
// Trailing text matches, but not in a subtree (e.g., "foobar.com" is not a
|
||||
// match for "bar.com").
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parses a GeneralSubtrees |value| and store the contents in |subtrees|.
|
||||
// The individual values stored into |subtrees| are not validated by this
|
||||
// function.
|
||||
// NOTE: |subtrees| is not pre-initialized by the function(it is expected to be
|
||||
// a default initialized object), and it will be modified regardless of the
|
||||
// return value.
|
||||
[[nodiscard]] bool ParseGeneralSubtrees(const der::Input& value,
|
||||
GeneralNames* subtrees,
|
||||
CertErrors* errors) {
|
||||
DCHECK(errors);
|
||||
|
||||
// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
|
||||
//
|
||||
// GeneralSubtree ::= SEQUENCE {
|
||||
// base GeneralName,
|
||||
// minimum [0] BaseDistance DEFAULT 0,
|
||||
// maximum [1] BaseDistance OPTIONAL }
|
||||
//
|
||||
// BaseDistance ::= INTEGER (0..MAX)
|
||||
der::Parser sequence_parser(value);
|
||||
// The GeneralSubtrees sequence should have at least 1 element.
|
||||
if (!sequence_parser.HasMore())
|
||||
return false;
|
||||
while (sequence_parser.HasMore()) {
|
||||
der::Parser subtree_sequence;
|
||||
if (!sequence_parser.ReadSequence(&subtree_sequence))
|
||||
return false;
|
||||
|
||||
der::Input raw_general_name;
|
||||
if (!subtree_sequence.ReadRawTLV(&raw_general_name))
|
||||
return false;
|
||||
|
||||
if (!ParseGeneralName(raw_general_name,
|
||||
GeneralNames::IP_ADDRESS_AND_NETMASK, subtrees,
|
||||
errors)) {
|
||||
errors->AddError(kFailedParsingGeneralName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// Within this profile, the minimum and maximum fields are not used with any
|
||||
// name forms, thus, the minimum MUST be zero, and maximum MUST be absent.
|
||||
// However, if an application encounters a critical name constraints
|
||||
// extension that specifies other values for minimum or maximum for a name
|
||||
// form that appears in a subsequent certificate, the application MUST
|
||||
// either process these fields or reject the certificate.
|
||||
|
||||
// Note that technically failing here isn't required: rather only need to
|
||||
// fail if a name of this type actually appears in a subsequent cert and
|
||||
// this extension was marked critical. However the minimum and maximum
|
||||
// fields appear uncommon enough that implementing that isn't useful.
|
||||
if (subtree_sequence.HasMore())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsAlphaDigit(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
// Returns true if 'local_part' contains only characters that are valid in a
|
||||
// non-quoted mailbox local-part. Does not check any other part of the syntax
|
||||
// requirements. Does not allow whitespace.
|
||||
bool IsAllowedRfc822LocalPart(std::string_view local_part) {
|
||||
if (local_part.empty()) {
|
||||
return false;
|
||||
}
|
||||
for (char c : local_part) {
|
||||
if (!(IsAlphaDigit(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
|
||||
c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
|
||||
c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
|
||||
c == '`' || c == '{' || c == '|' || c == '}' || c == '~' ||
|
||||
c == '.')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if 'domain' contains only characters that are valid in a
|
||||
// mailbox domain. Does not check any other part of the syntax
|
||||
// requirements. Does not allow IPv6-address-literal as text IPv6 addresses are
|
||||
// non-unique. Does not allow other address literals either as how to handle
|
||||
// them with domain/subdomain matching isn't specified/possible.
|
||||
bool IsAllowedRfc822Domain(std::string_view domain) {
|
||||
if (domain.empty()) {
|
||||
return false;
|
||||
}
|
||||
for (char c : domain) {
|
||||
if (!(IsAlphaDigit(c) || c == '-' || c == '.')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class Rfc822NameMatchType { kPermitted, kExcluded };
|
||||
bool Rfc822NameMatches(std::string_view local_part,
|
||||
std::string_view domain,
|
||||
std::string_view rfc822_constraint,
|
||||
Rfc822NameMatchType match_type,
|
||||
bool case_insensitive_local_part) {
|
||||
// In case of parsing errors, return a value that will cause the name to not
|
||||
// be permitted.
|
||||
const bool error_value =
|
||||
match_type == Rfc822NameMatchType::kPermitted ? false : true;
|
||||
|
||||
std::vector<std::string_view> constraint_components =
|
||||
bssl::string_util::SplitString(rfc822_constraint, '@');
|
||||
std::string_view constraint_local_part;
|
||||
std::string_view constraint_domain;
|
||||
if (constraint_components.size() == 1) {
|
||||
constraint_domain = constraint_components[0];
|
||||
} else if (constraint_components.size() == 2) {
|
||||
constraint_local_part = constraint_components[0];
|
||||
if (!IsAllowedRfc822LocalPart(constraint_local_part)) {
|
||||
return error_value;
|
||||
}
|
||||
constraint_domain = constraint_components[1];
|
||||
} else {
|
||||
// If we did the full parsing then it is possible for a @ to be in a quoted
|
||||
// local-part of the name, but we don't do that, so just error if @ appears
|
||||
// more than once.
|
||||
return error_value;
|
||||
}
|
||||
if (!IsAllowedRfc822Domain(constraint_domain)) {
|
||||
return error_value;
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// To indicate a particular mailbox, the constraint is the complete mail
|
||||
// address. For example, "root@example.com" indicates the root mailbox on
|
||||
// the host "example.com".
|
||||
if (!constraint_local_part.empty()) {
|
||||
return (case_insensitive_local_part
|
||||
? string_util::IsEqualNoCase(local_part, constraint_local_part)
|
||||
: local_part == constraint_local_part) &&
|
||||
string_util::IsEqualNoCase(domain, constraint_domain);
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// To specify any address within a domain, the constraint is specified with a
|
||||
// leading period (as with URIs). For example, ".example.com" indicates all
|
||||
// the Internet mail addresses in the domain "example.com", but not Internet
|
||||
// mail addresses on the host "example.com".
|
||||
if (!constraint_domain.empty() && constraint_domain[0] == '.') {
|
||||
return string_util::EndsWithNoCase(domain, constraint_domain);
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// To indicate all Internet mail addresses on a particular host, the
|
||||
// constraint is specified as the host name. For example, the constraint
|
||||
// "example.com" is satisfied by any mail address at the host "example.com".
|
||||
return string_util::IsEqualNoCase(domain, constraint_domain);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NameConstraints::~NameConstraints() = default;
|
||||
|
||||
// static
|
||||
std::unique_ptr<NameConstraints> NameConstraints::Create(
|
||||
const der::Input& extension_value,
|
||||
bool is_critical,
|
||||
CertErrors* errors) {
|
||||
DCHECK(errors);
|
||||
|
||||
auto name_constraints = std::make_unique<NameConstraints>();
|
||||
if (!name_constraints->Parse(extension_value, is_critical, errors))
|
||||
return nullptr;
|
||||
return name_constraints;
|
||||
}
|
||||
|
||||
bool NameConstraints::Parse(const der::Input& extension_value,
|
||||
bool is_critical,
|
||||
CertErrors* errors) {
|
||||
DCHECK(errors);
|
||||
|
||||
der::Parser extension_parser(extension_value);
|
||||
der::Parser sequence_parser;
|
||||
|
||||
// NameConstraints ::= SEQUENCE {
|
||||
// permittedSubtrees [0] GeneralSubtrees OPTIONAL,
|
||||
// excludedSubtrees [1] GeneralSubtrees OPTIONAL }
|
||||
if (!extension_parser.ReadSequence(&sequence_parser))
|
||||
return false;
|
||||
if (extension_parser.HasMore())
|
||||
return false;
|
||||
|
||||
std::optional<der::Input> permitted_subtrees_value;
|
||||
if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
|
||||
&permitted_subtrees_value)) {
|
||||
return false;
|
||||
}
|
||||
if (permitted_subtrees_value &&
|
||||
!ParseGeneralSubtrees(permitted_subtrees_value.value(),
|
||||
&permitted_subtrees_, errors)) {
|
||||
return false;
|
||||
}
|
||||
constrained_name_types_ |=
|
||||
permitted_subtrees_.present_name_types &
|
||||
(is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes);
|
||||
|
||||
std::optional<der::Input> excluded_subtrees_value;
|
||||
if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
|
||||
&excluded_subtrees_value)) {
|
||||
return false;
|
||||
}
|
||||
if (excluded_subtrees_value &&
|
||||
!ParseGeneralSubtrees(excluded_subtrees_value.value(),
|
||||
&excluded_subtrees_, errors)) {
|
||||
return false;
|
||||
}
|
||||
constrained_name_types_ |=
|
||||
excluded_subtrees_.present_name_types &
|
||||
(is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes);
|
||||
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// Conforming CAs MUST NOT issue certificates where name constraints is an
|
||||
// empty sequence. That is, either the permittedSubtrees field or the
|
||||
// excludedSubtrees MUST be present.
|
||||
if (!permitted_subtrees_value && !excluded_subtrees_value)
|
||||
return false;
|
||||
|
||||
if (sequence_parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NameConstraints::IsPermittedCert(const der::Input& subject_rdn_sequence,
|
||||
const GeneralNames* subject_alt_names,
|
||||
CertErrors* errors) const {
|
||||
// Checking NameConstraints is O(number_of_names * number_of_constraints).
|
||||
// Impose a hard limit to mitigate the use of name constraints as a DoS
|
||||
// mechanism. This mimics the similar check in BoringSSL x509/v_ncons.c
|
||||
// TODO(bbe): make both name constraint mechanisms subquadratic and remove
|
||||
// this check.
|
||||
|
||||
const size_t kMaxChecks = 1048576; // 1 << 20
|
||||
|
||||
// Names all come from a certificate, which is bound by size_t, so adding them
|
||||
// up can not overflow a size_t.
|
||||
size_t name_count = 0;
|
||||
// Constraints all come from a certificate, which is bound by a size_t, so
|
||||
// adding them up can not overflow a size_t.
|
||||
size_t constraint_count = 0;
|
||||
if (subject_alt_names) {
|
||||
name_count = subject_alt_names->rfc822_names.size() +
|
||||
subject_alt_names->dns_names.size() +
|
||||
subject_alt_names->directory_names.size() +
|
||||
subject_alt_names->ip_addresses.size();
|
||||
constraint_count = excluded_subtrees_.rfc822_names.size() +
|
||||
permitted_subtrees_.rfc822_names.size() +
|
||||
excluded_subtrees_.dns_names.size() +
|
||||
permitted_subtrees_.dns_names.size() +
|
||||
excluded_subtrees_.directory_names.size() +
|
||||
permitted_subtrees_.directory_names.size() +
|
||||
excluded_subtrees_.ip_address_ranges.size() +
|
||||
permitted_subtrees_.ip_address_ranges.size();
|
||||
} else {
|
||||
constraint_count += excluded_subtrees_.directory_names.size() +
|
||||
permitted_subtrees_.directory_names.size();
|
||||
name_count = subject_rdn_sequence.Length();
|
||||
}
|
||||
// Upper bound the number of possible checks, checking for overflow.
|
||||
size_t check_count = constraint_count * name_count;
|
||||
if ((constraint_count > 0 && check_count / constraint_count != name_count) ||
|
||||
check_count > kMaxChecks) {
|
||||
errors->AddError(cert_errors::kTooManyNameConstraintChecks);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> subject_email_addresses_to_check;
|
||||
if (!subject_alt_names &&
|
||||
(constrained_name_types() & GENERAL_NAME_RFC822_NAME)) {
|
||||
if (!FindEmailAddressesInName(subject_rdn_sequence,
|
||||
&subject_email_addresses_to_check)) {
|
||||
// Error parsing |subject_rdn_sequence|.
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Subject Alternative Name handling:
|
||||
//
|
||||
// RFC 5280 section 4.2.1.6:
|
||||
// id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
|
||||
//
|
||||
// SubjectAltName ::= GeneralNames
|
||||
//
|
||||
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
||||
|
||||
if (subject_alt_names) {
|
||||
// Check unsupported name types:
|
||||
// constrained_name_types() for the unsupported types will only be true if
|
||||
// that type of name was present in a name constraint that was marked
|
||||
// critical.
|
||||
//
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// If a name constraints extension that is marked as critical
|
||||
// imposes constraints on a particular name form, and an instance of
|
||||
// that name form appears in the subject field or subjectAltName
|
||||
// extension of a subsequent certificate, then the application MUST
|
||||
// either process the constraint or reject the certificate.
|
||||
if (constrained_name_types() & subject_alt_names->present_name_types &
|
||||
~kSupportedNameTypes) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check supported name types:
|
||||
|
||||
// Only check rfc822 SANs if any rfc822 constraints are present, since we
|
||||
// might fail if there are email addresses we don't know how to parse but
|
||||
// are technically correct.
|
||||
if (constrained_name_types() & GENERAL_NAME_RFC822_NAME) {
|
||||
for (const auto& rfc822_name : subject_alt_names->rfc822_names) {
|
||||
if (!IsPermittedRfc822Name(
|
||||
rfc822_name, /*case_insensitive_exclude_localpart=*/false)) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& dns_name : subject_alt_names->dns_names) {
|
||||
if (!IsPermittedDNSName(dns_name)) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& directory_name : subject_alt_names->directory_names) {
|
||||
if (!IsPermittedDirectoryName(directory_name)) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& ip_address : subject_alt_names->ip_addresses) {
|
||||
if (!IsPermittedIP(ip_address)) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subject handling:
|
||||
|
||||
// RFC 5280 section 4.2.1.10:
|
||||
// Legacy implementations exist where an electronic mail address is embedded
|
||||
// in the subject distinguished name in an attribute of type emailAddress
|
||||
// (Section 4.1.2.6). When constraints are imposed on the rfc822Name name
|
||||
// form, but the certificate does not include a subject alternative name, the
|
||||
// rfc822Name constraint MUST be applied to the attribute of type emailAddress
|
||||
// in the subject distinguished name.
|
||||
for (const auto& rfc822_name : subject_email_addresses_to_check) {
|
||||
// Whether local_part should be matched case-sensitive or not is somewhat
|
||||
// unclear. RFC 2821 says that it should be case-sensitive. RFC 2985 says
|
||||
// that emailAddress attributes in a Name are fully case-insensitive.
|
||||
// Some other verifier implementations always do local-part comparison
|
||||
// case-sensitive, while some always do it case-insensitive. Many but not
|
||||
// all SMTP servers interpret addresses as case-insensitive.
|
||||
//
|
||||
// Give how poorly specified this is, and the conflicting implementations
|
||||
// in the wild, this implementation will do case-insensitive match for
|
||||
// excluded names from the subject to avoid potentially allowing
|
||||
// something that wasn't expected.
|
||||
if (!IsPermittedRfc822Name(rfc822_name,
|
||||
/*case_insensitive_exclude_localpart=*/true)) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 5280 4.1.2.6:
|
||||
// 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.
|
||||
// This code assumes that criticality condition is checked by the caller, and
|
||||
// therefore only needs to avoid the IsPermittedDirectoryName check against an
|
||||
// empty subject in such a case.
|
||||
if (subject_alt_names && subject_rdn_sequence.Length() == 0)
|
||||
return;
|
||||
|
||||
if (!IsPermittedDirectoryName(subject_rdn_sequence)) {
|
||||
errors->AddError(cert_errors::kNotPermittedByNameConstraints);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool NameConstraints::IsPermittedRfc822Name(
|
||||
std::string_view name,
|
||||
bool case_insensitive_exclude_localpart) const {
|
||||
// RFC 5280 4.2.1.6. Subject Alternative Name
|
||||
//
|
||||
// When the subjectAltName extension contains an Internet mail address,
|
||||
// the address MUST be stored in the rfc822Name. The format of an
|
||||
// rfc822Name is a "Mailbox" as defined in Section 4.1.2 of [RFC2821].
|
||||
// A Mailbox has the form "Local-part@Domain". Note that a Mailbox has
|
||||
// no phrase (such as a common name) before it, has no comment (text
|
||||
// surrounded in parentheses) after it, and is not surrounded by "<" and
|
||||
// ">". Rules for encoding Internet mail addresses that include
|
||||
// internationalized domain names are specified in Section 7.5.
|
||||
|
||||
// Relevant parts from RFC 2821 & RFC 2822
|
||||
//
|
||||
// Mailbox = Local-part "@" Domain
|
||||
// Local-part = Dot-string / Quoted-string
|
||||
// ; MAY be case-sensitive
|
||||
//
|
||||
// Dot-string = Atom *("." Atom)
|
||||
// Atom = 1*atext
|
||||
// Quoted-string = DQUOTE *qcontent DQUOTE
|
||||
//
|
||||
//
|
||||
// atext = ALPHA / DIGIT / ; Any character except controls,
|
||||
// "!" / "#" / ; SP, and specials.
|
||||
// "$" / "%" / ; Used for atoms
|
||||
// "&" / "'" /
|
||||
// "*" / "+" /
|
||||
// "-" / "/" /
|
||||
// "=" / "?" /
|
||||
// "^" / "_" /
|
||||
// "`" / "{" /
|
||||
// "|" / "}" /
|
||||
// "~"
|
||||
//
|
||||
// atom = [CFWS] 1*atext [CFWS]
|
||||
//
|
||||
//
|
||||
// qtext = NO-WS-CTL / ; Non white space controls
|
||||
// %d33 / ; The rest of the US-ASCII
|
||||
// %d35-91 / ; characters not including "\"
|
||||
// %d93-126 ; or the quote character
|
||||
//
|
||||
// quoted-pair = ("\" text) / obs-qp
|
||||
// qcontent = qtext / quoted-pair
|
||||
//
|
||||
//
|
||||
// Domain = (sub-domain 1*("." sub-domain)) / address-literal
|
||||
// sub-domain = Let-dig [Ldh-str]
|
||||
//
|
||||
// Let-dig = ALPHA / DIGIT
|
||||
// Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
|
||||
//
|
||||
// address-literal = "[" IPv4-address-literal /
|
||||
// IPv6-address-literal /
|
||||
// General-address-literal "]"
|
||||
// ; See section 4.1.3
|
||||
|
||||
// However, no one actually implements all that. Known implementations just
|
||||
// do string comparisons, but that is technically incorrect. (Ex: a
|
||||
// constraint excluding |foo@example.com| should exclude a SAN of
|
||||
// |"foo"@example.com|, while a naive direct comparison will allow it.)
|
||||
//
|
||||
// We don't implement all that either, but do something a bit more fail-safe
|
||||
// by rejecting any addresses that contain characters that are not allowed in
|
||||
// the non-quoted formats.
|
||||
|
||||
std::vector<std::string_view> name_components =
|
||||
bssl::string_util::SplitString(name, '@');
|
||||
if (name_components.size() != 2) {
|
||||
// If we did the full parsing then it is possible for a @ to be in a quoted
|
||||
// local-part of the name, but we don't do that, so just fail if @ appears
|
||||
// more than once.
|
||||
return false;
|
||||
}
|
||||
if (!IsAllowedRfc822LocalPart(name_components[0]) ||
|
||||
!IsAllowedRfc822Domain(name_components[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& excluded_name : excluded_subtrees_.rfc822_names) {
|
||||
if (Rfc822NameMatches(name_components[0], name_components[1], excluded_name,
|
||||
Rfc822NameMatchType::kExcluded,
|
||||
case_insensitive_exclude_localpart)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If permitted subtrees are not constrained, any name that is not excluded is
|
||||
// allowed.
|
||||
if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_RFC822_NAME)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& permitted_name : permitted_subtrees_.rfc822_names) {
|
||||
if (Rfc822NameMatches(name_components[0], name_components[1],
|
||||
permitted_name, Rfc822NameMatchType::kPermitted,
|
||||
/*case_insenitive_local_part=*/false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NameConstraints::IsPermittedDNSName(std::string_view name) const {
|
||||
for (const auto& excluded_name : excluded_subtrees_.dns_names) {
|
||||
// When matching wildcard hosts against excluded subtrees, consider it a
|
||||
// match if the constraint would match any expansion of the wildcard. Eg,
|
||||
// *.bar.com should match a constraint of foo.bar.com.
|
||||
if (DNSNameMatches(name, excluded_name, WILDCARD_PARTIAL_MATCH))
|
||||
return false;
|
||||
}
|
||||
|
||||
// If permitted subtrees are not constrained, any name that is not excluded is
|
||||
// allowed.
|
||||
if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_DNS_NAME))
|
||||
return true;
|
||||
|
||||
for (const auto& permitted_name : permitted_subtrees_.dns_names) {
|
||||
// When matching wildcard hosts against permitted subtrees, consider it a
|
||||
// match only if the constraint would match all expansions of the wildcard.
|
||||
// Eg, *.bar.com should match a constraint of bar.com, but not foo.bar.com.
|
||||
if (DNSNameMatches(name, permitted_name, WILDCARD_FULL_MATCH))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NameConstraints::IsPermittedDirectoryName(
|
||||
const der::Input& name_rdn_sequence) const {
|
||||
for (const auto& excluded_name : excluded_subtrees_.directory_names) {
|
||||
if (VerifyNameInSubtree(name_rdn_sequence, excluded_name))
|
||||
return false;
|
||||
}
|
||||
|
||||
// If permitted subtrees are not constrained, any name that is not excluded is
|
||||
// allowed.
|
||||
if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_DIRECTORY_NAME))
|
||||
return true;
|
||||
|
||||
for (const auto& permitted_name : permitted_subtrees_.directory_names) {
|
||||
if (VerifyNameInSubtree(name_rdn_sequence, permitted_name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NameConstraints::IsPermittedIP(const fillins::IPAddress& ip) const {
|
||||
for (const auto& excluded_ip : excluded_subtrees_.ip_address_ranges) {
|
||||
if (fillins::IPAddressMatchesPrefix(ip, excluded_ip.first, excluded_ip.second))
|
||||
return false;
|
||||
}
|
||||
|
||||
// If permitted subtrees are not constrained, any name that is not excluded is
|
||||
// allowed.
|
||||
if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_IP_ADDRESS))
|
||||
return true;
|
||||
|
||||
for (const auto& permitted_ip : permitted_subtrees_.ip_address_ranges) {
|
||||
if (fillins::IPAddressMatchesPrefix(ip, permitted_ip.first, permitted_ip.second))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace net
|
106
pki/name_constraints.h
Normal file
106
pki/name_constraints.h
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_NAME_CONSTRAINTS_H_
|
||||
#define BSSL_PKI_NAME_CONSTRAINTS_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "fillins/ip_address.h"
|
||||
|
||||
#include "general_names.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
class CertErrors;
|
||||
|
||||
namespace der {
|
||||
class Input;
|
||||
} // namespace der
|
||||
|
||||
// Parses a NameConstraints extension value and allows testing whether names are
|
||||
// allowed under those constraints as defined by RFC 5280 section 4.2.1.10.
|
||||
class OPENSSL_EXPORT NameConstraints {
|
||||
public:
|
||||
~NameConstraints();
|
||||
|
||||
// Parses a DER-encoded NameConstraints extension and initializes this object.
|
||||
// |extension_value| should be the extnValue from the extension (not including
|
||||
// the OCTET STRING tag). |is_critical| should be true if the extension was
|
||||
// marked critical. Returns nullptr if parsing the the extension failed.
|
||||
// The object may reference data from |extension_value|, so is only valid as
|
||||
// long as |extension_value| is.
|
||||
static std::unique_ptr<NameConstraints> Create(
|
||||
const der::Input& extension_value,
|
||||
bool is_critical,
|
||||
CertErrors* errors);
|
||||
|
||||
// Tests if a certificate is allowed by the name constraints.
|
||||
// |subject_rdn_sequence| should be the DER-encoded value of the subject's
|
||||
// RDNSequence (not including Sequence tag), and may be an empty ASN.1
|
||||
// sequence. |subject_alt_names| should be the parsed representation of the
|
||||
// subjectAltName extension or nullptr if the extension was not present.
|
||||
// If the certificate is not allowed, an error will be added to |errors|.
|
||||
// Note that this method does not check hostname or IP address in commonName,
|
||||
// which is deprecated (crbug.com/308330).
|
||||
void IsPermittedCert(const der::Input& subject_rdn_sequence,
|
||||
const GeneralNames* subject_alt_names,
|
||||
CertErrors* errors) const;
|
||||
|
||||
// Returns true if the ASCII email address |name| is permitted. |name| should
|
||||
// be a "mailbox" as specified by RFC 2821, with the additional restriction
|
||||
// that quoted names and whitespace are not allowed by this implementation.
|
||||
bool IsPermittedRfc822Name(std::string_view name,
|
||||
bool case_insensitive_exclude_localpart) const;
|
||||
|
||||
// Returns true if the ASCII hostname |name| is permitted.
|
||||
// |name| may be a wildcard hostname (starts with "*."). Eg, "*.bar.com"
|
||||
// would not be permitted if "bar.com" is permitted and "foo.bar.com" is
|
||||
// excluded, while "*.baz.com" would only be permitted if "baz.com" is
|
||||
// permitted.
|
||||
bool IsPermittedDNSName(std::string_view name) const;
|
||||
|
||||
// Returns true if the directoryName |name_rdn_sequence| is permitted.
|
||||
// |name_rdn_sequence| should be the DER-encoded RDNSequence value (not
|
||||
// including the Sequence tag.)
|
||||
bool IsPermittedDirectoryName(const der::Input& name_rdn_sequence) const;
|
||||
|
||||
// Returns true if the iPAddress |ip| is permitted.
|
||||
bool IsPermittedIP(const fillins::IPAddress& ip) const;
|
||||
|
||||
// Returns a bitfield of GeneralNameTypes of all the types constrained by this
|
||||
// NameConstraints. Name types that aren't supported will only be present if
|
||||
// the name constraint they appeared in was marked critical.
|
||||
//
|
||||
// RFC 5280 section 4.2.1.10 says:
|
||||
// Applications conforming to this profile MUST be able to process name
|
||||
// constraints that are imposed on the directoryName name form and SHOULD be
|
||||
// able to process name constraints that are imposed on the rfc822Name,
|
||||
// uniformResourceIdentifier, dNSName, and iPAddress name forms.
|
||||
// If a name constraints extension that is marked as critical
|
||||
// imposes constraints on a particular name form, and an instance of
|
||||
// that name form appears in the subject field or subjectAltName
|
||||
// extension of a subsequent certificate, then the application MUST
|
||||
// either process the constraint or reject the certificate.
|
||||
int constrained_name_types() const { return constrained_name_types_; }
|
||||
|
||||
const GeneralNames& permitted_subtrees() const { return permitted_subtrees_; }
|
||||
const GeneralNames& excluded_subtrees() const { return excluded_subtrees_; }
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool Parse(const der::Input& extension_value,
|
||||
bool is_critical,
|
||||
CertErrors* errors);
|
||||
|
||||
GeneralNames permitted_subtrees_;
|
||||
GeneralNames excluded_subtrees_;
|
||||
int constrained_name_types_ = GENERAL_NAME_NONE;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_NAME_CONSTRAINTS_H_
|
1824
pki/name_constraints_unittest.cc
Normal file
1824
pki/name_constraints_unittest.cc
Normal file
File diff suppressed because it is too large
Load Diff
100
pki/nist_pkits_unittest.cc
Normal file
100
pki/nist_pkits_unittest.cc
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "nist_pkits_unittest.h"
|
||||
|
||||
#include "certificate_policies.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// 2.16.840.1.101.3.2.1.48.1
|
||||
const uint8_t kTestPolicy1[] = {0x60, 0x86, 0x48, 0x01, 0x65,
|
||||
0x03, 0x02, 0x01, 0x30, 0x01};
|
||||
|
||||
// 2.16.840.1.101.3.2.1.48.2
|
||||
const uint8_t kTestPolicy2[] = {0x60, 0x86, 0x48, 0x01, 0x65,
|
||||
0x03, 0x02, 0x01, 0x30, 0x02};
|
||||
|
||||
// 2.16.840.1.101.3.2.1.48.3
|
||||
const uint8_t kTestPolicy3[] = {0x60, 0x86, 0x48, 0x01, 0x65,
|
||||
0x03, 0x02, 0x01, 0x30, 0x03};
|
||||
|
||||
// 2.16.840.1.101.3.2.1.48.6
|
||||
const uint8_t kTestPolicy6[] = {0x60, 0x86, 0x48, 0x01, 0x65,
|
||||
0x03, 0x02, 0x01, 0x30, 0x06};
|
||||
|
||||
void SetPolicySetFromString(const char* const policy_names,
|
||||
std::set<der::Input>* out) {
|
||||
out->clear();
|
||||
std::istringstream stream(policy_names);
|
||||
for (std::string line; std::getline(stream, line, ',');) {
|
||||
size_t start = line.find_first_not_of(" \n\t\r\f\v");
|
||||
if (start == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
size_t end = line.find_last_not_of(" \n\t\r\f\v");
|
||||
if (end == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
std::string policy_name = line.substr(start, end + 1);
|
||||
if (policy_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (policy_name == "anyPolicy") {
|
||||
out->insert(der::Input(kAnyPolicyOid));
|
||||
} else if (policy_name == "NIST-test-policy-1") {
|
||||
out->insert(der::Input(kTestPolicy1));
|
||||
} else if (policy_name == "NIST-test-policy-2") {
|
||||
out->insert(der::Input(kTestPolicy2));
|
||||
} else if (policy_name == "NIST-test-policy-3") {
|
||||
out->insert(der::Input(kTestPolicy3));
|
||||
} else if (policy_name == "NIST-test-policy-6") {
|
||||
out->insert(der::Input(kTestPolicy6));
|
||||
} else {
|
||||
ADD_FAILURE() << "Unknown policy name: " << policy_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PkitsTestInfo::PkitsTestInfo() {
|
||||
SetInitialPolicySet("anyPolicy");
|
||||
SetUserConstrainedPolicySet("NIST-test-policy-1");
|
||||
}
|
||||
|
||||
PkitsTestInfo::PkitsTestInfo(const PkitsTestInfo& other) = default;
|
||||
|
||||
PkitsTestInfo::~PkitsTestInfo() = default;
|
||||
|
||||
void PkitsTestInfo::SetInitialExplicitPolicy(bool b) {
|
||||
initial_explicit_policy =
|
||||
b ? InitialExplicitPolicy::kTrue : InitialExplicitPolicy::kFalse;
|
||||
}
|
||||
|
||||
void PkitsTestInfo::SetInitialPolicyMappingInhibit(bool b) {
|
||||
initial_policy_mapping_inhibit = b ? InitialPolicyMappingInhibit::kTrue
|
||||
: InitialPolicyMappingInhibit::kFalse;
|
||||
}
|
||||
|
||||
void PkitsTestInfo::SetInitialInhibitAnyPolicy(bool b) {
|
||||
initial_inhibit_any_policy =
|
||||
b ? InitialAnyPolicyInhibit::kTrue : InitialAnyPolicyInhibit::kFalse;
|
||||
}
|
||||
|
||||
void PkitsTestInfo::SetInitialPolicySet(const char* const policy_names) {
|
||||
SetPolicySetFromString(policy_names, &initial_policy_set);
|
||||
}
|
||||
|
||||
void PkitsTestInfo::SetUserConstrainedPolicySet(
|
||||
const char* const policy_names) {
|
||||
SetPolicySetFromString(policy_names, &user_constrained_policy_set);
|
||||
}
|
||||
|
||||
} // namespace net
|
149
pki/nist_pkits_unittest.h
Normal file
149
pki/nist_pkits_unittest.h
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_NIST_PKITS_UNITTEST_H_
|
||||
#define BSSL_PKI_NIST_PKITS_UNITTEST_H_
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "test_helpers.h"
|
||||
#include "parse_values.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// Describes the inputs and outputs (other than the certificates) for
|
||||
// the PKITS tests.
|
||||
struct PkitsTestInfo {
|
||||
// Default construction results in the "default settings".
|
||||
PkitsTestInfo();
|
||||
PkitsTestInfo(const PkitsTestInfo& other);
|
||||
~PkitsTestInfo();
|
||||
|
||||
// Sets |initial_policy_set| to the specified policies. The
|
||||
// policies are described as comma-separated symbolic strings like
|
||||
// "anyPolicy" and "NIST-test-policy-1".
|
||||
//
|
||||
// If this isn't called, the default is "anyPolicy".
|
||||
void SetInitialPolicySet(const char* const policy_names);
|
||||
|
||||
// Sets |user_constrained_policy_set| to the specified policies. The
|
||||
// policies are described as comma-separated symbolic strings like
|
||||
// "anyPolicy" and "NIST-test-policy-1".
|
||||
//
|
||||
// If this isn't called, the default is "NIST-test-policy-1".
|
||||
void SetUserConstrainedPolicySet(const char* const policy_names);
|
||||
|
||||
void SetInitialExplicitPolicy(bool b);
|
||||
void SetInitialPolicyMappingInhibit(bool b);
|
||||
void SetInitialInhibitAnyPolicy(bool b);
|
||||
|
||||
// ----------------
|
||||
// Info
|
||||
// ----------------
|
||||
|
||||
// The PKITS test number. For example, "4.1.1".
|
||||
const char* test_number = nullptr;
|
||||
|
||||
// ----------------
|
||||
// Inputs
|
||||
// ----------------
|
||||
|
||||
// A set of policy OIDs to use for "initial-policy-set".
|
||||
std::set<der::Input> initial_policy_set;
|
||||
|
||||
// The value of "initial-explicit-policy".
|
||||
InitialExplicitPolicy initial_explicit_policy = InitialExplicitPolicy::kFalse;
|
||||
|
||||
// The value of "initial-policy-mapping-inhibit".
|
||||
InitialPolicyMappingInhibit initial_policy_mapping_inhibit =
|
||||
InitialPolicyMappingInhibit::kFalse;
|
||||
|
||||
// The value of "initial-inhibit-any-policy".
|
||||
InitialAnyPolicyInhibit initial_inhibit_any_policy =
|
||||
InitialAnyPolicyInhibit::kFalse;
|
||||
|
||||
// This is the time when PKITS was published.
|
||||
der::GeneralizedTime time = {2011, 4, 15, 0, 0, 0};
|
||||
|
||||
// ----------------
|
||||
// Expected outputs
|
||||
// ----------------
|
||||
|
||||
// Whether path validation should succeed.
|
||||
bool should_validate = false;
|
||||
|
||||
std::set<der::Input> user_constrained_policy_set;
|
||||
};
|
||||
|
||||
// Parameterized test class for PKITS tests.
|
||||
// The instantiating code should define a PkitsTestDelegate with an appropriate
|
||||
// static RunTest method, and then INSTANTIATE_TYPED_TEST_SUITE_P for each
|
||||
// testcase (each TYPED_TEST_SUITE_P in pkits_testcases-inl.h).
|
||||
template <typename PkitsTestDelegate>
|
||||
class PkitsTest : public ::testing::Test {
|
||||
public:
|
||||
template <size_t num_certs, size_t num_crls>
|
||||
void RunTest(const char* const (&cert_names)[num_certs],
|
||||
const char* const (&crl_names)[num_crls],
|
||||
const PkitsTestInfo& info) {
|
||||
std::vector<std::string> cert_ders;
|
||||
for (const std::string s : cert_names) {
|
||||
cert_ders.push_back(ReadTestFileToString(
|
||||
"testdata/nist-pkits/certs/" + s + ".crt"));
|
||||
}
|
||||
std::vector<std::string> crl_ders;
|
||||
for (const std::string s : crl_names) {
|
||||
crl_ders.push_back(ReadTestFileToString(
|
||||
"testdata/nist-pkits/crls/" + s + ".crl"));
|
||||
}
|
||||
|
||||
std::string_view test_number = info.test_number;
|
||||
|
||||
// Some of the PKITS tests are intentionally given different expectations
|
||||
// from PKITS.pdf.
|
||||
//
|
||||
// Empty user_constrained_policy_set due to short-circuit on invalid
|
||||
// signatures:
|
||||
//
|
||||
// 4.1.2 - Invalid CA Signature Test2
|
||||
// 4.1.3 - Invalid EE Signature Test3
|
||||
// 4.1.6 - Invalid DSA Signature Test6
|
||||
//
|
||||
// Expected to fail because DSA signatures are not supported:
|
||||
//
|
||||
// 4.1.4 - Valid DSA Signatures Test4
|
||||
// 4.1.5 - Valid DSA Parameter Inheritance Test5
|
||||
//
|
||||
// Expected to fail because Name constraints on
|
||||
// uniformResourceIdentifiers are not supported:
|
||||
//
|
||||
// 4.13.34 - Valid URI nameConstraints Test34
|
||||
// 4.13.36 - Valid URI nameConstraints Test36
|
||||
if (test_number == "4.1.2" || test_number == "4.1.3" ||
|
||||
test_number == "4.1.6") {
|
||||
PkitsTestInfo modified_info = info;
|
||||
modified_info.user_constrained_policy_set = {};
|
||||
PkitsTestDelegate::RunTest(cert_ders, crl_ders, modified_info);
|
||||
} else if (test_number == "4.1.4" || test_number == "4.1.5") {
|
||||
PkitsTestInfo modified_info = info;
|
||||
modified_info.user_constrained_policy_set = {};
|
||||
modified_info.should_validate = false;
|
||||
PkitsTestDelegate::RunTest(cert_ders, crl_ders, modified_info);
|
||||
} else if (test_number == "4.13.34" || test_number == "4.13.36") {
|
||||
PkitsTestInfo modified_info = info;
|
||||
modified_info.should_validate = false;
|
||||
PkitsTestDelegate::RunTest(cert_ders, crl_ders, modified_info);
|
||||
} else {
|
||||
PkitsTestDelegate::RunTest(cert_ders, crl_ders, info);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Inline the generated test code:
|
||||
#include "testdata/nist-pkits/pkits_testcases-inl.h"
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_NIST_PKITS_UNITTEST_H_
|
1050
pki/ocsp.cc
Normal file
1050
pki/ocsp.cc
Normal file
File diff suppressed because it is too large
Load Diff
324
pki/ocsp.h
Normal file
324
pki/ocsp.h
Normal file
@ -0,0 +1,324 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_OCSP_H_
|
||||
#define BSSL_PKI_OCSP_H_
|
||||
|
||||
#include "webutil/url/url.h"
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "ocsp_revocation_status.h"
|
||||
#include "ocsp_verify_result.h"
|
||||
#include "parse_certificate.h"
|
||||
#include "signature_algorithm.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
|
||||
class URL;
|
||||
|
||||
namespace bssl {
|
||||
|
||||
class ParsedCertificate;
|
||||
|
||||
// OCSPCertID contains a representation of a DER-encoded RFC 6960 "CertID".
|
||||
//
|
||||
// CertID ::= SEQUENCE {
|
||||
// hashAlgorithm AlgorithmIdentifier,
|
||||
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
|
||||
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
|
||||
// serialNumber CertificateSerialNumber
|
||||
// }
|
||||
struct OPENSSL_EXPORT OCSPCertID {
|
||||
OCSPCertID();
|
||||
~OCSPCertID();
|
||||
|
||||
DigestAlgorithm hash_algorithm;
|
||||
der::Input issuer_name_hash;
|
||||
der::Input issuer_key_hash;
|
||||
der::Input serial_number;
|
||||
};
|
||||
|
||||
// OCSPCertStatus contains a representation of a DER-encoded RFC 6960
|
||||
// "CertStatus". |revocation_time| and |has_reason| are only valid when
|
||||
// |status| is REVOKED. |revocation_reason| is only valid when |has_reason| is
|
||||
// true.
|
||||
//
|
||||
// CertStatus ::= CHOICE {
|
||||
// good [0] IMPLICIT NULL,
|
||||
// revoked [1] IMPLICIT RevokedInfo,
|
||||
// unknown [2] IMPLICIT UnknownInfo
|
||||
// }
|
||||
//
|
||||
// RevokedInfo ::= SEQUENCE {
|
||||
// revocationTime GeneralizedTime,
|
||||
// revocationReason [0] EXPLICIT CRLReason OPTIONAL
|
||||
// }
|
||||
//
|
||||
// UnknownInfo ::= NULL
|
||||
//
|
||||
// CRLReason ::= ENUMERATED {
|
||||
// unspecified (0),
|
||||
// keyCompromise (1),
|
||||
// cACompromise (2),
|
||||
// affiliationChanged (3),
|
||||
// superseded (4),
|
||||
// cessationOfOperation (5),
|
||||
// certificateHold (6),
|
||||
// -- value 7 is not used
|
||||
// removeFromCRL (8),
|
||||
// privilegeWithdrawn (9),
|
||||
// aACompromise (10)
|
||||
// }
|
||||
// (from RFC 5280)
|
||||
struct OCSPCertStatus {
|
||||
// Correspond to the values of CRLReason
|
||||
enum class RevocationReason {
|
||||
UNSPECIFIED = 0,
|
||||
KEY_COMPROMISE = 1,
|
||||
CA_COMPROMISE = 2,
|
||||
AFFILIATION_CHANGED = 3,
|
||||
SUPERSEDED = 4,
|
||||
CESSATION_OF_OPERATION = 5,
|
||||
CERTIFICATE_HOLD = 6,
|
||||
UNUSED = 7,
|
||||
REMOVE_FROM_CRL = 8,
|
||||
PRIVILEGE_WITHDRAWN = 9,
|
||||
AA_COMPROMISE = 10,
|
||||
|
||||
LAST = AA_COMPROMISE,
|
||||
};
|
||||
|
||||
OCSPRevocationStatus status;
|
||||
der::GeneralizedTime revocation_time;
|
||||
bool has_reason;
|
||||
RevocationReason revocation_reason;
|
||||
};
|
||||
|
||||
// OCSPSingleResponse contains a representation of a DER-encoded RFC 6960
|
||||
// "SingleResponse". The |cert_id_tlv| and |extensions| fields are pointers to
|
||||
// the original object and are only valid as long as it is alive. They also
|
||||
// aren't verified until they are parsed. |next_update| is only valid if
|
||||
// |has_next_update| is true and |extensions| is only valid if |has_extensions|
|
||||
// is true.
|
||||
//
|
||||
// SingleResponse ::= SEQUENCE {
|
||||
// certID CertID,
|
||||
// certStatus CertStatus,
|
||||
// thisUpdate GeneralizedTime,
|
||||
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
|
||||
// singleExtensions [1] EXPLICIT Extensions OPTIONAL
|
||||
// }
|
||||
struct OPENSSL_EXPORT OCSPSingleResponse {
|
||||
OCSPSingleResponse();
|
||||
~OCSPSingleResponse();
|
||||
|
||||
der::Input cert_id_tlv;
|
||||
OCSPCertStatus cert_status;
|
||||
der::GeneralizedTime this_update;
|
||||
bool has_next_update;
|
||||
der::GeneralizedTime next_update;
|
||||
bool has_extensions;
|
||||
der::Input extensions;
|
||||
};
|
||||
|
||||
// OCSPResponseData contains a representation of a DER-encoded RFC 6960
|
||||
// "ResponseData". The |responses| and |extensions| fields are pointers to the
|
||||
// original object and are only valid as long as it is alive. They also aren't
|
||||
// verified until they are parsed into OCSPSingleResponse and ParsedExtensions.
|
||||
// |extensions| is only valid if |has_extensions| is true.
|
||||
//
|
||||
// ResponseData ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// responderID ResponderID,
|
||||
// producedAt GeneralizedTime,
|
||||
// responses SEQUENCE OF SingleResponse,
|
||||
// responseExtensions [1] EXPLICIT Extensions OPTIONAL
|
||||
// }
|
||||
struct OPENSSL_EXPORT OCSPResponseData {
|
||||
enum class ResponderType { NAME, KEY_HASH };
|
||||
|
||||
struct ResponderID {
|
||||
ResponderType type;
|
||||
der::Input name;
|
||||
der::Input key_hash;
|
||||
};
|
||||
|
||||
OCSPResponseData();
|
||||
~OCSPResponseData();
|
||||
|
||||
uint8_t version;
|
||||
OCSPResponseData::ResponderID responder_id;
|
||||
der::GeneralizedTime produced_at;
|
||||
std::vector<der::Input> responses;
|
||||
bool has_extensions;
|
||||
der::Input extensions;
|
||||
};
|
||||
|
||||
// OCSPResponse contains a representation of a DER-encoded RFC 6960
|
||||
// "OCSPResponse" and the corresponding "BasicOCSPResponse". The |data| field
|
||||
// is a pointer to the original object and are only valid as long is it is
|
||||
// alive. The |data| field isn't verified until it is parsed into an
|
||||
// OCSPResponseData. |data|, |signature_algorithm|, |signature|, and
|
||||
// |has_certs| is only valid if |status| is SUCCESSFUL. |certs| is only valid
|
||||
// if |has_certs| is true.
|
||||
//
|
||||
// OCSPResponse ::= SEQUENCE {
|
||||
// responseStatus OCSPResponseStatus,
|
||||
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL
|
||||
// }
|
||||
//
|
||||
// ResponseBytes ::= SEQUENCE {
|
||||
// responseType OBJECT IDENTIFIER,
|
||||
// response OCTET STRING
|
||||
// }
|
||||
//
|
||||
// BasicOCSPResponse ::= SEQUENCE {
|
||||
// tbsResponseData ResponseData,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signature BIT STRING,
|
||||
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL
|
||||
// }
|
||||
//
|
||||
// OCSPResponseStatus ::= ENUMERATED {
|
||||
// successful (0), -- Response has valid confirmations
|
||||
// malformedRequest (1), -- Illegal confirmation request
|
||||
// internalError (2), -- Internal error in issuer
|
||||
// tryLater (3), -- Try again later
|
||||
// -- (4) is not used
|
||||
// sigRequired (5), -- Must sign the request
|
||||
// unauthorized (6) -- Request unauthorized
|
||||
// }
|
||||
struct OPENSSL_EXPORT OCSPResponse {
|
||||
// Correspond to the values of OCSPResponseStatus
|
||||
enum class ResponseStatus {
|
||||
SUCCESSFUL = 0,
|
||||
MALFORMED_REQUEST = 1,
|
||||
INTERNAL_ERROR = 2,
|
||||
TRY_LATER = 3,
|
||||
UNUSED = 4,
|
||||
SIG_REQUIRED = 5,
|
||||
UNAUTHORIZED = 6,
|
||||
|
||||
LAST = UNAUTHORIZED,
|
||||
};
|
||||
|
||||
OCSPResponse();
|
||||
~OCSPResponse();
|
||||
|
||||
ResponseStatus status;
|
||||
der::Input data;
|
||||
SignatureAlgorithm signature_algorithm;
|
||||
der::BitString signature;
|
||||
bool has_certs;
|
||||
std::vector<der::Input> certs;
|
||||
};
|
||||
|
||||
// From RFC 6960:
|
||||
//
|
||||
// id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp }
|
||||
// id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
|
||||
//
|
||||
// In dotted notation: 1.3.6.1.5.5.7.48.1.1
|
||||
inline constexpr uint8_t kBasicOCSPResponseOid[] = {
|
||||
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01};
|
||||
|
||||
// Parses a DER-encoded OCSP "CertID" as specified by RFC 6960. Returns true on
|
||||
// success and sets the results in |out|.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
OPENSSL_EXPORT bool ParseOCSPCertID(const der::Input& raw_tlv,
|
||||
OCSPCertID* out);
|
||||
|
||||
// Parses a DER-encoded OCSP "SingleResponse" as specified by RFC 6960. Returns
|
||||
// true on success and sets the results in |out|. The resulting |out|
|
||||
// references data from |raw_tlv| and is only valid for the lifetime of
|
||||
// |raw_tlv|.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
OPENSSL_EXPORT bool ParseOCSPSingleResponse(const der::Input& raw_tlv,
|
||||
OCSPSingleResponse* out);
|
||||
|
||||
// Parses a DER-encoded OCSP "ResponseData" as specified by RFC 6960. Returns
|
||||
// true on success and sets the results in |out|. The resulting |out|
|
||||
// references data from |raw_tlv| and is only valid for the lifetime of
|
||||
// |raw_tlv|.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
OPENSSL_EXPORT bool ParseOCSPResponseData(const der::Input& raw_tlv,
|
||||
OCSPResponseData* out);
|
||||
|
||||
// Parses a DER-encoded "OCSPResponse" as specified by RFC 6960. Returns true
|
||||
// on success and sets the results in |out|. The resulting |out|
|
||||
// references data from |raw_tlv| and is only valid for the lifetime of
|
||||
// |raw_tlv|.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
OPENSSL_EXPORT bool ParseOCSPResponse(const der::Input& raw_tlv,
|
||||
OCSPResponse* out);
|
||||
|
||||
// Checks the revocation status of the certificate |certificate_der| by using
|
||||
// the DER-encoded |raw_response|.
|
||||
//
|
||||
// Returns GOOD if the OCSP response indicates the certificate is not revoked,
|
||||
// REVOKED if it indicates it is revoked, or UNKNOWN for all other cases.
|
||||
//
|
||||
// * |raw_response|: A DER encoded OCSPResponse.
|
||||
// * |certificate_der|: The certificate being checked for revocation.
|
||||
// * |issuer_certificate_der|: The certificate that signed |certificate_der|.
|
||||
// The caller must have already performed path verification.
|
||||
// * |verify_time_epoch_seconds|: The time as the difference in seconds from
|
||||
// the POSIX epoch to use when checking revocation status.
|
||||
// * |max_age_seconds|: The maximum age in seconds for a CRL, implemented as
|
||||
// time since the |thisUpdate| field in the CRL TBSCertList. Responses
|
||||
// older than |max_age_seconds| will be considered invalid.
|
||||
// * |response_details|: Additional details about failures.
|
||||
[[nodiscard]] OPENSSL_EXPORT OCSPRevocationStatus
|
||||
CheckOCSP(std::string_view raw_response,
|
||||
std::string_view certificate_der,
|
||||
std::string_view issuer_certificate_der,
|
||||
int64_t verify_time_epoch_seconds,
|
||||
std::optional<int64_t> max_age_seconds,
|
||||
OCSPVerifyResult::ResponseStatus* response_details);
|
||||
|
||||
// Checks the revocation status of |certificate| by using the DER-encoded
|
||||
// |raw_response|.
|
||||
//
|
||||
// Arguments are the same as above, except that it takes already parsed
|
||||
// instances of the certificate and issuer certificate.
|
||||
[[nodiscard]] OPENSSL_EXPORT OCSPRevocationStatus
|
||||
CheckOCSP(std::string_view raw_response,
|
||||
const ParsedCertificate* certificate,
|
||||
const ParsedCertificate* issuer_certificate,
|
||||
int64_t verify_time_epoch_seconds,
|
||||
std::optional<int64_t> max_age_seconds,
|
||||
OCSPVerifyResult::ResponseStatus* response_details);
|
||||
|
||||
// Creates a DER-encoded OCSPRequest for |cert|. The request is fairly basic:
|
||||
// * No signature
|
||||
// * No requestorName
|
||||
// * No extensions
|
||||
// * Uses SHA1 for all hashes.
|
||||
//
|
||||
// Returns true on success and fills |request_der| with the resulting bytes.
|
||||
OPENSSL_EXPORT bool CreateOCSPRequest(const ParsedCertificate* cert,
|
||||
const ParsedCertificate* issuer,
|
||||
std::vector<uint8_t>* request_der);
|
||||
|
||||
// Creates a URL to issue a GET request for OCSP information for |cert|.
|
||||
OPENSSL_EXPORT URL CreateOCSPGetURL(const ParsedCertificate* cert,
|
||||
const ParsedCertificate* issuer,
|
||||
std::string_view ocsp_responder_url);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_OCSP_H_
|
17
pki/ocsp_parse_ocsp_cert_id_fuzzer.cc
Normal file
17
pki/ocsp_parse_ocsp_cert_id_fuzzer.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ocsp.h"
|
||||
#include "input.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
bssl::der::Input cert_id_der(data, size);
|
||||
net::OCSPCertID cert_id;
|
||||
net::ParseOCSPCertID(cert_id_der, &cert_id);
|
||||
|
||||
return 0;
|
||||
}
|
17
pki/ocsp_parse_ocsp_response_data_fuzzer.cc
Normal file
17
pki/ocsp_parse_ocsp_response_data_fuzzer.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ocsp.h"
|
||||
#include "input.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
bssl::der::Input response_data_der(data, size);
|
||||
net::OCSPResponseData response_data;
|
||||
net::ParseOCSPResponseData(response_data_der, &response_data);
|
||||
|
||||
return 0;
|
||||
}
|
17
pki/ocsp_parse_ocsp_response_fuzzer.cc
Normal file
17
pki/ocsp_parse_ocsp_response_fuzzer.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ocsp.h"
|
||||
#include "input.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
bssl::der::Input response_der(data, size);
|
||||
net::OCSPResponse response;
|
||||
net::ParseOCSPResponse(response_der, &response);
|
||||
|
||||
return 0;
|
||||
}
|
17
pki/ocsp_parse_ocsp_single_response_fuzzer.cc
Normal file
17
pki/ocsp_parse_ocsp_single_response_fuzzer.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ocsp.h"
|
||||
#include "input.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
bssl::der::Input single_response_der(data, size);
|
||||
net::OCSPSingleResponse single_response;
|
||||
net::ParseOCSPSingleResponse(single_response_der, &single_response);
|
||||
|
||||
return 0;
|
||||
}
|
21
pki/ocsp_revocation_status.h
Normal file
21
pki/ocsp_revocation_status.h
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_OCSP_REVOCATION_STATUS_H_
|
||||
#define BSSL_PKI_OCSP_REVOCATION_STATUS_H_
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// This value is histogrammed, so do not re-order or change values, and add
|
||||
// new values at the end.
|
||||
enum class OCSPRevocationStatus {
|
||||
GOOD = 0,
|
||||
REVOKED = 1,
|
||||
UNKNOWN = 2,
|
||||
MAX_VALUE = UNKNOWN
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_OCSP_REVOCATION_STATUS_H_
|
245
pki/ocsp_unittest.cc
Normal file
245
pki/ocsp_unittest.cc
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "webutil/url/url.h"
|
||||
#include "ocsp.h"
|
||||
|
||||
#include "string_util.h"
|
||||
#include "test_helpers.h"
|
||||
#include "encode_values.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/base64.h>
|
||||
#include <openssl/pool.h>
|
||||
#include "webutil/url/url.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int64_t kOCSPAgeOneWeek = 7 * 24 * 60 * 60;
|
||||
|
||||
std::string GetFilePath(const std::string& file_name) {
|
||||
return std::string("testdata/ocsp_unittest/") + file_name;
|
||||
}
|
||||
|
||||
std::shared_ptr<const ParsedCertificate> ParseCertificate(
|
||||
std::string_view data) {
|
||||
CertErrors errors;
|
||||
return ParsedCertificate::Create(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
|
||||
reinterpret_cast<const uint8_t*>(data.data()), data.size(), nullptr)),
|
||||
{}, &errors);
|
||||
}
|
||||
|
||||
struct TestParams {
|
||||
const char* file_name;
|
||||
OCSPRevocationStatus expected_revocation_status;
|
||||
OCSPVerifyResult::ResponseStatus expected_response_status;
|
||||
};
|
||||
|
||||
class CheckOCSPTest : public ::testing::TestWithParam<TestParams> {};
|
||||
|
||||
const TestParams kTestParams[] = {
|
||||
{"good_response.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"good_response_sha256.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"no_response.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::NO_MATCHING_RESPONSE},
|
||||
|
||||
{"malformed_request.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::ERROR_RESPONSE},
|
||||
|
||||
{"bad_status.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PARSE_RESPONSE_ERROR},
|
||||
|
||||
{"bad_ocsp_type.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PARSE_RESPONSE_ERROR},
|
||||
|
||||
{"bad_signature.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"ocsp_sign_direct.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"ocsp_sign_indirect.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"ocsp_sign_indirect_missing.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"ocsp_sign_bad_indirect.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"ocsp_extra_certs.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"has_version.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"responder_name.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"responder_id.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"has_extension.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"good_response_next_update.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"revoke_response.pem", OCSPRevocationStatus::REVOKED,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"revoke_response_reason.pem", OCSPRevocationStatus::REVOKED,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"unknown_response.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"multiple_response.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"other_response.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::NO_MATCHING_RESPONSE},
|
||||
|
||||
{"has_single_extension.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"has_critical_single_extension.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION},
|
||||
|
||||
{"has_critical_response_extension.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION},
|
||||
|
||||
{"has_critical_ct_extension.pem", OCSPRevocationStatus::GOOD,
|
||||
OCSPVerifyResult::PROVIDED},
|
||||
|
||||
{"missing_response.pem", OCSPRevocationStatus::UNKNOWN,
|
||||
OCSPVerifyResult::NO_MATCHING_RESPONSE},
|
||||
};
|
||||
|
||||
// Parameterised test name generator for tests depending on RenderTextBackend.
|
||||
struct PrintTestName {
|
||||
std::string operator()(const testing::TestParamInfo<TestParams>& info) const {
|
||||
std::string_view name(info.param.file_name);
|
||||
// Strip ".pem" from the end as GTest names cannot contain period.
|
||||
name.remove_suffix(4);
|
||||
return std::string(name);
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
CheckOCSPTest,
|
||||
::testing::ValuesIn(kTestParams),
|
||||
PrintTestName());
|
||||
|
||||
TEST_P(CheckOCSPTest, FromFile) {
|
||||
const TestParams& params = GetParam();
|
||||
|
||||
std::string ocsp_data;
|
||||
std::string ca_data;
|
||||
std::string cert_data;
|
||||
std::string request_data;
|
||||
const PemBlockMapping mappings[] = {
|
||||
{"OCSP RESPONSE", &ocsp_data},
|
||||
{"CA CERTIFICATE", &ca_data},
|
||||
{"CERTIFICATE", &cert_data},
|
||||
{"OCSP REQUEST", &request_data},
|
||||
};
|
||||
|
||||
ASSERT_TRUE(ReadTestDataFromPemFile(GetFilePath(params.file_name), mappings));
|
||||
|
||||
// Mar 5 00:00:00 2017 GMT
|
||||
int64_t kVerifyTime = 1488672000;
|
||||
|
||||
// Test that CheckOCSP() works.
|
||||
OCSPVerifyResult::ResponseStatus response_status;
|
||||
OCSPRevocationStatus revocation_status =
|
||||
CheckOCSP(ocsp_data, cert_data, ca_data, kVerifyTime, kOCSPAgeOneWeek,
|
||||
&response_status);
|
||||
|
||||
EXPECT_EQ(params.expected_revocation_status, revocation_status);
|
||||
EXPECT_EQ(params.expected_response_status, response_status);
|
||||
|
||||
// Check that CreateOCSPRequest() works.
|
||||
std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data);
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
std::shared_ptr<const ParsedCertificate> issuer = ParseCertificate(ca_data);
|
||||
ASSERT_TRUE(issuer);
|
||||
|
||||
std::vector<uint8_t> encoded_request;
|
||||
ASSERT_TRUE(CreateOCSPRequest(cert.get(), issuer.get(), &encoded_request));
|
||||
|
||||
EXPECT_EQ(der::Input(encoded_request.data(), encoded_request.size()),
|
||||
der::Input(&request_data));
|
||||
}
|
||||
|
||||
std::string_view kGetURLTestParams[] = {
|
||||
"http://www.example.com/",
|
||||
"http://www.example.com/path/",
|
||||
"http://www.example.com/path",
|
||||
"http://www.example.com/path?query"
|
||||
"http://user:pass@www.example.com/path?query",
|
||||
};
|
||||
|
||||
class CreateOCSPGetURLTest : public ::testing::TestWithParam<std::string_view> {
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
CreateOCSPGetURLTest,
|
||||
::testing::ValuesIn(kGetURLTestParams));
|
||||
|
||||
TEST_P(CreateOCSPGetURLTest, Basic) {
|
||||
std::string ca_data;
|
||||
std::string cert_data;
|
||||
std::string request_data;
|
||||
const PemBlockMapping mappings[] = {
|
||||
{"CA CERTIFICATE", &ca_data},
|
||||
{"CERTIFICATE", &cert_data},
|
||||
{"OCSP REQUEST", &request_data},
|
||||
};
|
||||
|
||||
// Load one of the test files. (Doesn't really matter which one as
|
||||
// constructing the DER is tested elsewhere).
|
||||
ASSERT_TRUE(
|
||||
ReadTestDataFromPemFile(GetFilePath("good_response.pem"), mappings));
|
||||
|
||||
std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data);
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
std::shared_ptr<const ParsedCertificate> issuer = ParseCertificate(ca_data);
|
||||
ASSERT_TRUE(issuer);
|
||||
|
||||
URL url = CreateOCSPGetURL(cert.get(), issuer.get(), GetParam());
|
||||
|
||||
// Try to extract the encoded data and compare against |request_data|.
|
||||
//
|
||||
// A known answer output test would be better as this just reverses the logic
|
||||
// from the implementation file.
|
||||
std::string b64 = url.spec().substr(GetParam().size() + 1);
|
||||
|
||||
// Hex un-escape the data.
|
||||
b64 = bssl::string_util::FindAndReplace(b64, "%2B", "+");
|
||||
b64 = bssl::string_util::FindAndReplace(b64, "%2F", "/");
|
||||
b64 = bssl::string_util::FindAndReplace(b64, "%3D", "=");
|
||||
|
||||
// Base64 decode the data.
|
||||
size_t len;
|
||||
EXPECT_TRUE(EVP_DecodedLength(&len, b64.size()));
|
||||
std::vector<uint8_t> decoded(len);
|
||||
EXPECT_TRUE(EVP_DecodeBase64(decoded.data(), &len, len,
|
||||
reinterpret_cast<const uint8_t*>(b64.data()),
|
||||
b64.size()));
|
||||
std::string decoded_string(decoded.begin(), decoded.begin() + len);
|
||||
|
||||
EXPECT_EQ(request_data, decoded_string);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace net
|
24
pki/ocsp_verify_result.cc
Normal file
24
pki/ocsp_verify_result.cc
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ocsp_verify_result.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
OCSPVerifyResult::OCSPVerifyResult() = default;
|
||||
OCSPVerifyResult::OCSPVerifyResult(const OCSPVerifyResult&) = default;
|
||||
OCSPVerifyResult::~OCSPVerifyResult() = default;
|
||||
|
||||
bool OCSPVerifyResult::operator==(const OCSPVerifyResult& other) const {
|
||||
if (response_status != other.response_status)
|
||||
return false;
|
||||
|
||||
if (response_status == PROVIDED) {
|
||||
// |revocation_status| is only defined when |response_status| is PROVIDED.
|
||||
return revocation_status == other.revocation_status;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
76
pki/ocsp_verify_result.h
Normal file
76
pki/ocsp_verify_result.h
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_OCSP_VERIFY_RESULT_H_
|
||||
#define BSSL_PKI_OCSP_VERIFY_RESULT_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
|
||||
#include "ocsp_revocation_status.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// The result of OCSP verification. This always contains a ResponseStatus, which
|
||||
// describes whether or not an OCSP response was provided, and response level
|
||||
// errors. It optionally contains an OCSPRevocationStatus when |response_status
|
||||
// = PROVIDED|. For example, a stapled OCSP response matching the certificate,
|
||||
// and indicating a non-revoked status, will have |response_status = PROVIDED|
|
||||
// and |revocation_status = GOOD|. This is populated as part of the certificate
|
||||
// verification process, and should not be modified at other layers.
|
||||
struct OPENSSL_EXPORT OCSPVerifyResult {
|
||||
OCSPVerifyResult();
|
||||
OCSPVerifyResult(const OCSPVerifyResult&);
|
||||
~OCSPVerifyResult();
|
||||
|
||||
bool operator==(const OCSPVerifyResult& other) const;
|
||||
|
||||
// This value is histogrammed, so do not re-order or change values, and add
|
||||
// new values at the end.
|
||||
enum ResponseStatus {
|
||||
// OCSP verification was not checked on this connection.
|
||||
NOT_CHECKED = 0,
|
||||
|
||||
// No OCSPResponse was stapled.
|
||||
MISSING = 1,
|
||||
|
||||
// An up-to-date OCSP response was stapled and matched the certificate.
|
||||
PROVIDED = 2,
|
||||
|
||||
// The stapled OCSP response did not have a SUCCESSFUL status.
|
||||
ERROR_RESPONSE = 3,
|
||||
|
||||
// The OCSPResponseData field producedAt was outside the certificate
|
||||
// validity period.
|
||||
BAD_PRODUCED_AT = 4,
|
||||
|
||||
// At least one OCSPSingleResponse was stapled, but none matched the
|
||||
// certificate.
|
||||
NO_MATCHING_RESPONSE = 5,
|
||||
|
||||
// A matching OCSPSingleResponse was stapled, but was either expired or not
|
||||
// yet valid.
|
||||
INVALID_DATE = 6,
|
||||
|
||||
// The OCSPResponse structure could not be parsed.
|
||||
PARSE_RESPONSE_ERROR = 7,
|
||||
|
||||
// The OCSPResponseData structure could not be parsed.
|
||||
PARSE_RESPONSE_DATA_ERROR = 8,
|
||||
|
||||
// Unhandled critical extension in either OCSPResponseData or
|
||||
// OCSPSingleResponse
|
||||
UNHANDLED_CRITICAL_EXTENSION = 9,
|
||||
RESPONSE_STATUS_MAX = UNHANDLED_CRITICAL_EXTENSION
|
||||
};
|
||||
|
||||
ResponseStatus response_status = NOT_CHECKED;
|
||||
|
||||
// The strictest CertStatus matching the certificate (REVOKED > UNKNOWN >
|
||||
// GOOD). Only valid if |response_status| = PROVIDED.
|
||||
OCSPRevocationStatus revocation_status = OCSPRevocationStatus::UNKNOWN;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_OCSP_VERIFY_RESULT_H_
|
956
pki/parse_certificate.cc
Normal file
956
pki/parse_certificate.cc
Normal file
@ -0,0 +1,956 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include "parse_certificate.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cert_error_params.h"
|
||||
#include "cert_errors.h"
|
||||
#include "general_names.h"
|
||||
#include "string_util.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include "parser.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kCertificateNotSequence,
|
||||
"Failed parsing Certificate SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kUnconsumedDataInsideCertificateSequence,
|
||||
"Unconsumed data inside Certificate SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kUnconsumedDataAfterCertificateSequence,
|
||||
"Unconsumed data after Certificate SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kTbsCertificateNotSequence,
|
||||
"Couldn't read tbsCertificate as SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(
|
||||
kSignatureAlgorithmNotSequence,
|
||||
"Couldn't read Certificate.signatureAlgorithm as SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kSignatureValueNotBitString,
|
||||
"Couldn't read Certificate.signatureValue as BIT STRING");
|
||||
DEFINE_CERT_ERROR_ID(kUnconsumedDataInsideTbsCertificateSequence,
|
||||
"Unconsumed data inside TBSCertificate");
|
||||
DEFINE_CERT_ERROR_ID(kTbsNotSequence, "Failed parsing TBSCertificate SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingVersion, "Failed reading version");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingVersion, "Failed parsing version");
|
||||
DEFINE_CERT_ERROR_ID(kVersionExplicitlyV1,
|
||||
"Version explicitly V1 (should be omitted)");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingSerialNumber, "Failed reading serialNumber");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingSignatureValue, "Failed reading signature");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingIssuer, "Failed reading issuer");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingValidity, "Failed reading validity");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingValidity, "Failed parsing validity");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingSubject, "Failed reading subject");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingSpki, "Failed reading subjectPublicKeyInfo");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingIssuerUniqueId,
|
||||
"Failed reading issuerUniqueId");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingIssuerUniqueId,
|
||||
"Failed parsing issuerUniqueId");
|
||||
DEFINE_CERT_ERROR_ID(
|
||||
kIssuerUniqueIdNotExpected,
|
||||
"Unexpected issuerUniqueId (must be V2 or V3 certificate)");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingSubjectUniqueId,
|
||||
"Failed reading subjectUniqueId");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingSubjectUniqueId,
|
||||
"Failed parsing subjectUniqueId");
|
||||
DEFINE_CERT_ERROR_ID(
|
||||
kSubjectUniqueIdNotExpected,
|
||||
"Unexpected subjectUniqueId (must be V2 or V3 certificate)");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingExtensions,
|
||||
"Failed reading extensions SEQUENCE");
|
||||
DEFINE_CERT_ERROR_ID(kUnexpectedExtensions,
|
||||
"Unexpected extensions (must be V3 certificate)");
|
||||
DEFINE_CERT_ERROR_ID(kSerialNumberIsNegative, "Serial number is negative");
|
||||
DEFINE_CERT_ERROR_ID(kSerialNumberIsZero, "Serial number is zero");
|
||||
DEFINE_CERT_ERROR_ID(kSerialNumberLengthOver20,
|
||||
"Serial number is longer than 20 octets");
|
||||
DEFINE_CERT_ERROR_ID(kSerialNumberNotValidInteger,
|
||||
"Serial number is not a valid INTEGER");
|
||||
|
||||
// Returns true if |input| is a SEQUENCE and nothing else.
|
||||
[[nodiscard]] bool IsSequenceTLV(const der::Input& input) {
|
||||
der::Parser parser(input);
|
||||
der::Parser unused_sequence_parser;
|
||||
if (!parser.ReadSequence(&unused_sequence_parser))
|
||||
return false;
|
||||
// Should by a single SEQUENCE by definition of the function.
|
||||
return !parser.HasMore();
|
||||
}
|
||||
|
||||
// Reads a SEQUENCE from |parser| and writes the full tag-length-value into
|
||||
// |out|. On failure |parser| may or may not have been advanced.
|
||||
[[nodiscard]] bool ReadSequenceTLV(der::Parser* parser, der::Input* out) {
|
||||
return parser->ReadRawTLV(out) && IsSequenceTLV(*out);
|
||||
}
|
||||
|
||||
// Parses a Version according to RFC 5280:
|
||||
//
|
||||
// Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
||||
//
|
||||
// No value other that v1, v2, or v3 is allowed (and if given will fail). RFC
|
||||
// 5280 minimally requires the handling of v3 (and overwhelmingly these are the
|
||||
// certificate versions in use today):
|
||||
//
|
||||
// Implementations SHOULD be prepared to accept any version certificate.
|
||||
// At a minimum, conforming implementations MUST recognize version 3
|
||||
// certificates.
|
||||
[[nodiscard]] bool ParseVersion(const der::Input& in,
|
||||
CertificateVersion* version) {
|
||||
der::Parser parser(in);
|
||||
uint64_t version64;
|
||||
if (!parser.ReadUint64(&version64))
|
||||
return false;
|
||||
|
||||
switch (version64) {
|
||||
case 0:
|
||||
*version = CertificateVersion::V1;
|
||||
break;
|
||||
case 1:
|
||||
*version = CertificateVersion::V2;
|
||||
break;
|
||||
case 2:
|
||||
*version = CertificateVersion::V3;
|
||||
break;
|
||||
default:
|
||||
// Don't allow any other version identifier.
|
||||
return false;
|
||||
}
|
||||
|
||||
// By definition the input to this function was a single INTEGER, so there
|
||||
// shouldn't be anything else after it.
|
||||
return !parser.HasMore();
|
||||
}
|
||||
|
||||
// Returns true if every bit in |bits| is zero (including empty).
|
||||
[[nodiscard]] bool BitStringIsAllZeros(const der::BitString& bits) {
|
||||
// Note that it is OK to read from the unused bits, since BitString parsing
|
||||
// guarantees they are all zero.
|
||||
for (size_t i = 0; i < bits.bytes().Length(); ++i) {
|
||||
if (bits.bytes().UnsafeData()[i] != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses a DistributionPointName.
|
||||
//
|
||||
// From RFC 5280:
|
||||
//
|
||||
// DistributionPointName ::= CHOICE {
|
||||
// fullName [0] GeneralNames,
|
||||
// nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
|
||||
bool ParseDistributionPointName(const der::Input& dp_name,
|
||||
ParsedDistributionPoint* distribution_point) {
|
||||
der::Parser parser(dp_name);
|
||||
std::optional<der::Input> der_full_name;
|
||||
if (!parser.ReadOptionalTag(
|
||||
der::kTagContextSpecific | der::kTagConstructed | 0,
|
||||
&der_full_name)) {
|
||||
return false;
|
||||
}
|
||||
if (der_full_name) {
|
||||
// TODO(mattm): surface the CertErrors.
|
||||
CertErrors errors;
|
||||
distribution_point->distribution_point_fullname =
|
||||
GeneralNames::CreateFromValue(*der_full_name, &errors);
|
||||
if (!distribution_point->distribution_point_fullname)
|
||||
return false;
|
||||
return !parser.HasMore();
|
||||
}
|
||||
|
||||
if (!parser.ReadOptionalTag(
|
||||
der::kTagContextSpecific | der::kTagConstructed | 1,
|
||||
&distribution_point
|
||||
->distribution_point_name_relative_to_crl_issuer)) {
|
||||
return false;
|
||||
}
|
||||
if (distribution_point->distribution_point_name_relative_to_crl_issuer) {
|
||||
return !parser.HasMore();
|
||||
}
|
||||
|
||||
// The CHOICE must contain either fullName or nameRelativeToCRLIssuer.
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 5280, section 4.2.1.13.
|
||||
//
|
||||
// DistributionPoint ::= SEQUENCE {
|
||||
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||
// reasons [1] ReasonFlags OPTIONAL,
|
||||
// cRLIssuer [2] GeneralNames OPTIONAL }
|
||||
bool ParseAndAddDistributionPoint(
|
||||
der::Parser* parser,
|
||||
std::vector<ParsedDistributionPoint>* distribution_points) {
|
||||
ParsedDistributionPoint distribution_point;
|
||||
|
||||
// DistributionPoint ::= SEQUENCE {
|
||||
der::Parser distrib_point_parser;
|
||||
if (!parser->ReadSequence(&distrib_point_parser))
|
||||
return false;
|
||||
|
||||
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||
std::optional<der::Input> distribution_point_name;
|
||||
if (!distrib_point_parser.ReadOptionalTag(
|
||||
der::kTagContextSpecific | der::kTagConstructed | 0,
|
||||
&distribution_point_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (distribution_point_name &&
|
||||
!ParseDistributionPointName(*distribution_point_name,
|
||||
&distribution_point)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reasons [1] ReasonFlags OPTIONAL,
|
||||
if (!distrib_point_parser.ReadOptionalTag(der::kTagContextSpecific | 1,
|
||||
&distribution_point.reasons)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// cRLIssuer [2] GeneralNames OPTIONAL }
|
||||
if (!distrib_point_parser.ReadOptionalTag(
|
||||
der::kTagContextSpecific | der::kTagConstructed | 2,
|
||||
&distribution_point.crl_issuer)) {
|
||||
return false;
|
||||
}
|
||||
// TODO(eroman): Parse "cRLIssuer"?
|
||||
|
||||
// RFC 5280, section 4.2.1.13:
|
||||
// either distributionPoint or cRLIssuer MUST be present.
|
||||
if (!distribution_point_name && !distribution_point.crl_issuer)
|
||||
return false;
|
||||
|
||||
if (distrib_point_parser.HasMore())
|
||||
return false;
|
||||
|
||||
distribution_points->push_back(std::move(distribution_point));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ParsedTbsCertificate::ParsedTbsCertificate() = default;
|
||||
|
||||
ParsedTbsCertificate::ParsedTbsCertificate(ParsedTbsCertificate&& other) =
|
||||
default;
|
||||
|
||||
ParsedTbsCertificate::~ParsedTbsCertificate() = default;
|
||||
|
||||
bool VerifySerialNumber(const der::Input& value,
|
||||
bool warnings_only,
|
||||
CertErrors* errors) {
|
||||
// If |warnings_only| was set to true, the exact same errors will be logged,
|
||||
// only they will be logged with a lower severity (warning rather than error).
|
||||
CertError::Severity error_severity =
|
||||
warnings_only ? CertError::SEVERITY_WARNING : CertError::SEVERITY_HIGH;
|
||||
|
||||
bool negative;
|
||||
if (!der::IsValidInteger(value, &negative)) {
|
||||
errors->Add(error_severity, kSerialNumberNotValidInteger, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.1.2.2:
|
||||
//
|
||||
// Note: Non-conforming CAs may issue certificates with serial numbers
|
||||
// that are negative or zero. Certificate users SHOULD be prepared to
|
||||
// gracefully handle such certificates.
|
||||
if (negative)
|
||||
errors->AddWarning(kSerialNumberIsNegative);
|
||||
if (value.Length() == 1 && value.UnsafeData()[0] == 0)
|
||||
errors->AddWarning(kSerialNumberIsZero);
|
||||
|
||||
// RFC 5280 section 4.1.2.2:
|
||||
//
|
||||
// Certificate users MUST be able to handle serialNumber values up to 20
|
||||
// octets. Conforming CAs MUST NOT use serialNumber values longer than 20
|
||||
// octets.
|
||||
if (value.Length() > 20) {
|
||||
errors->Add(error_severity, kSerialNumberLengthOver20,
|
||||
CreateCertErrorParams1SizeT("length", value.Length()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadUTCOrGeneralizedTime(der::Parser* parser, der::GeneralizedTime* out) {
|
||||
der::Input value;
|
||||
der::Tag tag;
|
||||
|
||||
if (!parser->ReadTagAndValue(&tag, &value))
|
||||
return false;
|
||||
|
||||
if (tag == der::kUtcTime)
|
||||
return der::ParseUTCTime(value, out);
|
||||
|
||||
if (tag == der::kGeneralizedTime)
|
||||
return der::ParseGeneralizedTime(value, out);
|
||||
|
||||
// Unrecognized tag.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseValidity(const der::Input& validity_tlv,
|
||||
der::GeneralizedTime* not_before,
|
||||
der::GeneralizedTime* not_after) {
|
||||
der::Parser parser(validity_tlv);
|
||||
|
||||
// Validity ::= SEQUENCE {
|
||||
der::Parser validity_parser;
|
||||
if (!parser.ReadSequence(&validity_parser))
|
||||
return false;
|
||||
|
||||
// notBefore Time,
|
||||
if (!ReadUTCOrGeneralizedTime(&validity_parser, not_before))
|
||||
return false;
|
||||
|
||||
// notAfter Time }
|
||||
if (!ReadUTCOrGeneralizedTime(&validity_parser, not_after))
|
||||
return false;
|
||||
|
||||
// By definition the input was a single Validity sequence, so there shouldn't
|
||||
// be unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
// The Validity type does not have an extension point.
|
||||
if (validity_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// Note that RFC 5280 doesn't require notBefore to be <=
|
||||
// notAfter, so that will not be considered a "parsing" error here. Instead it
|
||||
// will be considered an expired certificate later when testing against the
|
||||
// current timestamp.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseCertificate(const der::Input& certificate_tlv,
|
||||
der::Input* out_tbs_certificate_tlv,
|
||||
der::Input* out_signature_algorithm_tlv,
|
||||
der::BitString* out_signature_value,
|
||||
CertErrors* out_errors) {
|
||||
// |out_errors| is optional. But ensure it is non-null for the remainder of
|
||||
// this function.
|
||||
CertErrors unused_errors;
|
||||
if (!out_errors)
|
||||
out_errors = &unused_errors;
|
||||
|
||||
der::Parser parser(certificate_tlv);
|
||||
|
||||
// Certificate ::= SEQUENCE {
|
||||
der::Parser certificate_parser;
|
||||
if (!parser.ReadSequence(&certificate_parser)) {
|
||||
out_errors->AddError(kCertificateNotSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
// tbsCertificate TBSCertificate,
|
||||
if (!ReadSequenceTLV(&certificate_parser, out_tbs_certificate_tlv)) {
|
||||
out_errors->AddError(kTbsCertificateNotSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
if (!ReadSequenceTLV(&certificate_parser, out_signature_algorithm_tlv)) {
|
||||
out_errors->AddError(kSignatureAlgorithmNotSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
// signatureValue BIT STRING }
|
||||
std::optional<der::BitString> signature_value =
|
||||
certificate_parser.ReadBitString();
|
||||
if (!signature_value) {
|
||||
out_errors->AddError(kSignatureValueNotBitString);
|
||||
return false;
|
||||
}
|
||||
*out_signature_value = signature_value.value();
|
||||
|
||||
// There isn't an extension point at the end of Certificate.
|
||||
if (certificate_parser.HasMore()) {
|
||||
out_errors->AddError(kUnconsumedDataInsideCertificateSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
// By definition the input was a single Certificate, so there shouldn't be
|
||||
// unconsumed data.
|
||||
if (parser.HasMore()) {
|
||||
out_errors->AddError(kUnconsumedDataAfterCertificateSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// From RFC 5280 section 4.1:
|
||||
//
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
// }
|
||||
bool ParseTbsCertificate(const der::Input& tbs_tlv,
|
||||
const ParseCertificateOptions& options,
|
||||
ParsedTbsCertificate* out,
|
||||
CertErrors* errors) {
|
||||
// The rest of this function assumes that |errors| is non-null.
|
||||
CertErrors unused_errors;
|
||||
if (!errors)
|
||||
errors = &unused_errors;
|
||||
|
||||
// TODO(crbug.com/634443): Add useful error information to |errors|.
|
||||
|
||||
der::Parser parser(tbs_tlv);
|
||||
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
der::Parser tbs_parser;
|
||||
if (!parser.ReadSequence(&tbs_parser)) {
|
||||
errors->AddError(kTbsNotSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
std::optional<der::Input> version;
|
||||
if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
|
||||
&version)) {
|
||||
errors->AddError(kFailedReadingVersion);
|
||||
return false;
|
||||
}
|
||||
if (version) {
|
||||
if (!ParseVersion(version.value(), &out->version)) {
|
||||
errors->AddError(kFailedParsingVersion);
|
||||
return false;
|
||||
}
|
||||
if (out->version == CertificateVersion::V1) {
|
||||
errors->AddError(kVersionExplicitlyV1);
|
||||
// The correct way to specify v1 is to omit the version field since v1 is
|
||||
// the DEFAULT.
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
out->version = CertificateVersion::V1;
|
||||
}
|
||||
|
||||
// serialNumber CertificateSerialNumber,
|
||||
if (!tbs_parser.ReadTag(der::kInteger, &out->serial_number)) {
|
||||
errors->AddError(kFailedReadingSerialNumber);
|
||||
return false;
|
||||
}
|
||||
if (!VerifySerialNumber(out->serial_number,
|
||||
options.allow_invalid_serial_numbers, errors)) {
|
||||
// Invalid serial numbers are only considered fatal failures if
|
||||
// |!allow_invalid_serial_numbers|.
|
||||
if (!options.allow_invalid_serial_numbers)
|
||||
return false;
|
||||
}
|
||||
|
||||
// signature AlgorithmIdentifier,
|
||||
if (!ReadSequenceTLV(&tbs_parser, &out->signature_algorithm_tlv)) {
|
||||
errors->AddError(kFailedReadingSignatureValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
// issuer Name,
|
||||
if (!ReadSequenceTLV(&tbs_parser, &out->issuer_tlv)) {
|
||||
errors->AddError(kFailedReadingIssuer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// validity Validity,
|
||||
der::Input validity_tlv;
|
||||
if (!tbs_parser.ReadRawTLV(&validity_tlv)) {
|
||||
errors->AddError(kFailedReadingValidity);
|
||||
return false;
|
||||
}
|
||||
if (!ParseValidity(validity_tlv, &out->validity_not_before,
|
||||
&out->validity_not_after)) {
|
||||
errors->AddError(kFailedParsingValidity);
|
||||
return false;
|
||||
}
|
||||
|
||||
// subject Name,
|
||||
if (!ReadSequenceTLV(&tbs_parser, &out->subject_tlv)) {
|
||||
errors->AddError(kFailedReadingSubject);
|
||||
return false;
|
||||
}
|
||||
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
if (!ReadSequenceTLV(&tbs_parser, &out->spki_tlv)) {
|
||||
errors->AddError(kFailedReadingSpki);
|
||||
return false;
|
||||
}
|
||||
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
std::optional<der::Input> issuer_unique_id;
|
||||
if (!tbs_parser.ReadOptionalTag(der::ContextSpecificPrimitive(1),
|
||||
&issuer_unique_id)) {
|
||||
errors->AddError(kFailedReadingIssuerUniqueId);
|
||||
return false;
|
||||
}
|
||||
if (issuer_unique_id) {
|
||||
out->issuer_unique_id = der::ParseBitString(issuer_unique_id.value());
|
||||
if (!out->issuer_unique_id) {
|
||||
errors->AddError(kFailedParsingIssuerUniqueId);
|
||||
return false;
|
||||
}
|
||||
if (out->version != CertificateVersion::V2 &&
|
||||
out->version != CertificateVersion::V3) {
|
||||
errors->AddError(kIssuerUniqueIdNotExpected);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
std::optional<der::Input> subject_unique_id;
|
||||
if (!tbs_parser.ReadOptionalTag(der::ContextSpecificPrimitive(2),
|
||||
&subject_unique_id)) {
|
||||
errors->AddError(kFailedReadingSubjectUniqueId);
|
||||
return false;
|
||||
}
|
||||
if (subject_unique_id) {
|
||||
out->subject_unique_id = der::ParseBitString(subject_unique_id.value());
|
||||
if (!out->subject_unique_id) {
|
||||
errors->AddError(kFailedParsingSubjectUniqueId);
|
||||
return false;
|
||||
}
|
||||
if (out->version != CertificateVersion::V2 &&
|
||||
out->version != CertificateVersion::V3) {
|
||||
errors->AddError(kSubjectUniqueIdNotExpected);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(3),
|
||||
&out->extensions_tlv)) {
|
||||
errors->AddError(kFailedReadingExtensions);
|
||||
return false;
|
||||
}
|
||||
if (out->extensions_tlv) {
|
||||
// extensions_tlv must be a single element. Also check that it is a
|
||||
// SEQUENCE.
|
||||
if (!IsSequenceTLV(out->extensions_tlv.value())) {
|
||||
errors->AddError(kFailedReadingExtensions);
|
||||
return false;
|
||||
}
|
||||
if (out->version != CertificateVersion::V3) {
|
||||
errors->AddError(kUnexpectedExtensions);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that there IS an extension point at the end of TBSCertificate
|
||||
// (according to RFC 5912), so from that interpretation, unconsumed data would
|
||||
// be allowed in |tbs_parser|.
|
||||
//
|
||||
// However because only v1, v2, and v3 certificates are supported by the
|
||||
// parsing, there shouldn't be any subsequent data in those versions, so
|
||||
// reject.
|
||||
if (tbs_parser.HasMore()) {
|
||||
errors->AddError(kUnconsumedDataInsideTbsCertificateSequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
// By definition the input was a single TBSCertificate, so there shouldn't be
|
||||
// unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// Extension ::= SEQUENCE {
|
||||
// extnID OBJECT IDENTIFIER,
|
||||
// critical BOOLEAN DEFAULT FALSE,
|
||||
// extnValue OCTET STRING
|
||||
// -- contains the DER encoding of an ASN.1 value
|
||||
// -- corresponding to the extension type identified
|
||||
// -- by extnID
|
||||
// }
|
||||
bool ParseExtension(const der::Input& extension_tlv, ParsedExtension* out) {
|
||||
der::Parser parser(extension_tlv);
|
||||
|
||||
// Extension ::= SEQUENCE {
|
||||
der::Parser extension_parser;
|
||||
if (!parser.ReadSequence(&extension_parser))
|
||||
return false;
|
||||
|
||||
// extnID OBJECT IDENTIFIER,
|
||||
if (!extension_parser.ReadTag(der::kOid, &out->oid))
|
||||
return false;
|
||||
|
||||
// critical BOOLEAN DEFAULT FALSE,
|
||||
out->critical = false;
|
||||
bool has_critical;
|
||||
der::Input critical;
|
||||
if (!extension_parser.ReadOptionalTag(der::kBool, &critical, &has_critical))
|
||||
return false;
|
||||
if (has_critical) {
|
||||
if (!der::ParseBool(critical, &out->critical))
|
||||
return false;
|
||||
if (!out->critical)
|
||||
return false; // DER-encoding requires DEFAULT values be omitted.
|
||||
}
|
||||
|
||||
// extnValue OCTET STRING
|
||||
if (!extension_parser.ReadTag(der::kOctetString, &out->value))
|
||||
return false;
|
||||
|
||||
// The Extension type does not have an extension point (everything goes in
|
||||
// extnValue).
|
||||
if (extension_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// By definition the input was a single Extension sequence, so there shouldn't
|
||||
// be unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OPENSSL_EXPORT bool ParseExtensions(
|
||||
const der::Input& extensions_tlv,
|
||||
std::map<der::Input, ParsedExtension>* extensions) {
|
||||
der::Parser parser(extensions_tlv);
|
||||
|
||||
// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||||
der::Parser extensions_parser;
|
||||
if (!parser.ReadSequence(&extensions_parser))
|
||||
return false;
|
||||
|
||||
// The Extensions SEQUENCE must contains at least 1 element (otherwise it
|
||||
// should have been omitted).
|
||||
if (!extensions_parser.HasMore())
|
||||
return false;
|
||||
|
||||
extensions->clear();
|
||||
|
||||
while (extensions_parser.HasMore()) {
|
||||
ParsedExtension extension;
|
||||
|
||||
der::Input extension_tlv;
|
||||
if (!extensions_parser.ReadRawTLV(&extension_tlv))
|
||||
return false;
|
||||
|
||||
if (!ParseExtension(extension_tlv, &extension))
|
||||
return false;
|
||||
|
||||
bool is_duplicate =
|
||||
!extensions->insert(std::make_pair(extension.oid, extension)).second;
|
||||
|
||||
// RFC 5280 says that an extension should not appear more than once.
|
||||
if (is_duplicate)
|
||||
return false;
|
||||
}
|
||||
|
||||
// By definition the input was a single Extensions sequence, so there
|
||||
// shouldn't be unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OPENSSL_EXPORT bool ConsumeExtension(
|
||||
const der::Input& oid,
|
||||
std::map<der::Input, ParsedExtension>* unconsumed_extensions,
|
||||
ParsedExtension* extension) {
|
||||
auto it = unconsumed_extensions->find(oid);
|
||||
if (it == unconsumed_extensions->end())
|
||||
return false;
|
||||
|
||||
*extension = it->second;
|
||||
unconsumed_extensions->erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseBasicConstraints(const der::Input& basic_constraints_tlv,
|
||||
ParsedBasicConstraints* out) {
|
||||
der::Parser parser(basic_constraints_tlv);
|
||||
|
||||
// BasicConstraints ::= SEQUENCE {
|
||||
der::Parser sequence_parser;
|
||||
if (!parser.ReadSequence(&sequence_parser))
|
||||
return false;
|
||||
|
||||
// cA BOOLEAN DEFAULT FALSE,
|
||||
out->is_ca = false;
|
||||
bool has_ca;
|
||||
der::Input ca;
|
||||
if (!sequence_parser.ReadOptionalTag(der::kBool, &ca, &has_ca))
|
||||
return false;
|
||||
if (has_ca) {
|
||||
if (!der::ParseBool(ca, &out->is_ca))
|
||||
return false;
|
||||
// TODO(eroman): Should reject if CA was set to false, since
|
||||
// DER-encoding requires DEFAULT values be omitted. In
|
||||
// practice however there are a lot of certificates that use
|
||||
// the broken encoding.
|
||||
}
|
||||
|
||||
// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
|
||||
der::Input encoded_path_len;
|
||||
if (!sequence_parser.ReadOptionalTag(der::kInteger, &encoded_path_len,
|
||||
&out->has_path_len)) {
|
||||
return false;
|
||||
}
|
||||
if (out->has_path_len) {
|
||||
// TODO(eroman): Surface reason for failure if length was longer than uint8.
|
||||
if (!der::ParseUint8(encoded_path_len, &out->path_len))
|
||||
return false;
|
||||
} else {
|
||||
// Default initialize to 0 as a precaution.
|
||||
out->path_len = 0;
|
||||
}
|
||||
|
||||
// There shouldn't be any unconsumed data in the extension.
|
||||
if (sequence_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// By definition the input was a single BasicConstraints sequence, so there
|
||||
// shouldn't be unconsumed data.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(crbug.com/1314019): return std::optional<BitString> when converting
|
||||
// has_key_usage_ and key_usage_ into single std::optional field.
|
||||
bool ParseKeyUsage(const der::Input& key_usage_tlv, der::BitString* key_usage) {
|
||||
der::Parser parser(key_usage_tlv);
|
||||
std::optional<der::BitString> key_usage_internal = parser.ReadBitString();
|
||||
if (!key_usage_internal)
|
||||
return false;
|
||||
|
||||
// By definition the input was a single BIT STRING.
|
||||
if (parser.HasMore())
|
||||
return false;
|
||||
|
||||
// RFC 5280 section 4.2.1.3:
|
||||
//
|
||||
// When the keyUsage extension appears in a certificate, at least
|
||||
// one of the bits MUST be set to 1.
|
||||
if (BitStringIsAllZeros(key_usage_internal.value()))
|
||||
return false;
|
||||
|
||||
*key_usage = key_usage_internal.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseAuthorityInfoAccess(
|
||||
const der::Input& authority_info_access_tlv,
|
||||
std::vector<AuthorityInfoAccessDescription>* out_access_descriptions) {
|
||||
der::Parser parser(authority_info_access_tlv);
|
||||
|
||||
out_access_descriptions->clear();
|
||||
|
||||
// AuthorityInfoAccessSyntax ::=
|
||||
// SEQUENCE SIZE (1..MAX) OF AccessDescription
|
||||
der::Parser sequence_parser;
|
||||
if (!parser.ReadSequence(&sequence_parser))
|
||||
return false;
|
||||
if (!sequence_parser.HasMore())
|
||||
return false;
|
||||
|
||||
while (sequence_parser.HasMore()) {
|
||||
AuthorityInfoAccessDescription access_description;
|
||||
|
||||
// AccessDescription ::= SEQUENCE {
|
||||
der::Parser access_description_sequence_parser;
|
||||
if (!sequence_parser.ReadSequence(&access_description_sequence_parser))
|
||||
return false;
|
||||
|
||||
// accessMethod OBJECT IDENTIFIER,
|
||||
if (!access_description_sequence_parser.ReadTag(
|
||||
der::kOid, &access_description.access_method_oid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// accessLocation GeneralName }
|
||||
if (!access_description_sequence_parser.ReadRawTLV(
|
||||
&access_description.access_location)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (access_description_sequence_parser.HasMore())
|
||||
return false;
|
||||
|
||||
out_access_descriptions->push_back(access_description);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseAuthorityInfoAccessURIs(
|
||||
const der::Input& authority_info_access_tlv,
|
||||
std::vector<std::string_view>* out_ca_issuers_uris,
|
||||
std::vector<std::string_view>* out_ocsp_uris) {
|
||||
std::vector<AuthorityInfoAccessDescription> access_descriptions;
|
||||
if (!ParseAuthorityInfoAccess(authority_info_access_tlv,
|
||||
&access_descriptions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& access_description : access_descriptions) {
|
||||
der::Parser access_location_parser(access_description.access_location);
|
||||
der::Tag access_location_tag;
|
||||
der::Input access_location_value;
|
||||
if (!access_location_parser.ReadTagAndValue(&access_location_tag,
|
||||
&access_location_value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// GeneralName ::= CHOICE {
|
||||
if (access_location_tag == der::ContextSpecificPrimitive(6)) {
|
||||
// uniformResourceIdentifier [6] IA5String,
|
||||
std::string_view uri = access_location_value.AsStringView();
|
||||
if (!bssl::string_util::IsAscii(uri))
|
||||
return false;
|
||||
|
||||
if (access_description.access_method_oid == der::Input(kAdCaIssuersOid))
|
||||
out_ca_issuers_uris->push_back(uri);
|
||||
else if (access_description.access_method_oid == der::Input(kAdOcspOid))
|
||||
out_ocsp_uris->push_back(uri);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ParsedDistributionPoint::ParsedDistributionPoint() = default;
|
||||
ParsedDistributionPoint::ParsedDistributionPoint(
|
||||
ParsedDistributionPoint&& other) = default;
|
||||
ParsedDistributionPoint::~ParsedDistributionPoint() = default;
|
||||
|
||||
bool ParseCrlDistributionPoints(
|
||||
const der::Input& extension_value,
|
||||
std::vector<ParsedDistributionPoint>* distribution_points) {
|
||||
distribution_points->clear();
|
||||
|
||||
// RFC 5280, section 4.2.1.13.
|
||||
//
|
||||
// CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
||||
der::Parser extension_value_parser(extension_value);
|
||||
der::Parser distribution_points_parser;
|
||||
if (!extension_value_parser.ReadSequence(&distribution_points_parser))
|
||||
return false;
|
||||
if (extension_value_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// Sequence must have a minimum of 1 item.
|
||||
if (!distribution_points_parser.HasMore())
|
||||
return false;
|
||||
|
||||
while (distribution_points_parser.HasMore()) {
|
||||
if (!ParseAndAddDistributionPoint(&distribution_points_parser,
|
||||
distribution_points))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ParsedAuthorityKeyIdentifier::ParsedAuthorityKeyIdentifier() = default;
|
||||
ParsedAuthorityKeyIdentifier::~ParsedAuthorityKeyIdentifier() = default;
|
||||
ParsedAuthorityKeyIdentifier::ParsedAuthorityKeyIdentifier(
|
||||
ParsedAuthorityKeyIdentifier&& other) = default;
|
||||
ParsedAuthorityKeyIdentifier& ParsedAuthorityKeyIdentifier::operator=(
|
||||
ParsedAuthorityKeyIdentifier&& other) = default;
|
||||
|
||||
bool ParseAuthorityKeyIdentifier(
|
||||
const der::Input& extension_value,
|
||||
ParsedAuthorityKeyIdentifier* authority_key_identifier) {
|
||||
// RFC 5280, section 4.2.1.1.
|
||||
// AuthorityKeyIdentifier ::= SEQUENCE {
|
||||
// keyIdentifier [0] KeyIdentifier OPTIONAL,
|
||||
// authorityCertIssuer [1] GeneralNames OPTIONAL,
|
||||
// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
||||
//
|
||||
// KeyIdentifier ::= OCTET STRING
|
||||
|
||||
der::Parser extension_value_parser(extension_value);
|
||||
der::Parser aki_parser;
|
||||
if (!extension_value_parser.ReadSequence(&aki_parser))
|
||||
return false;
|
||||
if (extension_value_parser.HasMore())
|
||||
return false;
|
||||
|
||||
// TODO(mattm): Should having an empty AuthorityKeyIdentifier SEQUENCE be an
|
||||
// error? RFC 5280 doesn't explicitly say it.
|
||||
|
||||
// keyIdentifier [0] KeyIdentifier OPTIONAL,
|
||||
if (!aki_parser.ReadOptionalTag(der::ContextSpecificPrimitive(0),
|
||||
&authority_key_identifier->key_identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// authorityCertIssuer [1] GeneralNames OPTIONAL,
|
||||
if (!aki_parser.ReadOptionalTag(
|
||||
der::ContextSpecificConstructed(1),
|
||||
&authority_key_identifier->authority_cert_issuer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
||||
if (!aki_parser.ReadOptionalTag(
|
||||
der::ContextSpecificPrimitive(2),
|
||||
&authority_key_identifier->authority_cert_serial_number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- authorityCertIssuer and authorityCertSerialNumber MUST both
|
||||
// -- be present or both be absent
|
||||
if (authority_key_identifier->authority_cert_issuer.has_value() !=
|
||||
authority_key_identifier->authority_cert_serial_number.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There shouldn't be any unconsumed data in the AuthorityKeyIdentifier
|
||||
// SEQUENCE.
|
||||
if (aki_parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseSubjectKeyIdentifier(const der::Input& extension_value,
|
||||
der::Input* subject_key_identifier) {
|
||||
// SubjectKeyIdentifier ::= KeyIdentifier
|
||||
//
|
||||
// KeyIdentifier ::= OCTET STRING
|
||||
der::Parser extension_value_parser(extension_value);
|
||||
if (!extension_value_parser.ReadTag(der::kOctetString,
|
||||
subject_key_identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There shouldn't be any unconsumed data in the extension SEQUENCE.
|
||||
if (extension_value_parser.HasMore())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
629
pki/parse_certificate.h
Normal file
629
pki/parse_certificate.h
Normal file
@ -0,0 +1,629 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_PARSE_CERTIFICATE_H_
|
||||
#define BSSL_PKI_PARSE_CERTIFICATE_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "general_names.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace der {
|
||||
class Parser;
|
||||
}
|
||||
|
||||
class CertErrors;
|
||||
struct ParsedTbsCertificate;
|
||||
|
||||
// Returns true if the given serial number (CertificateSerialNumber in RFC 5280)
|
||||
// is valid:
|
||||
//
|
||||
// CertificateSerialNumber ::= INTEGER
|
||||
//
|
||||
// The input to this function is the (unverified) value octets of the INTEGER.
|
||||
// This function will verify that:
|
||||
//
|
||||
// * The octets are a valid DER-encoding of an INTEGER (for instance, minimal
|
||||
// encoding length).
|
||||
//
|
||||
// * No more than 20 octets are used.
|
||||
//
|
||||
// Note that it DOES NOT reject non-positive values (zero or negative).
|
||||
//
|
||||
// For reference, here is what RFC 5280 section 4.1.2.2 says:
|
||||
//
|
||||
// Given the uniqueness requirements above, serial numbers can be
|
||||
// expected to contain long integers. Certificate users MUST be able to
|
||||
// handle serialNumber values up to 20 octets. Conforming CAs MUST NOT
|
||||
// use serialNumber values longer than 20 octets.
|
||||
//
|
||||
// Note: Non-conforming CAs may issue certificates with serial numbers
|
||||
// that are negative or zero. Certificate users SHOULD be prepared to
|
||||
// gracefully handle such certificates.
|
||||
//
|
||||
// |errors| must be a non-null destination for any errors/warnings. If
|
||||
// |warnings_only| is set to true, then what would ordinarily be errors are
|
||||
// instead added as warnings.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool VerifySerialNumber(const der::Input& value,
|
||||
bool warnings_only,
|
||||
CertErrors* errors);
|
||||
|
||||
// Consumes a "Time" value (as defined by RFC 5280) from |parser|. On success
|
||||
// writes the result to |*out| and returns true. On failure no guarantees are
|
||||
// made about the state of |parser|.
|
||||
//
|
||||
// From RFC 5280:
|
||||
//
|
||||
// Time ::= CHOICE {
|
||||
// utcTime UTCTime,
|
||||
// generalTime GeneralizedTime }
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ReadUTCOrGeneralizedTime(
|
||||
der::Parser* parser,
|
||||
der::GeneralizedTime* out);
|
||||
|
||||
// Parses a DER-encoded "Validity" as specified by RFC 5280. Returns true on
|
||||
// success and sets the results in |not_before| and |not_after|:
|
||||
//
|
||||
// Validity ::= SEQUENCE {
|
||||
// notBefore Time,
|
||||
// notAfter Time }
|
||||
//
|
||||
// Note that upon success it is NOT guaranteed that |*not_before <= *not_after|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseValidity(const der::Input& validity_tlv,
|
||||
der::GeneralizedTime* not_before,
|
||||
der::GeneralizedTime* not_after);
|
||||
|
||||
struct OPENSSL_EXPORT ParseCertificateOptions {
|
||||
// If set to true, then parsing will skip checks on the certificate's serial
|
||||
// number. The only requirement will be that the serial number is an INTEGER,
|
||||
// however it is not required to be a valid DER-encoding (i.e. minimal
|
||||
// encoding), nor is it required to be constrained to any particular length.
|
||||
bool allow_invalid_serial_numbers = false;
|
||||
};
|
||||
|
||||
// Parses a DER-encoded "Certificate" as specified by RFC 5280. Returns true on
|
||||
// success and sets the results in the |out_*| parameters. On both the failure
|
||||
// and success case, if |out_errors| was non-null it may contain extra error
|
||||
// information.
|
||||
//
|
||||
// Note that on success the out parameters alias data from the input
|
||||
// |certificate_tlv|. Hence the output values are only valid as long as
|
||||
// |certificate_tlv| remains valid.
|
||||
//
|
||||
// On failure the out parameters have an undefined state, except for
|
||||
// out_errors. Some of them may have been updated during parsing, whereas
|
||||
// others may not have been changed.
|
||||
//
|
||||
// The out parameters represent each field of the Certificate SEQUENCE:
|
||||
// Certificate ::= SEQUENCE {
|
||||
//
|
||||
// The |out_tbs_certificate_tlv| parameter corresponds with "tbsCertificate"
|
||||
// from RFC 5280:
|
||||
// tbsCertificate TBSCertificate,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
// This can be further parsed using ParseTbsCertificate().
|
||||
//
|
||||
// The |out_signature_algorithm_tlv| parameter corresponds with
|
||||
// "signatureAlgorithm" from RFC 5280:
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
// This can be further parsed using SignatureValue::Create().
|
||||
//
|
||||
// The |out_signature_value| parameter corresponds with "signatureValue" from
|
||||
// RFC 5280:
|
||||
// signatureValue BIT STRING }
|
||||
//
|
||||
// Parsing guarantees that this is a valid BIT STRING.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseCertificate(
|
||||
const der::Input& certificate_tlv,
|
||||
der::Input* out_tbs_certificate_tlv,
|
||||
der::Input* out_signature_algorithm_tlv,
|
||||
der::BitString* out_signature_value,
|
||||
CertErrors* out_errors);
|
||||
|
||||
// Parses a DER-encoded "TBSCertificate" as specified by RFC 5280. Returns true
|
||||
// on success and sets the results in |out|. Certain invalid inputs may
|
||||
// be accepted based on the provided |options|.
|
||||
//
|
||||
// If |errors| was non-null then any warnings/errors that occur during parsing
|
||||
// are added to it.
|
||||
//
|
||||
// Note that on success |out| aliases data from the input |tbs_tlv|.
|
||||
// Hence the fields of the ParsedTbsCertificate are only valid as long as
|
||||
// |tbs_tlv| remains valid.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
//
|
||||
// Refer to the per-field documentation of ParsedTbsCertificate for details on
|
||||
// what validity checks parsing performs.
|
||||
//
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
// }
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseTbsCertificate(
|
||||
const der::Input& tbs_tlv,
|
||||
const ParseCertificateOptions& options,
|
||||
ParsedTbsCertificate* out,
|
||||
CertErrors* errors);
|
||||
|
||||
// Represents a "Version" from RFC 5280:
|
||||
// Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
||||
enum class CertificateVersion {
|
||||
V1,
|
||||
V2,
|
||||
V3,
|
||||
};
|
||||
|
||||
// ParsedTbsCertificate contains pointers to the main fields of a DER-encoded
|
||||
// RFC 5280 "TBSCertificate".
|
||||
//
|
||||
// ParsedTbsCertificate is expected to be filled by ParseTbsCertificate(), so
|
||||
// subsequent field descriptions are in terms of what ParseTbsCertificate()
|
||||
// sets.
|
||||
struct OPENSSL_EXPORT ParsedTbsCertificate {
|
||||
ParsedTbsCertificate();
|
||||
ParsedTbsCertificate(ParsedTbsCertificate&& other);
|
||||
ParsedTbsCertificate& operator=(ParsedTbsCertificate&& other) = default;
|
||||
~ParsedTbsCertificate();
|
||||
|
||||
// Corresponds with "version" from RFC 5280:
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
//
|
||||
// Parsing guarantees that the version is one of v1, v2, or v3.
|
||||
CertificateVersion version = CertificateVersion::V1;
|
||||
|
||||
// Corresponds with "serialNumber" from RFC 5280:
|
||||
// serialNumber CertificateSerialNumber,
|
||||
//
|
||||
// This field specifically contains the content bytes of the INTEGER. So for
|
||||
// instance if the serial number was 1000 then this would contain bytes
|
||||
// {0x03, 0xE8}.
|
||||
//
|
||||
// The serial number may or may not be a valid DER-encoded INTEGER:
|
||||
//
|
||||
// If the option |allow_invalid_serial_numbers=true| was used during
|
||||
// parsing, then nothing further can be assumed about these bytes.
|
||||
//
|
||||
// Otherwise if |allow_invalid_serial_numbers=false| then in addition
|
||||
// to being a valid DER-encoded INTEGER, parsing guarantees that
|
||||
// the serial number is at most 20 bytes long. Parsing does NOT guarantee
|
||||
// that the integer is positive (might be zero or negative).
|
||||
der::Input serial_number;
|
||||
|
||||
// Corresponds with "signatureAlgorithm" from RFC 5280:
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
//
|
||||
// This can be further parsed using SignatureValue::Create().
|
||||
der::Input signature_algorithm_tlv;
|
||||
|
||||
// Corresponds with "issuer" from RFC 5280:
|
||||
// issuer Name,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
der::Input issuer_tlv;
|
||||
|
||||
// Corresponds with "validity" from RFC 5280:
|
||||
// validity Validity,
|
||||
//
|
||||
// Where Validity is defined as:
|
||||
//
|
||||
// Validity ::= SEQUENCE {
|
||||
// notBefore Time,
|
||||
// notAfter Time }
|
||||
//
|
||||
// Parsing guarantees that notBefore (validity_not_before) and notAfter
|
||||
// (validity_not_after) are valid DER-encoded dates, however it DOES NOT
|
||||
// gurantee anything about their values. For instance notAfter could be
|
||||
// before notBefore, or the dates could indicate an expired certificate.
|
||||
// Consumers are responsible for testing expiration.
|
||||
der::GeneralizedTime validity_not_before;
|
||||
der::GeneralizedTime validity_not_after;
|
||||
|
||||
// Corresponds with "subject" from RFC 5280:
|
||||
// subject Name,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
der::Input subject_tlv;
|
||||
|
||||
// Corresponds with "subjectPublicKeyInfo" from RFC 5280:
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE.
|
||||
der::Input spki_tlv;
|
||||
|
||||
// Corresponds with "issuerUniqueID" from RFC 5280:
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
//
|
||||
// Parsing guarantees that if issuer_unique_id is present it is a valid BIT
|
||||
// STRING, and that the version is either v2 or v3
|
||||
std::optional<der::BitString> issuer_unique_id;
|
||||
|
||||
// Corresponds with "subjectUniqueID" from RFC 5280:
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
//
|
||||
// Parsing guarantees that if subject_unique_id is present it is a valid BIT
|
||||
// STRING, and that the version is either v2 or v3
|
||||
std::optional<der::BitString> subject_unique_id;
|
||||
|
||||
// Corresponds with "extensions" from RFC 5280:
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
//
|
||||
//
|
||||
// This contains the full (unverified) Tag-Length-Value for a SEQUENCE. No
|
||||
// guarantees are made regarding the value of this SEQUENCE. (Note that the
|
||||
// EXPLICIT outer tag is stripped.)
|
||||
//
|
||||
// Parsing guarantees that if extensions is present the version is v3.
|
||||
std::optional<der::Input> extensions_tlv;
|
||||
};
|
||||
|
||||
// ParsedExtension represents a parsed "Extension" from RFC 5280. It contains
|
||||
// der:Inputs which are not owned so the associated data must be kept alive.
|
||||
//
|
||||
// Extension ::= SEQUENCE {
|
||||
// extnID OBJECT IDENTIFIER,
|
||||
// critical BOOLEAN DEFAULT FALSE,
|
||||
// extnValue OCTET STRING
|
||||
// -- contains the DER encoding of an ASN.1 value
|
||||
// -- corresponding to the extension type identified
|
||||
// -- by extnID
|
||||
// }
|
||||
struct OPENSSL_EXPORT ParsedExtension {
|
||||
der::Input oid;
|
||||
// |value| will contain the contents of the OCTET STRING. For instance for
|
||||
// basicConstraints it will be the TLV for a SEQUENCE.
|
||||
der::Input value;
|
||||
bool critical = false;
|
||||
};
|
||||
|
||||
// Parses a DER-encoded "Extension" as specified by RFC 5280. Returns true on
|
||||
// success and sets the results in |out|.
|
||||
//
|
||||
// Note that on success |out| aliases data from the input |extension_tlv|.
|
||||
// Hence the fields of the ParsedExtension are only valid as long as
|
||||
// |extension_tlv| remains valid.
|
||||
//
|
||||
// On failure |out| has an undefined state. Some of its fields may have been
|
||||
// updated during parsing, whereas others may not have been changed.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseExtension(const der::Input& extension_tlv,
|
||||
ParsedExtension* out);
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-subjectKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 14 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.14
|
||||
inline constexpr uint8_t kSubjectKeyIdentifierOid[] = {0x55, 0x1d, 0x0e};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.15
|
||||
inline constexpr uint8_t kKeyUsageOid[] = {0x55, 0x1d, 0x0f};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.17
|
||||
inline constexpr uint8_t kSubjectAltNameOid[] = {0x55, 0x1d, 0x11};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.19
|
||||
inline constexpr uint8_t kBasicConstraintsOid[] = {0x55, 0x1d, 0x13};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-nameConstraints OBJECT IDENTIFIER ::= { id-ce 30 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.30
|
||||
inline constexpr uint8_t kNameConstraintsOid[] = {0x55, 0x1d, 0x1e};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.32
|
||||
inline constexpr uint8_t kCertificatePoliciesOid[] = {0x55, 0x1d, 0x20};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.35
|
||||
inline constexpr uint8_t kAuthorityKeyIdentifierOid[] = {0x55, 0x1d, 0x23};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-policyConstraints OBJECT IDENTIFIER ::= { id-ce 36 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.36
|
||||
inline constexpr uint8_t kPolicyConstraintsOid[] = {0x55, 0x1d, 0x24};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.37
|
||||
inline constexpr uint8_t kExtKeyUsageOid[] = {0x55, 0x1d, 0x25};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
|
||||
//
|
||||
// In dotted notation: 1.3.6.1.5.5.7.1.1
|
||||
inline constexpr uint8_t kAuthorityInfoAccessOid[] = {0x2B, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x01, 0x01};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
|
||||
//
|
||||
// In dotted notation: 1.3.6.1.5.5.7.48.2
|
||||
inline constexpr uint8_t kAdCaIssuersOid[] = {0x2B, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x30, 0x02};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
|
||||
//
|
||||
// In dotted notation: 1.3.6.1.5.5.7.48.1
|
||||
inline constexpr uint8_t kAdOcspOid[] = {0x2B, 0x06, 0x01, 0x05,
|
||||
0x05, 0x07, 0x30, 0x01};
|
||||
|
||||
// From RFC 5280:
|
||||
//
|
||||
// id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= { id-ce 31 }
|
||||
//
|
||||
// In dotted notation: 2.5.29.31
|
||||
inline constexpr uint8_t kCrlDistributionPointsOid[] = {0x55, 0x1d, 0x1f};
|
||||
|
||||
// From
|
||||
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/supported-extensions#msapplicationpolicies
|
||||
//
|
||||
// OID: XCN_OID_APPLICATION_CERT_POLICIES (1.3.6.1.4.1.311.21.10)
|
||||
inline constexpr uint8_t kMSApplicationPoliciesOid[] = {
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x0a};
|
||||
|
||||
// Parses the Extensions sequence as defined by RFC 5280. Extensions are added
|
||||
// to the map |extensions| keyed by the OID. Parsing guarantees that each OID
|
||||
// is unique. Note that certificate verification must consume each extension
|
||||
// marked as critical.
|
||||
//
|
||||
// Returns true on success and fills |extensions|. The output will reference
|
||||
// bytes in |extensions_tlv|, so that data must be kept alive.
|
||||
// On failure |extensions| may be partially written to and should not be used.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseExtensions(
|
||||
const der::Input& extensions_tlv,
|
||||
std::map<der::Input, ParsedExtension>* extensions);
|
||||
|
||||
// Removes the extension with OID |oid| from |unconsumed_extensions| and fills
|
||||
// |extension| with the matching extension value. If there was no extension
|
||||
// matching |oid| then returns |false|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ConsumeExtension(
|
||||
const der::Input& oid,
|
||||
std::map<der::Input, ParsedExtension>* unconsumed_extensions,
|
||||
ParsedExtension* extension);
|
||||
|
||||
struct ParsedBasicConstraints {
|
||||
bool is_ca = false;
|
||||
bool has_path_len = false;
|
||||
uint8_t path_len = 0;
|
||||
};
|
||||
|
||||
// Parses the BasicConstraints extension as defined by RFC 5280:
|
||||
//
|
||||
// BasicConstraints ::= SEQUENCE {
|
||||
// cA BOOLEAN DEFAULT FALSE,
|
||||
// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
|
||||
//
|
||||
// The maximum allowed value of pathLenConstraints will be whatever can fit
|
||||
// into a uint8_t.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseBasicConstraints(
|
||||
const der::Input& basic_constraints_tlv,
|
||||
ParsedBasicConstraints* out);
|
||||
|
||||
// KeyUsageBit contains the index for a particular key usage. The index is
|
||||
// measured from the most significant bit of a bit string.
|
||||
//
|
||||
// From RFC 5280 section 4.2.1.3:
|
||||
//
|
||||
// KeyUsage ::= BIT STRING {
|
||||
// digitalSignature (0),
|
||||
// nonRepudiation (1), -- recent editions of X.509 have
|
||||
// -- renamed this bit to contentCommitment
|
||||
// keyEncipherment (2),
|
||||
// dataEncipherment (3),
|
||||
// keyAgreement (4),
|
||||
// keyCertSign (5),
|
||||
// cRLSign (6),
|
||||
// encipherOnly (7),
|
||||
// decipherOnly (8) }
|
||||
enum KeyUsageBit {
|
||||
KEY_USAGE_BIT_DIGITAL_SIGNATURE = 0,
|
||||
KEY_USAGE_BIT_NON_REPUDIATION = 1,
|
||||
KEY_USAGE_BIT_KEY_ENCIPHERMENT = 2,
|
||||
KEY_USAGE_BIT_DATA_ENCIPHERMENT = 3,
|
||||
KEY_USAGE_BIT_KEY_AGREEMENT = 4,
|
||||
KEY_USAGE_BIT_KEY_CERT_SIGN = 5,
|
||||
KEY_USAGE_BIT_CRL_SIGN = 6,
|
||||
KEY_USAGE_BIT_ENCIPHER_ONLY = 7,
|
||||
KEY_USAGE_BIT_DECIPHER_ONLY = 8,
|
||||
};
|
||||
|
||||
// Parses the KeyUsage extension as defined by RFC 5280. Returns true on
|
||||
// success, and |key_usage| will alias data in |key_usage_tlv|. On failure
|
||||
// returns false, and |key_usage| may have been modified.
|
||||
//
|
||||
// In addition to validating that key_usage_tlv is a BIT STRING, this does
|
||||
// additional KeyUsage specific validations such as requiring at least 1 bit to
|
||||
// be set.
|
||||
//
|
||||
// To test if a particular key usage is set, call, e.g.:
|
||||
// key_usage->AssertsBit(KEY_USAGE_BIT_DIGITAL_SIGNATURE);
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseKeyUsage(const der::Input& key_usage_tlv,
|
||||
der::BitString* key_usage);
|
||||
|
||||
struct AuthorityInfoAccessDescription {
|
||||
// The accessMethod DER OID value.
|
||||
der::Input access_method_oid;
|
||||
// The accessLocation DER TLV.
|
||||
der::Input access_location;
|
||||
};
|
||||
// Parses the Authority Information Access extension defined by RFC 5280.
|
||||
// Returns true on success, and |out_access_descriptions| will alias data
|
||||
// in |authority_info_access_tlv|.On failure returns false, and
|
||||
// out_access_descriptions may have been partially filled.
|
||||
//
|
||||
// No validation is performed on the contents of the
|
||||
// AuthorityInfoAccessDescription fields.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseAuthorityInfoAccess(
|
||||
const der::Input& authority_info_access_tlv,
|
||||
std::vector<AuthorityInfoAccessDescription>* out_access_descriptions);
|
||||
|
||||
// Parses the Authority Information Access extension defined by RFC 5280,
|
||||
// extracting the caIssuers URIs and OCSP URIs.
|
||||
//
|
||||
// Returns true on success, and |out_ca_issuers_uris| and |out_ocsp_uris| will
|
||||
// alias data in |authority_info_access_tlv|. On failure returns false, and
|
||||
// |out_ca_issuers_uris| and |out_ocsp_uris| may have been partially filled.
|
||||
//
|
||||
// |out_ca_issuers_uris| is filled with the accessLocations of type
|
||||
// uniformResourceIdentifier for the accessMethod id-ad-caIssuers.
|
||||
// |out_ocsp_uris| is filled with the accessLocations of type
|
||||
// uniformResourceIdentifier for the accessMethod id-ad-ocsp.
|
||||
//
|
||||
// The values in |out_ca_issuers_uris| and |out_ocsp_uris| are checked to be
|
||||
// IA5String (ASCII strings), but no other validation is performed on them.
|
||||
//
|
||||
// accessMethods other than id-ad-caIssuers and id-ad-ocsp are silently ignored.
|
||||
// accessLocation types other than uniformResourceIdentifier are silently
|
||||
// ignored.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseAuthorityInfoAccessURIs(
|
||||
const der::Input& authority_info_access_tlv,
|
||||
std::vector<std::string_view>* out_ca_issuers_uris,
|
||||
std::vector<std::string_view>* out_ocsp_uris);
|
||||
|
||||
// ParsedDistributionPoint represents a parsed DistributionPoint from RFC 5280.
|
||||
//
|
||||
// DistributionPoint ::= SEQUENCE {
|
||||
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||
// reasons [1] ReasonFlags OPTIONAL,
|
||||
// cRLIssuer [2] GeneralNames OPTIONAL }
|
||||
struct OPENSSL_EXPORT ParsedDistributionPoint {
|
||||
ParsedDistributionPoint();
|
||||
ParsedDistributionPoint(ParsedDistributionPoint&& other);
|
||||
~ParsedDistributionPoint();
|
||||
|
||||
// The parsed fullName, if distributionPoint was present and was a fullName.
|
||||
std::unique_ptr<GeneralNames> distribution_point_fullname;
|
||||
|
||||
// If present, the DER encoded value of the nameRelativeToCRLIssuer field.
|
||||
// This should be a RelativeDistinguishedName, but the parser does not
|
||||
// validate it.
|
||||
std::optional<der::Input> distribution_point_name_relative_to_crl_issuer;
|
||||
|
||||
// If present, the DER encoded value of the reasons field. This should be a
|
||||
// ReasonFlags bitString, but the parser does not validate it.
|
||||
std::optional<der::Input> reasons;
|
||||
|
||||
// If present, the DER encoded value of the cRLIssuer field. This should be a
|
||||
// GeneralNames, but the parser does not validate it.
|
||||
std::optional<der::Input> crl_issuer;
|
||||
};
|
||||
|
||||
// Parses the value of a CRL Distribution Points extension (sequence of
|
||||
// DistributionPoint). Return true on success, and fills |distribution_points|
|
||||
// with values that reference data in |distribution_points_tlv|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseCrlDistributionPoints(
|
||||
const der::Input& distribution_points_tlv,
|
||||
std::vector<ParsedDistributionPoint>* distribution_points);
|
||||
|
||||
// Represents the AuthorityKeyIdentifier extension defined by RFC 5280 section
|
||||
// 4.2.1.1.
|
||||
//
|
||||
// AuthorityKeyIdentifier ::= SEQUENCE {
|
||||
// keyIdentifier [0] KeyIdentifier OPTIONAL,
|
||||
// authorityCertIssuer [1] GeneralNames OPTIONAL,
|
||||
// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
||||
//
|
||||
// KeyIdentifier ::= OCTET STRING
|
||||
struct OPENSSL_EXPORT ParsedAuthorityKeyIdentifier {
|
||||
ParsedAuthorityKeyIdentifier();
|
||||
~ParsedAuthorityKeyIdentifier();
|
||||
ParsedAuthorityKeyIdentifier(ParsedAuthorityKeyIdentifier&& other);
|
||||
ParsedAuthorityKeyIdentifier& operator=(ParsedAuthorityKeyIdentifier&& other);
|
||||
|
||||
// The keyIdentifier, which is an OCTET STRING.
|
||||
std::optional<der::Input> key_identifier;
|
||||
|
||||
// The authorityCertIssuer, which should be a GeneralNames, but this is not
|
||||
// enforced by ParseAuthorityKeyIdentifier.
|
||||
std::optional<der::Input> authority_cert_issuer;
|
||||
|
||||
// The DER authorityCertSerialNumber, which should be a
|
||||
// CertificateSerialNumber (an INTEGER) but this is not enforced by
|
||||
// ParseAuthorityKeyIdentifier.
|
||||
std::optional<der::Input> authority_cert_serial_number;
|
||||
};
|
||||
|
||||
// Parses the value of an authorityKeyIdentifier extension. Returns true on
|
||||
// success and fills |authority_key_identifier| with values that reference data
|
||||
// in |extension_value|. On failure the state of |authority_key_identifier| is
|
||||
// not guaranteed.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseAuthorityKeyIdentifier(
|
||||
const der::Input& extension_value,
|
||||
ParsedAuthorityKeyIdentifier* authority_key_identifier);
|
||||
|
||||
// Parses the value of a subjectKeyIdentifier extension. Returns true on
|
||||
// success and |subject_key_identifier| references data in |extension_value|.
|
||||
// On failure the state of |subject_key_identifier| is not guaranteed.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseSubjectKeyIdentifier(
|
||||
const der::Input& extension_value,
|
||||
der::Input* subject_key_identifier);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_PARSE_CERTIFICATE_H_
|
25
pki/parse_certificate_fuzzer.cc
Normal file
25
pki/parse_certificate_fuzzer.cc
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "parsed_certificate.h"
|
||||
#include "fillins/x509_util.h"
|
||||
#include <openssl/span.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
bssl::CertErrors errors;
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParsedCertificate::Create(
|
||||
x509_util::CreateCryptoBuffer(bssl::MakeSpan(data, size)), {},
|
||||
&errors);
|
||||
|
||||
// Severe errors must be provided iff the parsing failed.
|
||||
CHECK_EQ(errors.ContainsAnyErrorWithSeverity(net::CertError::SEVERITY_HIGH),
|
||||
cert == nullptr);
|
||||
|
||||
return 0;
|
||||
}
|
1176
pki/parse_certificate_unittest.cc
Normal file
1176
pki/parse_certificate_unittest.cc
Normal file
File diff suppressed because it is too large
Load Diff
226
pki/parse_name.cc
Normal file
226
pki/parse_name.cc
Normal file
@ -0,0 +1,226 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parse_name.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "string_util.h"
|
||||
#include "parse_values.h"
|
||||
#include <openssl/bytestring.h>
|
||||
#include <openssl/mem.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a string containing the dotted numeric form of |oid|, or an empty
|
||||
// string on error.
|
||||
std::string OidToString(der::Input oid) {
|
||||
CBS cbs;
|
||||
CBS_init(&cbs, oid.UnsafeData(), oid.Length());
|
||||
bssl::UniquePtr<char> text(CBS_asn1_oid_to_text(&cbs));
|
||||
if (!text)
|
||||
return std::string();
|
||||
return text.get();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool X509NameAttribute::ValueAsString(std::string* out) const {
|
||||
switch (value_tag) {
|
||||
case der::kTeletexString:
|
||||
return der::ParseTeletexStringAsLatin1(value, out);
|
||||
case der::kIA5String:
|
||||
return der::ParseIA5String(value, out);
|
||||
case der::kPrintableString:
|
||||
return der::ParsePrintableString(value, out);
|
||||
case der::kUtf8String:
|
||||
*out = value.AsString();
|
||||
return true;
|
||||
case der::kUniversalString:
|
||||
return der::ParseUniversalString(value, out);
|
||||
case der::kBmpString:
|
||||
return der::ParseBmpString(value, out);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool X509NameAttribute::ValueAsStringWithUnsafeOptions(
|
||||
PrintableStringHandling printable_string_handling,
|
||||
std::string* out) const {
|
||||
if (printable_string_handling == PrintableStringHandling::kAsUTF8Hack &&
|
||||
value_tag == der::kPrintableString) {
|
||||
*out = value.AsString();
|
||||
return true;
|
||||
}
|
||||
return ValueAsString(out);
|
||||
}
|
||||
|
||||
bool X509NameAttribute::ValueAsStringUnsafe(std::string* out) const {
|
||||
switch (value_tag) {
|
||||
case der::kIA5String:
|
||||
case der::kPrintableString:
|
||||
case der::kTeletexString:
|
||||
case der::kUtf8String:
|
||||
*out = value.AsString();
|
||||
return true;
|
||||
case der::kUniversalString:
|
||||
return der::ParseUniversalString(value, out);
|
||||
case der::kBmpString:
|
||||
return der::ParseBmpString(value, out);
|
||||
default:
|
||||
assert(0); // NOTREACHED
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool X509NameAttribute::AsRFC2253String(std::string* out) const {
|
||||
std::string type_string;
|
||||
std::string value_string;
|
||||
// TODO(mattm): Add streetAddress and domainComponent here?
|
||||
if (type == der::Input(kTypeCommonNameOid)) {
|
||||
type_string = "CN";
|
||||
} else if (type == der::Input(kTypeSurnameOid)) {
|
||||
type_string = "SN";
|
||||
} else if (type == der::Input(kTypeCountryNameOid)) {
|
||||
type_string = "C";
|
||||
} else if (type == der::Input(kTypeLocalityNameOid)) {
|
||||
type_string = "L";
|
||||
} else if (type == der::Input(kTypeStateOrProvinceNameOid)) {
|
||||
type_string = "ST";
|
||||
} else if (type == der::Input(kTypeOrganizationNameOid)) {
|
||||
type_string = "O";
|
||||
} else if (type == der::Input(kTypeOrganizationUnitNameOid)) {
|
||||
type_string = "OU";
|
||||
} else if (type == der::Input(kTypeGivenNameOid)) {
|
||||
type_string = "givenName";
|
||||
} else if (type == der::Input(kTypeEmailAddressOid)) {
|
||||
type_string = "emailAddress";
|
||||
} else {
|
||||
type_string = OidToString(type);
|
||||
if (type_string.empty())
|
||||
return false;
|
||||
value_string =
|
||||
"#" + bssl::string_util::HexEncode(value.UnsafeData(), value.Length());
|
||||
}
|
||||
|
||||
if (value_string.empty()) {
|
||||
std::string unescaped;
|
||||
if (!ValueAsStringUnsafe(&unescaped))
|
||||
return false;
|
||||
|
||||
bool nonprintable = false;
|
||||
for (unsigned int i = 0; i < unescaped.length(); ++i) {
|
||||
unsigned char c = static_cast<unsigned char>(unescaped[i]);
|
||||
if (i == 0 && c == '#') {
|
||||
value_string += "\\#";
|
||||
} else if (i == 0 && c == ' ') {
|
||||
value_string += "\\ ";
|
||||
} else if (i == unescaped.length() - 1 && c == ' ') {
|
||||
value_string += "\\ ";
|
||||
} else if (c == ',' || c == '+' || c == '"' || c == '\\' || c == '<' ||
|
||||
c == '>' || c == ';') {
|
||||
value_string += "\\";
|
||||
value_string += c;
|
||||
} else if (c < 32 || c > 126) {
|
||||
nonprintable = true;
|
||||
std::string h;
|
||||
h += c;
|
||||
value_string +=
|
||||
"\\" + bssl::string_util::HexEncode(
|
||||
reinterpret_cast<const uint8_t*>(h.data()), h.length());
|
||||
} else {
|
||||
value_string += c;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have non-printable characters in a TeletexString, we hex encode
|
||||
// since we don't handle Teletex control codes.
|
||||
if (nonprintable && value_tag == der::kTeletexString)
|
||||
value_string =
|
||||
"#" + bssl::string_util::HexEncode(value.UnsafeData(), value.Length());
|
||||
}
|
||||
|
||||
*out = type_string + "=" + value_string;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadRdn(der::Parser* parser, RelativeDistinguishedName* out) {
|
||||
while (parser->HasMore()) {
|
||||
der::Parser attr_type_and_value;
|
||||
if (!parser->ReadSequence(&attr_type_and_value))
|
||||
return false;
|
||||
// Read the attribute type, which must be an OBJECT IDENTIFIER.
|
||||
der::Input type;
|
||||
if (!attr_type_and_value.ReadTag(der::kOid, &type))
|
||||
return false;
|
||||
|
||||
// Read the attribute value.
|
||||
der::Tag tag;
|
||||
der::Input value;
|
||||
if (!attr_type_and_value.ReadTagAndValue(&tag, &value))
|
||||
return false;
|
||||
|
||||
// There should be no more elements in the sequence after reading the
|
||||
// attribute type and value.
|
||||
if (attr_type_and_value.HasMore())
|
||||
return false;
|
||||
|
||||
out->push_back(X509NameAttribute(type, tag, value));
|
||||
}
|
||||
|
||||
// RFC 5280 section 4.1.2.4
|
||||
// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
|
||||
return out->size() != 0;
|
||||
}
|
||||
|
||||
bool ParseName(const der::Input& name_tlv, RDNSequence* out) {
|
||||
der::Parser name_parser(name_tlv);
|
||||
der::Input name_value;
|
||||
if (!name_parser.ReadTag(der::kSequence, &name_value))
|
||||
return false;
|
||||
return ParseNameValue(name_value, out);
|
||||
}
|
||||
|
||||
bool ParseNameValue(const der::Input& name_value, RDNSequence* out) {
|
||||
der::Parser rdn_sequence_parser(name_value);
|
||||
while (rdn_sequence_parser.HasMore()) {
|
||||
der::Parser rdn_parser;
|
||||
if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser))
|
||||
return false;
|
||||
RelativeDistinguishedName type_and_values;
|
||||
if (!ReadRdn(&rdn_parser, &type_and_values))
|
||||
return false;
|
||||
out->push_back(type_and_values);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConvertToRFC2253(const RDNSequence& rdn_sequence, std::string* out) {
|
||||
std::string rdns_string;
|
||||
size_t size = rdn_sequence.size();
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
RelativeDistinguishedName rdn = rdn_sequence[size - i - 1];
|
||||
std::string rdn_string;
|
||||
for (const auto& atv : rdn) {
|
||||
if (!rdn_string.empty())
|
||||
rdn_string += "+";
|
||||
std::string atv_string;
|
||||
if (!atv.AsRFC2253String(&atv_string))
|
||||
return false;
|
||||
rdn_string += atv_string;
|
||||
}
|
||||
if (!rdns_string.empty())
|
||||
rdns_string += ",";
|
||||
rdns_string += rdn_string;
|
||||
}
|
||||
|
||||
*out = rdns_string;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
158
pki/parse_name.h
Normal file
158
pki/parse_name.h
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_PARSE_NAME_H_
|
||||
#define BSSL_PKI_PARSE_NAME_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "input.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// id-at-commonName: 2.5.4.3 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeCommonNameOid[] = {0x55, 0x04, 0x03};
|
||||
// id-at-surname: 2.5.4.4 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeSurnameOid[] = {0x55, 0x04, 0x04};
|
||||
// id-at-serialNumber: 2.5.4.5 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeSerialNumberOid[] = {0x55, 0x04, 0x05};
|
||||
// id-at-countryName: 2.5.4.6 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeCountryNameOid[] = {0x55, 0x04, 0x06};
|
||||
// id-at-localityName: 2.5.4.7 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeLocalityNameOid[] = {0x55, 0x04, 0x07};
|
||||
// id-at-stateOrProvinceName: 2.5.4.8 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeStateOrProvinceNameOid[] = {0x55, 0x04, 0x08};
|
||||
// street (streetAddress): 2.5.4.9 (RFC 4519)
|
||||
inline constexpr uint8_t kTypeStreetAddressOid[] = {0x55, 0x04, 0x09};
|
||||
// id-at-organizationName: 2.5.4.10 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeOrganizationNameOid[] = {0x55, 0x04, 0x0a};
|
||||
// id-at-organizationalUnitName: 2.5.4.11 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeOrganizationUnitNameOid[] = {0x55, 0x04, 0x0b};
|
||||
// id-at-title: 2.5.4.12 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeTitleOid[] = {0x55, 0x04, 0x0c};
|
||||
// id-at-name: 2.5.4.41 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeNameOid[] = {0x55, 0x04, 0x29};
|
||||
// id-at-givenName: 2.5.4.42 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeGivenNameOid[] = {0x55, 0x04, 0x2a};
|
||||
// id-at-initials: 2.5.4.43 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeInitialsOid[] = {0x55, 0x04, 0x2b};
|
||||
// id-at-generationQualifier: 2.5.4.44 (RFC 5280)
|
||||
inline constexpr uint8_t kTypeGenerationQualifierOid[] = {0x55, 0x04, 0x2c};
|
||||
// dc (domainComponent): 0.9.2342.19200300.100.1.25 (RFC 4519)
|
||||
inline constexpr uint8_t kTypeDomainComponentOid[] = {
|
||||
0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19};
|
||||
// RFC 5280 section A.1:
|
||||
//
|
||||
// pkcs-9 OBJECT IDENTIFIER ::=
|
||||
// { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
|
||||
//
|
||||
// id-emailAddress AttributeType ::= { pkcs-9 1 }
|
||||
//
|
||||
// In dotted form: 1.2.840.113549.1.9.1
|
||||
inline constexpr uint8_t kTypeEmailAddressOid[] = {0x2A, 0x86, 0x48, 0x86, 0xF7,
|
||||
0x0D, 0x01, 0x09, 0x01};
|
||||
|
||||
// X509NameAttribute contains a representation of a DER-encoded RFC 2253
|
||||
// "AttributeTypeAndValue".
|
||||
//
|
||||
// AttributeTypeAndValue ::= SEQUENCE {
|
||||
// type AttributeType,
|
||||
// value AttributeValue
|
||||
// }
|
||||
struct OPENSSL_EXPORT X509NameAttribute {
|
||||
X509NameAttribute(der::Input in_type,
|
||||
der::Tag in_value_tag,
|
||||
der::Input in_value)
|
||||
: type(in_type), value_tag(in_value_tag), value(in_value) {}
|
||||
|
||||
// Configures handling of PrintableString in the attribute value. Do
|
||||
// not use non-default handling without consulting //net owners. With
|
||||
// kAsUTF8Hack, PrintableStrings are interpreted as UTF-8 strings.
|
||||
enum class PrintableStringHandling { kDefault, kAsUTF8Hack };
|
||||
|
||||
// Attempts to convert the value represented by this struct into a
|
||||
// UTF-8 string and store it in |out|, returning whether the conversion
|
||||
// was successful.
|
||||
[[nodiscard]] bool ValueAsString(std::string* out) const;
|
||||
|
||||
// Attempts to convert the value represented by this struct into a
|
||||
// UTF-8 string and store it in |out|, returning whether the conversion
|
||||
// was successful. Allows configuring some non-standard string handling
|
||||
// options.
|
||||
//
|
||||
// Do not use without consulting //net owners.
|
||||
[[nodiscard]] bool ValueAsStringWithUnsafeOptions(
|
||||
PrintableStringHandling printable_string_handling,
|
||||
std::string* out) const;
|
||||
|
||||
// Attempts to convert the value represented by this struct into a
|
||||
// std::string and store it in |out|, returning whether the conversion was
|
||||
// successful. Due to some encodings being incompatible, the caller must
|
||||
// verify the attribute |value_tag|.
|
||||
//
|
||||
// Note: Don't use this function unless you know what you're doing. Use
|
||||
// ValueAsString instead.
|
||||
//
|
||||
// Note: The conversion doesn't verify that the value corresponds to the
|
||||
// ASN.1 definition of the value type.
|
||||
[[nodiscard]] bool ValueAsStringUnsafe(std::string* out) const;
|
||||
|
||||
// Formats the NameAttribute per RFC2253 into an ASCII string and stores
|
||||
// the result in |out|, returning whether the conversion was successful.
|
||||
[[nodiscard]] bool AsRFC2253String(std::string* out) const;
|
||||
|
||||
der::Input type;
|
||||
der::Tag value_tag;
|
||||
der::Input value;
|
||||
};
|
||||
|
||||
typedef std::vector<X509NameAttribute> RelativeDistinguishedName;
|
||||
typedef std::vector<RelativeDistinguishedName> RDNSequence;
|
||||
|
||||
// Parses all the ASN.1 AttributeTypeAndValue elements in |parser| and stores
|
||||
// each as an AttributeTypeAndValue object in |out|.
|
||||
//
|
||||
// AttributeTypeAndValue is defined in RFC 5280 section 4.1.2.4:
|
||||
//
|
||||
// AttributeTypeAndValue ::= SEQUENCE {
|
||||
// type AttributeType,
|
||||
// value AttributeValue }
|
||||
//
|
||||
// AttributeType ::= OBJECT IDENTIFIER
|
||||
//
|
||||
// AttributeValue ::= ANY -- DEFINED BY AttributeType
|
||||
//
|
||||
// DirectoryString ::= CHOICE {
|
||||
// teletexString TeletexString (SIZE (1..MAX)),
|
||||
// printableString PrintableString (SIZE (1..MAX)),
|
||||
// universalString UniversalString (SIZE (1..MAX)),
|
||||
// utf8String UTF8String (SIZE (1..MAX)),
|
||||
// bmpString BMPString (SIZE (1..MAX)) }
|
||||
//
|
||||
// The type of the component AttributeValue is determined by the AttributeType;
|
||||
// in general it will be a DirectoryString.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ReadRdn(der::Parser* parser,
|
||||
RelativeDistinguishedName* out);
|
||||
|
||||
// Parses a DER-encoded "Name" as specified by 5280. Returns true on success
|
||||
// and sets the results in |out|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseName(const der::Input& name_tlv,
|
||||
RDNSequence* out);
|
||||
// Parses a DER-encoded "Name" value (without the sequence tag & length) as
|
||||
// specified by 5280. Returns true on success and sets the results in |out|.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseNameValue(const der::Input& name_value,
|
||||
RDNSequence* out);
|
||||
|
||||
// Formats a RDNSequence |rdn_sequence| per RFC2253 as an ASCII string and
|
||||
// stores the result into |out|, and returns whether the conversion was
|
||||
// successful.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ConvertToRFC2253(const RDNSequence& rdn_sequence,
|
||||
std::string* out);
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_PARSE_NAME_H_
|
361
pki/parse_name_unittest.cc
Normal file
361
pki/parse_name_unittest.cc
Normal file
@ -0,0 +1,361 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parse_name.h"
|
||||
|
||||
#include "test_helpers.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
// Loads test data from file. The filename is constructed from the parameters:
|
||||
// |prefix| describes the type of data being tested, e.g. "ascii",
|
||||
// "unicode_bmp", "unicode_supplementary", and "invalid".
|
||||
// |value_type| indicates what ASN.1 type is used to encode the data.
|
||||
// |suffix| indicates any additional modifications, such as caseswapping,
|
||||
// whitespace adding, etc.
|
||||
::testing::AssertionResult LoadTestData(const std::string& prefix,
|
||||
const std::string& value_type,
|
||||
const std::string& suffix,
|
||||
std::string* result) {
|
||||
std::string path = "testdata/verify_name_match_unittest/names/" + prefix +
|
||||
"-" + value_type + "-" + suffix + ".pem";
|
||||
|
||||
const PemBlockMapping mappings[] = {
|
||||
{"NAME", result},
|
||||
};
|
||||
|
||||
return ReadTestDataFromPemFile(path, mappings);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST(ParseNameTest, IA5SafeStringValue) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kIA5String, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("Foo bar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_TRUE(value.ValueAsString(&result));
|
||||
ASSERT_EQ("Foo bar", result);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, IA5UnsafeStringValue) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0xFF, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kIA5String, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("Fo\377 bar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_FALSE(value.ValueAsString(&result));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, PrintableSafeStringValue) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kPrintableString, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("Foo bar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_TRUE(value.ValueAsString(&result));
|
||||
ASSERT_EQ("Foo bar", result);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, PrintableUnsafeStringValue) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0x5f, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kPrintableString, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("Fo_ bar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_FALSE(value.ValueAsString(&result));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, PrintableStringUnsafeOptions) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0x5f, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kPrintableString, der::Input(der));
|
||||
std::string result;
|
||||
ASSERT_FALSE(value.ValueAsStringWithUnsafeOptions(
|
||||
X509NameAttribute::PrintableStringHandling::kDefault, &result));
|
||||
ASSERT_TRUE(value.ValueAsStringWithUnsafeOptions(
|
||||
X509NameAttribute::PrintableStringHandling::kAsUTF8Hack, &result));
|
||||
ASSERT_EQ("Fo_ bar", result);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, TeletexSafeStringValue) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kTeletexString, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("Foo bar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_TRUE(value.ValueAsString(&result));
|
||||
ASSERT_EQ("Foo bar", result);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, TeletexLatin1StringValue) {
|
||||
const uint8_t der[] = {
|
||||
0x46, 0x6f, 0xd6, 0x20, 0x62, 0x61, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kTeletexString, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("Fo\xd6 bar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_TRUE(value.ValueAsString(&result));
|
||||
ASSERT_EQ("FoÖ bar", result);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, ConvertBmpString) {
|
||||
const uint8_t der[] = {
|
||||
0x00, 0x66, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x61, 0x00, 0x72,
|
||||
};
|
||||
X509NameAttribute value(der::Input(), der::kBmpString, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("foobar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_TRUE(value.ValueAsString(&result));
|
||||
ASSERT_EQ("foobar", result);
|
||||
}
|
||||
|
||||
// BmpString must encode characters in pairs of 2 bytes.
|
||||
TEST(ParseNameTest, ConvertInvalidBmpString) {
|
||||
const uint8_t der[] = {0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x72};
|
||||
X509NameAttribute value(der::Input(), der::kBmpString, der::Input(der));
|
||||
std::string result;
|
||||
ASSERT_FALSE(value.ValueAsStringUnsafe(&result));
|
||||
ASSERT_FALSE(value.ValueAsString(&result));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, ConvertUniversalString) {
|
||||
const uint8_t der[] = {0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x6f,
|
||||
0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x62,
|
||||
0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x72};
|
||||
X509NameAttribute value(der::Input(), der::kUniversalString, der::Input(der));
|
||||
std::string result_unsafe;
|
||||
ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
|
||||
ASSERT_EQ("foobar", result_unsafe);
|
||||
std::string result;
|
||||
ASSERT_TRUE(value.ValueAsString(&result));
|
||||
ASSERT_EQ("foobar", result);
|
||||
}
|
||||
|
||||
// UniversalString must encode characters in pairs of 4 bytes.
|
||||
TEST(ParseNameTest, ConvertInvalidUniversalString) {
|
||||
const uint8_t der[] = {0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72};
|
||||
X509NameAttribute value(der::Input(), der::kUniversalString, der::Input(der));
|
||||
std::string result;
|
||||
ASSERT_FALSE(value.ValueAsStringUnsafe(&result));
|
||||
ASSERT_FALSE(value.ValueAsString(&result));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, EmptyName) {
|
||||
const uint8_t der[] = {0x30, 0x00};
|
||||
der::Input rdn(der);
|
||||
RDNSequence atv;
|
||||
ASSERT_TRUE(ParseName(rdn, &atv));
|
||||
ASSERT_EQ(0u, atv.size());
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, ValidName) {
|
||||
const uint8_t der[] = {0x30, 0x3c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x14, 0x30,
|
||||
0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, 0x47,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63,
|
||||
0x2e, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
|
||||
0x03, 0x13, 0x0e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x41};
|
||||
der::Input rdn(der);
|
||||
RDNSequence atv;
|
||||
ASSERT_TRUE(ParseName(rdn, &atv));
|
||||
ASSERT_EQ(3u, atv.size());
|
||||
ASSERT_EQ(1u, atv[0].size());
|
||||
ASSERT_EQ(der::Input(kTypeCountryNameOid), atv[0][0].type);
|
||||
ASSERT_EQ("US", atv[0][0].value.AsString());
|
||||
ASSERT_EQ(1u, atv[1].size());
|
||||
ASSERT_EQ(der::Input(kTypeOrganizationNameOid), atv[1][0].type);
|
||||
ASSERT_EQ("Google Inc.", atv[1][0].value.AsString());
|
||||
ASSERT_EQ(1u, atv[2].size());
|
||||
ASSERT_EQ(der::Input(kTypeCommonNameOid), atv[2][0].type);
|
||||
ASSERT_EQ("Google Test CA", atv[2][0].value.AsString());
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, InvalidNameExtraData) {
|
||||
std::string invalid;
|
||||
ASSERT_TRUE(
|
||||
LoadTestData("invalid", "AttributeTypeAndValue", "extradata", &invalid));
|
||||
RDNSequence atv;
|
||||
ASSERT_FALSE(ParseName(SequenceValueFromString(&invalid), &atv));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, InvalidNameEmpty) {
|
||||
std::string invalid;
|
||||
ASSERT_TRUE(
|
||||
LoadTestData("invalid", "AttributeTypeAndValue", "empty", &invalid));
|
||||
RDNSequence atv;
|
||||
ASSERT_FALSE(ParseName(SequenceValueFromString(&invalid), &atv));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, InvalidNameBadType) {
|
||||
std::string invalid;
|
||||
ASSERT_TRUE(LoadTestData("invalid", "AttributeTypeAndValue",
|
||||
"badAttributeType", &invalid));
|
||||
RDNSequence atv;
|
||||
ASSERT_FALSE(ParseName(SequenceValueFromString(&invalid), &atv));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, InvalidNameNotSequence) {
|
||||
std::string invalid;
|
||||
ASSERT_TRUE(LoadTestData("invalid", "AttributeTypeAndValue", "setNotSequence",
|
||||
&invalid));
|
||||
RDNSequence atv;
|
||||
ASSERT_FALSE(ParseName(SequenceValueFromString(&invalid), &atv));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, InvalidNameNotSet) {
|
||||
std::string invalid;
|
||||
ASSERT_TRUE(LoadTestData("invalid", "RDN", "sequenceInsteadOfSet", &invalid));
|
||||
RDNSequence atv;
|
||||
ASSERT_FALSE(ParseName(SequenceValueFromString(&invalid), &atv));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, InvalidNameEmptyRdn) {
|
||||
std::string invalid;
|
||||
ASSERT_TRUE(LoadTestData("invalid", "RDN", "empty", &invalid));
|
||||
RDNSequence atv;
|
||||
ASSERT_FALSE(ParseName(SequenceValueFromString(&invalid), &atv));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatBasic) {
|
||||
const uint8_t der[] = {0x30, 0x3b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x16, 0x30,
|
||||
0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x49,
|
||||
0x73, 0x6f, 0x64, 0x65, 0x20, 0x4c, 0x69, 0x6d, 0x69,
|
||||
0x74, 0x65, 0x64, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03,
|
||||
0x55, 0x04, 0x03, 0x13, 0x0b, 0x53, 0x74, 0x65, 0x76,
|
||||
0x65, 0x20, 0x4b, 0x69, 0x6c, 0x6c, 0x65};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("CN=Steve Kille,O=Isode Limited,C=GB", output);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatMultiRDN) {
|
||||
const uint8_t der[] = {
|
||||
0x30, 0x44, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||
0x02, 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a,
|
||||
0x13, 0x0b, 0x57, 0x69, 0x64, 0x67, 0x65, 0x74, 0x20, 0x49, 0x6e, 0x63,
|
||||
0x2e, 0x31, 0x1f, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x05,
|
||||
0x53, 0x61, 0x6c, 0x65, 0x73, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03,
|
||||
0x13, 0x08, 0x4a, 0x2e, 0x20, 0x53, 0x6d, 0x69, 0x74, 0x68};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("OU=Sales+CN=J. Smith,O=Widget Inc.,C=US", output);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatQuoted) {
|
||||
const uint8_t der[] = {
|
||||
0x30, 0x40, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
|
||||
0x13, 0x02, 0x47, 0x42, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
|
||||
0x04, 0x0a, 0x13, 0x15, 0x53, 0x75, 0x65, 0x2c, 0x20, 0x47, 0x72,
|
||||
0x61, 0x62, 0x62, 0x69, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x52,
|
||||
0x75, 0x6e, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04,
|
||||
0x03, 0x13, 0x08, 0x4c, 0x2e, 0x20, 0x45, 0x61, 0x67, 0x6c, 0x65};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB", output);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatNonPrintable) {
|
||||
const uint8_t der[] = {0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x0d, 0x30,
|
||||
0x0b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x04, 0x54,
|
||||
0x65, 0x73, 0x74, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03,
|
||||
0x55, 0x04, 0x03, 0x13, 0x0c, 0x42, 0x65, 0x66, 0x6f,
|
||||
0x72, 0x65, 0x0d, 0x41, 0x66, 0x74, 0x65, 0x72};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("CN=Before\\0DAfter,O=Test,C=GB", output);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatUnknownOid) {
|
||||
const uint8_t der[] = {0x30, 0x30, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x0d, 0x30,
|
||||
0x0b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x04, 0x54,
|
||||
0x65, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x08,
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0x8b, 0x3a, 0x00, 0x13,
|
||||
0x04, 0x04, 0x02, 0x48, 0x69};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB", output);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatLargeOid) {
|
||||
const uint8_t der[] = {0x30, 0x16, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a,
|
||||
0x81, 0x0d, 0x06, 0x01, 0x99, 0x21, 0x01, 0x8b,
|
||||
0x3a, 0x00, 0x13, 0x04, 0x74, 0x65, 0x73, 0x74};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("2.61.6.1.3233.1.1466.0=#74657374", output);
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatInvalidOid) {
|
||||
// Same DER as RFC2253FormatLargeOid but with the last byte of the OID
|
||||
// replaced with 0x80, which ends the OID with a truncated multi-byte
|
||||
// component.
|
||||
const uint8_t der[] = {0x30, 0x16, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a,
|
||||
0x81, 0x0d, 0x06, 0x01, 0x99, 0x21, 0x01, 0x8b,
|
||||
0x3a, 0x80, 0x13, 0x04, 0x74, 0x65, 0x73, 0x74};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
EXPECT_FALSE(ConvertToRFC2253(rdn, &output));
|
||||
}
|
||||
|
||||
TEST(ParseNameTest, RFC2253FormatUTF8) {
|
||||
const uint8_t der[] = {0x30, 0x12, 0x31, 0x10, 0x30, 0x0e, 0x06,
|
||||
0x03, 0x55, 0x04, 0x04, 0x13, 0x07, 0x4c,
|
||||
0x75, 0xc4, 0x8d, 0x69, 0xc4, 0x87};
|
||||
der::Input rdn_input(der);
|
||||
RDNSequence rdn;
|
||||
ASSERT_TRUE(ParseName(rdn_input, &rdn));
|
||||
std::string output;
|
||||
ASSERT_TRUE(ConvertToRFC2253(rdn, &output));
|
||||
ASSERT_EQ("SN=Lu\\C4\\8Di\\C4\\87", output);
|
||||
}
|
||||
|
||||
} // namespace net
|
446
pki/parse_values.cc
Normal file
446
pki/parse_values.cc
Normal file
@ -0,0 +1,446 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parse_values.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "fillins/check.h"
|
||||
|
||||
#include "fillins/string_util.h"
|
||||
|
||||
#include "fillins/utf_string_conversions.h"
|
||||
#include "fillins/inet.h"
|
||||
#include "fillins/utf_string_conversions.h"
|
||||
#include <openssl/bytestring.h>
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ParseBoolInternal(const Input& in, bool* out, bool relaxed) {
|
||||
// According to ITU-T X.690 section 8.2, a bool is encoded as a single octet
|
||||
// where the octet of all zeroes is FALSE and a non-zero value for the octet
|
||||
// is TRUE.
|
||||
if (in.Length() != 1)
|
||||
return false;
|
||||
ByteReader data(in);
|
||||
uint8_t byte;
|
||||
if (!data.ReadByte(&byte))
|
||||
return false;
|
||||
if (byte == 0) {
|
||||
*out = false;
|
||||
return true;
|
||||
}
|
||||
// ITU-T X.690 section 11.1 specifies that for DER, the TRUE value must be
|
||||
// encoded as an octet of all ones.
|
||||
if (byte == 0xff || relaxed) {
|
||||
*out = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reads a positive decimal number with |digits| digits and stores it in
|
||||
// |*out|. This function does not check that the type of |*out| is large
|
||||
// enough to hold 10^digits - 1; the caller must choose an appropriate type
|
||||
// based on the number of digits they wish to parse.
|
||||
template <typename UINT>
|
||||
bool DecimalStringToUint(ByteReader& in, size_t digits, UINT* out) {
|
||||
UINT value = 0;
|
||||
for (size_t i = 0; i < digits; ++i) {
|
||||
uint8_t digit;
|
||||
if (!in.ReadByte(&digit)) {
|
||||
return false;
|
||||
}
|
||||
if (digit < '0' || digit > '9') {
|
||||
return false;
|
||||
}
|
||||
value = (value * 10) + (digit - '0');
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks that the values in a GeneralizedTime struct are valid. This involves
|
||||
// checking that the year is 4 digits, the month is between 1 and 12, the day
|
||||
// is a day that exists in that month (following current leap year rules),
|
||||
// hours are between 0 and 23, minutes between 0 and 59, and seconds between
|
||||
// 0 and 60 (to allow for leap seconds; no validation is done that a leap
|
||||
// second is on a day that could be a leap second).
|
||||
bool ValidateGeneralizedTime(const GeneralizedTime& time) {
|
||||
if (time.month < 1 || time.month > 12)
|
||||
return false;
|
||||
if (time.day < 1)
|
||||
return false;
|
||||
if (time.hours > 23) {
|
||||
return false;
|
||||
}
|
||||
if (time.minutes > 59) {
|
||||
return false;
|
||||
}
|
||||
// Leap seconds are allowed.
|
||||
if (time.seconds > 60) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate upper bound for day of month
|
||||
switch (time.month) {
|
||||
case 4:
|
||||
case 6:
|
||||
case 9:
|
||||
case 11:
|
||||
if (time.day > 30)
|
||||
return false;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
case 7:
|
||||
case 8:
|
||||
case 10:
|
||||
case 12:
|
||||
if (time.day > 31)
|
||||
return false;
|
||||
break;
|
||||
case 2:
|
||||
if (time.year % 4 == 0 &&
|
||||
(time.year % 100 != 0 || time.year % 400 == 0)) {
|
||||
if (time.day > 29)
|
||||
return false;
|
||||
} else {
|
||||
if (time.day > 28)
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
abort(); //NOTREACHED_NORETURN;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the number of bytes of numeric precision in a DER encoded INTEGER
|
||||
// value. |in| must be a valid DER encoding of an INTEGER for this to work.
|
||||
//
|
||||
// Normally the precision of the number is exactly in.Length(). However when
|
||||
// encoding positive numbers using DER it is possible to have a leading zero
|
||||
// (to prevent number from being interpreted as negative).
|
||||
//
|
||||
// For instance a 160-bit positive number might take 21 bytes to encode. This
|
||||
// function will return 20 in such a case.
|
||||
size_t GetUnsignedIntegerLength(const Input& in) {
|
||||
der::ByteReader reader(in);
|
||||
uint8_t first_byte;
|
||||
if (!reader.ReadByte(&first_byte))
|
||||
return 0; // Not valid DER as |in| was empty.
|
||||
|
||||
if (first_byte == 0 && in.Length() > 1)
|
||||
return in.Length() - 1;
|
||||
return in.Length();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ParseBool(const Input& in, bool* out) {
|
||||
return ParseBoolInternal(in, out, false /* relaxed */);
|
||||
}
|
||||
|
||||
// BER interprets any non-zero value as true, while DER requires a bool to
|
||||
// have either all bits zero (false) or all bits one (true). To support
|
||||
// malformed certs, we recognized the BER encoding instead of failing to
|
||||
// parse.
|
||||
bool ParseBoolRelaxed(const Input& in, bool* out) {
|
||||
return ParseBoolInternal(in, out, true /* relaxed */);
|
||||
}
|
||||
|
||||
// ITU-T X.690 section 8.3.2 specifies that an integer value must be encoded
|
||||
// in the smallest number of octets. If the encoding consists of more than
|
||||
// one octet, then the bits of the first octet and the most significant bit
|
||||
// of the second octet must not be all zeroes or all ones.
|
||||
bool IsValidInteger(const Input& in, bool* negative) {
|
||||
CBS cbs;
|
||||
CBS_init(&cbs, in.UnsafeData(), in.Length());
|
||||
int negative_int;
|
||||
if (!CBS_is_valid_asn1_integer(&cbs, &negative_int)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*negative = !!negative_int;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUint64(const Input& in, uint64_t* out) {
|
||||
// Reject non-minimally encoded numbers and negative numbers.
|
||||
bool negative;
|
||||
if (!IsValidInteger(in, &negative) || negative)
|
||||
return false;
|
||||
|
||||
// Reject (non-negative) integers whose value would overflow the output type.
|
||||
if (GetUnsignedIntegerLength(in) > sizeof(*out))
|
||||
return false;
|
||||
|
||||
ByteReader reader(in);
|
||||
uint8_t data;
|
||||
uint64_t value = 0;
|
||||
|
||||
while (reader.ReadByte(&data)) {
|
||||
value <<= 8;
|
||||
value |= data;
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUint8(const Input& in, uint8_t* out) {
|
||||
// TODO(eroman): Implement this more directly.
|
||||
uint64_t value;
|
||||
if (!ParseUint64(in, &value))
|
||||
return false;
|
||||
|
||||
if (value > 0xFF)
|
||||
return false;
|
||||
|
||||
*out = static_cast<uint8_t>(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
BitString::BitString(const Input& bytes, uint8_t unused_bits)
|
||||
: bytes_(bytes), unused_bits_(unused_bits) {
|
||||
DCHECK_LT(unused_bits, 8);
|
||||
DCHECK(unused_bits == 0 || bytes.Length() != 0);
|
||||
// The unused bits must be zero.
|
||||
DCHECK(bytes.Length() == 0 ||
|
||||
(bytes.UnsafeData()[bytes.Length() - 1] & ((1u << unused_bits) - 1)) ==
|
||||
0);
|
||||
}
|
||||
|
||||
bool BitString::AssertsBit(size_t bit_index) const {
|
||||
// Index of the byte that contains the bit.
|
||||
size_t byte_index = bit_index / 8;
|
||||
|
||||
// If the bit is outside of the bitstring, by definition it is not
|
||||
// asserted.
|
||||
if (byte_index >= bytes_.Length())
|
||||
return false;
|
||||
|
||||
// Within a byte, bits are ordered from most significant to least significant.
|
||||
// Convert |bit_index| to an index within the |byte_index| byte, measured from
|
||||
// its least significant bit.
|
||||
uint8_t bit_index_in_byte = 7 - (bit_index - byte_index * 8);
|
||||
|
||||
// BIT STRING parsing already guarantees that unused bits in a byte are zero
|
||||
// (otherwise it wouldn't be valid DER). Therefore it isn't necessary to check
|
||||
// |unused_bits_|
|
||||
uint8_t byte = bytes_.UnsafeData()[byte_index];
|
||||
return 0 != (byte & (1 << bit_index_in_byte));
|
||||
}
|
||||
|
||||
std::optional<BitString> ParseBitString(const Input& in) {
|
||||
ByteReader reader(in);
|
||||
|
||||
// From ITU-T X.690, section 8.6.2.2 (applies to BER, CER, DER):
|
||||
//
|
||||
// The initial octet shall encode, as an unsigned binary integer with
|
||||
// bit 1 as the least significant bit, the number of unused bits in the final
|
||||
// subsequent octet. The number shall be in the range zero to seven.
|
||||
uint8_t unused_bits;
|
||||
if (!reader.ReadByte(&unused_bits))
|
||||
return std::nullopt;
|
||||
if (unused_bits > 7)
|
||||
return std::nullopt;
|
||||
|
||||
Input bytes;
|
||||
if (!reader.ReadBytes(reader.BytesLeft(), &bytes))
|
||||
return std::nullopt; // Not reachable.
|
||||
|
||||
// Ensure that unused bits in the last byte are set to 0.
|
||||
if (unused_bits > 0) {
|
||||
// From ITU-T X.690, section 8.6.2.3 (applies to BER, CER, DER):
|
||||
//
|
||||
// If the bitstring is empty, there shall be no subsequent octets,
|
||||
// and the initial octet shall be zero.
|
||||
if (bytes.Length() == 0)
|
||||
return std::nullopt;
|
||||
uint8_t last_byte = bytes.UnsafeData()[bytes.Length() - 1];
|
||||
|
||||
// From ITU-T X.690, section 11.2.1 (applies to CER and DER, but not BER):
|
||||
//
|
||||
// Each unused bit in the final octet of the encoding of a bit string value
|
||||
// shall be set to zero.
|
||||
uint8_t mask = 0xFF >> (8 - unused_bits);
|
||||
if ((mask & last_byte) != 0)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return BitString(bytes, unused_bits);
|
||||
}
|
||||
|
||||
bool GeneralizedTime::InUTCTimeRange() const {
|
||||
return 1950 <= year && year < 2050;
|
||||
}
|
||||
|
||||
bool operator<(const GeneralizedTime& lhs, const GeneralizedTime& rhs) {
|
||||
return std::tie(lhs.year, lhs.month, lhs.day, lhs.hours, lhs.minutes,
|
||||
lhs.seconds) < std::tie(rhs.year, rhs.month, rhs.day,
|
||||
rhs.hours, rhs.minutes, rhs.seconds);
|
||||
}
|
||||
|
||||
bool operator>(const GeneralizedTime& lhs, const GeneralizedTime& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
bool operator<=(const GeneralizedTime& lhs, const GeneralizedTime& rhs) {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
bool operator>=(const GeneralizedTime& lhs, const GeneralizedTime& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
bool ParseUTCTime(const Input& in, GeneralizedTime* value) {
|
||||
ByteReader reader(in);
|
||||
GeneralizedTime time;
|
||||
if (!DecimalStringToUint(reader, 2, &time.year) ||
|
||||
!DecimalStringToUint(reader, 2, &time.month) ||
|
||||
!DecimalStringToUint(reader, 2, &time.day) ||
|
||||
!DecimalStringToUint(reader, 2, &time.hours) ||
|
||||
!DecimalStringToUint(reader, 2, &time.minutes) ||
|
||||
!DecimalStringToUint(reader, 2, &time.seconds)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t zulu;
|
||||
if (!reader.ReadByte(&zulu) || zulu != 'Z' || reader.HasMore())
|
||||
return false;
|
||||
if (time.year < 50) {
|
||||
time.year += 2000;
|
||||
} else {
|
||||
time.year += 1900;
|
||||
}
|
||||
if (!ValidateGeneralizedTime(time))
|
||||
return false;
|
||||
*value = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseGeneralizedTime(const Input& in, GeneralizedTime* value) {
|
||||
ByteReader reader(in);
|
||||
GeneralizedTime time;
|
||||
if (!DecimalStringToUint(reader, 4, &time.year) ||
|
||||
!DecimalStringToUint(reader, 2, &time.month) ||
|
||||
!DecimalStringToUint(reader, 2, &time.day) ||
|
||||
!DecimalStringToUint(reader, 2, &time.hours) ||
|
||||
!DecimalStringToUint(reader, 2, &time.minutes) ||
|
||||
!DecimalStringToUint(reader, 2, &time.seconds)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t zulu;
|
||||
if (!reader.ReadByte(&zulu) || zulu != 'Z' || reader.HasMore())
|
||||
return false;
|
||||
if (!ValidateGeneralizedTime(time))
|
||||
return false;
|
||||
*value = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseIA5String(Input in, std::string* out) {
|
||||
for (char c : in.AsStringView()) {
|
||||
if (static_cast<uint8_t>(c) > 127)
|
||||
return false;
|
||||
}
|
||||
*out = in.AsString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseVisibleString(Input in, std::string* out) {
|
||||
// ITU-T X.680:
|
||||
// VisibleString : "Defining registration number 6" + SPACE
|
||||
// 6 includes all the characters from '!' .. '~' (33 .. 126), space is 32.
|
||||
// Also ITU-T X.691 says it much more clearly:
|
||||
// "for VisibleString [the range] is 32 to 126 ... For VisibleString .. all
|
||||
// the values in the range are present."
|
||||
for (char c : in.AsStringView()) {
|
||||
if (static_cast<uint8_t>(c) < 32 || static_cast<uint8_t>(c) > 126)
|
||||
return false;
|
||||
}
|
||||
*out = in.AsString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsePrintableString(Input in, std::string* out) {
|
||||
for (char c : in.AsStringView()) {
|
||||
if (!(fillins::IsAsciiAlpha(c) || c == ' ' || (c >= '\'' && c <= ':') ||
|
||||
c == '=' || c == '?')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*out = in.AsString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseTeletexStringAsLatin1(Input in, std::string* out) {
|
||||
out->clear();
|
||||
// Convert from Latin-1 to UTF-8.
|
||||
size_t utf8_length = in.Length();
|
||||
for (size_t i = 0; i < in.Length(); i++) {
|
||||
if (in.UnsafeData()[i] > 0x7f)
|
||||
utf8_length++;
|
||||
}
|
||||
out->reserve(utf8_length);
|
||||
for (size_t i = 0; i < in.Length(); i++) {
|
||||
uint8_t u = in.UnsafeData()[i];
|
||||
if (u <= 0x7f) {
|
||||
out->push_back(u);
|
||||
} else {
|
||||
out->push_back(0xc0 | (u >> 6));
|
||||
out->push_back(0x80 | (u & 0x3f));
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(utf8_length, out->size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUniversalString(Input in, std::string* out) {
|
||||
if (in.Length() % 4 != 0)
|
||||
return false;
|
||||
|
||||
out->clear();
|
||||
std::vector<uint32_t> in_32bit(in.Length() / 4);
|
||||
if (in.Length())
|
||||
memcpy(in_32bit.data(), in.UnsafeData(), in.Length());
|
||||
for (const uint32_t c : in_32bit) {
|
||||
// UniversalString is UCS-4 in big-endian order.
|
||||
auto codepoint = static_cast<uint32_t>(ntohl(c));
|
||||
if (!CBU_IS_UNICODE_CHAR(codepoint))
|
||||
return false;
|
||||
|
||||
fillins::WriteUnicodeCharacter(codepoint, out);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseBmpString(Input in, std::string* out) {
|
||||
if (in.Length() % 2 != 0)
|
||||
return false;
|
||||
|
||||
out->clear();
|
||||
std::vector<uint16_t> in_16bit(in.Length() / 2);
|
||||
if (in.Length())
|
||||
memcpy(in_16bit.data(), in.UnsafeData(), in.Length());
|
||||
for (const uint16_t c : in_16bit) {
|
||||
// BMPString is UCS-2 in big-endian order.
|
||||
uint32_t codepoint = ntohs(c);
|
||||
|
||||
// BMPString only supports codepoints in the Basic Multilingual Plane;
|
||||
// surrogates are not allowed. CBU_IS_UNICODE_CHAR excludes the surrogate
|
||||
// code points, among other invalid values.
|
||||
if (!CBU_IS_UNICODE_CHAR(codepoint))
|
||||
return false;
|
||||
|
||||
fillins::WriteUnicodeCharacter(codepoint, out);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace bssl::der
|
152
pki/parse_values.h
Normal file
152
pki/parse_values.h
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_DER_PARSE_VALUES_H_
|
||||
#define BSSL_DER_PARSE_VALUES_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#include "input.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
// Reads a DER-encoded ASN.1 BOOLEAN value from |in| and puts the resulting
|
||||
// value in |out|. Returns whether the encoded value could successfully be
|
||||
// read.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseBool(const Input& in, bool* out);
|
||||
|
||||
// Like ParseBool, except it is more relaxed in what inputs it accepts: Any
|
||||
// value that is a valid BER encoding will be parsed successfully.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseBoolRelaxed(const Input& in, bool* out);
|
||||
|
||||
// Checks the validity of a DER-encoded ASN.1 INTEGER value from |in|, and
|
||||
// determines the sign of the number. Returns true on success and
|
||||
// fills |negative|. Otherwise returns false and does not modify the out
|
||||
// parameter.
|
||||
//
|
||||
// in: The value portion of an INTEGER.
|
||||
// negative: Out parameter that is set to true if the number is negative
|
||||
// and false otherwise (zero is non-negative).
|
||||
[[nodiscard]] OPENSSL_EXPORT bool IsValidInteger(const Input& in, bool* negative);
|
||||
|
||||
// Reads a DER-encoded ASN.1 INTEGER value from |in| and puts the resulting
|
||||
// value in |out|. ASN.1 INTEGERs are arbitrary precision; this function is
|
||||
// provided as a convenience when the caller knows that the value is unsigned
|
||||
// and is between 0 and 2^64-1. This function returns false if the value is too
|
||||
// big to fit in a uint64_t, is negative, or if there is an error reading the
|
||||
// integer.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseUint64(const Input& in, uint64_t* out);
|
||||
|
||||
// Same as ParseUint64() but for a uint8_t.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseUint8(const Input& in, uint8_t* out);
|
||||
|
||||
// The BitString class is a helper for representing a valid parsed BIT STRING.
|
||||
//
|
||||
// * The bits are ordered within each octet of bytes() from most to least
|
||||
// significant, as in the DER encoding.
|
||||
//
|
||||
// * There may be at most 7 unused bits.
|
||||
class OPENSSL_EXPORT BitString {
|
||||
public:
|
||||
BitString() = default;
|
||||
|
||||
// |unused_bits| represents the number of bits in the last octet of |bytes|,
|
||||
// starting from the least significant bit, that are unused. It MUST be < 8.
|
||||
// And if bytes is empty, then it MUST be 0.
|
||||
BitString(const Input& bytes, uint8_t unused_bits);
|
||||
|
||||
const Input& bytes() const { return bytes_; }
|
||||
uint8_t unused_bits() const { return unused_bits_; }
|
||||
|
||||
// Returns true if the bit string contains 1 at the specified position.
|
||||
// Otherwise returns false.
|
||||
//
|
||||
// A return value of false can mean either:
|
||||
// * The bit value at |bit_index| is 0.
|
||||
// * There is no bit at |bit_index| (index is beyond the end).
|
||||
[[nodiscard]] bool AssertsBit(size_t bit_index) const;
|
||||
|
||||
private:
|
||||
Input bytes_;
|
||||
uint8_t unused_bits_ = 0;
|
||||
|
||||
// Default assignment and copy constructor are OK.
|
||||
};
|
||||
|
||||
// Reads a DER-encoded ASN.1 BIT STRING value from |in| and returns the
|
||||
// resulting octet string and number of unused bits.
|
||||
//
|
||||
// On failure, returns std::nullopt.
|
||||
[[nodiscard]] OPENSSL_EXPORT std::optional<BitString> ParseBitString(
|
||||
const Input& in);
|
||||
|
||||
struct OPENSSL_EXPORT GeneralizedTime {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hours;
|
||||
uint8_t minutes;
|
||||
uint8_t seconds;
|
||||
|
||||
// Returns true if the value is in UTCTime's range.
|
||||
bool InUTCTimeRange() const;
|
||||
};
|
||||
|
||||
OPENSSL_EXPORT bool operator<(const GeneralizedTime& lhs,
|
||||
const GeneralizedTime& rhs);
|
||||
OPENSSL_EXPORT bool operator<=(const GeneralizedTime& lhs,
|
||||
const GeneralizedTime& rhs);
|
||||
OPENSSL_EXPORT bool operator>(const GeneralizedTime& lhs,
|
||||
const GeneralizedTime& rhs);
|
||||
OPENSSL_EXPORT bool operator>=(const GeneralizedTime& lhs,
|
||||
const GeneralizedTime& rhs);
|
||||
|
||||
// Reads a DER-encoded ASN.1 UTCTime value from |in| and puts the resulting
|
||||
// value in |out|, returning true if the UTCTime could be parsed successfully.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseUTCTime(const Input& in,
|
||||
GeneralizedTime* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 GeneralizedTime value from |in| and puts the
|
||||
// resulting value in |out|, returning true if the GeneralizedTime could
|
||||
// be parsed successfully. This function is even more restrictive than the
|
||||
// DER rules - it follows the rules from RFC5280, which does not allow for
|
||||
// fractional seconds.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseGeneralizedTime(const Input& in,
|
||||
GeneralizedTime* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 IA5String value from |in| and stores the result in
|
||||
// |out| as ASCII, returning true if successful.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseIA5String(Input in, std::string* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 VisibleString value from |in| and stores the result
|
||||
// in |out| as ASCII, returning true if successful.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseVisibleString(Input in, std::string* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 PrintableString value from |in| and stores the
|
||||
// result in |out| as ASCII, returning true if successful.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParsePrintableString(Input in, std::string* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 TeletexString value from |in|, treating it as
|
||||
// Latin-1, and stores the result in |out| as UTF-8, returning true if
|
||||
// successful.
|
||||
//
|
||||
// This is for compatibility with legacy implementations that would use Latin-1
|
||||
// encoding but tag it as TeletexString.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseTeletexStringAsLatin1(Input in,
|
||||
std::string* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 UniversalString value from |in| and stores the
|
||||
// result in |out| as UTF-8, returning true if successful.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseUniversalString(Input in, std::string* out);
|
||||
|
||||
// Reads a DER-encoded ASN.1 BMPString value from |in| and stores the
|
||||
// result in |out| as UTF-8, returning true if successful.
|
||||
[[nodiscard]] OPENSSL_EXPORT bool ParseBmpString(Input in, std::string* out);
|
||||
|
||||
} // namespace bssl::der
|
||||
|
||||
#endif // BSSL_DER_PARSE_VALUES_H_
|
465
pki/parse_values_unittest.cc
Normal file
465
pki/parse_values_unittest.cc
Normal file
@ -0,0 +1,465 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parse_values.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl::der::test {
|
||||
|
||||
namespace {
|
||||
|
||||
template <size_t N>
|
||||
Input FromStringLiteral(const char(&data)[N]) {
|
||||
// Strings are null-terminated. The null terminating byte shouldn't be
|
||||
// included in the Input, so the size is N - 1 instead of N.
|
||||
return Input(reinterpret_cast<const uint8_t*>(data), N - 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ParseValuesTest, ParseBool) {
|
||||
uint8_t buf[] = {0xFF, 0x00};
|
||||
Input value(buf, 1);
|
||||
bool out;
|
||||
EXPECT_TRUE(ParseBool(value, &out));
|
||||
EXPECT_TRUE(out);
|
||||
|
||||
buf[0] = 0;
|
||||
EXPECT_TRUE(ParseBool(value, &out));
|
||||
EXPECT_FALSE(out);
|
||||
|
||||
buf[0] = 1;
|
||||
EXPECT_FALSE(ParseBool(value, &out));
|
||||
EXPECT_TRUE(ParseBoolRelaxed(value, &out));
|
||||
EXPECT_TRUE(out);
|
||||
|
||||
buf[0] = 0xFF;
|
||||
value = Input(buf, 2);
|
||||
EXPECT_FALSE(ParseBool(value, &out));
|
||||
value = Input(buf, 0);
|
||||
EXPECT_FALSE(ParseBool(value, &out));
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParseTimes) {
|
||||
GeneralizedTime out;
|
||||
|
||||
EXPECT_TRUE(ParseUTCTime(FromStringLiteral("140218161200Z"), &out));
|
||||
|
||||
// DER-encoded UTCTime must end with 'Z'.
|
||||
EXPECT_FALSE(ParseUTCTime(FromStringLiteral("140218161200X"), &out));
|
||||
|
||||
// Check that a negative number (-4 in this case) doesn't get parsed as
|
||||
// a 2-digit number.
|
||||
EXPECT_FALSE(ParseUTCTime(FromStringLiteral("-40218161200Z"), &out));
|
||||
|
||||
// Check that numbers with a leading 0 don't get parsed in octal by making
|
||||
// the second digit an invalid octal digit (e.g. 09).
|
||||
EXPECT_TRUE(ParseUTCTime(FromStringLiteral("090218161200Z"), &out));
|
||||
|
||||
// Check that the length is validated.
|
||||
EXPECT_FALSE(ParseUTCTime(FromStringLiteral("140218161200"), &out));
|
||||
EXPECT_FALSE(ParseUTCTime(FromStringLiteral("140218161200Z0"), &out));
|
||||
|
||||
// Check strictness of UTCTime parsers.
|
||||
EXPECT_FALSE(ParseUTCTime(FromStringLiteral("1402181612Z"), &out));
|
||||
|
||||
// Check format of GeneralizedTime.
|
||||
|
||||
// Years 0 and 9999 are allowed.
|
||||
EXPECT_TRUE(ParseGeneralizedTime(FromStringLiteral("00000101000000Z"), &out));
|
||||
EXPECT_EQ(0, out.year);
|
||||
EXPECT_TRUE(ParseGeneralizedTime(FromStringLiteral("99991231235960Z"), &out));
|
||||
EXPECT_EQ(9999, out.year);
|
||||
|
||||
// Leap seconds are allowed.
|
||||
EXPECT_TRUE(ParseGeneralizedTime(FromStringLiteral("20140218161260Z"), &out));
|
||||
|
||||
// But nothing larger than a leap second.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140218161261Z"), &out));
|
||||
|
||||
// Minutes only go up to 59.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140218166000Z"), &out));
|
||||
|
||||
// Hours only go up to 23.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140218240000Z"), &out));
|
||||
// The 0th day of a month is invalid.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140200161200Z"), &out));
|
||||
// The 0th month is invalid.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140018161200Z"), &out));
|
||||
// Months greater than 12 are invalid.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20141318161200Z"), &out));
|
||||
|
||||
// Some months have 31 days.
|
||||
EXPECT_TRUE(ParseGeneralizedTime(FromStringLiteral("20140131000000Z"), &out));
|
||||
|
||||
// September has only 30 days.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140931000000Z"), &out));
|
||||
|
||||
// February has only 28 days...
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140229000000Z"), &out));
|
||||
|
||||
// ... unless it's a leap year.
|
||||
EXPECT_TRUE(ParseGeneralizedTime(FromStringLiteral("20160229000000Z"), &out));
|
||||
|
||||
// There aren't any leap days in years divisible by 100...
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("21000229000000Z"), &out));
|
||||
|
||||
// ...unless it's also divisible by 400.
|
||||
EXPECT_TRUE(ParseGeneralizedTime(FromStringLiteral("20000229000000Z"), &out));
|
||||
|
||||
// Check more perverse invalid inputs.
|
||||
|
||||
// Check that trailing null bytes are not ignored.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20001231010203Z\0"), &out));
|
||||
|
||||
// Check what happens when a null byte is in the middle of the input.
|
||||
EXPECT_FALSE(ParseGeneralizedTime(FromStringLiteral(
|
||||
"200\0"
|
||||
"1231010203Z"),
|
||||
&out));
|
||||
|
||||
// The year can't be in hex.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("0x201231000000Z"), &out));
|
||||
|
||||
// The last byte must be 'Z'.
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20001231000000X"), &out));
|
||||
|
||||
// Check that the length is validated.
|
||||
EXPECT_FALSE(ParseGeneralizedTime(FromStringLiteral("20140218161200"), &out));
|
||||
EXPECT_FALSE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140218161200Z0"), &out));
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, TimesCompare) {
|
||||
GeneralizedTime time1;
|
||||
GeneralizedTime time2;
|
||||
GeneralizedTime time3;
|
||||
|
||||
ASSERT_TRUE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140218161200Z"), &time1));
|
||||
// Test that ParseUTCTime correctly normalizes the year.
|
||||
ASSERT_TRUE(ParseUTCTime(FromStringLiteral("150218161200Z"), &time2));
|
||||
ASSERT_TRUE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20160218161200Z"), &time3));
|
||||
EXPECT_TRUE(time1 < time2);
|
||||
EXPECT_TRUE(time2 < time3);
|
||||
|
||||
EXPECT_TRUE(time2 > time1);
|
||||
EXPECT_TRUE(time2 >= time1);
|
||||
EXPECT_TRUE(time2 <= time3);
|
||||
EXPECT_TRUE(time1 <= time1);
|
||||
EXPECT_TRUE(time1 >= time1);
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, UTCTimeRange) {
|
||||
GeneralizedTime time;
|
||||
ASSERT_TRUE(
|
||||
ParseGeneralizedTime(FromStringLiteral("20140218161200Z"), &time));
|
||||
EXPECT_TRUE(time.InUTCTimeRange());
|
||||
|
||||
time.year = 1950;
|
||||
EXPECT_TRUE(time.InUTCTimeRange());
|
||||
|
||||
time.year = 1949;
|
||||
EXPECT_FALSE(time.InUTCTimeRange());
|
||||
|
||||
time.year = 2049;
|
||||
EXPECT_TRUE(time.InUTCTimeRange());
|
||||
|
||||
time.year = 2050;
|
||||
EXPECT_FALSE(time.InUTCTimeRange());
|
||||
}
|
||||
|
||||
struct Uint64TestData {
|
||||
bool should_pass;
|
||||
const uint8_t input[9];
|
||||
size_t length;
|
||||
uint64_t expected_value;
|
||||
};
|
||||
|
||||
const Uint64TestData kUint64TestData[] = {
|
||||
{true, {0x00}, 1, 0},
|
||||
// This number fails because it is not a minimal representation.
|
||||
{false, {0x00, 0x00}, 2},
|
||||
{true, {0x01}, 1, 1},
|
||||
{false, {0xFF}, 1},
|
||||
{true, {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 8, INT64_MAX},
|
||||
{true,
|
||||
{0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
|
||||
9,
|
||||
UINT64_MAX},
|
||||
// This number fails because it is negative.
|
||||
{false, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 8},
|
||||
{false, {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8},
|
||||
{false, {0x00, 0x01}, 2},
|
||||
{false, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}, 9},
|
||||
{false, {0}, 0},
|
||||
};
|
||||
|
||||
TEST(ParseValuesTest, ParseUint64) {
|
||||
for (size_t i = 0; i < std::size(kUint64TestData); i++) {
|
||||
const Uint64TestData& test_case = kUint64TestData[i];
|
||||
SCOPED_TRACE(i);
|
||||
|
||||
uint64_t result;
|
||||
EXPECT_EQ(test_case.should_pass,
|
||||
ParseUint64(Input(test_case.input, test_case.length), &result));
|
||||
if (test_case.should_pass) {
|
||||
EXPECT_EQ(test_case.expected_value, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Uint8TestData {
|
||||
bool should_pass;
|
||||
const uint8_t input[9];
|
||||
size_t length;
|
||||
uint8_t expected_value;
|
||||
};
|
||||
|
||||
const Uint8TestData kUint8TestData[] = {
|
||||
{true, {0x00}, 1, 0},
|
||||
// This number fails because it is not a minimal representation.
|
||||
{false, {0x00, 0x00}, 2},
|
||||
{true, {0x01}, 1, 1},
|
||||
{false, {0x01, 0xFF}, 2},
|
||||
{false, {0x03, 0x83}, 2},
|
||||
{true, {0x7F}, 1, 0x7F},
|
||||
{true, {0x00, 0xFF}, 2, 0xFF},
|
||||
// This number fails because it is negative.
|
||||
{false, {0xFF}, 1},
|
||||
{false, {0x80}, 1},
|
||||
{false, {0x00, 0x01}, 2},
|
||||
{false, {0}, 0},
|
||||
};
|
||||
|
||||
TEST(ParseValuesTest, ParseUint8) {
|
||||
for (size_t i = 0; i < std::size(kUint8TestData); i++) {
|
||||
const Uint8TestData& test_case = kUint8TestData[i];
|
||||
SCOPED_TRACE(i);
|
||||
|
||||
uint8_t result;
|
||||
EXPECT_EQ(test_case.should_pass,
|
||||
ParseUint8(Input(test_case.input, test_case.length), &result));
|
||||
if (test_case.should_pass) {
|
||||
EXPECT_EQ(test_case.expected_value, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IsValidIntegerTestData {
|
||||
bool should_pass;
|
||||
const uint8_t input[2];
|
||||
size_t length;
|
||||
bool negative;
|
||||
};
|
||||
|
||||
const IsValidIntegerTestData kIsValidIntegerTestData[] = {
|
||||
// Empty input (invalid DER).
|
||||
{false, {0x00}, 0},
|
||||
|
||||
// The correct encoding for zero.
|
||||
{true, {0x00}, 1, false},
|
||||
|
||||
// Invalid representation of zero (not minimal)
|
||||
{false, {0x00, 0x00}, 2},
|
||||
|
||||
// Valid single byte negative numbers.
|
||||
{true, {0x80}, 1, true},
|
||||
{true, {0xFF}, 1, true},
|
||||
|
||||
// Non-minimal negative number.
|
||||
{false, {0xFF, 0x80}, 2},
|
||||
|
||||
// Positive number with a legitimate leading zero.
|
||||
{true, {0x00, 0x80}, 2, false},
|
||||
|
||||
// A legitimate negative number that starts with FF (MSB of second byte is
|
||||
// 0 so OK).
|
||||
{true, {0xFF, 0x7F}, 2, true},
|
||||
};
|
||||
|
||||
TEST(ParseValuesTest, IsValidInteger) {
|
||||
for (size_t i = 0; i < std::size(kIsValidIntegerTestData); i++) {
|
||||
const auto& test_case = kIsValidIntegerTestData[i];
|
||||
SCOPED_TRACE(i);
|
||||
|
||||
bool negative;
|
||||
EXPECT_EQ(
|
||||
test_case.should_pass,
|
||||
IsValidInteger(Input(test_case.input, test_case.length), &negative));
|
||||
if (test_case.should_pass) {
|
||||
EXPECT_EQ(test_case.negative, negative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests parsing an empty BIT STRING.
|
||||
TEST(ParseValuesTest, ParseBitStringEmptyNoUnusedBits) {
|
||||
const uint8_t kData[] = {0x00};
|
||||
|
||||
std::optional<BitString> bit_string = ParseBitString(Input(kData));
|
||||
ASSERT_TRUE(bit_string.has_value());
|
||||
|
||||
EXPECT_EQ(0u, bit_string->unused_bits());
|
||||
EXPECT_EQ(0u, bit_string->bytes().Length());
|
||||
|
||||
EXPECT_FALSE(bit_string->AssertsBit(0));
|
||||
EXPECT_FALSE(bit_string->AssertsBit(1));
|
||||
EXPECT_FALSE(bit_string->AssertsBit(3));
|
||||
}
|
||||
|
||||
// Tests parsing an empty BIT STRING that incorrectly claims one unused bit.
|
||||
TEST(ParseValuesTest, ParseBitStringEmptyOneUnusedBit) {
|
||||
const uint8_t kData[] = {0x01};
|
||||
|
||||
std::optional<BitString> bit_string = ParseBitString(Input(kData));
|
||||
EXPECT_FALSE(bit_string.has_value());
|
||||
}
|
||||
|
||||
// Tests parsing an empty BIT STRING that is not minmally encoded (the entire
|
||||
// last byte is comprised of unused bits).
|
||||
TEST(ParseValuesTest, ParseBitStringNonEmptyTooManyUnusedBits) {
|
||||
const uint8_t kData[] = {0x08, 0x00};
|
||||
|
||||
std::optional<BitString> bit_string = ParseBitString(Input(kData));
|
||||
EXPECT_FALSE(bit_string.has_value());
|
||||
}
|
||||
|
||||
// Tests parsing a BIT STRING of 7 bits each of which are 1.
|
||||
TEST(ParseValuesTest, ParseBitStringSevenOneBits) {
|
||||
const uint8_t kData[] = {0x01, 0xFE};
|
||||
|
||||
std::optional<BitString> bit_string = ParseBitString(Input(kData));
|
||||
ASSERT_TRUE(bit_string.has_value());
|
||||
|
||||
EXPECT_EQ(1u, bit_string->unused_bits());
|
||||
EXPECT_EQ(1u, bit_string->bytes().Length());
|
||||
EXPECT_EQ(0xFE, bit_string->bytes().UnsafeData()[0]);
|
||||
|
||||
EXPECT_TRUE(bit_string->AssertsBit(0));
|
||||
EXPECT_TRUE(bit_string->AssertsBit(1));
|
||||
EXPECT_TRUE(bit_string->AssertsBit(2));
|
||||
EXPECT_TRUE(bit_string->AssertsBit(3));
|
||||
EXPECT_TRUE(bit_string->AssertsBit(4));
|
||||
EXPECT_TRUE(bit_string->AssertsBit(5));
|
||||
EXPECT_TRUE(bit_string->AssertsBit(6));
|
||||
EXPECT_FALSE(bit_string->AssertsBit(7));
|
||||
EXPECT_FALSE(bit_string->AssertsBit(8));
|
||||
}
|
||||
|
||||
// Tests parsing a BIT STRING of 7 bits each of which are 1. The unused bit
|
||||
// however is set to 1, which is an invalid encoding.
|
||||
TEST(ParseValuesTest, ParseBitStringSevenOneBitsUnusedBitIsOne) {
|
||||
const uint8_t kData[] = {0x01, 0xFF};
|
||||
|
||||
std::optional<BitString> bit_string = ParseBitString(Input(kData));
|
||||
EXPECT_FALSE(bit_string.has_value());
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParseIA5String) {
|
||||
const uint8_t valid_der[] = {0x46, 0x6f, 0x6f, 0x20, 0x62,
|
||||
0x61, 0x72, 0x01, 0x7f};
|
||||
std::string s;
|
||||
EXPECT_TRUE(ParseIA5String(der::Input(valid_der), &s));
|
||||
EXPECT_EQ("Foo bar\x01\x7f", s);
|
||||
|
||||
// 0x80 is not a valid character in IA5String.
|
||||
const uint8_t invalid_der[] = {0x46, 0x6f, 0x80, 0x20, 0x62, 0x61, 0x72};
|
||||
EXPECT_FALSE(ParseIA5String(der::Input(invalid_der), &s));
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParseVisibleString) {
|
||||
const uint8_t valid_der[] = {0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72, 0x7e};
|
||||
std::string s;
|
||||
EXPECT_TRUE(ParseVisibleString(der::Input(valid_der), &s));
|
||||
EXPECT_EQ("Foo bar\x7e", s);
|
||||
|
||||
// 0x7f is not a valid character in VisibleString
|
||||
const uint8_t invalid_der[] = {0x46, 0x6f, 0x7f, 0x20, 0x62, 0x61, 0x72};
|
||||
EXPECT_FALSE(ParseVisibleString(der::Input(invalid_der), &s));
|
||||
|
||||
// 0x1f is not a valid character in VisibleString
|
||||
const uint8_t invalid_der2[] = {0x46, 0x6f, 0x1f, 0x20, 0x62, 0x61, 0x72};
|
||||
EXPECT_FALSE(ParseVisibleString(der::Input(invalid_der2), &s));
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParsePrintableString) {
|
||||
const uint8_t valid_der[] = {0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72};
|
||||
std::string s;
|
||||
EXPECT_TRUE(ParsePrintableString(der::Input(valid_der), &s));
|
||||
EXPECT_EQ("Foo bar", s);
|
||||
|
||||
// 0x5f '_' is not a valid character in PrintableString.
|
||||
const uint8_t invalid_der[] = {0x46, 0x6f, 0x5f, 0x20, 0x62, 0x61, 0x72};
|
||||
EXPECT_FALSE(ParsePrintableString(der::Input(invalid_der), &s));
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParseTeletexStringAsLatin1) {
|
||||
const uint8_t valid_der[] = {0x46, 0x6f, 0xd6, 0x20, 0x62, 0x61, 0x72};
|
||||
std::string s;
|
||||
EXPECT_TRUE(ParseTeletexStringAsLatin1(der::Input(valid_der), &s));
|
||||
EXPECT_EQ("FoÖ bar", s);
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParseBmpString) {
|
||||
const uint8_t valid_der[] = {0x00, 0x66, 0x00, 0x6f, 0x00, 0x6f,
|
||||
0x00, 0x62, 0x00, 0x61, 0x00, 0x72};
|
||||
std::string s;
|
||||
EXPECT_TRUE(ParseBmpString(der::Input(valid_der), &s));
|
||||
EXPECT_EQ("foobar", s);
|
||||
|
||||
const uint8_t valid_nonascii_der[] = {0x27, 0x28, 0x26, 0xa1, 0x2b, 0x50};
|
||||
EXPECT_TRUE(ParseBmpString(der::Input(valid_nonascii_der), &s));
|
||||
EXPECT_EQ("✨⚡⭐", s);
|
||||
|
||||
// BmpString must encode characters in pairs of 2 bytes.
|
||||
const uint8_t invalid_odd_der[] = {0x00, 0x66, 0x00, 0x6f, 0x00};
|
||||
EXPECT_FALSE(ParseBmpString(der::Input(invalid_odd_der), &s));
|
||||
|
||||
// UTF-16BE encoding of U+1D11E, MUSICAL SYMBOL G CLEF, which is not valid in
|
||||
// UCS-2.
|
||||
const uint8_t invalid_bmp_valid_utf16_with_surrogate[] = {0xd8, 0x34, 0xdd,
|
||||
0x1e};
|
||||
EXPECT_FALSE(
|
||||
ParseBmpString(der::Input(invalid_bmp_valid_utf16_with_surrogate), &s));
|
||||
}
|
||||
|
||||
TEST(ParseValuesTest, ParseUniversalString) {
|
||||
const uint8_t valid_der[] = {0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x6f,
|
||||
0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x62,
|
||||
0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x72};
|
||||
std::string s;
|
||||
EXPECT_TRUE(ParseUniversalString(der::Input(valid_der), &s));
|
||||
EXPECT_EQ("foobar", s);
|
||||
|
||||
const uint8_t valid_non_ascii_der[] = {0x0, 0x1, 0xf4, 0xe, 0x0, 0x0, 0x0,
|
||||
0x20, 0x0, 0x1, 0xd1, 0x1e, 0x0, 0x0,
|
||||
0x26, 0x69, 0x0, 0x0, 0x26, 0x6b};
|
||||
EXPECT_TRUE(ParseUniversalString(der::Input(valid_non_ascii_der), &s));
|
||||
EXPECT_EQ("🐎 𝄞♩♫", s);
|
||||
|
||||
// UniversalString must encode characters in groups of 4 bytes.
|
||||
const uint8_t invalid_non_4_multiple_der[] = {0x00, 0x00, 0x00,
|
||||
0x66, 0x00, 0x00};
|
||||
EXPECT_FALSE(
|
||||
ParseUniversalString(der::Input(invalid_non_4_multiple_der), &s));
|
||||
}
|
||||
|
||||
} // namespace bssl::der::test
|
295
pki/parsed_certificate.cc
Normal file
295
pki/parsed_certificate.cc
Normal file
@ -0,0 +1,295 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parsed_certificate.h"
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "certificate_policies.h"
|
||||
#include "extended_key_usage.h"
|
||||
#include "name_constraints.h"
|
||||
#include "signature_algorithm.h"
|
||||
#include "verify_name_match.h"
|
||||
#include "parser.h"
|
||||
#include <openssl/pool.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingCertificate, "Failed parsing Certificate");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingTbsCertificate,
|
||||
"Failed parsing TBSCertificate");
|
||||
DEFINE_CERT_ERROR_ID(kFailedReadingIssuerOrSubject,
|
||||
"Failed reading issuer or subject");
|
||||
DEFINE_CERT_ERROR_ID(kFailedNormalizingSubject, "Failed normalizing subject");
|
||||
DEFINE_CERT_ERROR_ID(kFailedNormalizingIssuer, "Failed normalizing issuer");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingExtensions, "Failed parsing extensions");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingBasicConstraints,
|
||||
"Failed parsing basic constraints");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingKeyUsage, "Failed parsing key usage");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingEku, "Failed parsing extended key usage");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingSubjectAltName,
|
||||
"Failed parsing subjectAltName");
|
||||
DEFINE_CERT_ERROR_ID(kSubjectAltNameNotCritical,
|
||||
"Empty subject and subjectAltName is not critical");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingNameConstraints,
|
||||
"Failed parsing name constraints");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingAia, "Failed parsing authority info access");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingPolicies,
|
||||
"Failed parsing certificate policies");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingPolicyConstraints,
|
||||
"Failed parsing policy constraints");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingPolicyMappings,
|
||||
"Failed parsing policy mappings");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingInhibitAnyPolicy,
|
||||
"Failed parsing inhibit any policy");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingAuthorityKeyIdentifier,
|
||||
"Failed parsing authority key identifier");
|
||||
DEFINE_CERT_ERROR_ID(kFailedParsingSubjectKeyIdentifier,
|
||||
"Failed parsing subject key identifier");
|
||||
|
||||
[[nodiscard]] bool GetSequenceValue(const der::Input& tlv, der::Input* value) {
|
||||
der::Parser parser(tlv);
|
||||
return parser.ReadTag(der::kSequence, value) && !parser.HasMore();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ParsedCertificate::GetExtension(const der::Input& extension_oid,
|
||||
ParsedExtension* parsed_extension) const {
|
||||
if (!tbs_.extensions_tlv)
|
||||
return false;
|
||||
|
||||
auto it = extensions_.find(extension_oid);
|
||||
if (it == extensions_.end()) {
|
||||
*parsed_extension = ParsedExtension();
|
||||
return false;
|
||||
}
|
||||
|
||||
*parsed_extension = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
ParsedCertificate::ParsedCertificate(PrivateConstructor) {}
|
||||
ParsedCertificate::~ParsedCertificate() = default;
|
||||
|
||||
// static
|
||||
std::shared_ptr<const ParsedCertificate> ParsedCertificate::Create(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER> backing_data,
|
||||
const ParseCertificateOptions& options,
|
||||
CertErrors* errors) {
|
||||
// |errors| is an optional parameter, but to keep the code simpler, use a
|
||||
// dummy object when one wasn't provided.
|
||||
CertErrors unused_errors;
|
||||
if (!errors)
|
||||
errors = &unused_errors;
|
||||
|
||||
auto result = std::make_shared<ParsedCertificate>(PrivateConstructor{});
|
||||
result->cert_data_ = std::move(backing_data);
|
||||
result->cert_ = der::Input(CRYPTO_BUFFER_data(result->cert_data_.get()),
|
||||
CRYPTO_BUFFER_len(result->cert_data_.get()));
|
||||
|
||||
if (!ParseCertificate(result->cert_, &result->tbs_certificate_tlv_,
|
||||
&result->signature_algorithm_tlv_,
|
||||
&result->signature_value_, errors)) {
|
||||
errors->AddError(kFailedParsingCertificate);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ParseTbsCertificate(result->tbs_certificate_tlv_, options, &result->tbs_,
|
||||
errors)) {
|
||||
errors->AddError(kFailedParsingTbsCertificate);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Attempt to parse the signature algorithm contained in the Certificate.
|
||||
result->signature_algorithm_ =
|
||||
ParseSignatureAlgorithm(result->signature_algorithm_tlv_);
|
||||
|
||||
der::Input subject_value;
|
||||
if (!GetSequenceValue(result->tbs_.subject_tlv, &subject_value)) {
|
||||
errors->AddError(kFailedReadingIssuerOrSubject);
|
||||
return nullptr;
|
||||
}
|
||||
if (!NormalizeName(subject_value, &result->normalized_subject_, errors)) {
|
||||
errors->AddError(kFailedNormalizingSubject);
|
||||
return nullptr;
|
||||
}
|
||||
der::Input issuer_value;
|
||||
if (!GetSequenceValue(result->tbs_.issuer_tlv, &issuer_value)) {
|
||||
errors->AddError(kFailedReadingIssuerOrSubject);
|
||||
return nullptr;
|
||||
}
|
||||
if (!NormalizeName(issuer_value, &result->normalized_issuer_, errors)) {
|
||||
errors->AddError(kFailedNormalizingIssuer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Parse the standard X.509 extensions.
|
||||
if (result->tbs_.extensions_tlv) {
|
||||
// ParseExtensions() ensures there are no duplicates, and maps the (unique)
|
||||
// OID to the extension value.
|
||||
if (!ParseExtensions(result->tbs_.extensions_tlv.value(),
|
||||
&result->extensions_)) {
|
||||
errors->AddError(kFailedParsingExtensions);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ParsedExtension extension;
|
||||
|
||||
// Basic constraints.
|
||||
if (result->GetExtension(der::Input(kBasicConstraintsOid), &extension)) {
|
||||
result->has_basic_constraints_ = true;
|
||||
if (!ParseBasicConstraints(extension.value,
|
||||
&result->basic_constraints_)) {
|
||||
errors->AddError(kFailedParsingBasicConstraints);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Key Usage.
|
||||
if (result->GetExtension(der::Input(kKeyUsageOid), &extension)) {
|
||||
result->has_key_usage_ = true;
|
||||
if (!ParseKeyUsage(extension.value, &result->key_usage_)) {
|
||||
errors->AddError(kFailedParsingKeyUsage);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Extended Key Usage.
|
||||
if (result->GetExtension(der::Input(kExtKeyUsageOid), &extension)) {
|
||||
result->has_extended_key_usage_ = true;
|
||||
if (!ParseEKUExtension(extension.value, &result->extended_key_usage_)) {
|
||||
errors->AddError(kFailedParsingEku);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Subject alternative name.
|
||||
if (result->GetExtension(der::Input(kSubjectAltNameOid),
|
||||
&result->subject_alt_names_extension_)) {
|
||||
// RFC 5280 section 4.2.1.6:
|
||||
// SubjectAltName ::= GeneralNames
|
||||
result->subject_alt_names_ = GeneralNames::Create(
|
||||
result->subject_alt_names_extension_.value, errors);
|
||||
if (!result->subject_alt_names_) {
|
||||
errors->AddError(kFailedParsingSubjectAltName);
|
||||
return nullptr;
|
||||
}
|
||||
// RFC 5280 section 4.1.2.6:
|
||||
// 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.
|
||||
if (subject_value.Length() == 0 &&
|
||||
!result->subject_alt_names_extension_.critical) {
|
||||
errors->AddError(kSubjectAltNameNotCritical);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Name constraints.
|
||||
if (result->GetExtension(der::Input(kNameConstraintsOid), &extension)) {
|
||||
result->name_constraints_ =
|
||||
NameConstraints::Create(extension.value, extension.critical, errors);
|
||||
if (!result->name_constraints_) {
|
||||
errors->AddError(kFailedParsingNameConstraints);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Authority information access.
|
||||
if (result->GetExtension(der::Input(kAuthorityInfoAccessOid),
|
||||
&result->authority_info_access_extension_)) {
|
||||
result->has_authority_info_access_ = true;
|
||||
if (!ParseAuthorityInfoAccessURIs(
|
||||
result->authority_info_access_extension_.value,
|
||||
&result->ca_issuers_uris_, &result->ocsp_uris_)) {
|
||||
errors->AddError(kFailedParsingAia);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Policies.
|
||||
if (result->GetExtension(der::Input(kCertificatePoliciesOid), &extension)) {
|
||||
result->has_policy_oids_ = true;
|
||||
if (!ParseCertificatePoliciesExtensionOids(
|
||||
extension.value, false /*fail_parsing_unknown_qualifier_oids*/,
|
||||
&result->policy_oids_, errors)) {
|
||||
errors->AddError(kFailedParsingPolicies);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Policy constraints.
|
||||
if (result->GetExtension(der::Input(kPolicyConstraintsOid), &extension)) {
|
||||
result->has_policy_constraints_ = true;
|
||||
if (!ParsePolicyConstraints(extension.value,
|
||||
&result->policy_constraints_)) {
|
||||
errors->AddError(kFailedParsingPolicyConstraints);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Policy mappings.
|
||||
if (result->GetExtension(der::Input(kPolicyMappingsOid), &extension)) {
|
||||
result->has_policy_mappings_ = true;
|
||||
if (!ParsePolicyMappings(extension.value, &result->policy_mappings_)) {
|
||||
errors->AddError(kFailedParsingPolicyMappings);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Inhibit Any Policy.
|
||||
if (result->GetExtension(der::Input(kInhibitAnyPolicyOid), &extension)) {
|
||||
result->has_inhibit_any_policy_ = true;
|
||||
if (!ParseInhibitAnyPolicy(extension.value,
|
||||
&result->inhibit_any_policy_)) {
|
||||
errors->AddError(kFailedParsingInhibitAnyPolicy);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Subject Key Identifier.
|
||||
if (result->GetExtension(der::Input(kSubjectKeyIdentifierOid),
|
||||
&extension)) {
|
||||
result->subject_key_identifier_ = std::make_optional<der::Input>();
|
||||
if (!ParseSubjectKeyIdentifier(
|
||||
extension.value, &result->subject_key_identifier_.value())) {
|
||||
errors->AddError(kFailedParsingSubjectKeyIdentifier);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Authority Key Identifier.
|
||||
if (result->GetExtension(der::Input(kAuthorityKeyIdentifierOid),
|
||||
&extension)) {
|
||||
result->authority_key_identifier_ =
|
||||
std::make_optional<ParsedAuthorityKeyIdentifier>();
|
||||
if (!ParseAuthorityKeyIdentifier(
|
||||
extension.value, &result->authority_key_identifier_.value())) {
|
||||
errors->AddError(kFailedParsingAuthorityKeyIdentifier);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
bool ParsedCertificate::CreateAndAddToVector(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER> cert_data,
|
||||
const ParseCertificateOptions& options,
|
||||
std::vector<std::shared_ptr<const ParsedCertificate>>* chain,
|
||||
CertErrors* errors) {
|
||||
std::shared_ptr<const ParsedCertificate> cert(
|
||||
Create(std::move(cert_data), options, errors));
|
||||
if (!cert)
|
||||
return false;
|
||||
chain->push_back(std::move(cert));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace net
|
337
pki/parsed_certificate.h
Normal file
337
pki/parsed_certificate.h
Normal file
@ -0,0 +1,337 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_PARSED_CERTIFICATE_H_
|
||||
#define BSSL_PKI_PARSED_CERTIFICATE_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "fillins/check.h"
|
||||
|
||||
#include "certificate_policies.h"
|
||||
#include "parse_certificate.h"
|
||||
#include "signature_algorithm.h"
|
||||
#include "input.h"
|
||||
#include <optional>
|
||||
#include <openssl/base.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
struct GeneralNames;
|
||||
class NameConstraints;
|
||||
class ParsedCertificate;
|
||||
class CertErrors;
|
||||
|
||||
using ParsedCertificateList =
|
||||
std::vector<std::shared_ptr<const ParsedCertificate>>;
|
||||
|
||||
// Represents an X.509 certificate, including Certificate, TBSCertificate, and
|
||||
// standard extensions.
|
||||
// Creating a ParsedCertificate does not completely parse and validate the
|
||||
// certificate data. Presence of a member in this class implies the DER was
|
||||
// parsed successfully to that level, but does not imply the contents of that
|
||||
// member are valid, unless otherwise specified. See the documentation for each
|
||||
// member or the documentation of the type it returns.
|
||||
class OPENSSL_EXPORT ParsedCertificate {
|
||||
private:
|
||||
// Used to make constructors private while still being compatible with
|
||||
// |std::make_shared|.
|
||||
class PrivateConstructor {
|
||||
private:
|
||||
friend ParsedCertificate;
|
||||
PrivateConstructor() = default;
|
||||
};
|
||||
|
||||
public:
|
||||
~ParsedCertificate();
|
||||
// Map from OID to ParsedExtension.
|
||||
using ExtensionsMap = std::map<der::Input, ParsedExtension>;
|
||||
|
||||
// Creates a ParsedCertificate given a DER-encoded Certificate. Returns
|
||||
// nullptr on failure. Failure will occur if the standard certificate fields
|
||||
// and supported extensions cannot be parsed.
|
||||
// On either success or failure, if |errors| is non-null it may have error
|
||||
// information added to it.
|
||||
static std::shared_ptr<const ParsedCertificate> Create(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER> cert_data,
|
||||
const ParseCertificateOptions& options,
|
||||
CertErrors* errors);
|
||||
|
||||
// Creates a ParsedCertificate by copying the provided |data|, and appends it
|
||||
// to |chain|. Returns true if the certificate was successfully parsed and
|
||||
// added. If false is return, |chain| is unmodified.
|
||||
//
|
||||
// On either success or failure, if |errors| is non-null it may have error
|
||||
// information added to it.
|
||||
static bool CreateAndAddToVector(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER> cert_data,
|
||||
const ParseCertificateOptions& options,
|
||||
std::vector<std::shared_ptr<const ParsedCertificate>>* chain,
|
||||
CertErrors* errors);
|
||||
|
||||
explicit ParsedCertificate(PrivateConstructor);
|
||||
|
||||
ParsedCertificate(const ParsedCertificate&) = delete;
|
||||
ParsedCertificate& operator=(const ParsedCertificate&) = delete;
|
||||
|
||||
// Returns the DER-encoded certificate data for this cert.
|
||||
const der::Input& der_cert() const { return cert_; }
|
||||
|
||||
// Returns the CRYPTO_BUFFER backing this object.
|
||||
CRYPTO_BUFFER* cert_buffer() const { return cert_data_.get(); }
|
||||
|
||||
// Accessors for raw fields of the Certificate.
|
||||
const der::Input& tbs_certificate_tlv() const { return tbs_certificate_tlv_; }
|
||||
|
||||
const der::Input& signature_algorithm_tlv() const {
|
||||
return signature_algorithm_tlv_;
|
||||
}
|
||||
|
||||
const der::BitString& signature_value() const { return signature_value_; }
|
||||
|
||||
// Accessor for struct containing raw fields of the TbsCertificate.
|
||||
const ParsedTbsCertificate& tbs() const { return tbs_; }
|
||||
|
||||
// Returns the signatureAlgorithm of the Certificate (not the tbsCertificate).
|
||||
// If the signature algorithm is unknown/unsupported, this returns nullopt.
|
||||
std::optional<SignatureAlgorithm> signature_algorithm() const {
|
||||
return signature_algorithm_;
|
||||
}
|
||||
|
||||
// Returns the DER-encoded raw subject value (including the outer sequence
|
||||
// tag). This is guaranteed to be valid DER, though the contents of unhandled
|
||||
// string types are treated as raw bytes.
|
||||
der::Input subject_tlv() const { return tbs_.subject_tlv; }
|
||||
// Returns the DER-encoded normalized subject value (not including outer
|
||||
// Sequence tag). This is guaranteed to be valid DER, though the contents of
|
||||
// unhandled string types are treated as raw bytes.
|
||||
der::Input normalized_subject() const {
|
||||
return der::Input(&normalized_subject_);
|
||||
}
|
||||
// Returns the DER-encoded raw issuer value (including the outer sequence
|
||||
// tag). This is guaranteed to be valid DER, though the contents of unhandled
|
||||
// string types are treated as raw bytes.
|
||||
der::Input issuer_tlv() const { return tbs_.issuer_tlv; }
|
||||
// Returns the DER-encoded normalized issuer value (not including outer
|
||||
// Sequence tag). This is guaranteed to be valid DER, though the contents of
|
||||
// unhandled string types are treated as raw bytes.
|
||||
der::Input normalized_issuer() const {
|
||||
return der::Input(&normalized_issuer_);
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a BasicConstraints extension.
|
||||
bool has_basic_constraints() const { return has_basic_constraints_; }
|
||||
|
||||
// Returns the ParsedBasicConstraints struct. Caller must check
|
||||
// has_basic_constraints() before accessing this.
|
||||
const ParsedBasicConstraints& basic_constraints() const {
|
||||
DCHECK(has_basic_constraints_);
|
||||
return basic_constraints_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a KeyUsage extension.
|
||||
bool has_key_usage() const { return has_key_usage_; }
|
||||
|
||||
// Returns the KeyUsage BitString. Caller must check
|
||||
// has_key_usage() before accessing this.
|
||||
const der::BitString& key_usage() const {
|
||||
DCHECK(has_key_usage_);
|
||||
return key_usage_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a ExtendedKeyUsage extension.
|
||||
bool has_extended_key_usage() const { return has_extended_key_usage_; }
|
||||
|
||||
// Returns the ExtendedKeyUsage key purpose OIDs. Caller must check
|
||||
// has_extended_key_usage() before accessing this.
|
||||
const std::vector<der::Input>& extended_key_usage() const {
|
||||
DCHECK(has_extended_key_usage_);
|
||||
return extended_key_usage_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a SubjectAltName extension.
|
||||
bool has_subject_alt_names() const { return subject_alt_names_ != nullptr; }
|
||||
|
||||
// Returns the ParsedExtension struct for the SubjectAltName extension.
|
||||
// If the cert did not have a SubjectAltName extension, this will be a
|
||||
// default-initialized ParsedExtension struct.
|
||||
const ParsedExtension& subject_alt_names_extension() const {
|
||||
return subject_alt_names_extension_;
|
||||
}
|
||||
|
||||
// Returns the GeneralNames class parsed from SubjectAltName extension, or
|
||||
// nullptr if no SubjectAltName extension was present.
|
||||
const GeneralNames* subject_alt_names() const {
|
||||
return subject_alt_names_.get();
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a NameConstraints extension.
|
||||
bool has_name_constraints() const { return name_constraints_ != nullptr; }
|
||||
|
||||
// Returns the parsed NameConstraints extension. Must not be called if
|
||||
// has_name_constraints() is false.
|
||||
const NameConstraints& name_constraints() const {
|
||||
DCHECK(name_constraints_);
|
||||
return *name_constraints_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has an AuthorityInfoAccess extension.
|
||||
bool has_authority_info_access() const { return has_authority_info_access_; }
|
||||
|
||||
// Returns the ParsedExtension struct for the AuthorityInfoAccess extension.
|
||||
const ParsedExtension& authority_info_access_extension() const {
|
||||
return authority_info_access_extension_;
|
||||
}
|
||||
|
||||
// Returns any caIssuers URIs from the AuthorityInfoAccess extension.
|
||||
const std::vector<std::string_view>& ca_issuers_uris() const {
|
||||
return ca_issuers_uris_;
|
||||
}
|
||||
|
||||
// Returns any OCSP URIs from the AuthorityInfoAccess extension.
|
||||
const std::vector<std::string_view>& ocsp_uris() const { return ocsp_uris_; }
|
||||
|
||||
// Returns true if the certificate has a Policies extension.
|
||||
bool has_policy_oids() const { return has_policy_oids_; }
|
||||
|
||||
// Returns the policy OIDs. Caller must check has_policy_oids() before
|
||||
// accessing this.
|
||||
const std::vector<der::Input>& policy_oids() const {
|
||||
DCHECK(has_policy_oids());
|
||||
return policy_oids_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a PolicyConstraints extension.
|
||||
bool has_policy_constraints() const { return has_policy_constraints_; }
|
||||
|
||||
// Returns the ParsedPolicyConstraints struct. Caller must check
|
||||
// has_policy_constraints() before accessing this.
|
||||
const ParsedPolicyConstraints& policy_constraints() const {
|
||||
DCHECK(has_policy_constraints_);
|
||||
return policy_constraints_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a PolicyMappings extension.
|
||||
bool has_policy_mappings() const { return has_policy_mappings_; }
|
||||
|
||||
// Returns the PolicyMappings extension. Caller must check
|
||||
// has_policy_mappings() before accessing this.
|
||||
const std::vector<ParsedPolicyMapping>& policy_mappings() const {
|
||||
DCHECK(has_policy_mappings_);
|
||||
return policy_mappings_;
|
||||
}
|
||||
|
||||
// Returns true if the certificate has a InhibitAnyPolicy extension.
|
||||
bool has_inhibit_any_policy() const { return has_inhibit_any_policy_; }
|
||||
|
||||
// Returns the Inhibit Any Policy extension. Caller must check
|
||||
// has_inhibit_any_policy() before accessing this.
|
||||
uint8_t inhibit_any_policy() const {
|
||||
DCHECK(has_inhibit_any_policy_);
|
||||
return inhibit_any_policy_;
|
||||
}
|
||||
|
||||
// Returns the AuthorityKeyIdentifier extension, or nullopt if there wasn't
|
||||
// one.
|
||||
const std::optional<ParsedAuthorityKeyIdentifier>& authority_key_identifier()
|
||||
const {
|
||||
return authority_key_identifier_;
|
||||
}
|
||||
|
||||
// Returns the SubjectKeyIdentifier extension, or nullopt if there wasn't
|
||||
// one.
|
||||
const std::optional<der::Input>& subject_key_identifier() const {
|
||||
return subject_key_identifier_;
|
||||
}
|
||||
|
||||
// Returns a map of all the extensions in the certificate.
|
||||
const ExtensionsMap& extensions() const { return extensions_; }
|
||||
|
||||
// Gets the value for extension matching |extension_oid|. Returns false if the
|
||||
// extension is not present.
|
||||
bool GetExtension(const der::Input& extension_oid,
|
||||
ParsedExtension* parsed_extension) const;
|
||||
|
||||
private:
|
||||
// The backing store for the certificate data.
|
||||
bssl::UniquePtr<CRYPTO_BUFFER> cert_data_;
|
||||
|
||||
// Points to the raw certificate DER.
|
||||
der::Input cert_;
|
||||
|
||||
der::Input tbs_certificate_tlv_;
|
||||
der::Input signature_algorithm_tlv_;
|
||||
der::BitString signature_value_;
|
||||
ParsedTbsCertificate tbs_;
|
||||
|
||||
// The signatureAlgorithm from the Certificate.
|
||||
std::optional<SignatureAlgorithm> signature_algorithm_;
|
||||
|
||||
// Normalized DER-encoded Subject (not including outer Sequence tag).
|
||||
std::string normalized_subject_;
|
||||
// Normalized DER-encoded Issuer (not including outer Sequence tag).
|
||||
std::string normalized_issuer_;
|
||||
|
||||
// BasicConstraints extension.
|
||||
bool has_basic_constraints_ = false;
|
||||
ParsedBasicConstraints basic_constraints_;
|
||||
|
||||
// KeyUsage extension.
|
||||
bool has_key_usage_ = false;
|
||||
der::BitString key_usage_;
|
||||
|
||||
// ExtendedKeyUsage extension.
|
||||
bool has_extended_key_usage_ = false;
|
||||
std::vector<der::Input> extended_key_usage_;
|
||||
|
||||
// Raw SubjectAltName extension.
|
||||
ParsedExtension subject_alt_names_extension_;
|
||||
// Parsed SubjectAltName extension.
|
||||
std::unique_ptr<GeneralNames> subject_alt_names_;
|
||||
|
||||
// NameConstraints extension.
|
||||
std::unique_ptr<NameConstraints> name_constraints_;
|
||||
|
||||
// AuthorityInfoAccess extension.
|
||||
bool has_authority_info_access_ = false;
|
||||
ParsedExtension authority_info_access_extension_;
|
||||
// CaIssuers and Ocsp URIs parsed from the AuthorityInfoAccess extension. Note
|
||||
// that the AuthorityInfoAccess may have contained other AccessDescriptions
|
||||
// which are not represented here.
|
||||
std::vector<std::string_view> ca_issuers_uris_;
|
||||
std::vector<std::string_view> ocsp_uris_;
|
||||
|
||||
// Policies extension. This list will already have been checked for
|
||||
// duplicates.
|
||||
bool has_policy_oids_ = false;
|
||||
std::vector<der::Input> policy_oids_;
|
||||
|
||||
// Policy constraints extension.
|
||||
bool has_policy_constraints_ = false;
|
||||
ParsedPolicyConstraints policy_constraints_;
|
||||
|
||||
// Policy mappings extension.
|
||||
bool has_policy_mappings_ = false;
|
||||
std::vector<ParsedPolicyMapping> policy_mappings_;
|
||||
|
||||
// Inhibit Any Policy extension.
|
||||
bool has_inhibit_any_policy_ = false;
|
||||
uint8_t inhibit_any_policy_;
|
||||
|
||||
// AuthorityKeyIdentifier extension.
|
||||
std::optional<ParsedAuthorityKeyIdentifier> authority_key_identifier_;
|
||||
|
||||
// SubjectKeyIdentifier extension.
|
||||
std::optional<der::Input> subject_key_identifier_;
|
||||
|
||||
// All of the extensions.
|
||||
ExtensionsMap extensions_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_PARSED_CERTIFICATE_H_
|
593
pki/parsed_certificate_unittest.cc
Normal file
593
pki/parsed_certificate_unittest.cc
Normal file
@ -0,0 +1,593 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parsed_certificate.h"
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "parse_certificate.h"
|
||||
#include "test_helpers.h"
|
||||
#include "input.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/pool.h>
|
||||
|
||||
// TODO(eroman): Add tests for parsing of policy mappings.
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetFilePath(const std::string& file_name) {
|
||||
return std::string("testdata/parse_certificate_unittest/") + file_name;
|
||||
}
|
||||
|
||||
// Reads and parses a certificate from the PEM file |file_name|.
|
||||
//
|
||||
// Returns nullptr if the certificate parsing failed, and verifies that any
|
||||
// errors match the ERRORS block in the .pem file.
|
||||
std::shared_ptr<const ParsedCertificate> ParseCertificateFromFile(
|
||||
const std::string& file_name,
|
||||
const ParseCertificateOptions& options) {
|
||||
std::string data;
|
||||
std::string expected_errors;
|
||||
|
||||
// Read the certificate data and error expectations from a single PEM file.
|
||||
const PemBlockMapping mappings[] = {
|
||||
{"CERTIFICATE", &data},
|
||||
{"ERRORS", &expected_errors, true /*optional*/},
|
||||
};
|
||||
std::string test_file_path = GetFilePath(file_name);
|
||||
EXPECT_TRUE(ReadTestDataFromPemFile(test_file_path, mappings));
|
||||
|
||||
CertErrors errors;
|
||||
std::shared_ptr<const ParsedCertificate> cert = ParsedCertificate::Create(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
|
||||
reinterpret_cast<const uint8_t*>(data.data()), data.size(), nullptr)),
|
||||
options, &errors);
|
||||
|
||||
// The errors are baselined for |!allow_invalid_serial_numbers|. So if
|
||||
// requesting a non-default option skip the error checks.
|
||||
// TODO(eroman): This is ugly.
|
||||
if (!options.allow_invalid_serial_numbers)
|
||||
VerifyCertErrors(expected_errors, errors, test_file_path);
|
||||
|
||||
// Every parse failure being tested should emit error information.
|
||||
if (!cert) {
|
||||
EXPECT_FALSE(errors.ToDebugString().empty());
|
||||
}
|
||||
|
||||
return cert;
|
||||
}
|
||||
|
||||
der::Input DavidBenOid() {
|
||||
// This OID corresponds with
|
||||
// 1.2.840.113554.4.1.72585.0 (https://davidben.net/oid)
|
||||
static const uint8_t kOid[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
|
||||
0x04, 0x01, 0x84, 0xb7, 0x09, 0x00};
|
||||
return der::Input(kOid);
|
||||
}
|
||||
|
||||
// Parses an Extension whose critical field is true (255).
|
||||
TEST(ParsedCertificateTest, ExtensionCritical) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("extension_critical.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
const uint8_t kExpectedValue[] = {0x30, 0x00};
|
||||
|
||||
ParsedExtension extension;
|
||||
ASSERT_TRUE(cert->GetExtension(DavidBenOid(), &extension));
|
||||
|
||||
EXPECT_TRUE(extension.critical);
|
||||
EXPECT_EQ(DavidBenOid(), extension.oid);
|
||||
EXPECT_EQ(der::Input(kExpectedValue), extension.value);
|
||||
}
|
||||
|
||||
// Parses an Extension whose critical field is false (omitted).
|
||||
TEST(ParsedCertificateTest, ExtensionNotCritical) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("extension_not_critical.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
const uint8_t kExpectedValue[] = {0x30, 0x00};
|
||||
|
||||
ParsedExtension extension;
|
||||
ASSERT_TRUE(cert->GetExtension(DavidBenOid(), &extension));
|
||||
|
||||
EXPECT_FALSE(extension.critical);
|
||||
EXPECT_EQ(DavidBenOid(), extension.oid);
|
||||
EXPECT_EQ(der::Input(kExpectedValue), extension.value);
|
||||
}
|
||||
|
||||
// Parses an Extension whose critical field is 0. This is in one sense FALSE,
|
||||
// however because critical has DEFAULT of false this is in fact invalid
|
||||
// DER-encoding.
|
||||
TEST(ParsedCertificateTest, ExtensionCritical0) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("extension_critical_0.pem", {}));
|
||||
}
|
||||
|
||||
// Parses an Extension whose critical field is 3. Under DER-encoding BOOLEAN
|
||||
// values must an octet of either all zero bits, or all 1 bits, so this is not
|
||||
// valid.
|
||||
TEST(ParsedCertificateTest, ExtensionCritical3) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("extension_critical_3.pem", {}));
|
||||
}
|
||||
|
||||
// Parses an Extensions that is an empty sequence.
|
||||
TEST(ParsedCertificateTest, ExtensionsEmptySequence) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("extensions_empty_sequence.pem", {}));
|
||||
}
|
||||
|
||||
// Parses an Extensions that is not a sequence.
|
||||
TEST(ParsedCertificateTest, ExtensionsNotSequence) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("extensions_not_sequence.pem", {}));
|
||||
}
|
||||
|
||||
// Parses an Extensions that has data after the sequence.
|
||||
TEST(ParsedCertificateTest, ExtensionsDataAfterSequence) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("extensions_data_after_sequence.pem", {}));
|
||||
}
|
||||
|
||||
// Parses an Extensions that contains duplicated key usages.
|
||||
TEST(ParsedCertificateTest, ExtensionsDuplicateKeyUsage) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("extensions_duplicate_key_usage.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a certificate with a bad key usage extension (BIT STRING with zero
|
||||
// elements).
|
||||
TEST(ParsedCertificateTest, BadKeyUsage) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("bad_key_usage.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a certificate that has a PolicyQualifierInfo that is missing the
|
||||
// qualifier field.
|
||||
TEST(ParsedCertificateTest, BadPolicyQualifiers) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("bad_policy_qualifiers.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a certificate that uses an unknown signature algorithm OID (00).
|
||||
TEST(ParsedCertificateTest, BadSignatureAlgorithmOid) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("bad_signature_algorithm_oid.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
ASSERT_FALSE(cert->signature_algorithm());
|
||||
}
|
||||
|
||||
// The validity encodes time as UTCTime but following the BER rules rather than
|
||||
// DER rules (i.e. YYMMDDHHMMZ instead of YYMMDDHHMMSSZ).
|
||||
TEST(ParsedCertificateTest, BadValidity) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("bad_validity.pem", {}));
|
||||
}
|
||||
|
||||
// The signature algorithm contains an unexpected parameters field.
|
||||
TEST(ParsedCertificateTest, FailedSignatureAlgorithm) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("failed_signature_algorithm.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
ASSERT_FALSE(cert->signature_algorithm());
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, IssuerBadPrintableString) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("issuer_bad_printable_string.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, NameConstraintsBadIp) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("name_constraints_bad_ip.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, PolicyQualifiersEmptySequence) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("policy_qualifiers_empty_sequence.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectBlankSubjectAltNameNotCritical) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile(
|
||||
"subject_blank_subjectaltname_not_critical.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectNotAscii) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("subject_not_ascii.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectNotPrintableString) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("subject_not_printable_string.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectAltNameBadIp) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("subjectaltname_bad_ip.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectAltNameDnsNotAscii) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("subjectaltname_dns_not_ascii.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectAltNameGeneralNamesEmptySequence) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile(
|
||||
"subjectaltname_general_names_empty_sequence.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, SubjectAltNameTrailingData) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("subjectaltname_trailing_data.pem", {}));
|
||||
}
|
||||
|
||||
TEST(ParsedCertificateTest, V1ExplicitVersion) {
|
||||
ASSERT_FALSE(ParseCertificateFromFile("v1_explicit_version.pem", {}));
|
||||
}
|
||||
|
||||
// Parses an Extensions that contains an extended key usages.
|
||||
TEST(ParsedCertificateTest, ExtendedKeyUsage) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("extended_key_usage.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
ASSERT_EQ(4u, cert->extensions().size());
|
||||
|
||||
ParsedExtension extension;
|
||||
ASSERT_TRUE(cert->GetExtension(der::Input(kExtKeyUsageOid), &extension));
|
||||
|
||||
EXPECT_FALSE(extension.critical);
|
||||
EXPECT_EQ(45u, extension.value.Length());
|
||||
|
||||
EXPECT_TRUE(cert->has_extended_key_usage());
|
||||
EXPECT_EQ(4u, cert->extended_key_usage().size());
|
||||
}
|
||||
|
||||
// Parses an Extensions that contains a key usage.
|
||||
TEST(ParsedCertificateTest, KeyUsage) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("key_usage.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
ASSERT_TRUE(cert->has_key_usage());
|
||||
|
||||
EXPECT_EQ(5u, cert->key_usage().unused_bits());
|
||||
const uint8_t kExpectedBytes[] = {0xA0};
|
||||
EXPECT_EQ(der::Input(kExpectedBytes), cert->key_usage().bytes());
|
||||
|
||||
EXPECT_TRUE(cert->key_usage().AssertsBit(0));
|
||||
EXPECT_FALSE(cert->key_usage().AssertsBit(1));
|
||||
EXPECT_TRUE(cert->key_usage().AssertsBit(2));
|
||||
}
|
||||
|
||||
// Parses an Extensions that contains a policies extension.
|
||||
TEST(ParsedCertificateTest, Policies) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("policies.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
ASSERT_EQ(4u, cert->extensions().size());
|
||||
|
||||
ParsedExtension extension;
|
||||
ASSERT_TRUE(
|
||||
cert->GetExtension(der::Input(kCertificatePoliciesOid), &extension));
|
||||
|
||||
EXPECT_FALSE(extension.critical);
|
||||
EXPECT_EQ(95u, extension.value.Length());
|
||||
|
||||
EXPECT_TRUE(cert->has_policy_oids());
|
||||
EXPECT_EQ(2u, cert->policy_oids().size());
|
||||
}
|
||||
|
||||
// Parses an Extensions that contains a subjectaltname extension.
|
||||
TEST(ParsedCertificateTest, SubjectAltName) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("subject_alt_name.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
ASSERT_TRUE(cert->has_subject_alt_names());
|
||||
}
|
||||
|
||||
// Parses an Extensions that contains multiple extensions, sourced from a
|
||||
// real-world certificate.
|
||||
TEST(ParsedCertificateTest, ExtensionsReal) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("extensions_real.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
ASSERT_EQ(7u, cert->extensions().size());
|
||||
|
||||
EXPECT_TRUE(cert->has_key_usage());
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_TRUE(cert->has_authority_info_access());
|
||||
EXPECT_TRUE(cert->has_policy_oids());
|
||||
|
||||
ASSERT_TRUE(cert->authority_key_identifier());
|
||||
ASSERT_TRUE(cert->authority_key_identifier()->key_identifier);
|
||||
EXPECT_FALSE(cert->authority_key_identifier()->authority_cert_issuer);
|
||||
EXPECT_FALSE(cert->authority_key_identifier()->authority_cert_serial_number);
|
||||
const uint8_t expected_authority_key_identifier[] = {
|
||||
0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64,
|
||||
0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e,
|
||||
};
|
||||
EXPECT_EQ(der::Input(expected_authority_key_identifier),
|
||||
cert->authority_key_identifier()->key_identifier);
|
||||
|
||||
ASSERT_TRUE(cert->subject_key_identifier());
|
||||
const uint8_t expected_subject_key_identifier[] = {
|
||||
0x4a, 0xdd, 0x06, 0x16, 0x1b, 0xbc, 0xf6, 0x68, 0xb5, 0x76,
|
||||
0xf5, 0x81, 0xb6, 0xbb, 0x62, 0x1a, 0xba, 0x5a, 0x81, 0x2f};
|
||||
EXPECT_EQ(der::Input(expected_subject_key_identifier),
|
||||
cert->subject_key_identifier());
|
||||
|
||||
ParsedExtension extension;
|
||||
ASSERT_TRUE(
|
||||
cert->GetExtension(der::Input(kCertificatePoliciesOid), &extension));
|
||||
|
||||
EXPECT_FALSE(extension.critical);
|
||||
EXPECT_EQ(16u, extension.value.Length());
|
||||
|
||||
// TODO(eroman): Verify the other extensions' values.
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with no CA or pathlen.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsNotCa) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("basic_constraints_not_ca.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_FALSE(cert->basic_constraints().is_ca);
|
||||
EXPECT_FALSE(cert->basic_constraints().has_path_len);
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA but no pathlen.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsCaNoPath) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("basic_constraints_ca_no_path.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_TRUE(cert->basic_constraints().is_ca);
|
||||
EXPECT_FALSE(cert->basic_constraints().has_path_len);
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA and pathlen of 9.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsCaPath9) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("basic_constraints_ca_path_9.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_TRUE(cert->basic_constraints().is_ca);
|
||||
EXPECT_TRUE(cert->basic_constraints().has_path_len);
|
||||
EXPECT_EQ(9u, cert->basic_constraints().path_len);
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA and pathlen of 255 (largest allowed size).
|
||||
TEST(ParsedCertificateTest, BasicConstraintsPathlen255) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("basic_constraints_pathlen_255.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_TRUE(cert->basic_constraints().is_ca);
|
||||
EXPECT_TRUE(cert->basic_constraints().has_path_len);
|
||||
EXPECT_EQ(255, cert->basic_constraints().path_len);
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA and pathlen of 256 (too large).
|
||||
TEST(ParsedCertificateTest, BasicConstraintsPathlen256) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("basic_constraints_pathlen_256.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA and a negative pathlen.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsNegativePath) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("basic_constraints_negative_path.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA and pathlen that is very large (and
|
||||
// couldn't fit in a 64-bit integer).
|
||||
TEST(ParsedCertificateTest, BasicConstraintsPathTooLarge) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("basic_constraints_path_too_large.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA explicitly set to false. This violates
|
||||
// DER-encoding rules, however is commonly used, so it is accepted.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsCaFalse) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("basic_constraints_ca_false.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_FALSE(cert->basic_constraints().is_ca);
|
||||
EXPECT_FALSE(cert->basic_constraints().has_path_len);
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA set to true and an unexpected NULL at
|
||||
// the end.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsUnconsumedData) {
|
||||
ASSERT_FALSE(
|
||||
ParseCertificateFromFile("basic_constraints_unconsumed_data.pem", {}));
|
||||
}
|
||||
|
||||
// Parses a BasicConstraints with CA omitted (false), but with a pathlen of 1.
|
||||
// This is valid DER for the ASN.1, however is not valid when interpreting the
|
||||
// BasicConstraints at a higher level.
|
||||
TEST(ParsedCertificateTest, BasicConstraintsPathLenButNotCa) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("basic_constraints_pathlen_not_ca.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_basic_constraints());
|
||||
EXPECT_FALSE(cert->basic_constraints().is_ca);
|
||||
EXPECT_TRUE(cert->basic_constraints().has_path_len);
|
||||
EXPECT_EQ(1u, cert->basic_constraints().path_len);
|
||||
}
|
||||
|
||||
// Tests parsing a certificate that contains a policyConstraints
|
||||
// extension having requireExplicitPolicy:3.
|
||||
TEST(ParsedCertificateTest, PolicyConstraintsRequire) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("policy_constraints_require.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_policy_constraints());
|
||||
EXPECT_TRUE(cert->policy_constraints().require_explicit_policy.has_value());
|
||||
EXPECT_EQ(3, cert->policy_constraints().require_explicit_policy.value());
|
||||
EXPECT_FALSE(cert->policy_constraints().inhibit_policy_mapping.has_value());
|
||||
}
|
||||
|
||||
// Tests parsing a certificate that contains a policyConstraints
|
||||
// extension having inhibitPolicyMapping:1.
|
||||
TEST(ParsedCertificateTest, PolicyConstraintsInhibit) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("policy_constraints_inhibit.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_policy_constraints());
|
||||
EXPECT_FALSE(cert->policy_constraints().require_explicit_policy.has_value());
|
||||
EXPECT_TRUE(cert->policy_constraints().inhibit_policy_mapping.has_value());
|
||||
EXPECT_EQ(1, cert->policy_constraints().inhibit_policy_mapping.value());
|
||||
}
|
||||
|
||||
// Tests parsing a certificate that contains a policyConstraints
|
||||
// extension having requireExplicitPolicy:5,inhibitPolicyMapping:2.
|
||||
TEST(ParsedCertificateTest, PolicyConstraintsInhibitRequire) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("policy_constraints_inhibit_require.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
EXPECT_TRUE(cert->has_policy_constraints());
|
||||
EXPECT_TRUE(cert->policy_constraints().require_explicit_policy.has_value());
|
||||
EXPECT_EQ(5, cert->policy_constraints().require_explicit_policy.value());
|
||||
EXPECT_TRUE(cert->policy_constraints().inhibit_policy_mapping.has_value());
|
||||
EXPECT_EQ(2, cert->policy_constraints().inhibit_policy_mapping.value());
|
||||
}
|
||||
|
||||
// Tests parsing a certificate that has a policyConstraints
|
||||
// extension with an empty sequence.
|
||||
TEST(ParsedCertificateTest, PolicyConstraintsEmpty) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("policy_constraints_empty.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
}
|
||||
|
||||
// Tests a certificate with a serial number with a leading 0 padding byte in
|
||||
// the encoding since it is not negative.
|
||||
TEST(ParsedCertificateTest, SerialNumberZeroPadded) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_zero_padded.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
static const uint8_t expected_serial[3] = {0x00, 0x80, 0x01};
|
||||
EXPECT_EQ(der::Input(expected_serial), cert->tbs().serial_number);
|
||||
}
|
||||
|
||||
// Tests a serial number where the MSB is >= 0x80, causing the encoded
|
||||
// length to be 21 bytes long. This is an error, as RFC 5280 specifies a
|
||||
// maximum of 20 bytes.
|
||||
TEST(ParsedCertificateTest, SerialNumberZeroPadded21BytesLong) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_zero_padded_21_bytes.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
|
||||
// Try again with allow_invalid_serial_numbers=true. Parsing should succeed.
|
||||
ParseCertificateOptions options;
|
||||
options.allow_invalid_serial_numbers = true;
|
||||
cert = ParseCertificateFromFile("serial_zero_padded_21_bytes.pem", options);
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
static const uint8_t expected_serial[21] = {
|
||||
0x00, 0x80, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13};
|
||||
EXPECT_EQ(der::Input(expected_serial), cert->tbs().serial_number);
|
||||
}
|
||||
|
||||
// Tests a serial number which is negative. CAs are not supposed to include
|
||||
// negative serial numbers, however RFC 5280 expects consumers to deal with it
|
||||
// anyway.
|
||||
TEST(ParsedCertificateTest, SerialNumberNegative) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_negative.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
static const uint8_t expected_serial[2] = {0x80, 0x01};
|
||||
EXPECT_EQ(der::Input(expected_serial), cert->tbs().serial_number);
|
||||
}
|
||||
|
||||
// Tests a serial number which is very long. RFC 5280 specifies a maximum of 20
|
||||
// bytes.
|
||||
TEST(ParsedCertificateTest, SerialNumber37BytesLong) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_37_bytes.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
|
||||
// Try again with allow_invalid_serial_numbers=true. Parsing should succeed.
|
||||
ParseCertificateOptions options;
|
||||
options.allow_invalid_serial_numbers = true;
|
||||
cert = ParseCertificateFromFile("serial_37_bytes.pem", options);
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
static const uint8_t expected_serial[37] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
|
||||
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
|
||||
0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25};
|
||||
EXPECT_EQ(der::Input(expected_serial), cert->tbs().serial_number);
|
||||
}
|
||||
|
||||
// Tests a serial number which is zero. RFC 5280 says they should be positive,
|
||||
// however also recommends supporting non-positive ones, so parsing here
|
||||
// is expected to succeed.
|
||||
TEST(ParsedCertificateTest, SerialNumberZero) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_zero.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
static const uint8_t expected_serial[] = {0x00};
|
||||
EXPECT_EQ(der::Input(expected_serial), cert->tbs().serial_number);
|
||||
}
|
||||
|
||||
// Tests a serial number which not a number (NULL).
|
||||
TEST(ParsedCertificateTest, SerialNotNumber) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_not_number.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
}
|
||||
|
||||
// Tests a serial number which uses a non-minimal INTEGER encoding
|
||||
TEST(ParsedCertificateTest, SerialNotMinimal) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("serial_not_minimal.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
}
|
||||
|
||||
// Tests parsing a certificate that has an inhibitAnyPolicy extension.
|
||||
TEST(ParsedCertificateTest, InhibitAnyPolicy) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("inhibit_any_policy.pem", {});
|
||||
ASSERT_TRUE(cert);
|
||||
|
||||
ParsedExtension extension;
|
||||
ASSERT_TRUE(cert->GetExtension(der::Input(kInhibitAnyPolicyOid), &extension));
|
||||
|
||||
uint8_t skip_count;
|
||||
ASSERT_TRUE(ParseInhibitAnyPolicy(extension.value, &skip_count));
|
||||
EXPECT_EQ(3, skip_count);
|
||||
}
|
||||
|
||||
// Tests a subjectKeyIdentifier that is not an OCTET_STRING.
|
||||
TEST(ParsedCertificateTest, SubjectKeyIdentifierNotOctetString) {
|
||||
std::shared_ptr<const ParsedCertificate> cert = ParseCertificateFromFile(
|
||||
"subject_key_identifier_not_octet_string.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
}
|
||||
|
||||
// Tests an authorityKeyIdentifier that is not a SEQUENCE.
|
||||
TEST(ParsedCertificateTest, AuthourityKeyIdentifierNotSequence) {
|
||||
std::shared_ptr<const ParsedCertificate> cert =
|
||||
ParseCertificateFromFile("authority_key_identifier_not_sequence.pem", {});
|
||||
ASSERT_FALSE(cert);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace net
|
156
pki/parser.cc
Normal file
156
pki/parser.cc
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
#include "fillins/check.h"
|
||||
#include "parse_values.h"
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
Parser::Parser() {
|
||||
CBS_init(&cbs_, nullptr, 0);
|
||||
}
|
||||
|
||||
Parser::Parser(const Input& input) {
|
||||
CBS_init(&cbs_, input.UnsafeData(), input.Length());
|
||||
}
|
||||
|
||||
bool Parser::PeekTagAndValue(Tag* tag, Input* out) {
|
||||
CBS peeker = cbs_;
|
||||
CBS tmp_out;
|
||||
size_t header_len;
|
||||
unsigned tag_value;
|
||||
if (!CBS_get_any_asn1_element(&peeker, &tmp_out, &tag_value, &header_len) ||
|
||||
!CBS_skip(&tmp_out, header_len)) {
|
||||
return false;
|
||||
}
|
||||
advance_len_ = CBS_len(&tmp_out) + header_len;
|
||||
*tag = tag_value;
|
||||
*out = Input(CBS_data(&tmp_out), CBS_len(&tmp_out));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::Advance() {
|
||||
if (advance_len_ == 0)
|
||||
return false;
|
||||
bool ret = !!CBS_skip(&cbs_, advance_len_);
|
||||
advance_len_ = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Parser::HasMore() {
|
||||
return CBS_len(&cbs_) > 0;
|
||||
}
|
||||
|
||||
bool Parser::ReadRawTLV(Input* out) {
|
||||
CBS tmp_out;
|
||||
if (!CBS_get_any_asn1_element(&cbs_, &tmp_out, nullptr, nullptr))
|
||||
return false;
|
||||
*out = Input(CBS_data(&tmp_out), CBS_len(&tmp_out));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::ReadTagAndValue(Tag* tag, Input* out) {
|
||||
if (!PeekTagAndValue(tag, out))
|
||||
return false;
|
||||
CHECK(Advance());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::ReadOptionalTag(Tag tag, std::optional<Input>* out) {
|
||||
if (!HasMore()) {
|
||||
*out = std::nullopt;
|
||||
return true;
|
||||
}
|
||||
Tag actual_tag;
|
||||
Input value;
|
||||
if (!PeekTagAndValue(&actual_tag, &value)) {
|
||||
return false;
|
||||
}
|
||||
if (actual_tag == tag) {
|
||||
CHECK(Advance());
|
||||
*out = value;
|
||||
} else {
|
||||
advance_len_ = 0;
|
||||
*out = std::nullopt;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::ReadOptionalTag(Tag tag, Input* out, bool* present) {
|
||||
std::optional<Input> tmp_out;
|
||||
if (!ReadOptionalTag(tag, &tmp_out))
|
||||
return false;
|
||||
*present = tmp_out.has_value();
|
||||
*out = tmp_out.value_or(der::Input());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::SkipOptionalTag(Tag tag, bool* present) {
|
||||
Input out;
|
||||
return ReadOptionalTag(tag, &out, present);
|
||||
}
|
||||
|
||||
bool Parser::ReadTag(Tag tag, Input* out) {
|
||||
Tag actual_tag;
|
||||
Input value;
|
||||
if (!PeekTagAndValue(&actual_tag, &value) || actual_tag != tag) {
|
||||
return false;
|
||||
}
|
||||
CHECK(Advance());
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::SkipTag(Tag tag) {
|
||||
Input out;
|
||||
return ReadTag(tag, &out);
|
||||
}
|
||||
|
||||
// Type-specific variants of ReadTag
|
||||
|
||||
bool Parser::ReadConstructed(Tag tag, Parser* out) {
|
||||
if (!IsConstructed(tag))
|
||||
return false;
|
||||
Input data;
|
||||
if (!ReadTag(tag, &data))
|
||||
return false;
|
||||
*out = Parser(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::ReadSequence(Parser* out) {
|
||||
return ReadConstructed(kSequence, out);
|
||||
}
|
||||
|
||||
bool Parser::ReadUint8(uint8_t* out) {
|
||||
Input encoded_int;
|
||||
if (!ReadTag(kInteger, &encoded_int))
|
||||
return false;
|
||||
return ParseUint8(encoded_int, out);
|
||||
}
|
||||
|
||||
bool Parser::ReadUint64(uint64_t* out) {
|
||||
Input encoded_int;
|
||||
if (!ReadTag(kInteger, &encoded_int))
|
||||
return false;
|
||||
return ParseUint64(encoded_int, out);
|
||||
}
|
||||
|
||||
std::optional<BitString> Parser::ReadBitString() {
|
||||
Input value;
|
||||
if (!ReadTag(kBitString, &value))
|
||||
return std::nullopt;
|
||||
return ParseBitString(value);
|
||||
}
|
||||
|
||||
bool Parser::ReadGeneralizedTime(GeneralizedTime* out) {
|
||||
Input value;
|
||||
if (!ReadTag(kGeneralizedTime, &value))
|
||||
return false;
|
||||
return ParseGeneralizedTime(value, out);
|
||||
}
|
||||
|
||||
} // namespace bssl::der
|
212
pki/parser.h
Normal file
212
pki/parser.h
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_DER_PARSER_H_
|
||||
#define BSSL_DER_PARSER_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#include "input.h"
|
||||
#include "tag.h"
|
||||
#include <optional>
|
||||
#include <openssl/bytestring.h>
|
||||
|
||||
namespace bssl::der {
|
||||
|
||||
class BitString;
|
||||
struct GeneralizedTime;
|
||||
|
||||
// Parses a DER-encoded ASN.1 structure. DER (distinguished encoding rules)
|
||||
// encodes each data value with a tag, length, and value (TLV). The tag
|
||||
// indicates the type of the ASN.1 value. Depending on the type of the value,
|
||||
// it could contain arbitrary bytes, so the length of the value is encoded
|
||||
// after the tag and before the value to indicate how many bytes of value
|
||||
// follow. DER also defines how the values are encoded for particular types.
|
||||
//
|
||||
// This Parser places a few restrictions on the DER encoding it can parse. The
|
||||
// largest restriction is that it only supports tags which have a tag number
|
||||
// no greater than 30 - these are the tags that fit in a single octet. The
|
||||
// second restriction is that the maximum length for a value that can be parsed
|
||||
// is 4GB. Both of these restrictions should be fine for any reasonable input.
|
||||
//
|
||||
// The Parser class is mainly focused on parsing the TLV structure of DER
|
||||
// encoding, and does not directly handle parsing primitive values (other
|
||||
// functions in the bssl::der namespace are provided for this.) When a Parser
|
||||
// is created, it is passed in a reference to the encoded data. Because the
|
||||
// encoded data is not owned by the Parser, the data cannot change during the
|
||||
// lifespan of the Parser. The Parser functions by keeping a pointer to the
|
||||
// current TLV which starts at the beginning of the input and advancing through
|
||||
// the input as each TLV is read. As such, a Parser instance is thread-unsafe.
|
||||
//
|
||||
// Most methods for using the Parser write the current tag and/or value to
|
||||
// the output parameters provided and then advance the input to the next TLV.
|
||||
// None of the methods explicitly expose the length because it is part of the
|
||||
// value. All methods return a boolean indicating whether there was a parsing
|
||||
// error with the current TLV.
|
||||
//
|
||||
// Some methods are provided in the Parser class as convenience to both read
|
||||
// the current TLV from the input and also parse the DER encoded value,
|
||||
// converting it to a corresponding C++ type. These methods simply combine
|
||||
// ReadTag() with the appropriate ParseType() free function.
|
||||
//
|
||||
// The design of DER encoding allows for nested data structures with
|
||||
// constructed values, where the value is a series of TLVs. The Parser class
|
||||
// is not designed to traverse through a nested encoding from a single object,
|
||||
// but it does facilitate parsing nested data structures through the
|
||||
// convenience methods ReadSequence() and the more general ReadConstructed(),
|
||||
// which provide the user with another Parser object to traverse the next
|
||||
// level of TLVs.
|
||||
//
|
||||
// For a brief example of how to use the Parser, suppose we have the following
|
||||
// ASN.1 type definition:
|
||||
//
|
||||
// Foo ::= SEQUENCE {
|
||||
// bar OCTET STRING OPTIONAL,
|
||||
// quux OCTET STRING }
|
||||
//
|
||||
// If we have a DER-encoded Foo in an Input |encoded_value|, the
|
||||
// following code shows an example of how to parse the quux field from the
|
||||
// encoded data.
|
||||
//
|
||||
// bool ReadQuux(const Input& encoded_value, Input* quux_out) {
|
||||
// Parser parser(encoded_value);
|
||||
// Parser foo_parser;
|
||||
// if (!parser.ReadSequence(&foo_parser))
|
||||
// return false;
|
||||
// if (!foo_parser->SkipOptionalTag(kOctetString))
|
||||
// return false;
|
||||
// if (!foo_parser->ReadTag(kOctetString, quux_out))
|
||||
// return false;
|
||||
// return true;
|
||||
// }
|
||||
class OPENSSL_EXPORT Parser {
|
||||
public:
|
||||
// Default constructor; equivalent to calling Parser(Input()). This only
|
||||
// exists so that a Parser can be stack allocated and passed in to
|
||||
// ReadConstructed() and similar methods.
|
||||
Parser();
|
||||
|
||||
// Creates a parser to parse over the data represented by input. This class
|
||||
// assumes that the underlying data will not change over the lifetime of
|
||||
// the Parser object.
|
||||
explicit Parser(const Input& input);
|
||||
|
||||
Parser(const Parser&) = default;
|
||||
Parser& operator=(const Parser&) = default;
|
||||
|
||||
// Returns whether there is any more data left in the input to parse. This
|
||||
// does not guarantee that the data is parseable.
|
||||
bool HasMore();
|
||||
|
||||
// Reads the current TLV from the input and advances. If the tag or length
|
||||
// encoding for the current value is invalid, this method returns false and
|
||||
// does not advance the input. Otherwise, it returns true, putting the
|
||||
// read tag in |tag| and the value in |out|.
|
||||
[[nodiscard]] bool ReadTagAndValue(Tag* tag, Input* out);
|
||||
|
||||
// Reads the current TLV from the input and advances. Unlike ReadTagAndValue
|
||||
// where only the value is put in |out|, this puts the raw bytes from the
|
||||
// tag, length, and value in |out|.
|
||||
[[nodiscard]] bool ReadRawTLV(Input* out);
|
||||
|
||||
// Basic methods for reading or skipping the current TLV, with an
|
||||
// expectation of what the current tag should be. It should be possible
|
||||
// to parse any structure with these 4 methods; convenience methods are also
|
||||
// provided to make some cases easier.
|
||||
|
||||
// If the current tag in the input is |tag|, it puts the corresponding value
|
||||
// in |out| and advances the input to the next TLV. If the current tag is
|
||||
// something else, then |out| is set to nullopt and the input is not
|
||||
// advanced. Like ReadTagAndValue, it returns false if the encoding is
|
||||
// invalid and does not advance the input.
|
||||
[[nodiscard]] bool ReadOptionalTag(Tag tag, std::optional<Input>* out);
|
||||
|
||||
// If the current tag in the input is |tag|, it puts the corresponding value
|
||||
// in |out|, sets |was_present| to true, and advances the input to the next
|
||||
// TLV. If the current tag is something else, then |was_present| is set to
|
||||
// false and the input is not advanced. Like ReadTagAndValue, it returns
|
||||
// false if the encoding is invalid and does not advance the input.
|
||||
// DEPRECATED: use the std::optional version above in new code.
|
||||
// TODO(mattm): convert the existing callers and remove this override.
|
||||
[[nodiscard]] bool ReadOptionalTag(Tag tag, Input* out, bool* was_present);
|
||||
|
||||
// Like ReadOptionalTag, but the value is discarded.
|
||||
[[nodiscard]] bool SkipOptionalTag(Tag tag, bool* was_present);
|
||||
|
||||
// If the current tag matches |tag|, it puts the current value in |out|,
|
||||
// advances the input, and returns true. Otherwise, it returns false.
|
||||
[[nodiscard]] bool ReadTag(Tag tag, Input* out);
|
||||
|
||||
// Advances the input and returns true if the current tag matches |tag|;
|
||||
// otherwise it returns false.
|
||||
[[nodiscard]] bool SkipTag(Tag tag);
|
||||
|
||||
// Convenience methods to combine parsing the TLV with parsing the DER
|
||||
// encoding for a specific type.
|
||||
|
||||
// Reads the current TLV from the input, checks that the tag matches |tag|
|
||||
// and is a constructed tag, and creates a new Parser from the value.
|
||||
[[nodiscard]] bool ReadConstructed(Tag tag, Parser* out);
|
||||
|
||||
// A more specific form of ReadConstructed that expects the current tag
|
||||
// to be 0x30 (SEQUENCE).
|
||||
[[nodiscard]] bool ReadSequence(Parser* out);
|
||||
|
||||
// Expects the current tag to be kInteger, and calls ParseUint8 on the
|
||||
// current value. Note that DER-encoded integers are arbitrary precision,
|
||||
// so this method will fail for valid input that represents an integer
|
||||
// outside the range of an uint8_t.
|
||||
//
|
||||
// Note that on failure the Parser is left in an undefined state (the
|
||||
// input may or may not have been advanced).
|
||||
[[nodiscard]] bool ReadUint8(uint8_t* out);
|
||||
|
||||
// Expects the current tag to be kInteger, and calls ParseUint64 on the
|
||||
// current value. Note that DER-encoded integers are arbitrary precision,
|
||||
// so this method will fail for valid input that represents an integer
|
||||
// outside the range of an uint64_t.
|
||||
//
|
||||
// Note that on failure the Parser is left in an undefined state (the
|
||||
// input may or may not have been advanced).
|
||||
[[nodiscard]] bool ReadUint64(uint64_t* out);
|
||||
|
||||
// Reads a BIT STRING. On success returns BitString. On failure, returns
|
||||
// std::nullopt.
|
||||
//
|
||||
// Note that on failure the Parser is left in an undefined state (the
|
||||
// input may or may not have been advanced).
|
||||
[[nodiscard]] std::optional<BitString> ReadBitString();
|
||||
|
||||
// Reads a GeneralizeTime. On success fills |out| and returns true.
|
||||
//
|
||||
// Note that on failure the Parser is left in an undefined state (the
|
||||
// input may or may not have been advanced).
|
||||
[[nodiscard]] bool ReadGeneralizedTime(GeneralizedTime* out);
|
||||
|
||||
// Lower level methods. The previous methods couple reading data from the
|
||||
// input with advancing the Parser's internal pointer to the next TLV; these
|
||||
// lower level methods decouple those two steps into methods that read from
|
||||
// the current TLV and a method that advances the internal pointer to the
|
||||
// next TLV.
|
||||
|
||||
// Reads the current TLV from the input, putting the tag in |tag| and the raw
|
||||
// value in |out|, but does not advance the input. Returns true if the tag
|
||||
// and length are successfully read and the output exists.
|
||||
[[nodiscard]] bool PeekTagAndValue(Tag* tag, Input* out);
|
||||
|
||||
// Advances the input to the next TLV. This method only needs to be called
|
||||
// after PeekTagAndValue; all other methods will advance the input if they
|
||||
// read something.
|
||||
bool Advance();
|
||||
|
||||
private:
|
||||
CBS cbs_;
|
||||
size_t advance_len_ = 0;
|
||||
};
|
||||
|
||||
} // namespace bssl::der
|
||||
|
||||
#endif // BSSL_DER_PARSER_H_
|
365
pki/parser_unittest.cc
Normal file
365
pki/parser_unittest.cc
Normal file
@ -0,0 +1,365 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace bssl::der::test {
|
||||
|
||||
TEST(ParserTest, ConsumesAllBytesOfTLV) {
|
||||
const uint8_t der[] = {0x04 /* OCTET STRING */, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_EQ(kOctetString, tag);
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, CanReadRawTLV) {
|
||||
const uint8_t der[] = {0x02, 0x01, 0x01};
|
||||
Parser parser((Input(der)));
|
||||
Input tlv;
|
||||
ASSERT_TRUE(parser.ReadRawTLV(&tlv));
|
||||
ByteReader tlv_reader(tlv);
|
||||
size_t tlv_len = tlv_reader.BytesLeft();
|
||||
ASSERT_EQ(3u, tlv_len);
|
||||
Input tlv_data;
|
||||
ASSERT_TRUE(tlv_reader.ReadBytes(tlv_len, &tlv_data));
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, IgnoresContentsOfInnerValues) {
|
||||
// This is a SEQUENCE which has one member. The member is another SEQUENCE
|
||||
// with an invalid encoding - its length is too long.
|
||||
const uint8_t der[] = {0x30, 0x02, 0x30, 0x7e};
|
||||
Parser parser((Input(der)));
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
}
|
||||
|
||||
TEST(ParserTest, FailsIfLengthOverlapsAnotherTLV) {
|
||||
// This DER encoding has 2 top-level TLV tuples. The first is a SEQUENCE;
|
||||
// the second is an INTEGER. The SEQUENCE contains an INTEGER, but its length
|
||||
// is longer than what it has contents for.
|
||||
const uint8_t der[] = {0x30, 0x02, 0x02, 0x01, 0x02, 0x01, 0x01};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Parser inner_sequence;
|
||||
ASSERT_TRUE(parser.ReadSequence(&inner_sequence));
|
||||
uint64_t int_value;
|
||||
ASSERT_TRUE(parser.ReadUint64(&int_value));
|
||||
ASSERT_EQ(1u, int_value);
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
|
||||
// Try to read the INTEGER from the SEQUENCE, which should fail.
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_FALSE(inner_sequence.ReadTagAndValue(&tag, &value));
|
||||
}
|
||||
|
||||
TEST(ParserTest, ReadOptionalTagPresent) {
|
||||
// DER encoding of 2 top-level TLV values:
|
||||
// INTEGER { 1 }
|
||||
// OCTET_STRING { `02` }
|
||||
const uint8_t der[] = {0x02, 0x01, 0x01, 0x04, 0x01, 0x02};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Input value;
|
||||
bool present;
|
||||
ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &value, &present));
|
||||
ASSERT_TRUE(present);
|
||||
const uint8_t expected_int_value[] = {0x01};
|
||||
ASSERT_EQ(Input(expected_int_value), value);
|
||||
|
||||
Tag tag;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_EQ(kOctetString, tag);
|
||||
const uint8_t expected_octet_string_value[] = {0x02};
|
||||
ASSERT_EQ(Input(expected_octet_string_value), value);
|
||||
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, ReadOptionalTag2Present) {
|
||||
// DER encoding of 2 top-level TLV values:
|
||||
// INTEGER { 1 }
|
||||
// OCTET_STRING { `02` }
|
||||
const uint8_t der[] = {0x02, 0x01, 0x01, 0x04, 0x01, 0x02};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
std::optional<Input> optional_value;
|
||||
ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &optional_value));
|
||||
ASSERT_TRUE(optional_value.has_value());
|
||||
const uint8_t expected_int_value[] = {0x01};
|
||||
ASSERT_EQ(Input(expected_int_value), *optional_value);
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_EQ(kOctetString, tag);
|
||||
const uint8_t expected_octet_string_value[] = {0x02};
|
||||
ASSERT_EQ(Input(expected_octet_string_value), value);
|
||||
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, ReadOptionalTagNotPresent) {
|
||||
// DER encoding of 1 top-level TLV value:
|
||||
// OCTET_STRING { `02` }
|
||||
const uint8_t der[] = {0x04, 0x01, 0x02};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Input value;
|
||||
bool present;
|
||||
ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &value, &present));
|
||||
ASSERT_FALSE(present);
|
||||
|
||||
Tag tag;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_EQ(kOctetString, tag);
|
||||
const uint8_t expected_octet_string_value[] = {0x02};
|
||||
ASSERT_EQ(Input(expected_octet_string_value), value);
|
||||
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, ReadOptionalTag2NotPresent) {
|
||||
// DER encoding of 1 top-level TLV value:
|
||||
// OCTET_STRING { `02` }
|
||||
const uint8_t der[] = {0x04, 0x01, 0x02};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
std::optional<Input> optional_value;
|
||||
ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &optional_value));
|
||||
ASSERT_FALSE(optional_value.has_value());
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_EQ(kOctetString, tag);
|
||||
const uint8_t expected_octet_string_value[] = {0x02};
|
||||
ASSERT_EQ(Input(expected_octet_string_value), value);
|
||||
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, CanSkipOptionalTagAtEndOfInput) {
|
||||
const uint8_t der[] = {0x02 /* INTEGER */, 0x01, 0x01};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
bool present;
|
||||
ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &value, &present));
|
||||
ASSERT_FALSE(present);
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, SkipOptionalTagDoesntConsumePresentNonMatchingTLVs) {
|
||||
const uint8_t der[] = {0x02 /* INTEGER */, 0x01, 0x01};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
bool present;
|
||||
ASSERT_TRUE(parser.SkipOptionalTag(kOctetString, &present));
|
||||
ASSERT_FALSE(present);
|
||||
ASSERT_TRUE(parser.SkipOptionalTag(kInteger, &present));
|
||||
ASSERT_TRUE(present);
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, TagNumbersAboveThirtySupported) {
|
||||
// Context-specific class, tag number 31, length 0.
|
||||
const uint8_t der[] = {0x9f, 0x1f, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
EXPECT_EQ(kTagContextSpecific | 31u, tag);
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, ParseTags) {
|
||||
{
|
||||
// Universal primitive tag, tag number 4.
|
||||
const uint8_t der[] = {0x04, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
EXPECT_EQ(kOctetString, tag);
|
||||
}
|
||||
|
||||
{
|
||||
// Universal constructed tag, tag number 16.
|
||||
const uint8_t der[] = {0x30, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
EXPECT_EQ(kSequence, tag);
|
||||
}
|
||||
|
||||
{
|
||||
// Application primitive tag, tag number 1.
|
||||
const uint8_t der[] = {0x41, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
EXPECT_EQ(kTagApplication | 1, tag);
|
||||
}
|
||||
|
||||
{
|
||||
// Context-specific constructed tag, tag number 30.
|
||||
const uint8_t der[] = {0xbe, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
EXPECT_EQ(kTagContextSpecific | kTagConstructed | 30, tag);
|
||||
}
|
||||
|
||||
{
|
||||
// Private primitive tag, tag number 15.
|
||||
const uint8_t der[] = {0xcf, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
|
||||
EXPECT_EQ(kTagPrivate | 15, tag);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ParserTest, IncompleteEncodingTagOnly) {
|
||||
const uint8_t der[] = {0x01};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_TRUE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, IncompleteEncodingLengthTruncated) {
|
||||
// Tag: octet string; length: long form, should have 2 total octets, but
|
||||
// the last one is missing. (There's also no value.)
|
||||
const uint8_t der[] = {0x04, 0x81};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_TRUE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, IncompleteEncodingValueShorterThanLength) {
|
||||
// Tag: octet string; length: 2; value: first octet 'T', second octet missing.
|
||||
const uint8_t der[] = {0x04, 0x02, 0x84};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_TRUE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, LengthMustBeEncodedWithMinimumNumberOfOctets) {
|
||||
const uint8_t der[] = {0x01, 0x81, 0x01, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_TRUE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, LengthMustNotHaveLeadingZeroes) {
|
||||
// Tag: octet string; length: 3 bytes of length encoding a value of 128
|
||||
// (it should be encoded in only 2 bytes). Value: 128 bytes of 0.
|
||||
const uint8_t der[] = {
|
||||
0x04, 0x83, 0x80, 0x81, 0x80, // group the 0s separately
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag tag;
|
||||
Input value;
|
||||
ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
|
||||
ASSERT_TRUE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, ReadConstructedFailsForNonConstructedTags) {
|
||||
// Tag number is for SEQUENCE, but the constructed bit isn't set.
|
||||
const uint8_t der[] = {0x10, 0x00};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Tag expected_tag = 0x10;
|
||||
Parser sequence_parser;
|
||||
ASSERT_FALSE(parser.ReadConstructed(expected_tag, &sequence_parser));
|
||||
|
||||
// Check that we didn't fail above because of a tag mismatch or an improperly
|
||||
// encoded TLV.
|
||||
Input value;
|
||||
ASSERT_TRUE(parser.ReadTag(expected_tag, &value));
|
||||
ASSERT_FALSE(parser.HasMore());
|
||||
}
|
||||
|
||||
TEST(ParserTest, CannotAdvanceAfterReadOptionalTag) {
|
||||
const uint8_t der[] = {0x02, 0x01, 0x01};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
Input value;
|
||||
bool present;
|
||||
ASSERT_TRUE(parser.ReadOptionalTag(0x04, &value, &present));
|
||||
ASSERT_FALSE(present);
|
||||
ASSERT_FALSE(parser.Advance());
|
||||
}
|
||||
|
||||
// Reads a valid BIT STRING with 1 unused bit.
|
||||
TEST(ParserTest, ReadBitString) {
|
||||
const uint8_t der[] = {0x03, 0x03, 0x01, 0xAA, 0xBE};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
std::optional<BitString> bit_string = parser.ReadBitString();
|
||||
ASSERT_TRUE(bit_string.has_value());
|
||||
EXPECT_FALSE(parser.HasMore());
|
||||
|
||||
EXPECT_EQ(1u, bit_string->unused_bits());
|
||||
ASSERT_EQ(2u, bit_string->bytes().Length());
|
||||
EXPECT_EQ(0xAA, bit_string->bytes().UnsafeData()[0]);
|
||||
EXPECT_EQ(0xBE, bit_string->bytes().UnsafeData()[1]);
|
||||
}
|
||||
|
||||
// Tries reading a BIT STRING. This should fail because the tag is not for a
|
||||
// BIT STRING.
|
||||
TEST(ParserTest, ReadBitStringBadTag) {
|
||||
const uint8_t der[] = {0x05, 0x03, 0x01, 0xAA, 0xBE};
|
||||
Parser parser((Input(der)));
|
||||
|
||||
std::optional<BitString> bit_string = parser.ReadBitString();
|
||||
EXPECT_FALSE(bit_string.has_value());
|
||||
}
|
||||
|
||||
} // namespace bssl::der::test
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,221 @@
|
||||
From 8776d775ed86427e37b17ce1ea4728e99cb45275 Mon Sep 17 00:00:00 2001
|
||||
From: Bob Beck <bbe@google.com>
|
||||
Date: Thu, 1 Jun 2023 10:54:40 +0200
|
||||
Subject: [PATCH 2/3] Conditionalize the use of DVLOG/LOG in path builder on
|
||||
DVLOG existing
|
||||
|
||||
---
|
||||
net/cert/pki/path_builder.cc | 35 +++++++++++++++++++++
|
||||
net/cert/pki/path_builder_pkits_unittest.cc | 2 ++
|
||||
2 files changed, 37 insertions(+)
|
||||
|
||||
diff --git a/net/cert/pki/path_builder.cc b/net/cert/pki/path_builder.cc
|
||||
index c373c8d4cda99..8b84fca03e962 100644
|
||||
--- a/net/cert/pki/path_builder.cc
|
||||
+++ b/net/cert/pki/path_builder.cc
|
||||
@@ -37,6 +37,7 @@ std::string FingerPrintParsedCertificate(const net::ParsedCertificate* cert) {
|
||||
}
|
||||
|
||||
// TODO(mattm): decide how much debug logging to keep.
|
||||
+// TODO(bbe): perhaps none - currently conditionalizing on DVLOG..
|
||||
std::string CertDebugString(const ParsedCertificate* cert) {
|
||||
RDNSequence subject;
|
||||
std::string subject_str;
|
||||
@@ -47,6 +48,7 @@ std::string CertDebugString(const ParsedCertificate* cert) {
|
||||
return FingerPrintParsedCertificate(cert) + " " + subject_str;
|
||||
}
|
||||
|
||||
+#if defined(DVLOG)
|
||||
std::string PathDebugString(const ParsedCertificateList& certs) {
|
||||
std::string s;
|
||||
for (const auto& cert : certs) {
|
||||
@@ -56,6 +58,7 @@ std::string PathDebugString(const ParsedCertificateList& certs) {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
+#endif
|
||||
|
||||
// This structure describes a certificate and its trust level. Note that |cert|
|
||||
// may be null to indicate an "empty" entry.
|
||||
@@ -249,7 +252,9 @@ CertIssuersIter::CertIssuersIter(
|
||||
cert_issuer_sources_(cert_issuer_sources),
|
||||
trust_store_(trust_store),
|
||||
debug_data_(debug_data) {
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(2) << "CertIssuersIter created for " << CertDebugString(cert());
|
||||
+#endif
|
||||
}
|
||||
|
||||
void CertIssuersIter::GetNextIssuer(IssuerEntry* out) {
|
||||
@@ -289,8 +294,10 @@ void CertIssuersIter::GetNextIssuer(IssuerEntry* out) {
|
||||
if (HasCurrentIssuer()) {
|
||||
SortRemainingIssuers();
|
||||
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(2) << "CertIssuersIter returning issuer " << cur_issuer_ << " of "
|
||||
<< issuers_.size() << " for " << CertDebugString(cert());
|
||||
+#endif
|
||||
// Still have issuers that haven't been returned yet, return the highest
|
||||
// priority one (head of remaining list). A reference to the returned issuer
|
||||
// is retained, since |present_issuers_| points to data owned by it.
|
||||
@@ -298,8 +305,10 @@ void CertIssuersIter::GetNextIssuer(IssuerEntry* out) {
|
||||
return;
|
||||
}
|
||||
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(2) << "CertIssuersIter reached the end of all available issuers for "
|
||||
<< CertDebugString(cert());
|
||||
+#endif
|
||||
// Reached the end of all available issuers.
|
||||
*out = IssuerEntry();
|
||||
}
|
||||
@@ -331,7 +340,9 @@ void CertIssuersIter::DoAsyncIssuerQuery() {
|
||||
std::unique_ptr<CertIssuerSource::Request> request;
|
||||
cert_issuer_source->AsyncGetIssuersOf(cert(), &request);
|
||||
if (request) {
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "AsyncGetIssuersOf pending for " << CertDebugString(cert());
|
||||
+#endif
|
||||
pending_async_requests_.push_back(std::move(request));
|
||||
}
|
||||
}
|
||||
@@ -558,16 +569,20 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
cur_path_.Length() >= max_path_building_depth) {
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_errors->GetOtherErrors()->AddError(cert_errors::kDepthLimitExceeded);
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter reached depth limit. Returning partial path "
|
||||
"and backtracking:\n"
|
||||
<< PathDebugString(*out_certs);
|
||||
+#endif
|
||||
cur_path_.Pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!next_issuer_.cert) {
|
||||
if (cur_path_.Empty()) {
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter exhausted all paths...";
|
||||
+#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -587,14 +602,18 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_errors->GetErrorsForCert(out_certs->size() - 1)
|
||||
->AddError(cert_errors::kNoIssuersFound);
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter returning partial path and backtracking:\n"
|
||||
<< PathDebugString(*out_certs);
|
||||
+#endif
|
||||
cur_path_.Pop();
|
||||
return true;
|
||||
} else {
|
||||
// No more issuers for current chain, go back up and see if there are
|
||||
// any more for the previous cert.
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter backtracking...";
|
||||
+#endif
|
||||
cur_path_.Pop();
|
||||
continue;
|
||||
}
|
||||
@@ -610,7 +629,9 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
// unspecified trust. This may allow a successful path to be built to a
|
||||
// different root (or to the same cert if it's self-signed).
|
||||
if (cur_path_.Empty()) {
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "Leaf is a trust anchor, considering as UNSPECIFIED";
|
||||
+#endif
|
||||
next_issuer_.trust = CertificateTrust::ForUnspecified();
|
||||
}
|
||||
break;
|
||||
@@ -619,7 +640,9 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
// unspecified trust. This may allow a successful path to be built to a
|
||||
// trusted root.
|
||||
if (!cur_path_.Empty()) {
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "Issuer is a trust leaf, considering as UNSPECIFIED";
|
||||
+#endif
|
||||
next_issuer_.trust = CertificateTrust::ForUnspecified();
|
||||
}
|
||||
break;
|
||||
@@ -639,8 +662,10 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
!VerifyCertificateIsSelfSigned(*next_issuer_.cert,
|
||||
delegate->GetVerifyCache(),
|
||||
/*errors=*/nullptr)) {
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "Leaf is trusted with require_leaf_selfsigned but is "
|
||||
"not self-signed, considering as UNSPECIFIED";
|
||||
+#endif
|
||||
next_issuer_.trust = CertificateTrust::ForUnspecified();
|
||||
}
|
||||
break;
|
||||
@@ -660,12 +685,16 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
case CertificateTrustType::TRUSTED_ANCHOR_OR_LEAF:
|
||||
case CertificateTrustType::TRUSTED_LEAF: {
|
||||
// If the issuer has a known trust level, can stop building the path.
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(2) << "CertPathIter got anchor: "
|
||||
<< CertDebugString(next_issuer_.cert.get());
|
||||
+#endif
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_certs->push_back(std::move(next_issuer_.cert));
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter returning path:\n"
|
||||
<< PathDebugString(*out_certs);
|
||||
+#endif
|
||||
*out_last_cert_trust = next_issuer_.trust;
|
||||
next_issuer_ = IssuerEntry();
|
||||
return true;
|
||||
@@ -674,8 +703,10 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
// Skip this cert if it is already in the chain.
|
||||
if (cur_path_.IsPresent(next_issuer_.cert.get())) {
|
||||
cur_path_.back()->increment_skipped_issuer_count();
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter skipping dupe cert: "
|
||||
<< CertDebugString(next_issuer_.cert.get());
|
||||
+#endif
|
||||
next_issuer_ = IssuerEntry();
|
||||
continue;
|
||||
}
|
||||
@@ -684,7 +715,9 @@ bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
std::move(next_issuer_.cert), &cert_issuer_sources_, trust_store_,
|
||||
debug_data_));
|
||||
next_issuer_ = IssuerEntry();
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter cur_path_ =\n" << cur_path_.PathDebugString();
|
||||
+#endif
|
||||
// Continue descending the tree.
|
||||
continue;
|
||||
}
|
||||
@@ -832,8 +865,10 @@ CertPathBuilder::Result CertPathBuilder::Run() {
|
||||
&result_path->user_constrained_policy_set, &result_path->errors);
|
||||
}
|
||||
|
||||
+#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathBuilder VerifyCertificateChain errors:\n"
|
||||
<< result_path->errors.ToDebugString(result_path->certs);
|
||||
+#endif
|
||||
|
||||
// Give the delegate a chance to add errors to the path.
|
||||
delegate_->CheckPathAfterVerification(*this, result_path.get());
|
||||
diff --git a/net/cert/pki/path_builder_pkits_unittest.cc b/net/cert/pki/path_builder_pkits_unittest.cc
|
||||
index 090cce0cbd52b..c8fd0bd993996 100644
|
||||
--- a/net/cert/pki/path_builder_pkits_unittest.cc
|
||||
+++ b/net/cert/pki/path_builder_pkits_unittest.cc
|
||||
@@ -232,10 +232,12 @@ class PathBuilderPkitsTestDelegate {
|
||||
|
||||
if (info.should_validate != result.HasValidPath()) {
|
||||
for (size_t i = 0; i < result.paths.size(); ++i) {
|
||||
+#if defined(DVLOG)
|
||||
const net::CertPathBuilderResultPath* result_path =
|
||||
result.paths[i].get();
|
||||
LOG(ERROR) << "path " << i << " errors:\n"
|
||||
<< result_path->errors.ToDebugString(result_path->certs);
|
||||
+#endif
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.41.0.162.gfafddb0af9-goog
|
||||
|
@ -0,0 +1,112 @@
|
||||
From 9a38f227afca29cb3e373ca973d2f68126a0145e Mon Sep 17 00:00:00 2001
|
||||
From: Bob Beck <bbe@google.com>
|
||||
Date: Fri, 2 Jun 2023 11:08:50 +0200
|
||||
Subject: [PATCH 3/3] disable path builder tests with unsupported dependencies
|
||||
|
||||
---
|
||||
net/cert/pki/path_builder_unittest.cc | 12 ++++++++++++
|
||||
1 file changed, 12 insertions(+)
|
||||
|
||||
diff --git a/net/cert/pki/path_builder_unittest.cc b/net/cert/pki/path_builder_unittest.cc
|
||||
index d2cf0626cd474..112d0cafd811b 100644
|
||||
--- a/net/cert/pki/path_builder_unittest.cc
|
||||
+++ b/net/cert/pki/path_builder_unittest.cc
|
||||
@@ -34,6 +34,7 @@ namespace net {
|
||||
|
||||
namespace {
|
||||
|
||||
+#if !defined(_BORINGSSL_LIBPKI_)
|
||||
using ::testing::_;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Exactly;
|
||||
@@ -43,6 +44,7 @@ using ::testing::Return;
|
||||
using ::testing::SaveArg;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
+#endif // !_BORINGSSL_LIBPKI_
|
||||
|
||||
class TestPathBuilderDelegate : public SimplePathBuilderDelegate {
|
||||
public:
|
||||
@@ -161,6 +163,7 @@ class AsyncCertIssuerSourceStatic : public CertIssuerSource {
|
||||
}
|
||||
|
||||
const void* kKey = &kKey;
|
||||
+#if !defined(_BORINGSSL_LIBPKI_)
|
||||
class TrustStoreThatStoresUserData : public TrustStore {
|
||||
public:
|
||||
class Data : public base::SupportsUserData::Data {
|
||||
@@ -202,6 +205,7 @@ TEST(PathBuilderResultUserDataTest, ModifyUserDataInConstructor) {
|
||||
ASSERT_TRUE(data);
|
||||
EXPECT_EQ(1234, data->value);
|
||||
}
|
||||
+#endif // !_BORINGSSL_LIBPKI_
|
||||
|
||||
class PathBuilderMultiRootTest : public ::testing::Test {
|
||||
public:
|
||||
@@ -1566,6 +1570,7 @@ TEST_F(PathBuilderKeyRolloverTest, TestDuplicateIntermediateAndRoot) {
|
||||
EXPECT_EQ(newroot_->der_cert(), path.certs[2]->der_cert());
|
||||
}
|
||||
|
||||
+#if !defined(_BORINGSSL_LIBPKI_)
|
||||
class MockCertIssuerSourceRequest : public CertIssuerSource::Request {
|
||||
public:
|
||||
MOCK_METHOD2(GetNext, void(ParsedCertificateList*, base::SupportsUserData*));
|
||||
@@ -1578,6 +1583,7 @@ class MockCertIssuerSource : public CertIssuerSource {
|
||||
MOCK_METHOD2(AsyncGetIssuersOf,
|
||||
void(const ParsedCertificate*, std::unique_ptr<Request>*));
|
||||
};
|
||||
+#endif // !_BORINGSSL_LIBPKI_
|
||||
|
||||
// Helper class to pass the Request to the PathBuilder when it calls
|
||||
// AsyncGetIssuersOf. (GoogleMock has a ByMove helper, but it apparently can
|
||||
@@ -1613,6 +1619,7 @@ class AppendCertToList {
|
||||
std::shared_ptr<const ParsedCertificate> cert_;
|
||||
};
|
||||
|
||||
+#if !defined(_BORINGSSL_LIBPKI_)
|
||||
// Test that a single CertIssuerSource returning multiple async batches of
|
||||
// issuers is handled correctly. Due to the StrictMocks, it also tests that path
|
||||
// builder does not request issuers of certs that it shouldn't.
|
||||
@@ -1782,6 +1789,7 @@ TEST_F(PathBuilderKeyRolloverTest, TestDuplicateAsyncIntermediates) {
|
||||
EXPECT_EQ(newintermediate_, path1.certs[1]);
|
||||
EXPECT_EQ(newroot_, path1.certs[2]);
|
||||
}
|
||||
+#endif // !_BORINGSSL_LIBPKI_
|
||||
|
||||
class PathBuilderSimpleChainTest : public ::testing::Test {
|
||||
public:
|
||||
@@ -1936,6 +1944,7 @@ class CertPathBuilderDelegateBase : public SimplePathBuilderDelegate {
|
||||
}
|
||||
};
|
||||
|
||||
+#if !defined(_BORINGSSL_LIBPKI_)
|
||||
class MockPathBuilderDelegate : public CertPathBuilderDelegateBase {
|
||||
public:
|
||||
MOCK_METHOD2(CheckPathAfterVerification,
|
||||
@@ -1951,6 +1960,7 @@ TEST_F(PathBuilderCheckPathAfterVerificationTest, NoOpToValidPath) {
|
||||
CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate);
|
||||
EXPECT_TRUE(result.HasValidPath());
|
||||
}
|
||||
+#endif // !_BORINGSSL_LIBPKI_
|
||||
|
||||
DEFINE_CERT_ERROR_ID(kWarningFromDelegate, "Warning from delegate");
|
||||
|
||||
@@ -2002,6 +2012,7 @@ TEST_F(PathBuilderCheckPathAfterVerificationTest, AddsErrorToValidPath) {
|
||||
EXPECT_TRUE(cert2_errors->ContainsError(kErrorFromDelegate));
|
||||
}
|
||||
|
||||
+#if !defined(_BORINGSSL_LIBPKI_)
|
||||
TEST_F(PathBuilderCheckPathAfterVerificationTest, NoopToAlreadyInvalidPath) {
|
||||
StrictMock<MockPathBuilderDelegate> delegate;
|
||||
// Just verify that the hook is called (on an invalid path).
|
||||
@@ -2034,6 +2045,7 @@ TEST_F(PathBuilderCheckPathAfterVerificationTest, SetsDelegateData) {
|
||||
|
||||
EXPECT_EQ(0xB33F, data->value);
|
||||
}
|
||||
+#endif // !_BORINGSSL_LIBPKI_
|
||||
|
||||
TEST(PathBuilderPrioritizationTest, DatePrioritization) {
|
||||
std::string test_dir =
|
||||
--
|
||||
2.41.0.162.gfafddb0af9-goog
|
||||
|
914
pki/path_builder.cc
Normal file
914
pki/path_builder.cc
Normal file
@ -0,0 +1,914 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "path_builder.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "fillins/net_errors.h"
|
||||
|
||||
#include "cert_issuer_source.h"
|
||||
#include "certificate_policies.h"
|
||||
#include "common_cert_errors.h"
|
||||
#include "parse_certificate.h"
|
||||
#include "parse_name.h" // For CertDebugString.
|
||||
#include "string_util.h"
|
||||
#include "trust_store.h"
|
||||
#include "verify_certificate_chain.h"
|
||||
#include "verify_name_match.h"
|
||||
#include "parser.h"
|
||||
#include "tag.h"
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
using CertIssuerSources = std::vector<CertIssuerSource*>;
|
||||
|
||||
// Returns a hex-encoded sha256 of the DER-encoding of |cert|.
|
||||
std::string FingerPrintParsedCertificate(const ParsedCertificate* cert) {
|
||||
uint8_t digest[SHA256_DIGEST_LENGTH];
|
||||
SHA256(cert->der_cert().UnsafeData(), cert->der_cert().Length(), digest);
|
||||
return bssl::string_util::HexEncode(digest, sizeof(digest));
|
||||
}
|
||||
|
||||
// TODO(mattm): decide how much debug logging to keep.
|
||||
// TODO(bbe): perhaps none - currently conditionalizing on DVLOG..
|
||||
std::string CertDebugString(const ParsedCertificate* cert) {
|
||||
RDNSequence subject;
|
||||
std::string subject_str;
|
||||
if (!ParseName(cert->tbs().subject_tlv, &subject) ||
|
||||
!ConvertToRFC2253(subject, &subject_str))
|
||||
subject_str = "???";
|
||||
|
||||
return FingerPrintParsedCertificate(cert) + " " + subject_str;
|
||||
}
|
||||
|
||||
#if defined(DVLOG)
|
||||
std::string PathDebugString(const ParsedCertificateList& certs) {
|
||||
std::string s;
|
||||
for (const auto& cert : certs) {
|
||||
if (!s.empty())
|
||||
s += "\n";
|
||||
s += " " + CertDebugString(cert.get());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
// This structure describes a certificate and its trust level. Note that |cert|
|
||||
// may be null to indicate an "empty" entry.
|
||||
struct IssuerEntry {
|
||||
std::shared_ptr<const ParsedCertificate> cert;
|
||||
CertificateTrust trust;
|
||||
int trust_and_key_id_match_ordering;
|
||||
};
|
||||
|
||||
enum KeyIdentifierMatch {
|
||||
// |target| has a keyIdentifier and it matches |issuer|'s
|
||||
// subjectKeyIdentifier.
|
||||
kMatch = 0,
|
||||
// |target| does not have authorityKeyIdentifier or |issuer| does not have
|
||||
// subjectKeyIdentifier.
|
||||
kNoData = 1,
|
||||
// |target|'s authorityKeyIdentifier does not match |issuer|.
|
||||
kMismatch = 2,
|
||||
};
|
||||
|
||||
// Returns an integer that represents the relative ordering of |issuer| for
|
||||
// prioritizing certificates in path building based on |issuer|'s
|
||||
// subjectKeyIdentifier and |target|'s authorityKeyIdentifier. Lower return
|
||||
// values indicate higer priority.
|
||||
KeyIdentifierMatch CalculateKeyIdentifierMatch(
|
||||
const ParsedCertificate* target,
|
||||
const ParsedCertificate* issuer) {
|
||||
if (!target->authority_key_identifier())
|
||||
return kNoData;
|
||||
|
||||
// TODO(crbug.com/635205): If issuer does not have a subjectKeyIdentifier,
|
||||
// could try synthesizing one using the standard SHA-1 method. Ideally in a
|
||||
// way where any issuers that do have a matching subjectKeyIdentifier could
|
||||
// be tried first before doing the extra work.
|
||||
if (target->authority_key_identifier()->key_identifier &&
|
||||
issuer->subject_key_identifier()) {
|
||||
if (target->authority_key_identifier()->key_identifier !=
|
||||
issuer->subject_key_identifier().value()) {
|
||||
return kMismatch;
|
||||
}
|
||||
return kMatch;
|
||||
}
|
||||
|
||||
return kNoData;
|
||||
}
|
||||
|
||||
// Returns an integer that represents the relative ordering of |issuer| based
|
||||
// on |issuer_trust| and authorityKeyIdentifier matching for prioritizing
|
||||
// certificates in path building. Lower return values indicate higer priority.
|
||||
int TrustAndKeyIdentifierMatchToOrder(const ParsedCertificate* target,
|
||||
const ParsedCertificate* issuer,
|
||||
const CertificateTrust& issuer_trust) {
|
||||
enum {
|
||||
kTrustedAndKeyIdMatch = 0,
|
||||
kTrustedAndKeyIdNoData = 1,
|
||||
kKeyIdMatch = 2,
|
||||
kKeyIdNoData = 3,
|
||||
kTrustedAndKeyIdMismatch = 4,
|
||||
kKeyIdMismatch = 5,
|
||||
kDistrustedAndKeyIdMatch = 6,
|
||||
kDistrustedAndKeyIdNoData = 7,
|
||||
kDistrustedAndKeyIdMismatch = 8,
|
||||
};
|
||||
|
||||
KeyIdentifierMatch key_id_match = CalculateKeyIdentifierMatch(target, issuer);
|
||||
switch (issuer_trust.type) {
|
||||
case CertificateTrustType::TRUSTED_ANCHOR:
|
||||
case CertificateTrustType::TRUSTED_ANCHOR_OR_LEAF:
|
||||
switch (key_id_match) {
|
||||
case kMatch:
|
||||
return kTrustedAndKeyIdMatch;
|
||||
case kNoData:
|
||||
return kTrustedAndKeyIdNoData;
|
||||
case kMismatch:
|
||||
return kTrustedAndKeyIdMismatch;
|
||||
}
|
||||
break;
|
||||
case CertificateTrustType::UNSPECIFIED:
|
||||
case CertificateTrustType::TRUSTED_LEAF:
|
||||
switch (key_id_match) {
|
||||
case kMatch:
|
||||
return kKeyIdMatch;
|
||||
case kNoData:
|
||||
return kKeyIdNoData;
|
||||
case kMismatch:
|
||||
return kKeyIdMismatch;
|
||||
}
|
||||
break;
|
||||
case CertificateTrustType::DISTRUSTED:
|
||||
switch (key_id_match) {
|
||||
case kMatch:
|
||||
return kDistrustedAndKeyIdMatch;
|
||||
case kNoData:
|
||||
return kDistrustedAndKeyIdNoData;
|
||||
case kMismatch:
|
||||
return kDistrustedAndKeyIdMismatch;
|
||||
}
|
||||
break;
|
||||
}
|
||||
assert(0); // NOTREACHED
|
||||
return -1;
|
||||
}
|
||||
|
||||
// CertIssuersIter iterates through the intermediates from |cert_issuer_sources|
|
||||
// which may be issuers of |cert|.
|
||||
class CertIssuersIter {
|
||||
public:
|
||||
// Constructs the CertIssuersIter. |*cert_issuer_sources|, |*trust_store|,
|
||||
// and |*debug_data| must be valid for the lifetime of the CertIssuersIter.
|
||||
CertIssuersIter(std::shared_ptr<const ParsedCertificate> cert,
|
||||
CertIssuerSources* cert_issuer_sources,
|
||||
TrustStore* trust_store,
|
||||
void* debug_data);
|
||||
|
||||
CertIssuersIter(const CertIssuersIter&) = delete;
|
||||
CertIssuersIter& operator=(const CertIssuersIter&) = delete;
|
||||
|
||||
// Gets the next candidate issuer, or clears |*out| when all issuers have been
|
||||
// exhausted.
|
||||
void GetNextIssuer(IssuerEntry* out);
|
||||
|
||||
// Returns true if candidate issuers were found for |cert_|.
|
||||
bool had_non_skipped_issuers() const {
|
||||
return issuers_.size() > skipped_issuer_count_;
|
||||
}
|
||||
|
||||
void increment_skipped_issuer_count() { skipped_issuer_count_++; }
|
||||
|
||||
// Returns the |cert| for which issuers are being retrieved.
|
||||
const ParsedCertificate* cert() const { return cert_.get(); }
|
||||
std::shared_ptr<const ParsedCertificate> reference_cert() const {
|
||||
return cert_;
|
||||
}
|
||||
|
||||
private:
|
||||
void AddIssuers(ParsedCertificateList issuers);
|
||||
void DoAsyncIssuerQuery();
|
||||
|
||||
// Returns true if |issuers_| contains unconsumed certificates.
|
||||
bool HasCurrentIssuer() const { return cur_issuer_ < issuers_.size(); }
|
||||
|
||||
// Sorts the remaining entries in |issuers_| in the preferred order to
|
||||
// explore. Does not change the ordering for indices before cur_issuer_.
|
||||
void SortRemainingIssuers();
|
||||
|
||||
std::shared_ptr<const ParsedCertificate> cert_;
|
||||
CertIssuerSources* cert_issuer_sources_;
|
||||
TrustStore* trust_store_;
|
||||
|
||||
// The list of issuers for |cert_|. This is added to incrementally (first
|
||||
// synchronous results, then possibly multiple times as asynchronous results
|
||||
// arrive.) The issuers may be re-sorted each time new issuers are added, but
|
||||
// only the results from |cur_| onwards should be sorted, since the earlier
|
||||
// results were already returned.
|
||||
// Elements should not be removed from |issuers_| once added, since
|
||||
// |present_issuers_| will point to data owned by the certs.
|
||||
std::vector<IssuerEntry> issuers_;
|
||||
// The index of the next cert in |issuers_| to return.
|
||||
size_t cur_issuer_ = 0;
|
||||
// The number of issuers that were skipped due to the loop checker.
|
||||
size_t skipped_issuer_count_ = 0;
|
||||
// Set to true whenever new issuers are appended at the end, to indicate the
|
||||
// ordering needs to be checked.
|
||||
bool issuers_needs_sort_ = false;
|
||||
|
||||
// Set of DER-encoded values for the certs in |issuers_|. Used to prevent
|
||||
// duplicates. This is based on the full DER of the cert to allow different
|
||||
// versions of the same certificate to be tried in different candidate paths.
|
||||
// This points to data owned by |issuers_|.
|
||||
std::unordered_set<std::string_view> present_issuers_;
|
||||
|
||||
// Tracks which requests have been made yet.
|
||||
bool did_initial_query_ = false;
|
||||
bool did_async_issuer_query_ = false;
|
||||
// Index into pending_async_requests_ that is the next one to process.
|
||||
size_t cur_async_request_ = 0;
|
||||
// Owns the Request objects for any asynchronous requests so that they will be
|
||||
// cancelled if CertIssuersIter is destroyed.
|
||||
std::vector<std::unique_ptr<CertIssuerSource::Request>>
|
||||
pending_async_requests_;
|
||||
|
||||
void* debug_data_;
|
||||
};
|
||||
|
||||
CertIssuersIter::CertIssuersIter(
|
||||
std::shared_ptr<const ParsedCertificate> in_cert,
|
||||
CertIssuerSources* cert_issuer_sources,
|
||||
TrustStore* trust_store,
|
||||
void* debug_data)
|
||||
: cert_(std::move(in_cert)),
|
||||
cert_issuer_sources_(cert_issuer_sources),
|
||||
trust_store_(trust_store),
|
||||
debug_data_(debug_data) {
|
||||
#if defined(DVLOG)
|
||||
DVLOG(2) << "CertIssuersIter created for " << CertDebugString(cert());
|
||||
#endif
|
||||
}
|
||||
|
||||
void CertIssuersIter::GetNextIssuer(IssuerEntry* out) {
|
||||
if (!did_initial_query_) {
|
||||
did_initial_query_ = true;
|
||||
for (auto* cert_issuer_source : *cert_issuer_sources_) {
|
||||
ParsedCertificateList new_issuers;
|
||||
cert_issuer_source->SyncGetIssuersOf(cert(), &new_issuers);
|
||||
AddIssuers(std::move(new_issuers));
|
||||
}
|
||||
}
|
||||
|
||||
// If there aren't any issuers, block until async results are ready.
|
||||
if (!HasCurrentIssuer()) {
|
||||
if (!did_async_issuer_query_) {
|
||||
// Now issue request(s) for async ones (AIA, etc).
|
||||
DoAsyncIssuerQuery();
|
||||
}
|
||||
|
||||
// TODO(eroman): Rather than blocking on the async requests in FIFO order,
|
||||
// consume in the order they become ready.
|
||||
while (!HasCurrentIssuer() &&
|
||||
cur_async_request_ < pending_async_requests_.size()) {
|
||||
ParsedCertificateList new_issuers;
|
||||
pending_async_requests_[cur_async_request_]->GetNext(&new_issuers,
|
||||
debug_data_);
|
||||
if (new_issuers.empty()) {
|
||||
// Request is exhausted, no more results pending from that
|
||||
// CertIssuerSource.
|
||||
pending_async_requests_[cur_async_request_++].reset();
|
||||
} else {
|
||||
AddIssuers(std::move(new_issuers));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (HasCurrentIssuer()) {
|
||||
SortRemainingIssuers();
|
||||
|
||||
#if defined(DVLOG)
|
||||
DVLOG(2) << "CertIssuersIter returning issuer " << cur_issuer_ << " of "
|
||||
<< issuers_.size() << " for " << CertDebugString(cert());
|
||||
#endif
|
||||
// Still have issuers that haven't been returned yet, return the highest
|
||||
// priority one (head of remaining list). A reference to the returned issuer
|
||||
// is retained, since |present_issuers_| points to data owned by it.
|
||||
*out = issuers_[cur_issuer_++];
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(DVLOG)
|
||||
DVLOG(2) << "CertIssuersIter reached the end of all available issuers for "
|
||||
<< CertDebugString(cert());
|
||||
#endif
|
||||
// Reached the end of all available issuers.
|
||||
*out = IssuerEntry();
|
||||
}
|
||||
|
||||
void CertIssuersIter::AddIssuers(ParsedCertificateList new_issuers) {
|
||||
for (std::shared_ptr<const ParsedCertificate>& issuer : new_issuers) {
|
||||
if (present_issuers_.find(issuer->der_cert().AsStringView()) !=
|
||||
present_issuers_.end())
|
||||
continue;
|
||||
present_issuers_.insert(issuer->der_cert().AsStringView());
|
||||
|
||||
// Look up the trust for this issuer.
|
||||
IssuerEntry entry;
|
||||
entry.cert = std::move(issuer);
|
||||
entry.trust = trust_store_->GetTrust(entry.cert.get(), debug_data_);
|
||||
entry.trust_and_key_id_match_ordering = TrustAndKeyIdentifierMatchToOrder(
|
||||
cert(), entry.cert.get(), entry.trust);
|
||||
|
||||
issuers_.push_back(std::move(entry));
|
||||
issuers_needs_sort_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CertIssuersIter::DoAsyncIssuerQuery() {
|
||||
DCHECK(!did_async_issuer_query_);
|
||||
did_async_issuer_query_ = true;
|
||||
cur_async_request_ = 0;
|
||||
for (auto* cert_issuer_source : *cert_issuer_sources_) {
|
||||
std::unique_ptr<CertIssuerSource::Request> request;
|
||||
cert_issuer_source->AsyncGetIssuersOf(cert(), &request);
|
||||
if (request) {
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "AsyncGetIssuersOf pending for " << CertDebugString(cert());
|
||||
#endif
|
||||
pending_async_requests_.push_back(std::move(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CertIssuersIter::SortRemainingIssuers() {
|
||||
if (!issuers_needs_sort_)
|
||||
return;
|
||||
|
||||
std::stable_sort(
|
||||
issuers_.begin() + cur_issuer_, issuers_.end(),
|
||||
[](const IssuerEntry& issuer1, const IssuerEntry& issuer2) {
|
||||
// TODO(crbug.com/635205): Add other prioritization hints. (See big list
|
||||
// of possible sorting hints in RFC 4158.)
|
||||
const bool issuer1_self_issued = issuer1.cert->normalized_subject() ==
|
||||
issuer1.cert->normalized_issuer();
|
||||
const bool issuer2_self_issued = issuer2.cert->normalized_subject() ==
|
||||
issuer2.cert->normalized_issuer();
|
||||
return std::tie(issuer1.trust_and_key_id_match_ordering,
|
||||
issuer2_self_issued,
|
||||
// Newer(larger) notBefore & notAfter dates are
|
||||
// preferred, hence |issuer2| is on the LHS of
|
||||
// the comparison and |issuer1| on the RHS.
|
||||
issuer2.cert->tbs().validity_not_before,
|
||||
issuer2.cert->tbs().validity_not_after) <
|
||||
std::tie(issuer2.trust_and_key_id_match_ordering,
|
||||
issuer1_self_issued,
|
||||
issuer1.cert->tbs().validity_not_before,
|
||||
issuer1.cert->tbs().validity_not_after);
|
||||
});
|
||||
|
||||
issuers_needs_sort_ = false;
|
||||
}
|
||||
|
||||
// CertIssuerIterPath tracks which certs are present in the path and prevents
|
||||
// paths from being built which repeat any certs (including different versions
|
||||
// of the same cert, based on Subject+SubjectAltName+SPKI).
|
||||
// (RFC 5280 forbids duplicate certificates per section 6.1, and RFC 4158
|
||||
// further recommends disallowing the same Subject+SubjectAltName+SPKI in
|
||||
// section 2.4.2.)
|
||||
class CertIssuerIterPath {
|
||||
public:
|
||||
// Returns true if |cert| is already present in the path.
|
||||
bool IsPresent(const ParsedCertificate* cert) const {
|
||||
return present_certs_.find(GetKey(cert)) != present_certs_.end();
|
||||
}
|
||||
|
||||
// Appends |cert_issuers_iter| to the path. The cert referred to by
|
||||
// |cert_issuers_iter| must not be present in the path already.
|
||||
void Append(std::unique_ptr<CertIssuersIter> cert_issuers_iter) {
|
||||
bool added =
|
||||
present_certs_.insert(GetKey(cert_issuers_iter->cert())).second;
|
||||
DCHECK(added);
|
||||
cur_path_.push_back(std::move(cert_issuers_iter));
|
||||
}
|
||||
|
||||
// Pops the last CertIssuersIter off the path.
|
||||
void Pop() {
|
||||
size_t num_erased = present_certs_.erase(GetKey(cur_path_.back()->cert()));
|
||||
DCHECK_EQ(num_erased, 1U);
|
||||
cur_path_.pop_back();
|
||||
}
|
||||
|
||||
// Copies the ParsedCertificate elements of the current path to |*out_path|.
|
||||
void CopyPath(ParsedCertificateList* out_path) {
|
||||
out_path->clear();
|
||||
for (const auto& node : cur_path_)
|
||||
out_path->push_back(node->reference_cert());
|
||||
}
|
||||
|
||||
// Returns true if the path is empty.
|
||||
bool Empty() const { return cur_path_.empty(); }
|
||||
|
||||
// Returns the last CertIssuersIter in the path.
|
||||
CertIssuersIter* back() { return cur_path_.back().get(); }
|
||||
|
||||
// Returns the length of the path.
|
||||
size_t Length() const { return cur_path_.size(); }
|
||||
|
||||
std::string PathDebugString() {
|
||||
std::string s;
|
||||
for (const auto& node : cur_path_) {
|
||||
if (!s.empty())
|
||||
s += "\n";
|
||||
s += " " + CertDebugString(node->cert());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
using Key = std::tuple<std::string_view, std::string_view, std::string_view>;
|
||||
|
||||
static Key GetKey(const ParsedCertificate* cert) {
|
||||
// TODO(mattm): ideally this would use a normalized version of
|
||||
// SubjectAltName, but it's not that important just for LoopChecker.
|
||||
//
|
||||
// Note that subject_alt_names_extension().value will be empty if the cert
|
||||
// had no SubjectAltName extension, so there is no need for a condition on
|
||||
// has_subject_alt_names().
|
||||
return Key(cert->normalized_subject().AsStringView(),
|
||||
cert->subject_alt_names_extension().value.AsStringView(),
|
||||
cert->tbs().spki_tlv.AsStringView());
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<CertIssuersIter>> cur_path_;
|
||||
|
||||
// This refers to data owned by |cur_path_|.
|
||||
// TODO(mattm): use unordered_set. Requires making a hash function for Key.
|
||||
std::set<Key> present_certs_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
const ParsedCertificate* CertPathBuilderResultPath::GetTrustedCert() const {
|
||||
if (certs.empty())
|
||||
return nullptr;
|
||||
|
||||
switch (last_cert_trust.type) {
|
||||
case CertificateTrustType::TRUSTED_ANCHOR:
|
||||
case CertificateTrustType::TRUSTED_ANCHOR_OR_LEAF:
|
||||
case CertificateTrustType::TRUSTED_LEAF:
|
||||
return certs.back().get();
|
||||
case CertificateTrustType::UNSPECIFIED:
|
||||
case CertificateTrustType::DISTRUSTED:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert(0); // NOTREACHED
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// CertPathIter generates possible paths from |cert| to a trust anchor in
|
||||
// |trust_store|, using intermediates from the |cert_issuer_source| objects if
|
||||
// necessary.
|
||||
class CertPathIter {
|
||||
public:
|
||||
CertPathIter(std::shared_ptr<const ParsedCertificate> cert,
|
||||
TrustStore* trust_store,
|
||||
void* debug_data);
|
||||
|
||||
CertPathIter(const CertPathIter&) = delete;
|
||||
CertPathIter& operator=(const CertPathIter&) = delete;
|
||||
|
||||
// Adds a CertIssuerSource to provide intermediates for use in path building.
|
||||
// The |*cert_issuer_source| must remain valid for the lifetime of the
|
||||
// CertPathIter.
|
||||
void AddCertIssuerSource(CertIssuerSource* cert_issuer_source);
|
||||
|
||||
// Gets the next candidate path, and fills it into |out_certs| and
|
||||
// |out_last_cert_trust|. Note that the returned path is unverified and must
|
||||
// still be run through a chain validator. If a candidate path could not be
|
||||
// built, a partial path will be returned and |out_errors| will have an error
|
||||
// added.
|
||||
// If the return value is true, GetNextPath may be called again to backtrack
|
||||
// and continue path building. Once all paths have been exhausted returns
|
||||
// false. If deadline or iteration limit is exceeded, sets |out_certs| to the
|
||||
// current path being explored and returns false.
|
||||
bool GetNextPath(ParsedCertificateList* out_certs,
|
||||
CertificateTrust* out_last_cert_trust,
|
||||
CertPathErrors* out_errors,
|
||||
CertPathBuilderDelegate* delegate,
|
||||
uint32_t* iteration_count,
|
||||
const uint32_t max_iteration_count,
|
||||
const uint32_t max_path_building_depth);
|
||||
|
||||
private:
|
||||
// Stores the next candidate issuer, until it is used during the
|
||||
// STATE_GET_NEXT_ISSUER_COMPLETE step.
|
||||
IssuerEntry next_issuer_;
|
||||
// The current path being explored, made up of CertIssuerIters. Each node
|
||||
// keeps track of the state of searching for issuers of that cert, so that
|
||||
// when backtracking it can resume the search where it left off.
|
||||
CertIssuerIterPath cur_path_;
|
||||
// The CertIssuerSources for retrieving candidate issuers.
|
||||
CertIssuerSources cert_issuer_sources_;
|
||||
// The TrustStore for checking if a path ends in a trust anchor.
|
||||
TrustStore* trust_store_;
|
||||
|
||||
void* debug_data_;
|
||||
};
|
||||
|
||||
CertPathIter::CertPathIter(std::shared_ptr<const ParsedCertificate> cert,
|
||||
TrustStore* trust_store,
|
||||
void* debug_data)
|
||||
: trust_store_(trust_store), debug_data_(debug_data) {
|
||||
// Initialize |next_issuer_| to the target certificate.
|
||||
next_issuer_.cert = std::move(cert);
|
||||
next_issuer_.trust =
|
||||
trust_store_->GetTrust(next_issuer_.cert.get(), debug_data_);
|
||||
}
|
||||
|
||||
void CertPathIter::AddCertIssuerSource(CertIssuerSource* cert_issuer_source) {
|
||||
cert_issuer_sources_.push_back(cert_issuer_source);
|
||||
}
|
||||
|
||||
bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
|
||||
CertificateTrust* out_last_cert_trust,
|
||||
CertPathErrors* out_errors,
|
||||
CertPathBuilderDelegate* delegate,
|
||||
uint32_t* iteration_count,
|
||||
const uint32_t max_iteration_count,
|
||||
const uint32_t max_path_building_depth) {
|
||||
out_certs->clear();
|
||||
*out_last_cert_trust = CertificateTrust::ForUnspecified();
|
||||
|
||||
while (true) {
|
||||
if (delegate->IsDeadlineExpired()) {
|
||||
if (cur_path_.Empty()) {
|
||||
// If the deadline is already expired before the first call to
|
||||
// GetNextPath, cur_path_ will be empty. Return the leaf cert in that
|
||||
// case.
|
||||
if (next_issuer_.cert)
|
||||
out_certs->push_back(next_issuer_.cert);
|
||||
} else {
|
||||
cur_path_.CopyPath(out_certs);
|
||||
}
|
||||
out_errors->GetOtherErrors()->AddError(cert_errors::kDeadlineExceeded);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are not done yet, so if the current path is at the depth limit then
|
||||
// we must backtrack to find an acceptable solution.
|
||||
if (max_path_building_depth > 0 &&
|
||||
cur_path_.Length() >= max_path_building_depth) {
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_errors->GetOtherErrors()->AddError(cert_errors::kDepthLimitExceeded);
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter reached depth limit. Returning partial path "
|
||||
"and backtracking:\n"
|
||||
<< PathDebugString(*out_certs);
|
||||
#endif
|
||||
cur_path_.Pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!next_issuer_.cert) {
|
||||
if (cur_path_.Empty()) {
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter exhausted all paths...";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
(*iteration_count)++;
|
||||
if (max_iteration_count > 0 && *iteration_count > max_iteration_count) {
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_errors->GetOtherErrors()->AddError(
|
||||
cert_errors::kIterationLimitExceeded);
|
||||
return false;
|
||||
}
|
||||
|
||||
cur_path_.back()->GetNextIssuer(&next_issuer_);
|
||||
if (!next_issuer_.cert) {
|
||||
if (!cur_path_.back()->had_non_skipped_issuers()) {
|
||||
// If the end of a path was reached without finding an anchor, return
|
||||
// the partial path before backtracking.
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_errors->GetErrorsForCert(out_certs->size() - 1)
|
||||
->AddError(cert_errors::kNoIssuersFound);
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter returning partial path and backtracking:\n"
|
||||
<< PathDebugString(*out_certs);
|
||||
#endif
|
||||
cur_path_.Pop();
|
||||
return true;
|
||||
} else {
|
||||
// No more issuers for current chain, go back up and see if there are
|
||||
// any more for the previous cert.
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter backtracking...";
|
||||
#endif
|
||||
cur_path_.Pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides for cert with trust appearing in the wrong place for the type
|
||||
// of trust (trusted leaf in non-leaf position, or trust anchor in leaf
|
||||
// position.)
|
||||
switch (next_issuer_.trust.type) {
|
||||
case CertificateTrustType::TRUSTED_ANCHOR:
|
||||
// If the leaf cert is trusted only as an anchor, treat it as having
|
||||
// unspecified trust. This may allow a successful path to be built to a
|
||||
// different root (or to the same cert if it's self-signed).
|
||||
if (cur_path_.Empty()) {
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "Leaf is a trust anchor, considering as UNSPECIFIED";
|
||||
#endif
|
||||
next_issuer_.trust = CertificateTrust::ForUnspecified();
|
||||
}
|
||||
break;
|
||||
case CertificateTrustType::TRUSTED_LEAF:
|
||||
// If a non-leaf cert is trusted only as a leaf, treat it as having
|
||||
// unspecified trust. This may allow a successful path to be built to a
|
||||
// trusted root.
|
||||
if (!cur_path_.Empty()) {
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "Issuer is a trust leaf, considering as UNSPECIFIED";
|
||||
#endif
|
||||
next_issuer_.trust = CertificateTrust::ForUnspecified();
|
||||
}
|
||||
break;
|
||||
case CertificateTrustType::DISTRUSTED:
|
||||
case CertificateTrustType::UNSPECIFIED:
|
||||
case CertificateTrustType::TRUSTED_ANCHOR_OR_LEAF:
|
||||
// No override necessary.
|
||||
break;
|
||||
}
|
||||
|
||||
// Overrides for trusted leaf cert with require_leaf_selfsigned. If the leaf
|
||||
// isn't actually self-signed, treat it as unspecified.
|
||||
switch (next_issuer_.trust.type) {
|
||||
case CertificateTrustType::TRUSTED_LEAF:
|
||||
case CertificateTrustType::TRUSTED_ANCHOR_OR_LEAF:
|
||||
if (cur_path_.Empty() && next_issuer_.trust.require_leaf_selfsigned &&
|
||||
!VerifyCertificateIsSelfSigned(*next_issuer_.cert,
|
||||
delegate->GetVerifyCache(),
|
||||
/*errors=*/nullptr)) {
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "Leaf is trusted with require_leaf_selfsigned but is "
|
||||
"not self-signed, considering as UNSPECIFIED";
|
||||
#endif
|
||||
next_issuer_.trust = CertificateTrust::ForUnspecified();
|
||||
}
|
||||
break;
|
||||
case CertificateTrustType::TRUSTED_ANCHOR:
|
||||
case CertificateTrustType::DISTRUSTED:
|
||||
case CertificateTrustType::UNSPECIFIED:
|
||||
// No override necessary.
|
||||
break;
|
||||
}
|
||||
|
||||
switch (next_issuer_.trust.type) {
|
||||
// If the trust for this issuer is "known" (either because it is
|
||||
// distrusted, or because it is trusted) then stop building and return the
|
||||
// path.
|
||||
case CertificateTrustType::DISTRUSTED:
|
||||
case CertificateTrustType::TRUSTED_ANCHOR:
|
||||
case CertificateTrustType::TRUSTED_ANCHOR_OR_LEAF:
|
||||
case CertificateTrustType::TRUSTED_LEAF: {
|
||||
// If the issuer has a known trust level, can stop building the path.
|
||||
#if defined(DVLOG)
|
||||
DVLOG(2) << "CertPathIter got anchor: "
|
||||
<< CertDebugString(next_issuer_.cert.get());
|
||||
#endif
|
||||
cur_path_.CopyPath(out_certs);
|
||||
out_certs->push_back(std::move(next_issuer_.cert));
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter returning path:\n"
|
||||
<< PathDebugString(*out_certs);
|
||||
#endif
|
||||
*out_last_cert_trust = next_issuer_.trust;
|
||||
next_issuer_ = IssuerEntry();
|
||||
return true;
|
||||
}
|
||||
case CertificateTrustType::UNSPECIFIED: {
|
||||
// Skip this cert if it is already in the chain.
|
||||
if (cur_path_.IsPresent(next_issuer_.cert.get())) {
|
||||
cur_path_.back()->increment_skipped_issuer_count();
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter skipping dupe cert: "
|
||||
<< CertDebugString(next_issuer_.cert.get());
|
||||
#endif
|
||||
next_issuer_ = IssuerEntry();
|
||||
continue;
|
||||
}
|
||||
|
||||
cur_path_.Append(std::make_unique<CertIssuersIter>(
|
||||
std::move(next_issuer_.cert), &cert_issuer_sources_, trust_store_,
|
||||
debug_data_));
|
||||
next_issuer_ = IssuerEntry();
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathIter cur_path_ =\n" << cur_path_.PathDebugString();
|
||||
#endif
|
||||
// Continue descending the tree.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CertPathBuilderResultPath::CertPathBuilderResultPath() = default;
|
||||
CertPathBuilderResultPath::~CertPathBuilderResultPath() = default;
|
||||
|
||||
bool CertPathBuilderResultPath::IsValid() const {
|
||||
return GetTrustedCert() && !errors.ContainsHighSeverityErrors();
|
||||
}
|
||||
|
||||
CertPathBuilder::Result::Result() = default;
|
||||
CertPathBuilder::Result::Result(Result&&) = default;
|
||||
CertPathBuilder::Result::~Result() = default;
|
||||
CertPathBuilder::Result& CertPathBuilder::Result::operator=(Result&&) = default;
|
||||
|
||||
bool CertPathBuilder::Result::HasValidPath() const {
|
||||
return GetBestValidPath() != nullptr;
|
||||
}
|
||||
|
||||
bool CertPathBuilder::Result::AnyPathContainsError(CertErrorId error_id) const {
|
||||
for (const auto& path : paths) {
|
||||
if (path->errors.ContainsError(error_id))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const CertPathBuilderResultPath* CertPathBuilder::Result::GetBestValidPath()
|
||||
const {
|
||||
const CertPathBuilderResultPath* result_path = GetBestPathPossiblyInvalid();
|
||||
|
||||
if (result_path && result_path->IsValid())
|
||||
return result_path;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CertPathBuilderResultPath*
|
||||
CertPathBuilder::Result::GetBestPathPossiblyInvalid() const {
|
||||
DCHECK((paths.empty() && best_result_index == 0) ||
|
||||
best_result_index < paths.size());
|
||||
|
||||
if (best_result_index >= paths.size())
|
||||
return nullptr;
|
||||
|
||||
return paths[best_result_index].get();
|
||||
}
|
||||
|
||||
CertPathBuilder::CertPathBuilder(
|
||||
std::shared_ptr<const ParsedCertificate> cert,
|
||||
TrustStore* trust_store,
|
||||
CertPathBuilderDelegate* delegate,
|
||||
const der::GeneralizedTime& time,
|
||||
KeyPurpose key_purpose,
|
||||
InitialExplicitPolicy initial_explicit_policy,
|
||||
const std::set<der::Input>& user_initial_policy_set,
|
||||
InitialPolicyMappingInhibit initial_policy_mapping_inhibit,
|
||||
InitialAnyPolicyInhibit initial_any_policy_inhibit)
|
||||
: cert_path_iter_(
|
||||
std::make_unique<CertPathIter>(std::move(cert),
|
||||
trust_store,
|
||||
/*debug_data=*/&out_result_)),
|
||||
delegate_(delegate),
|
||||
time_(time),
|
||||
key_purpose_(key_purpose),
|
||||
initial_explicit_policy_(initial_explicit_policy),
|
||||
user_initial_policy_set_(user_initial_policy_set),
|
||||
initial_policy_mapping_inhibit_(initial_policy_mapping_inhibit),
|
||||
initial_any_policy_inhibit_(initial_any_policy_inhibit) {
|
||||
DCHECK(delegate);
|
||||
// The TrustStore also implements the CertIssuerSource interface.
|
||||
AddCertIssuerSource(trust_store);
|
||||
}
|
||||
|
||||
CertPathBuilder::~CertPathBuilder() = default;
|
||||
|
||||
void CertPathBuilder::AddCertIssuerSource(
|
||||
CertIssuerSource* cert_issuer_source) {
|
||||
cert_path_iter_->AddCertIssuerSource(cert_issuer_source);
|
||||
}
|
||||
|
||||
void CertPathBuilder::SetIterationLimit(uint32_t limit) {
|
||||
max_iteration_count_ = limit;
|
||||
}
|
||||
|
||||
void CertPathBuilder::SetDepthLimit(uint32_t limit) {
|
||||
max_path_building_depth_ = limit;
|
||||
}
|
||||
|
||||
void CertPathBuilder::SetExploreAllPaths(bool explore_all_paths) {
|
||||
explore_all_paths_ = explore_all_paths;
|
||||
}
|
||||
|
||||
CertPathBuilder::Result CertPathBuilder::Run() {
|
||||
uint32_t iteration_count = 0;
|
||||
|
||||
while (true) {
|
||||
std::unique_ptr<CertPathBuilderResultPath> result_path =
|
||||
std::make_unique<CertPathBuilderResultPath>();
|
||||
|
||||
if (!cert_path_iter_->GetNextPath(
|
||||
&result_path->certs, &result_path->last_cert_trust,
|
||||
&result_path->errors, delegate_, &iteration_count,
|
||||
max_iteration_count_, max_path_building_depth_)) {
|
||||
// There are no more paths to check or limits were exceeded.
|
||||
if (result_path->errors.ContainsError(
|
||||
cert_errors::kIterationLimitExceeded)) {
|
||||
out_result_.exceeded_iteration_limit = true;
|
||||
}
|
||||
if (result_path->errors.ContainsError(cert_errors::kDeadlineExceeded)) {
|
||||
out_result_.exceeded_deadline = true;
|
||||
}
|
||||
if (!result_path->certs.empty()) {
|
||||
// It shouldn't be possible to get here without adding one of the
|
||||
// errors above, but just in case, add an error if there isn't one
|
||||
// already.
|
||||
if (!result_path->errors.ContainsHighSeverityErrors()) {
|
||||
result_path->errors.GetOtherErrors()->AddError(
|
||||
cert_errors::kInternalError);
|
||||
}
|
||||
AddResultPath(std::move(result_path));
|
||||
}
|
||||
out_result_.iteration_count = iteration_count;
|
||||
return std::move(out_result_);
|
||||
}
|
||||
|
||||
if (result_path->last_cert_trust.HasUnspecifiedTrust()) {
|
||||
// Partial path, don't attempt to verify. Just double check that it is
|
||||
// marked with an error, and move on.
|
||||
if (!result_path->errors.ContainsHighSeverityErrors()) {
|
||||
result_path->errors.GetOtherErrors()->AddError(
|
||||
cert_errors::kInternalError);
|
||||
}
|
||||
} else {
|
||||
// Verify the entire certificate chain.
|
||||
VerifyCertificateChain(
|
||||
result_path->certs, result_path->last_cert_trust, delegate_, time_,
|
||||
key_purpose_, initial_explicit_policy_, user_initial_policy_set_,
|
||||
initial_policy_mapping_inhibit_, initial_any_policy_inhibit_,
|
||||
&result_path->user_constrained_policy_set, &result_path->errors);
|
||||
}
|
||||
|
||||
#if defined(DVLOG)
|
||||
DVLOG(1) << "CertPathBuilder VerifyCertificateChain errors:\n"
|
||||
<< result_path->errors.ToDebugString(result_path->certs);
|
||||
#endif
|
||||
|
||||
// Give the delegate a chance to add errors to the path.
|
||||
delegate_->CheckPathAfterVerification(*this, result_path.get());
|
||||
|
||||
bool path_is_good = result_path->IsValid();
|
||||
|
||||
AddResultPath(std::move(result_path));
|
||||
|
||||
if (path_is_good && !explore_all_paths_) {
|
||||
out_result_.iteration_count = iteration_count;
|
||||
// Found a valid path, return immediately.
|
||||
return std::move(out_result_);
|
||||
}
|
||||
// Path did not verify. Try more paths.
|
||||
}
|
||||
}
|
||||
|
||||
void CertPathBuilder::AddResultPath(
|
||||
std::unique_ptr<CertPathBuilderResultPath> result_path) {
|
||||
// TODO(mattm): If there are no valid paths, set best_result_index based on
|
||||
// number or severity of errors. If there are multiple valid paths, could set
|
||||
// best_result_index based on prioritization (since due to AIA and such, the
|
||||
// actual order results were discovered may not match the ideal).
|
||||
if (!out_result_.HasValidPath()) {
|
||||
const CertPathBuilderResultPath* old_best_path =
|
||||
out_result_.GetBestPathPossiblyInvalid();
|
||||
// If |result_path| is a valid path or if the previous best result did not
|
||||
// end in a trust anchor but the |result_path| does, then update the best
|
||||
// result to the new result.
|
||||
if (result_path->IsValid() ||
|
||||
(!result_path->last_cert_trust.HasUnspecifiedTrust() && old_best_path &&
|
||||
old_best_path->last_cert_trust.HasUnspecifiedTrust())) {
|
||||
out_result_.best_result_index = out_result_.paths.size();
|
||||
}
|
||||
}
|
||||
if (result_path->certs.size() > out_result_.max_depth_seen) {
|
||||
out_result_.max_depth_seen = result_path->certs.size();
|
||||
}
|
||||
out_result_.paths.push_back(std::move(result_path));
|
||||
}
|
||||
|
||||
} // namespace net
|
240
pki/path_builder.h
Normal file
240
pki/path_builder.h
Normal file
@ -0,0 +1,240 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BSSL_PKI_PATH_BUILDER_H_
|
||||
#define BSSL_PKI_PATH_BUILDER_H_
|
||||
|
||||
#include "fillins/openssl_util.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
#include "cert_errors.h"
|
||||
#include "parsed_certificate.h"
|
||||
#include "trust_store.h"
|
||||
#include "verify_certificate_chain.h"
|
||||
#include "input.h"
|
||||
#include "parse_values.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace der {
|
||||
struct GeneralizedTime;
|
||||
}
|
||||
|
||||
class CertPathBuilder;
|
||||
class CertPathIter;
|
||||
class CertIssuerSource;
|
||||
|
||||
// Base class for custom data that CertPathBuilderDelegate can attach to paths.
|
||||
class OPENSSL_EXPORT CertPathBuilderDelegateData {
|
||||
public:
|
||||
virtual ~CertPathBuilderDelegateData() = default;
|
||||
};
|
||||
|
||||
// Represents a single candidate path that was built or is being processed.
|
||||
//
|
||||
// This is used both to represent valid paths, as well as invalid/partial ones.
|
||||
//
|
||||
// Consumers must use |IsValid()| to test whether the
|
||||
// CertPathBuilderResultPath is the result of a successful certificate
|
||||
// verification.
|
||||
struct OPENSSL_EXPORT CertPathBuilderResultPath {
|
||||
CertPathBuilderResultPath();
|
||||
~CertPathBuilderResultPath();
|
||||
|
||||
// Returns true if the candidate path is valid. A "valid" path is one which
|
||||
// chains to a trusted root, and did not have any high severity errors added
|
||||
// to it during certificate verification.
|
||||
bool IsValid() const;
|
||||
|
||||
// Returns the chain's root certificate or nullptr if the chain doesn't
|
||||
// chain to a trust anchor.
|
||||
const ParsedCertificate* GetTrustedCert() const;
|
||||
|
||||
// Path in the forward direction:
|
||||
//
|
||||
// certs[0] is the target certificate
|
||||
// certs[i] was issued by certs[i+1]
|
||||
// certs.back() is the root certificate (which may or may not be trusted).
|
||||
ParsedCertificateList certs;
|
||||
|
||||
// Describes the trustedness of the final certificate in the chain,
|
||||
// |certs.back()|
|
||||
//
|
||||
// For result paths where |IsValid()|, the final certificate is trusted.
|
||||
// However for failed or partially constructed paths the final certificate may
|
||||
// not be a trust anchor.
|
||||
CertificateTrust last_cert_trust;
|
||||
|
||||
// The set of policies that the certificate is valid for (of the
|
||||
// subset of policies user requested during verification).
|
||||
std::set<der::Input> user_constrained_policy_set;
|
||||
|
||||
// Slot for per-path data that may set by CertPathBuilderDelegate. The
|
||||
// specific type is chosen by the delegate. Can be nullptr when unused.
|
||||
std::unique_ptr<CertPathBuilderDelegateData> delegate_data;
|
||||
|
||||
// The set of errors and warnings associated with this path (bucketed
|
||||
// per-certificate). Note that consumers should always use |IsValid()| to
|
||||
// determine validity of the CertPathBuilderResultPath, and not just inspect
|
||||
// |errors|.
|
||||
CertPathErrors errors;
|
||||
};
|
||||
|
||||
// CertPathBuilderDelegate controls policies for certificate verification and
|
||||
// path building.
|
||||
class OPENSSL_EXPORT CertPathBuilderDelegate
|
||||
: public VerifyCertificateChainDelegate {
|
||||
public:
|
||||
// This is called during path building on candidate paths which have already
|
||||
// been run through RFC 5280 verification. |path| may already have errors
|
||||
// and warnings set on it. Delegates can "reject" a candidate path from path
|
||||
// building by adding high severity errors.
|
||||
virtual void CheckPathAfterVerification(const CertPathBuilder& path_builder,
|
||||
CertPathBuilderResultPath* path) = 0;
|
||||
|
||||
// This is called during path building in between attempts to build candidate
|
||||
// paths. Delegates can cause path building to stop and return indicating
|
||||
// the deadline was exceeded by returning true from this function.
|
||||
virtual bool IsDeadlineExpired() = 0;
|
||||
};
|
||||
|
||||
// Checks whether a certificate is trusted by building candidate paths to trust
|
||||
// anchors and verifying those paths according to RFC 5280. Each instance of
|
||||
// CertPathBuilder is used for a single verification.
|
||||
//
|
||||
// WARNING: This implementation is currently experimental. Consult an OWNER
|
||||
// before using it.
|
||||
class OPENSSL_EXPORT CertPathBuilder {
|
||||
public:
|
||||
// Provides the overall result of path building. This includes the paths that
|
||||
// were attempted.
|
||||
struct OPENSSL_EXPORT Result {
|
||||
Result();
|
||||
Result(Result&&);
|
||||
|
||||
Result(const Result&) = delete;
|
||||
Result& operator=(const Result&) = delete;
|
||||
|
||||
~Result();
|
||||
Result& operator=(Result&&);
|
||||
|
||||
// Returns true if there was a valid path.
|
||||
bool HasValidPath() const;
|
||||
|
||||
// Returns true if any of the attempted paths contain |error_id|.
|
||||
bool AnyPathContainsError(CertErrorId error_id) const;
|
||||
|
||||
// Returns the CertPathBuilderResultPath for the best valid path, or nullptr
|
||||
// if there was none.
|
||||
const CertPathBuilderResultPath* GetBestValidPath() const;
|
||||
|
||||
// Returns the best CertPathBuilderResultPath or nullptr if there was none.
|
||||
const CertPathBuilderResultPath* GetBestPathPossiblyInvalid() const;
|
||||
|
||||
// List of paths that were attempted and the result for each.
|
||||
std::vector<std::unique_ptr<CertPathBuilderResultPath>> paths;
|
||||
|
||||
// Index into |paths|. Before use, |paths.empty()| must be checked.
|
||||
// NOTE: currently the definition of "best" is fairly limited. Valid is
|
||||
// better than invalid, but otherwise nothing is guaranteed.
|
||||
size_t best_result_index = 0;
|
||||
|
||||
// The iteration count reached by path building.
|
||||
uint32_t iteration_count = 0;
|
||||
|
||||
// The max depth seen while path building.
|
||||
uint32_t max_depth_seen = 0;
|
||||
|
||||
// True if the search stopped because it exceeded the iteration limit
|
||||
// configured with |SetIterationLimit|.
|
||||
bool exceeded_iteration_limit = false;
|
||||
|
||||
// True if the search stopped because delegate->IsDeadlineExpired() returned
|
||||
// true.
|
||||
bool exceeded_deadline = false;
|
||||
};
|
||||
|
||||
// Creates a CertPathBuilder that attempts to find a path from |cert| to a
|
||||
// trust anchor in |trust_store| and is valid at |time|.
|
||||
//
|
||||
// The caller must keep |trust_store| and |delegate| valid for the lifetime
|
||||
// of the CertPathBuilder.
|
||||
//
|
||||
// See VerifyCertificateChain() for a more detailed explanation of the
|
||||
// same-named parameters not defined below.
|
||||
//
|
||||
// * |delegate|: Must be non-null. The delegate is called at various points in
|
||||
// path building to verify specific parts of certificates or the
|
||||
// final chain. See CertPathBuilderDelegate and
|
||||
// VerifyCertificateChainDelegate for more information.
|
||||
CertPathBuilder(std::shared_ptr<const ParsedCertificate> cert,
|
||||
TrustStore* trust_store,
|
||||
CertPathBuilderDelegate* delegate,
|
||||
const der::GeneralizedTime& time,
|
||||
KeyPurpose key_purpose,
|
||||
InitialExplicitPolicy initial_explicit_policy,
|
||||
const std::set<der::Input>& user_initial_policy_set,
|
||||
InitialPolicyMappingInhibit initial_policy_mapping_inhibit,
|
||||
InitialAnyPolicyInhibit initial_any_policy_inhibit);
|
||||
|
||||
CertPathBuilder(const CertPathBuilder&) = delete;
|
||||
CertPathBuilder& operator=(const CertPathBuilder&) = delete;
|
||||
|
||||
~CertPathBuilder();
|
||||
|
||||
// Adds a CertIssuerSource to provide intermediates for use in path building.
|
||||
// Multiple sources may be added. Must not be called after Run is called.
|
||||
// The |*cert_issuer_source| must remain valid for the lifetime of the
|
||||
// CertPathBuilder.
|
||||
//
|
||||
// (If no issuer sources are added, the target certificate will only verify if
|
||||
// it is a trust anchor or is directly signed by a trust anchor.)
|
||||
void AddCertIssuerSource(CertIssuerSource* cert_issuer_source);
|
||||
|
||||
// Sets a limit to the number of times to repeat the process of considering a
|
||||
// new intermediate over all potential paths. Setting |limit| to 0 disables
|
||||
// the iteration limit, which is the default.
|
||||
void SetIterationLimit(uint32_t limit);
|
||||
|
||||
// Sets a limit to the number of certificates to be added in a path from leaf
|
||||
// to root. Setting |limit| to 0 disables this limit, which is the default.
|
||||
void SetDepthLimit(uint32_t limit);
|
||||
|
||||
// If |explore_all_paths| is false (the default), path building will stop as
|
||||
// soon as a valid path is found. If |explore_all_paths| is true, path
|
||||
// building will continue until all possible paths have been exhausted (or
|
||||
// iteration limit / deadline is exceeded).
|
||||
void SetExploreAllPaths(bool explore_all_paths);
|
||||
|
||||
// Executes verification of the target certificate.
|
||||
//
|
||||
// Run must not be called more than once on each CertPathBuilder instance.
|
||||
Result Run();
|
||||
|
||||
private:
|
||||
void AddResultPath(std::unique_ptr<CertPathBuilderResultPath> result_path);
|
||||
|
||||
// |out_result_| may be referenced by other members, so should be initialized
|
||||
// first.
|
||||
Result out_result_;
|
||||
|
||||
std::unique_ptr<CertPathIter> cert_path_iter_;
|
||||
CertPathBuilderDelegate * delegate_;
|
||||
const der::GeneralizedTime time_;
|
||||
const KeyPurpose key_purpose_;
|
||||
const InitialExplicitPolicy initial_explicit_policy_;
|
||||
const std::set<der::Input> user_initial_policy_set_;
|
||||
const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_;
|
||||
const InitialAnyPolicyInhibit initial_any_policy_inhibit_;
|
||||
uint32_t max_iteration_count_ = 0;
|
||||
uint32_t max_path_building_depth_ = 0;
|
||||
bool explore_all_paths_ = false;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // BSSL_PKI_PATH_BUILDER_H_
|
306
pki/path_builder_pkits_unittest.cc
Normal file
306
pki/path_builder_pkits_unittest.cc
Normal file
@ -0,0 +1,306 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "path_builder.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "fillins/net_errors.h"
|
||||
|
||||
#include "cert_issuer_source_static.h"
|
||||
#include "common_cert_errors.h"
|
||||
#include "crl.h"
|
||||
#include "parse_certificate.h"
|
||||
#include "parsed_certificate.h"
|
||||
#include "simple_path_builder_delegate.h"
|
||||
#include "trust_store_in_memory.h"
|
||||
#include "verify_certificate_chain.h"
|
||||
#include "encode_values.h"
|
||||
#include "input.h"
|
||||
#include <openssl/pool.h>
|
||||
|
||||
#include "nist_pkits_unittest.h"
|
||||
|
||||
constexpr int64_t kOneYear = 60 * 60 * 24 * 365;
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
class CrlCheckingPathBuilderDelegate : public SimplePathBuilderDelegate {
|
||||
public:
|
||||
CrlCheckingPathBuilderDelegate(const std::vector<std::string>& der_crls,
|
||||
int64_t verify_time,
|
||||
int64_t max_age,
|
||||
size_t min_rsa_modulus_length_bits,
|
||||
DigestPolicy digest_policy)
|
||||
: SimplePathBuilderDelegate(min_rsa_modulus_length_bits, digest_policy),
|
||||
der_crls_(der_crls),
|
||||
verify_time_(verify_time),
|
||||
max_age_(max_age) {}
|
||||
|
||||
void CheckPathAfterVerification(const CertPathBuilder& path_builder,
|
||||
CertPathBuilderResultPath* path) override {
|
||||
SimplePathBuilderDelegate::CheckPathAfterVerification(path_builder, path);
|
||||
|
||||
if (!path->IsValid())
|
||||
return;
|
||||
|
||||
// It would be preferable if this test could use
|
||||
// CheckValidatedChainRevocation somehow, but that only supports getting
|
||||
// CRLs by http distributionPoints. So this just settles for writing a
|
||||
// little bit of wrapper code to test CheckCRL directly.
|
||||
const ParsedCertificateList& certs = path->certs;
|
||||
for (size_t reverse_i = 0; reverse_i < certs.size(); ++reverse_i) {
|
||||
size_t i = certs.size() - reverse_i - 1;
|
||||
|
||||
// Trust anchors bypass OCSP/CRL revocation checks. (The only way to
|
||||
// revoke trust anchors is via CRLSet or the built-in SPKI block list).
|
||||
if (reverse_i == 0 && path->last_cert_trust.IsTrustAnchor())
|
||||
continue;
|
||||
|
||||
// RFC 5280 6.3.3. [If the CRL was not specified in a distribution
|
||||
// point], assume a DP with both the reasons and the
|
||||
// cRLIssuer fields omitted and a distribution point
|
||||
// name of the certificate issuer.
|
||||
// Since this implementation only supports URI names in distribution
|
||||
// points, this means a default-initialized ParsedDistributionPoint is
|
||||
// sufficient.
|
||||
ParsedDistributionPoint fake_cert_dp;
|
||||
const ParsedDistributionPoint* cert_dp = &fake_cert_dp;
|
||||
|
||||
// If the target cert does have a distribution point, use it.
|
||||
std::vector<ParsedDistributionPoint> distribution_points;
|
||||
ParsedExtension crl_dp_extension;
|
||||
if (certs[i]->GetExtension(der::Input(kCrlDistributionPointsOid),
|
||||
&crl_dp_extension)) {
|
||||
ASSERT_TRUE(ParseCrlDistributionPoints(crl_dp_extension.value,
|
||||
&distribution_points));
|
||||
// TODO(mattm): some test cases (some of the 4.14.* onlySomeReasons
|
||||
// tests)) have two CRLs and two distribution points, one point
|
||||
// corresponding to each CRL. Should select the matching point for
|
||||
// each CRL. (Doesn't matter currently since we don't support
|
||||
// reasons.)
|
||||
|
||||
// Look for a DistributionPoint without reasons.
|
||||
for (const auto& dp : distribution_points) {
|
||||
if (!dp.reasons) {
|
||||
cert_dp = &dp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If there were only DistributionPoints with reasons, just use the
|
||||
// first one.
|
||||
if (cert_dp == &fake_cert_dp && !distribution_points.empty())
|
||||
cert_dp = &distribution_points[0];
|
||||
}
|
||||
|
||||
bool cert_good = false;
|
||||
|
||||
for (const auto& der_crl : der_crls_) {
|
||||
CRLRevocationStatus crl_status =
|
||||
CheckCRL(der_crl, certs, i, *cert_dp, verify_time_, max_age_);
|
||||
if (crl_status == CRLRevocationStatus::REVOKED) {
|
||||
path->errors.GetErrorsForCert(i)->AddError(
|
||||
cert_errors::kCertificateRevoked);
|
||||
return;
|
||||
}
|
||||
if (crl_status == CRLRevocationStatus::GOOD) {
|
||||
cert_good = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cert_good) {
|
||||
// PKITS tests assume hard-fail revocation checking.
|
||||
// From PKITS 4.4: "When running the tests in this section, the
|
||||
// application should be configured in such a way that the
|
||||
// certification path is not accepted unless valid, up-to-date
|
||||
// revocation data is available for every certificate in the path."
|
||||
path->errors.GetErrorsForCert(i)->AddError(
|
||||
cert_errors::kUnableToCheckRevocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> der_crls_;
|
||||
int64_t verify_time_;
|
||||
int64_t max_age_;
|
||||
};
|
||||
|
||||
class PathBuilderPkitsTestDelegate {
|
||||
public:
|
||||
static void RunTest(std::vector<std::string> cert_ders,
|
||||
std::vector<std::string> crl_ders,
|
||||
const PkitsTestInfo& orig_info) {
|
||||
PkitsTestInfo info = orig_info;
|
||||
|
||||
ASSERT_FALSE(cert_ders.empty());
|
||||
ParsedCertificateList certs;
|
||||
for (const std::string& der : cert_ders) {
|
||||
CertErrors errors;
|
||||
ASSERT_TRUE(ParsedCertificate::CreateAndAddToVector(
|
||||
bssl::UniquePtr<CRYPTO_BUFFER>(
|
||||
CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t*>(der.data()),
|
||||
der.size(), nullptr)),
|
||||
{}, &certs, &errors))
|
||||
<< errors.ToDebugString();
|
||||
}
|
||||
// First entry in the PKITS chain is the trust anchor.
|
||||
// TODO(mattm): test with all possible trust anchors in the trust store?
|
||||
TrustStoreInMemory trust_store;
|
||||
|
||||
trust_store.AddTrustAnchor(certs[0]);
|
||||
|
||||
// TODO(mattm): test with other irrelevant certs in cert_issuer_sources?
|
||||
CertIssuerSourceStatic cert_issuer_source;
|
||||
for (size_t i = 1; i < cert_ders.size() - 1; ++i)
|
||||
cert_issuer_source.AddCert(certs[i]);
|
||||
|
||||
std::shared_ptr<const ParsedCertificate> target_cert(certs.back());
|
||||
|
||||
int64_t verify_time;
|
||||
ASSERT_TRUE(der::GeneralizedTimeToPosixTime(info.time, &verify_time));
|
||||
CrlCheckingPathBuilderDelegate path_builder_delegate(
|
||||
crl_ders, verify_time, /*max_age=*/kOneYear * 2, 1024,
|
||||
SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1);
|
||||
|
||||
std::string_view test_number = info.test_number;
|
||||
if (test_number == "4.4.19" || test_number == "4.5.3" ||
|
||||
test_number == "4.5.4" || test_number == "4.5.6") {
|
||||
// 4.4.19 - fails since CRL is signed by a certificate that is not part
|
||||
// of the verified chain, which is not supported.
|
||||
// 4.5.3 - fails since non-URI distribution point names are not supported
|
||||
// 4.5.4, 4.5.6 - fails since CRL is signed by a certificate that is not
|
||||
// part of verified chain, and also non-URI distribution
|
||||
// point names not supported
|
||||
info.should_validate = false;
|
||||
} else if (test_number == "4.14.1" || test_number == "4.14.4" ||
|
||||
test_number == "4.14.5" || test_number == "4.14.7" ||
|
||||
test_number == "4.14.18" || test_number == "4.14.19" ||
|
||||
test_number == "4.14.22" || test_number == "4.14.24" ||
|
||||
test_number == "4.14.25" || test_number == "4.14.28" ||
|
||||
test_number == "4.14.29" || test_number == "4.14.30" ||
|
||||
test_number == "4.14.33") {
|
||||
// 4.14 tests:
|
||||
// .1 - fails since non-URI distribution point names not supported
|
||||
// .2, .3 - fails since non-URI distribution point names not supported
|
||||
// (but test is expected to fail for other reason)
|
||||
// .4, .5 - fails since non-URI distribution point names not supported,
|
||||
// also uses nameRelativeToCRLIssuer which is not supported
|
||||
// .6 - fails since non-URI distribution point names not supported, also
|
||||
// uses nameRelativeToCRLIssuer which is not supported (but test is
|
||||
// expected to fail for other reason)
|
||||
// .7 - fails since relative distributionPointName not supported
|
||||
// .8, .9 - fails since relative distributionPointName not supported (but
|
||||
// test is expected to fail for other reason)
|
||||
// .10, .11, .12, .13, .14, .27, .35 - PASS
|
||||
// .15, .16, .17, .20, .21 - fails since onlySomeReasons is not supported
|
||||
// (but test is expected to fail for other
|
||||
// reason)
|
||||
// .18, .19 - fails since onlySomeReasons is not supported
|
||||
// .22, .24, .25, .28, .29, .30, .33 - fails since indirect CRLs are not
|
||||
// supported
|
||||
// .23, .26, .31, .32, .34 - fails since indirect CRLs are not supported
|
||||
// (but test is expected to fail for other
|
||||
// reason)
|
||||
info.should_validate = false;
|
||||
} else if (test_number == "4.15.1" || test_number == "4.15.5") {
|
||||
// 4.15 tests:
|
||||
// .1 - fails due to unhandled critical deltaCRLIndicator extension
|
||||
// .2, .3, .6, .7, .8, .9, .10 - PASS since expected cert status is
|
||||
// reflected in base CRL and delta CRL is
|
||||
// ignored
|
||||
// .5 - fails, cert status is "on hold" in base CRL but the delta CRL
|
||||
// which removes the cert from CRL is ignored
|
||||
info.should_validate = false;
|
||||
} else if (test_number == "4.15.4") {
|
||||
// 4.15.4 - Invalid delta-CRL Test4 has the target cert marked revoked in
|
||||
// a delta-CRL. Since delta-CRLs are not supported, the chain validates
|
||||
// successfully.
|
||||
info.should_validate = true;
|
||||
}
|
||||
|
||||
CertPathBuilder path_builder(
|
||||
std::move(target_cert), &trust_store, &path_builder_delegate, info.time,
|
||||
KeyPurpose::ANY_EKU, info.initial_explicit_policy,
|
||||
info.initial_policy_set, info.initial_policy_mapping_inhibit,
|
||||
info.initial_inhibit_any_policy);
|
||||
path_builder.AddCertIssuerSource(&cert_issuer_source);
|
||||
|
||||
CertPathBuilder::Result result = path_builder.Run();
|
||||
|
||||
if (info.should_validate != result.HasValidPath()) {
|
||||
for (size_t i = 0; i < result.paths.size(); ++i) {
|
||||
#if defined(DVLOG)
|
||||
const CertPathBuilderResultPath* result_path =
|
||||
result.paths[i].get();
|
||||
LOG(ERROR) << "path " << i << " errors:\n"
|
||||
<< result_path->errors.ToDebugString(result_path->certs);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(info.should_validate, result.HasValidPath());
|
||||
|
||||
if (result.HasValidPath()) {
|
||||
EXPECT_EQ(info.user_constrained_policy_set,
|
||||
result.GetBestValidPath()->user_constrained_policy_set);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest01SignatureVerification,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest02ValidityPeriods,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest03VerifyingNameChaining,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest04BasicCertificateRevocationTests,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(
|
||||
PathBuilder,
|
||||
PkitsTest05VerifyingPathswithSelfIssuedCertificates,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest06VerifyingBasicConstraints,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest07KeyUsage,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest08CertificatePolicies,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest09RequireExplicitPolicy,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest10PolicyMappings,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest11InhibitPolicyMapping,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest12InhibitAnyPolicy,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest13NameConstraints,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest14DistributionPoints,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest15DeltaCRLs,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
PkitsTest16PrivateCertificateExtensions,
|
||||
PathBuilderPkitsTestDelegate);
|
||||
|
||||
} // namespace net
|
2524
pki/path_builder_unittest.cc
Normal file
2524
pki/path_builder_unittest.cc
Normal file
File diff suppressed because it is too large
Load Diff
55
pki/path_builder_verify_certificate_chain_unittest.cc
Normal file
55
pki/path_builder_verify_certificate_chain_unittest.cc
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "path_builder.h"
|
||||
|
||||
#include "cert_issuer_source_static.h"
|
||||
#include "simple_path_builder_delegate.h"
|
||||
#include "trust_store_in_memory.h"
|
||||
#include "verify_certificate_chain_typed_unittest.h"
|
||||
|
||||
namespace bssl {
|
||||
|
||||
namespace {
|
||||
|
||||
class PathBuilderTestDelegate {
|
||||
public:
|
||||
static void Verify(const VerifyCertChainTest& test,
|
||||
const std::string& test_file_path) {
|
||||
SimplePathBuilderDelegate path_builder_delegate(1024, test.digest_policy);
|
||||
ASSERT_FALSE(test.chain.empty());
|
||||
|
||||
TrustStoreInMemory trust_store;
|
||||
trust_store.AddCertificate(test.chain.back(), test.last_cert_trust);
|
||||
|
||||
CertIssuerSourceStatic intermediate_cert_issuer_source;
|
||||
for (size_t i = 1; i < test.chain.size(); ++i)
|
||||
intermediate_cert_issuer_source.AddCert(test.chain[i]);
|
||||
|
||||
// First cert in the |chain| is the target.
|
||||
CertPathBuilder path_builder(
|
||||
test.chain.front(), &trust_store, &path_builder_delegate, test.time,
|
||||
test.key_purpose, test.initial_explicit_policy,
|
||||
test.user_initial_policy_set, test.initial_policy_mapping_inhibit,
|
||||
test.initial_any_policy_inhibit);
|
||||
path_builder.AddCertIssuerSource(&intermediate_cert_issuer_source);
|
||||
|
||||
CertPathBuilder::Result result = path_builder.Run();
|
||||
EXPECT_EQ(!test.HasHighSeverityErrors(), result.HasValidPath());
|
||||
if (result.HasValidPath()) {
|
||||
VerifyUserConstrainedPolicySet(
|
||||
test.expected_user_constrained_policy_set,
|
||||
result.GetBestValidPath()->user_constrained_policy_set,
|
||||
test_file_path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder,
|
||||
VerifyCertificateChainSingleRootTest,
|
||||
PathBuilderTestDelegate);
|
||||
|
||||
} // namespace net
|
143
pki/pem.cc
Normal file
143
pki/pem.cc
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2010 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "string_util.h"
|
||||
#include "pem.h"
|
||||
|
||||
#include "fillins/base64.h"
|
||||
#include <string_view>
|
||||
|
||||
#include "fillins/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kPEMHeaderBeginBlock = "-----BEGIN ";
|
||||
constexpr std::string_view kPEMHeaderEndBlock = "-----END ";
|
||||
constexpr std::string_view kPEMHeaderTail = "-----";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace bssl {
|
||||
|
||||
|
||||
|
||||
struct PEMTokenizer::PEMType {
|
||||
std::string type;
|
||||
std::string header;
|
||||
std::string footer;
|
||||
};
|
||||
|
||||
PEMTokenizer::PEMTokenizer(
|
||||
std::string_view str,
|
||||
const std::vector<std::string>& allowed_block_types) {
|
||||
Init(str, allowed_block_types);
|
||||
}
|
||||
|
||||
PEMTokenizer::~PEMTokenizer() = default;
|
||||
|
||||
bool PEMTokenizer::GetNext() {
|
||||
while (pos_ != std::string_view::npos) {
|
||||
// Scan for the beginning of the next PEM encoded block.
|
||||
pos_ = str_.find(kPEMHeaderBeginBlock, pos_);
|
||||
if (pos_ == std::string_view::npos)
|
||||
return false; // No more PEM blocks
|
||||
|
||||
std::vector<PEMType>::const_iterator it;
|
||||
// Check to see if it is of an acceptable block type.
|
||||
for (it = block_types_.begin(); it != block_types_.end(); ++it) {
|
||||
if (!bssl::string_util::StartsWith(str_.substr(pos_), it->header))
|
||||
continue;
|
||||
|
||||
// Look for a footer matching the header. If none is found, then all
|
||||
// data following this point is invalid and should not be parsed.
|
||||
std::string_view::size_type footer_pos = str_.find(it->footer, pos_);
|
||||
if (footer_pos == std::string_view::npos) {
|
||||
pos_ = std::string_view::npos;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Chop off the header and footer and parse the data in between.
|
||||
std::string_view::size_type data_begin = pos_ + it->header.size();
|
||||
pos_ = footer_pos + it->footer.size();
|
||||
block_type_ = it->type;
|
||||
|
||||
std::string_view encoded = str_.substr(data_begin, footer_pos - data_begin);
|
||||
if (!fillins::Base64Decode(fillins::CollapseWhitespaceASCII(encoded, true),
|
||||
&data_)) {
|
||||
// The most likely cause for a decode failure is a datatype that
|
||||
// includes PEM headers, which are not supported.
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the block did not match any acceptable type, move past it and
|
||||
// continue the search. Otherwise, |pos_| has been updated to the most
|
||||
// appropriate search position to continue searching from and should not
|
||||
// be adjusted.
|
||||
if (it == block_types_.end())
|
||||
pos_ += kPEMHeaderBeginBlock.size();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PEMTokenizer::Init(std::string_view str,
|
||||
const std::vector<std::string>& allowed_block_types) {
|
||||
str_ = str;
|
||||
pos_ = 0;
|
||||
|
||||
// Construct PEM header/footer strings for all the accepted types, to
|
||||
// reduce parsing later.
|
||||
for (const auto& allowed_block_type : allowed_block_types) {
|
||||
PEMType allowed_type;
|
||||
allowed_type.type = allowed_block_type;
|
||||
allowed_type.header = kPEMHeaderBeginBlock;
|
||||
allowed_type.header.append(allowed_block_type);
|
||||
allowed_type.header.append(kPEMHeaderTail);
|
||||
allowed_type.footer = kPEMHeaderEndBlock;
|
||||
allowed_type.footer.append(allowed_block_type);
|
||||
allowed_type.footer.append(kPEMHeaderTail);
|
||||
block_types_.push_back(allowed_type);
|
||||
}
|
||||
}
|
||||
|
||||
std::string PEMEncode(std::string_view data, const std::string& type) {
|
||||
std::string b64_encoded;
|
||||
fillins::Base64Encode(data, &b64_encoded);
|
||||
|
||||
// Divide the Base-64 encoded data into 64-character chunks, as per
|
||||
// 4.3.2.4 of RFC 1421.
|
||||
static const size_t kChunkSize = 64;
|
||||
size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize;
|
||||
|
||||
std::string pem_encoded;
|
||||
pem_encoded.reserve(
|
||||
// header & footer
|
||||
17 + 15 + type.size() * 2 +
|
||||
// encoded data
|
||||
b64_encoded.size() +
|
||||
// newline characters for line wrapping in encoded data
|
||||
chunks);
|
||||
|
||||
pem_encoded = kPEMHeaderBeginBlock;
|
||||
pem_encoded.append(type);
|
||||
pem_encoded.append(kPEMHeaderTail);
|
||||
pem_encoded.append("\n");
|
||||
|
||||
for (size_t i = 0, chunk_offset = 0; i < chunks;
|
||||
++i, chunk_offset += kChunkSize) {
|
||||
pem_encoded.append(b64_encoded, chunk_offset, kChunkSize);
|
||||
pem_encoded.append("\n");
|
||||
}
|
||||
|
||||
pem_encoded.append(kPEMHeaderEndBlock);
|
||||
pem_encoded.append(type);
|
||||
pem_encoded.append(kPEMHeaderTail);
|
||||
pem_encoded.append("\n");
|
||||
return pem_encoded;
|
||||
}
|
||||
|
||||
} // namespace net
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user