diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-09-29 16:16:15 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-11-09 10:04:06 +0000 |
commit | a95a7417ad456115a1ef2da4bb8320531c0821f1 (patch) | |
tree | edcd59279e486d2fd4a8f88a7ed025bcf925c6e6 /chromium/net/cert/pki | |
parent | 33fc33aa94d4add0878ec30dc818e34e1dd3cc2a (diff) | |
download | qtwebengine-chromium-a95a7417ad456115a1ef2da4bb8320531c0821f1.tar.gz |
BASELINE: Update Chromium to 106.0.5249.126
Change-Id: Ib0bb21c437a7d1686e21c33f2d329f2ac425b7ab
Reviewed-on: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/438936
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/cert/pki')
82 files changed, 23246 insertions, 0 deletions
diff --git a/chromium/net/cert/pki/README.md b/chromium/net/cert/pki/README.md new file mode 100644 index 00000000000..9e936b61b99 --- /dev/null +++ b/chromium/net/cert/pki/README.md @@ -0,0 +1,11 @@ +# Web PKI Certificate path building and verification + +This directory contains the core internal code used for path building +and verifying certificates. This is existing temporarily in this +directory while changes are made to remove chromium specific +dependencies. This code will be moving to it's own separate library +in boringssl (Issue 1322914). + +Please do not depend on this directory continuing to exist, and please +try to avoid adding dependencies on anything in this directory from +outside of //net/cert. diff --git a/chromium/net/cert/pki/cert_error_id.cc b/chromium/net/cert/pki/cert_error_id.cc new file mode 100644 index 00000000000..793b92ffb2c --- /dev/null +++ b/chromium/net/cert/pki/cert_error_id.cc @@ -0,0 +1,14 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/cert_error_id.h" + +namespace net { + +const char* CertErrorIdToDebugString(CertErrorId id) { + // The CertErrorId is simply a pointer for a C-string literal. + return reinterpret_cast<const char*>(id); +} + +} // namespace net diff --git a/chromium/net/cert/pki/cert_error_id.h b/chromium/net/cert/pki/cert_error_id.h new file mode 100644 index 00000000000..1c0e4ec947b --- /dev/null +++ b/chromium/net/cert/pki/cert_error_id.h @@ -0,0 +1,37 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CERT_ERROR_ID_H_ +#define NET_CERT_PKI_CERT_ERROR_ID_H_ + +#include "net/base/net_export.h" + +namespace net { + +// 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. +NET_EXPORT const char* CertErrorIdToDebugString(CertErrorId id); + +} // namespace net + +#endif // NET_CERT_PKI_CERT_ERROR_ID_H_ diff --git a/chromium/net/cert/pki/cert_error_params.cc b/chromium/net/cert/pki/cert_error_params.cc new file mode 100644 index 00000000000..0d4f2b61d83 --- /dev/null +++ b/chromium/net/cert/pki/cert_error_params.cc @@ -0,0 +1,140 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/cert_error_params.h" + +#include <memory> + +#include "base/check.h" +#include "base/strings/string_number_conversions.h" +#include "net/der/input.h" + +namespace net { + +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 += ": " + base::HexEncode(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(": ") + base::NumberToString(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(": ") + base::NumberToString(value1_) + "\n" + + name2_ + std::string(": ") + base::NumberToString(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); +} + +NET_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 diff --git a/chromium/net/cert/pki/cert_error_params.h b/chromium/net/cert/pki/cert_error_params.h new file mode 100644 index 00000000000..b00d0f2e8a4 --- /dev/null +++ b/chromium/net/cert/pki/cert_error_params.h @@ -0,0 +1,67 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CERT_ERROR_PARAMS_H_ +#define NET_CERT_PKI_CERT_ERROR_PARAMS_H_ + +#include <memory> +#include <string> + +#include "net/base/net_export.h" + +namespace net { + +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 NET_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. +NET_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams1Der( + const char* name, + const der::Input& der); + +// Same as CreateCertErrorParams1Der() but has a second DER blob. +NET_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. +NET_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams1SizeT( + const char* name, + size_t value); + +// Same as CreateCertErrorParams1SizeT() but has a second size_t. +NET_EXPORT std::unique_ptr<CertErrorParams> CreateCertErrorParams2SizeT( + const char* name1, + size_t value1, + const char* name2, + size_t value2); + +} // namespace net + +#endif // NET_CERT_PKI_CERT_ERROR_PARAMS_H_ diff --git a/chromium/net/cert/pki/cert_errors.cc b/chromium/net/cert/pki/cert_errors.cc new file mode 100644 index 00000000000..833fb1d3638 --- /dev/null +++ b/chromium/net/cert/pki/cert_errors.cc @@ -0,0 +1,201 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/cert_errors.h" + +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/parse_name.h" +#include "net/cert/pki/parsed_certificate.h" + +#include <sstream> + +namespace net { + +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 severity, + CertErrorId id, + std::unique_ptr<CertErrorParams> params) + : severity(severity), id(id), params(std::move(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 diff --git a/chromium/net/cert/pki/cert_errors.h b/chromium/net/cert/pki/cert_errors.h new file mode 100644 index 00000000000..98f635da34b --- /dev/null +++ b/chromium/net/cert/pki/cert_errors.h @@ -0,0 +1,168 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ---------------------------- +// Overview of error design +// ---------------------------- +// +// 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 NET_CERT_PKI_CERT_ERRORS_H_ +#define NET_CERT_PKI_CERT_ERRORS_H_ + +#include <memory> +#include <vector> + +#include "base/compiler_specific.h" +#include "net/base/net_export.h" +#include "net/cert/pki/cert_error_id.h" +#include "net/cert/pki/parsed_certificate.h" + +namespace net { + +class CertErrorParams; + +// CertError represents either an error or a warning. +struct NET_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 NET_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 NET_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 // NET_CERT_PKI_CERT_ERRORS_H_ diff --git a/chromium/net/cert/pki/cert_issuer_source.h b/chromium/net/cert/pki/cert_issuer_source.h new file mode 100644 index 00000000000..1568cd058f3 --- /dev/null +++ b/chromium/net/cert/pki/cert_issuer_source.h @@ -0,0 +1,67 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CERT_ISSUER_SOURCE_H_ +#define NET_CERT_PKI_CERT_ISSUER_SOURCE_H_ + +#include <memory> +#include <vector> + +#include "net/base/net_export.h" +#include "net/cert/pki/parsed_certificate.h" + +namespace net { + +// 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 NET_EXPORT CertIssuerSource { + public: + class NET_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) = 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 // NET_CERT_PKI_CERT_ISSUER_SOURCE_H_ diff --git a/chromium/net/cert/pki/cert_issuer_source_static.cc b/chromium/net/cert/pki/cert_issuer_source_static.cc new file mode 100644 index 00000000000..c41aede9d6f --- /dev/null +++ b/chromium/net/cert/pki/cert_issuer_source_static.cc @@ -0,0 +1,36 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/cert_issuer_source_static.h" + +namespace net { + +CertIssuerSourceStatic::CertIssuerSourceStatic() = default; +CertIssuerSourceStatic::~CertIssuerSourceStatic() = default; + +void CertIssuerSourceStatic::AddCert(scoped_refptr<ParsedCertificate> cert) { + intermediates_.insert(std::make_pair( + cert->normalized_subject().AsStringPiece(), std::move(cert))); +} + +void CertIssuerSourceStatic::Clear() { + intermediates_.clear(); +} + +void CertIssuerSourceStatic::SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) { + auto range = + intermediates_.equal_range(cert->normalized_issuer().AsStringPiece()); + 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 diff --git a/chromium/net/cert/pki/cert_issuer_source_static.h b/chromium/net/cert/pki/cert_issuer_source_static.h new file mode 100644 index 00000000000..c3be882d023 --- /dev/null +++ b/chromium/net/cert/pki/cert_issuer_source_static.h @@ -0,0 +1,50 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CERT_ISSUER_SOURCE_STATIC_H_ +#define NET_CERT_PKI_CERT_ISSUER_SOURCE_STATIC_H_ + +#include <unordered_map> + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/cert/pki/cert_issuer_source.h" + +namespace net { + +// Synchronously returns issuers from a pre-supplied set. +class NET_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(scoped_refptr<ParsedCertificate> cert); + + // Clears the set of certificates. + void Clear(); + + // 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<base::StringPiece, + scoped_refptr<ParsedCertificate>, + base::StringPieceHash> + intermediates_; +}; + +} // namespace net + +#endif // NET_CERT_PKI_CERT_ISSUER_SOURCE_STATIC_H_ diff --git a/chromium/net/cert/pki/cert_issuer_source_static_unittest.cc b/chromium/net/cert/pki/cert_issuer_source_static_unittest.cc new file mode 100644 index 00000000000..02727cc6724 --- /dev/null +++ b/chromium/net/cert/pki/cert_issuer_source_static_unittest.cc @@ -0,0 +1,37 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/cert_issuer_source_static.h" + +#include "net/cert/pki/cert_issuer_source_sync_unittest.h" +#include "net/cert/pki/parsed_certificate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class CertIssuerSourceStaticTestDelegate { + public: + void AddCert(scoped_refptr<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); + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/cert_issuer_source_sync_unittest.h b/chromium/net/cert/pki/cert_issuer_source_sync_unittest.h new file mode 100644 index 00000000000..e3f165036db --- /dev/null +++ b/chromium/net/cert/pki/cert_issuer_source_sync_unittest.h @@ -0,0 +1,216 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CERT_ISSUER_SOURCE_SYNC_UNITTEST_H_ +#define NET_CERT_PKI_CERT_ISSUER_SOURCE_SYNC_UNITTEST_H_ + +#include <algorithm> + +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/cert_issuer_source.h" +#include "net/cert/pki/test_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +namespace net { + +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, + scoped_refptr<ParsedCertificate>* result) { + std::string der; + ::testing::AssertionResult r = + ReadTestPem("net/data/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(scoped_refptr<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(scoped_refptr<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_; + scoped_refptr<ParsedCertificate> root_; + scoped_refptr<ParsedCertificate> i1_1_; + scoped_refptr<ParsedCertificate> i1_2_; + scoped_refptr<ParsedCertificate> i2_; + scoped_refptr<ParsedCertificate> i3_1_; + scoped_refptr<ParsedCertificate> i3_2_; + scoped_refptr<ParsedCertificate> c1_; + scoped_refptr<ParsedCertificate> c2_; + scoped_refptr<ParsedCertificate> d_; + scoped_refptr<ParsedCertificate> e1_; + scoped_refptr<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 // NET_CERT_PKI_CERT_ISSUER_SOURCE_SYNC_UNITTEST_H_ diff --git a/chromium/net/cert/pki/certificate_policies.cc b/chromium/net/cert/pki/certificate_policies.cc new file mode 100644 index 00000000000..e7a3c17e435 --- /dev/null +++ b/chromium/net/cert/pki/certificate_policies.cc @@ -0,0 +1,372 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "net/cert/pki/certificate_policies.h" + +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +namespace net { + +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; + + absl::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; + } + + absl::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 diff --git a/chromium/net/cert/pki/certificate_policies.h b/chromium/net/cert/pki/certificate_policies.h new file mode 100644 index 00000000000..182bf9a82f5 --- /dev/null +++ b/chromium/net/cert/pki/certificate_policies.h @@ -0,0 +1,135 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CERTIFICATE_POLICIES_H_ +#define NET_CERT_PKI_CERTIFICATE_POLICIES_H_ + +#include <stdint.h> + +#include <vector> + +#include "net/base/net_export.h" +#include "net/der/input.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +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 NET_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). +NET_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). +NET_EXPORT bool ParseCertificatePoliciesExtensionOids( + const der::Input& extension_value, + bool fail_parsing_unknown_qualifier_oids, + std::vector<der::Input>* policy_oids, + CertErrors* errors); + +struct ParsedPolicyConstraints { + absl::optional<uint8_t> require_explicit_policy; + + absl::optional<uint8_t> inhibit_policy_mapping; +}; + +// Parses a PolicyConstraints SEQUENCE as defined by RFC 5280. Returns true on +// success, and sets |out|. +[[nodiscard]] NET_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]] NET_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]] NET_EXPORT bool ParsePolicyMappings( + const der::Input& policy_mappings_tlv, + std::vector<ParsedPolicyMapping>* mappings); + +} // namespace net + +#endif // NET_CERT_PKI_CERTIFICATE_POLICIES_H_ diff --git a/chromium/net/cert/pki/certificate_policies_unittest.cc b/chromium/net/cert/pki/certificate_policies_unittest.cc new file mode 100644 index 00000000000..b38aff49a73 --- /dev/null +++ b/chromium/net/cert/pki/certificate_policies_unittest.cc @@ -0,0 +1,313 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/certificate_policies.h" + +#include "net/cert/pki/test_helpers.h" +#include "net/der/input.h" +#include "net/der/parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +::testing::AssertionResult LoadTestData(const std::string& name, + std::string* result) { + std::string path = "net/data/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 diff --git a/chromium/net/cert/pki/common_cert_errors.cc b/chromium/net/cert/pki/common_cert_errors.cc new file mode 100644 index 00000000000..d282999c472 --- /dev/null +++ b/chromium/net/cert/pki/common_cert_errors.cc @@ -0,0 +1,66 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/common_cert_errors.h" + +namespace net::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(kChainIsLength1, "Cannot verify a chain of length 1"); +DEFINE_CERT_ERROR_ID(kUnconsumedCriticalExtension, + "Unconsumed critical extension"); +DEFINE_CERT_ERROR_ID( + kTargetCertInconsistentCaBits, + "Target certificate looks like a CA but does not set all CA properties"); +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(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(kEkuLacksClientAuth, + "The extended key usage does not include client auth"); +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 diff --git a/chromium/net/cert/pki/common_cert_errors.h b/chromium/net/cert/pki/common_cert_errors.h new file mode 100644 index 00000000000..2819671f4c9 --- /dev/null +++ b/chromium/net/cert/pki/common_cert_errors.h @@ -0,0 +1,145 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_COMMON_CERT_ERRORS_H_ +#define NET_CERT_PKI_COMMON_CERT_ERRORS_H_ + +#include "net/base/net_export.h" +#include "net/cert/pki/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 net::cert_errors { + +// An internal error occurred which prevented path building or verification +// from finishing. +NET_EXPORT extern const CertErrorId kInternalError; + +// The verification time is after the certificate's notAfter time. +NET_EXPORT extern const CertErrorId kValidityFailedNotAfter; + +// The verification time is before the certificate's notBefore time. +NET_EXPORT extern const CertErrorId kValidityFailedNotBefore; + +// The certificate is actively distrusted by the trust store (this is separate +// from other revocation mechanisms). +NET_EXPORT extern const CertErrorId kDistrustedByTrustStore; + +// The certificate disagrees on what the signature algorithm was +// (Certificate.signatureAlgorithm != TBSCertificate.signature). +NET_EXPORT extern const CertErrorId kSignatureAlgorithmMismatch; + +// Certificate verification was called with an empty chain. +NET_EXPORT extern const CertErrorId kChainIsEmpty; + +// Certificate verification was called with a chain of length 1, which is not +// supported (i.e. the target certificate cannot also be a trusted +// certificate). See https://crbug.com/814994. +NET_EXPORT extern const CertErrorId kChainIsLength1; + +// The certificate contains an unknown extension which is marked as critical. +NET_EXPORT extern const CertErrorId kUnconsumedCriticalExtension; + +// The target certificate appears to be a CA (has Basic Constraints CA=true), +// however does not have a keyUsage consistent with being a CA (keyCertSign). +NET_EXPORT extern const CertErrorId kTargetCertInconsistentCaBits; + +// The certificate is being used to sign other certificates, however the +// keyCertSign KeyUsage was not set. +NET_EXPORT extern const CertErrorId kKeyCertSignBitNotSet; + +// The chain violates the max_path_length from BasicConstraints. +NET_EXPORT extern const CertErrorId kMaxPathLengthViolated; + +// The certificate being used to sign other certificates has a +// BasicConstraints extension, however it sets CA=false +NET_EXPORT extern const CertErrorId kBasicConstraintsIndicatesNotCa; + +// The certificate being used to sign other certificates does not include a +// BasicConstraints extension. +NET_EXPORT extern const CertErrorId kMissingBasicConstraints; + +// The certificate has a subject or subjectAltName that violates an issuer's +// name constraints. +NET_EXPORT extern const CertErrorId kNotPermittedByNameConstraints; + +// The chain has an excessive number of names and/or name constraints. +NET_EXPORT extern const CertErrorId kTooManyNameConstraintChecks; + +// The certificate's issuer field does not match the subject of its alleged +// issuer. +NET_EXPORT extern const CertErrorId kSubjectDoesNotMatchIssuer; + +// Failed to verify the certificate's signature using its issuer's public key. +NET_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. +NET_EXPORT extern const CertErrorId kSignatureAlgorithmsDifferentEncoding; + +// The certificate verification is being done for serverAuth, however the +// certificate lacks serverAuth in its ExtendedKeyUsages. +NET_EXPORT extern const CertErrorId kEkuLacksServerAuth; + +// The certificate verification is being done for clientAuth, however the +// certificate lacks clientAuth in its ExtendedKeyUsages. +NET_EXPORT extern const CertErrorId kEkuLacksClientAuth; + +// The root certificate in a chain is not trusted. +NET_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). +NET_EXPORT extern const CertErrorId kNoValidPolicy; + +// The certificate is trying to map to, or from, anyPolicy. +NET_EXPORT extern const CertErrorId kPolicyMappingAnyPolicy; + +// The public key in this certificate could not be parsed. +NET_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. +NET_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. +NET_EXPORT extern const CertErrorId kUnacceptablePublicKey; + +// The certificate's EKU is missing serverAuth. However Netscape Server Gated +// Crypto is present instead. +NET_EXPORT extern const CertErrorId kEkuLacksServerAuthButHasGatedCrypto; + +// The certificate has been revoked. +NET_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. +NET_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. +NET_EXPORT extern const CertErrorId kUnableToCheckRevocation; + +// Path building was unable to find any issuers for the certificate. +NET_EXPORT extern const CertErrorId kNoIssuersFound; + +// Deadline was reached during path building. +NET_EXPORT extern const CertErrorId kDeadlineExceeded; + +// Iteration limit was reached during path building. +NET_EXPORT extern const CertErrorId kIterationLimitExceeded; + +// Depth limit was reached during path building. +NET_EXPORT extern const CertErrorId kDepthLimitExceeded; + +} // namespace net::cert_errors + +#endif // NET_CERT_PKI_COMMON_CERT_ERRORS_H_ diff --git a/chromium/net/cert/pki/crl.cc b/chromium/net/cert/pki/crl.cc new file mode 100644 index 00000000000..c3a0c9dc5fa --- /dev/null +++ b/chromium/net/cert/pki/crl.cc @@ -0,0 +1,623 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/crl.h" + +#include "base/stl_util.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/revocation_util.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/cert/pki/verify_name_match.h" +#include "net/cert/pki/verify_signed_data.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +namespace net { + +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; + net::CertErrors unused_errors; + return parser.ReadTag(der::kSequence, &name_rdn) && + NormalizeName(name_rdn, out_normalized_name, &unused_errors) && + !parser.HasMore(); +} + +bool ContainsExactMatchingName(std::vector<base::StringPiece> a, + std::vector<base::StringPiece> b) { + std::sort(a.begin(), a.end()); + std::sort(b.begin(), b.end()); + return !base::STLSetIntersection<std::vector<base::StringPiece>>(a, b) + .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 } + absl::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 + absl::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 = absl::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 = absl::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, + absl::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 } + absl::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, + absl::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, + absl::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 absl::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(base::StringPiece raw_crl, + const ParsedCertificateList& valid_chain, + size_t target_cert_index, + const ParsedDistributionPoint& cert_dp, + const base::Time& verify_time, + const base::TimeDelta& max_age) { + 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. + absl::optional<SignatureAlgorithm> signature_algorithm = + ParseSignatureAlgorithm(signature_algorithm_tlv, + /*errors=*/nullptr); + 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). + absl::optional<SignatureAlgorithm> tbs_alg = + ParseSignatureAlgorithm(tbs_cert_list.signature_algorithm_tlv, + /*errors=*/nullptr); + 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, + base::OptionalOrNullptr(tbs_cert_list.next_update), verify_time, + max_age)) { + 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)) { + 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 diff --git a/chromium/net/cert/pki/crl.h b/chromium/net/cert/pki/crl.h new file mode 100644 index 00000000000..e6add49add4 --- /dev/null +++ b/chromium/net/cert/pki/crl.h @@ -0,0 +1,224 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_CRL_H_ +#define NET_CERT_PKI_CRL_H_ + +#include "base/strings/string_piece_forward.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/cert/pki/general_names.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +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]] NET_EXPORT_PRIVATE 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]] NET_EXPORT_PRIVATE 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 NET_EXPORT_PRIVATE 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; + absl::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. + absl::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. + absl::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]] NET_EXPORT_PRIVATE bool ParseIssuingDistributionPoint( + const der::Input& extension_value, + std::unique_ptr<GeneralNames>* out_distribution_point_names, + ContainedCertsType* out_only_contains_cert_type); + +NET_EXPORT_PRIVATE CRLRevocationStatus +GetCRLStatusForCert(const der::Input& cert_serial, + CrlVersion crl_version, + const absl::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|: The time to use when checking revocation status. +// * |max_age|: The maximum age for a CRL, implemented as time since +// the |thisUpdate| field in the CRL TBSCertList. Responses older than +// |max_age| will be considered invalid. +[[nodiscard]] NET_EXPORT CRLRevocationStatus +CheckCRL(base::StringPiece raw_crl, + const ParsedCertificateList& valid_chain, + size_t target_cert_index, + const ParsedDistributionPoint& cert_dp, + const base::Time& verify_time, + const base::TimeDelta& max_age); + +} // namespace net + +#endif // NET_CERT_PKI_CRL_H_ diff --git a/chromium/net/cert/pki/extended_key_usage.cc b/chromium/net/cert/pki/extended_key_usage.cc new file mode 100644 index 00000000000..e4e97b30175 --- /dev/null +++ b/chromium/net/cert/pki/extended_key_usage.cc @@ -0,0 +1,40 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/extended_key_usage.h" + +#include "net/der/input.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +namespace net { + +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 diff --git a/chromium/net/cert/pki/extended_key_usage.h b/chromium/net/cert/pki/extended_key_usage.h new file mode 100644 index 00000000000..f2ce9eb3e36 --- /dev/null +++ b/chromium/net/cert/pki/extended_key_usage.h @@ -0,0 +1,88 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_EXTENDED_KEY_USAGE_H_ +#define NET_CERT_PKI_EXTENDED_KEY_USAGE_H_ + +#include <vector> + +#include "net/base/net_export.h" +#include "net/der/input.h" + +namespace net { + +// 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. +NET_EXPORT bool ParseEKUExtension(const der::Input& extension_value, + std::vector<der::Input>* eku_oids); + +} // namespace net + +#endif // NET_CERT_PKI_EXTENDED_KEY_USAGE_H_ diff --git a/chromium/net/cert/pki/extended_key_usage_unittest.cc b/chromium/net/cert/pki/extended_key_usage_unittest.cc new file mode 100644 index 00000000000..f98ad799882 --- /dev/null +++ b/chromium/net/cert/pki/extended_key_usage_unittest.cc @@ -0,0 +1,166 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "net/cert/pki/extended_key_usage.h" +#include "net/der/input.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +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 diff --git a/chromium/net/cert/pki/general_names.cc b/chromium/net/cert/pki/general_names.cc new file mode 100644 index 00000000000..0a598dd24fe --- /dev/null +++ b/chromium/net/cert/pki/general_names.cc @@ -0,0 +1,236 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/general_names.h" + +#include "base/check_op.h" +#include "base/strings/string_util.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/der/input.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +namespace net { + +DEFINE_CERT_ERROR_ID(kFailedParsingGeneralName, "Failed parsing GeneralName"); + +namespace { + +DEFINE_CERT_ERROR_ID(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 IPAddressBytes& mask, unsigned prefix_length) { + size_t zero_bits = mask.size() * CHAR_BIT - prefix_length; + size_t zero_bytes = zero_bits / CHAR_BIT; + std::vector<uint8_t> zeros(zero_bytes, 0); + if (memcmp(zeros.data(), mask.data() + mask.size() - zero_bytes, zero_bytes)) + return false; + size_t leftover_bits = zero_bits % CHAR_BIT; + if (leftover_bits) { + uint8_t b = mask[mask.size() - zero_bytes - 1]; + for (size_t i = 0; i < leftover_bits; ++i) { + if (b & (1 << i)) + return false; + } + } + return true; +} + +} // namespace + +GeneralNames::GeneralNames() = 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 base::StringPiece s = value.AsStringPiece(); + if (!base::IsStringASCII(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 base::StringPiece s = value.AsStringPiece(); + if (!base::IsStringASCII(s)) { + errors->AddError(kDnsNameNotAscii); + return false; + } + subtrees->dns_names.push_back(s); + } else if (tag == der::ContextSpecificConstructed(3)) { + // x400Address [3] ORAddress, + name_type = GENERAL_NAME_X400_ADDRESS; + 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 base::StringPiece s = value.AsStringPiece(); + if (!base::IsStringASCII(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() != IPAddress::kIPv4AddressSize && + value.Length() != 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() != IPAddress::kIPv4AddressSize * 2 && + value.Length() != IPAddress::kIPv6AddressSize * 2) { + errors->AddError(kFailedParsingIp); + return false; + } + const IPAddress mask(value.UnsafeData() + value.Length() / 2, + value.Length() / 2); + const unsigned mask_prefix_length = MaskPrefixLength(mask); + if (!IsSuffixZero(mask.bytes(), mask_prefix_length)) { + errors->AddError(kFailedParsingIp); + return false; + } + subtrees->ip_address_ranges.emplace_back( + 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 diff --git a/chromium/net/cert/pki/general_names.h b/chromium/net/cert/pki/general_names.h new file mode 100644 index 00000000000..0bacddfe98e --- /dev/null +++ b/chromium/net/cert/pki/general_names.h @@ -0,0 +1,124 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_GENERAL_NAMES_H_ +#define NET_CERT_PKI_GENERAL_NAMES_H_ + +#include <memory> +#include <vector> + +#include "base/strings/string_piece_forward.h" +#include "net/base/ip_address.h" +#include "net/base/net_export.h" +#include "net/cert/pki/cert_error_id.h" + +namespace net { + +class CertErrors; + +NET_EXPORT extern const CertErrorId kFailedParsingGeneralName; + +namespace der { +class Input; +} // namespace der + +// Bitfield values for the GeneralName types defined in RFC 5280. The ordering +// and exact values are not important, but match the order from the RFC for +// convenience. +enum GeneralNameTypes { + GENERAL_NAME_NONE = 0, + GENERAL_NAME_OTHER_NAME = 1 << 0, + GENERAL_NAME_RFC822_NAME = 1 << 1, + GENERAL_NAME_DNS_NAME = 1 << 2, + GENERAL_NAME_X400_ADDRESS = 1 << 3, + GENERAL_NAME_DIRECTORY_NAME = 1 << 4, + GENERAL_NAME_EDI_PARTY_NAME = 1 << 5, + GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER = 1 << 6, + GENERAL_NAME_IP_ADDRESS = 1 << 7, + GENERAL_NAME_REGISTERED_ID = 1 << 8, + GENERAL_NAME_ALL_TYPES = (1 << 9) - 1, +}; + +// Represents a GeneralNames structure. When processing GeneralNames, it is +// often necessary to know which types of names were present, and to check +// all the names of a certain type. Therefore, a bitfield of all the name +// types is kept, and the names are split into members for each type. +struct NET_EXPORT GeneralNames { + // Controls parsing of iPAddress names in ParseGeneralName. + // IP_ADDRESS_ONLY parses the iPAddress names as a 4 or 16 byte IP address. + // IP_ADDRESS_AND_NETMASK parses the iPAddress names as 8 or 32 bytes + // containing an IP address followed by a netmask. + enum ParseGeneralNameIPAddressType { + IP_ADDRESS_ONLY, + IP_ADDRESS_AND_NETMASK, + }; + + GeneralNames(); + ~GeneralNames(); + + // Create a GeneralNames object representing the DER-encoded + // |general_names_tlv|. 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<base::StringPiece> rfc822_names; + + // ASCII hostnames. + std::vector<base::StringPiece> 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<base::StringPiece> 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<IPAddress> ip_addresses; + + // iPAddress ranges, as <IP, prefix length> pairs. This will be populated + // if the GeneralNames represents a Name Constraints. + std::vector<std::pair<IPAddress, unsigned>> ip_address_ranges; + + // 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]] NET_EXPORT bool ParseGeneralName( + const der::Input& input, + GeneralNames::ParseGeneralNameIPAddressType ip_address_type, + GeneralNames* subtrees, + CertErrors* errors); + +} // namespace net + +#endif // NET_CERT_PKI_GENERAL_NAMES_H_ diff --git a/chromium/net/cert/pki/name_constraints.cc b/chromium/net/cert/pki/name_constraints.cc new file mode 100644 index 00000000000..b66abdbef6c --- /dev/null +++ b/chromium/net/cert/pki/name_constraints.cc @@ -0,0 +1,428 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/name_constraints.h" + +#include <limits.h> + +#include <memory> + +#include "base/check.h" +#include "base/numerics/clamped_math.h" +#include "base/strings/string_util.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/common_cert_errors.h" +#include "net/cert/pki/verify_name_match.h" +#include "net/der/input.h" +#include "net/der/parser.h" +#include "net/der/tag.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +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_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(base::StringPiece name, + base::StringPiece 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) { + base::StringPiece dns_constraint_domain = + dns_constraint.substr(dns_constraint_dot_pos + 1); + base::StringPiece wildcard_domain = name.substr(2); + if (base::EqualsCaseInsensitiveASCII(wildcard_domain, + dns_constraint_domain)) { + return true; + } + } + } + + if (!base::EndsWith(name, dns_constraint, + base::CompareCase::INSENSITIVE_ASCII)) { + 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; +} + +} // 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; + + absl::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); + + absl::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. + const size_t kMaxChecks = 1048576; // 1 << 20 + base::ClampedNumeric<size_t> check_count = 0; + + if (subject_alt_names) { + check_count += + base::ClampMul(subject_alt_names->dns_names.size(), + base::ClampAdd(excluded_subtrees_.dns_names.size(), + permitted_subtrees_.dns_names.size())); + check_count += base::ClampMul( + subject_alt_names->directory_names.size(), + base::ClampAdd(excluded_subtrees_.directory_names.size(), + permitted_subtrees_.directory_names.size())); + check_count += base::ClampMul( + subject_alt_names->ip_addresses.size(), + base::ClampAdd(excluded_subtrees_.ip_address_ranges.size(), + permitted_subtrees_.ip_address_ranges.size())); + } + + if (!(subject_alt_names && subject_rdn_sequence.Length() == 0)) { + check_count += base::ClampAdd(excluded_subtrees_.directory_names.size(), + permitted_subtrees_.directory_names.size()); + } + + if (check_count > kMaxChecks) { + errors->AddError(cert_errors::kTooManyNameConstraintChecks); + 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: + 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. + if (!subject_alt_names && + (constrained_name_types() & GENERAL_NAME_RFC822_NAME)) { + bool contained_email_address = false; + if (!NameContainsEmailAddress(subject_rdn_sequence, + &contained_email_address)) { + errors->AddError(cert_errors::kNotPermittedByNameConstraints); + return; + } + if (contained_email_address) { + 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::IsPermittedDNSName(base::StringPiece 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 IPAddress& ip) const { + for (const auto& excluded_ip : excluded_subtrees_.ip_address_ranges) { + if (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 (IPAddressMatchesPrefix(ip, permitted_ip.first, permitted_ip.second)) + return true; + } + + return false; +} + +} // namespace net diff --git a/chromium/net/cert/pki/name_constraints.h b/chromium/net/cert/pki/name_constraints.h new file mode 100644 index 00000000000..0fe0452da51 --- /dev/null +++ b/chromium/net/cert/pki/name_constraints.h @@ -0,0 +1,100 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_NAME_CONSTRAINTS_H_ +#define NET_CERT_PKI_NAME_CONSTRAINTS_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/strings/string_piece_forward.h" +#include "net/base/ip_address.h" +#include "net/base/net_export.h" +#include "net/cert/pki/general_names.h" + +namespace net { + +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 NET_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 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(base::StringPiece 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 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 // NET_CERT_PKI_NAME_CONSTRAINTS_H_ diff --git a/chromium/net/cert/pki/name_constraints_unittest.cc b/chromium/net/cert/pki/name_constraints_unittest.cc new file mode 100644 index 00000000000..32a97af4f4b --- /dev/null +++ b/chromium/net/cert/pki/name_constraints_unittest.cc @@ -0,0 +1,1221 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/name_constraints.h" + +#include <memory> + +#include "net/base/ip_address.h" +#include "net/cert/pki/common_cert_errors.h" +#include "net/cert/pki/test_helpers.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +::testing::AssertionResult LoadTestData(const char* token, + const std::string& basename, + std::string* result) { + std::string path = "net/data/name_constraints_unittest/" + basename; + + const PemBlockMapping mappings[] = { + {token, result}, + }; + + return ReadTestDataFromPemFile(path, mappings); +} + +::testing::AssertionResult LoadTestName(const std::string& basename, + std::string* result) { + return LoadTestData("NAME", basename, result); +} + +::testing::AssertionResult LoadTestNameConstraint(const std::string& basename, + std::string* result) { + return LoadTestData("NAME CONSTRAINTS", basename, result); +} + +::testing::AssertionResult LoadTestSubjectAltNameData( + const std::string& basename, + std::string* result) { + return LoadTestData("SUBJECT ALTERNATIVE NAME", basename, result); +} + +::testing::AssertionResult LoadTestSubjectAltName( + const std::string& basename, + std::unique_ptr<GeneralNames>* result, + std::string* result_der) { + ::testing::AssertionResult load_result = + LoadTestSubjectAltNameData(basename, result_der); + if (!load_result) + return load_result; + CertErrors errors; + *result = GeneralNames::Create(der::Input(result_der), &errors); + if (!*result) + return ::testing::AssertionFailure() << "Create failed"; + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult IsPermittedCert( + const NameConstraints* name_constraints, + const der::Input& subject_rdn_sequence, + const GeneralNames* subject_alt_names) { + CertErrors errors; + name_constraints->IsPermittedCert(subject_rdn_sequence, subject_alt_names, + &errors); + if (!errors.ContainsAnyErrorWithSeverity(CertError::SEVERITY_HIGH)) + return ::testing::AssertionSuccess(); + if (!errors.ContainsError(cert_errors::kNotPermittedByNameConstraints)) + ADD_FAILURE() << "unexpected error " << errors.ToDebugString(); + return ::testing::AssertionFailure(); +} + +} // namespace + +class ParseNameConstraints + : public ::testing::TestWithParam<::testing::tuple<bool>> { + public: + bool is_critical() const { return ::testing::get<0>(GetParam()); } +}; + +// Run the tests with the name constraints marked critical and non-critical. For +// supported name types, the results should be the same for both. +INSTANTIATE_TEST_SUITE_P(InstantiationName, + ParseNameConstraints, + ::testing::Values(true, false)); + +TEST_P(ParseNameConstraints, DNSNames) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + EXPECT_TRUE(name_constraints->IsPermittedDNSName("permitted.example.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("permitted.example.com.")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("a.permitted.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("apermitted.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("apermitted.example.com.")); + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("alsopermitted.example.com")); + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("excluded.permitted.example.com")); + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("a.excluded.permitted.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName( + "stillnotpermitted.excluded.permitted.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName( + "a.stillnotpermitted.excluded.permitted.example.com")); + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("extraneousexclusion.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName( + "a.extraneousexclusion.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("other.example.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("other.com")); + + // Wildcard names: + // Pattern could match excluded.permitted.example.com, thus should not be + // allowed. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.permitted.example.com")); + // Entirely within excluded name, obviously not allowed. + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("*.excluded.permitted.example.com")); + // Within permitted.example.com and cannot match any exclusion, thus these are + // allowed. + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("*.foo.permitted.example.com")); + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("*.alsopermitted.example.com")); + // Matches permitted.example2.com, but also matches other .example2.com names + // which are not in either permitted or excluded, so not allowed. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.example2.com")); + // Partial wildcards are not supported, so these name are permitted even if + // it seems like they shouldn't be. It's fine, since certificate verification + // won't treat them as wildcard names either. + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("*xcluded.permitted.example.com")); + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("exclude*.permitted.example.com")); + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("excl*ded.permitted.example.com")); + // Garbage wildcard data. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.*")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName(".*")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*")); + // Matches SAN with trailing dot. + EXPECT_TRUE(name_constraints->IsPermittedDNSName("permitted.example3.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("permitted.example3.com.")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("a.permitted.example3.com")); + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("a.permitted.example3.com.")); + + EXPECT_EQ(GENERAL_NAME_DNS_NAME, name_constraints->constrained_name_types()); + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-permitted.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-dnsname.pem", &san, &san_der)); + EXPECT_FALSE( + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-directoryname.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-ipaddress.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, + DNSNamesWithMultipleLevelsBetweenExcludedAndPermitted) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname2.pem", &a)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // Matches permitted exactly. + EXPECT_TRUE(name_constraints->IsPermittedDNSName("com")); + // Contained within permitted and doesn't match excluded (foo.bar.com). + EXPECT_TRUE(name_constraints->IsPermittedDNSName("bar.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("baz.bar.com")); + // Matches excluded exactly. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("foo.bar.com")); + // Contained within excluded. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("baz.foo.bar.com")); + + // Cannot match anything within excluded. + EXPECT_TRUE(name_constraints->IsPermittedDNSName("*.baz.bar.com")); + // Wildcard hostnames only match a single label, so cannot match excluded + // which has two labels before .com. + EXPECT_TRUE(name_constraints->IsPermittedDNSName("*.com")); + + // Partial match of foo.bar.com. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.bar.com")); + // All expansions of wildcard are within excluded. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.foo.bar.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesPermittedWithLeadingDot) { + std::string a; + ASSERT_TRUE( + LoadTestNameConstraint("dnsname-permitted_with_leading_dot.pem", &a)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // A permitted dNSName constraint of ".bar.com" should only match subdomains + // of .bar.com, but not bar.com itself. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("bar.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("foobar.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("foo.bar.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("*.bar.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesExcludedWithLeadingDot) { + std::string a; + ASSERT_TRUE( + LoadTestNameConstraint("dnsname-excluded_with_leading_dot.pem", &a)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // An excluded dNSName constraint of ".bar.com" should only match subdomains + // of .bar.com, but not bar.com itself. + EXPECT_TRUE(name_constraints->IsPermittedDNSName("com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("bar.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("foobar.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("foo.bar.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.bar.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesPermittedTwoDot) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-permitted_two_dot.pem", &a)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // A dNSName constraint of ".." isn't meaningful. Shouldn't match anything. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("com.")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("foo.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("*.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesExcludeOnly) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-excluded.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // Only "excluded.permitted.example.com" is excluded, and since permitted is + // empty, any dNSName outside that is allowed. + EXPECT_TRUE(name_constraints->IsPermittedDNSName("")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("foo.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("permitted.example.com")); + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("excluded.permitted.example.com")); + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("a.excluded.permitted.example.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesExcludeAll) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-excludeall.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // "permitted.example.com" is in the permitted section, but since "" is + // excluded, nothing is permitted. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("foo.com")); + EXPECT_FALSE(name_constraints->IsPermittedDNSName("permitted.example.com")); + EXPECT_FALSE( + name_constraints->IsPermittedDNSName("foo.permitted.example.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesExcludeDot) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-exclude_dot.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // "." is excluded, which should match nothing. + EXPECT_FALSE(name_constraints->IsPermittedDNSName("foo.com")); + EXPECT_TRUE(name_constraints->IsPermittedDNSName("permitted.example.com")); + EXPECT_TRUE( + name_constraints->IsPermittedDNSName("foo.permitted.example.com")); +} + +TEST_P(ParseNameConstraints, DNSNamesFailOnInvalidIA5String) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("dnsname.pem", &a)); + + size_t replace_location = a.find("permitted.example2.com"); + ASSERT_NE(std::string::npos, replace_location); + a.replace(replace_location, 1, 1, -1); + + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&a), is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, DirectoryNames) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("directoryname.pem", &constraints_der)); + + std::string name_us; + ASSERT_TRUE(LoadTestName("name-us.pem", &name_us)); + std::string name_us_ca; + ASSERT_TRUE(LoadTestName("name-us-california.pem", &name_us_ca)); + std::string name_us_ca_mountain_view; + ASSERT_TRUE(LoadTestName("name-us-california-mountain_view.pem", + &name_us_ca_mountain_view)); + std::string name_us_az; + ASSERT_TRUE(LoadTestName("name-us-arizona.pem", &name_us_az)); + std::string name_jp; + ASSERT_TRUE(LoadTestName("name-jp.pem", &name_jp)); + std::string name_jp_tokyo; + ASSERT_TRUE(LoadTestName("name-jp-tokyo.pem", &name_jp_tokyo)); + std::string name_de; + ASSERT_TRUE(LoadTestName("name-de.pem", &name_de)); + std::string name_ca; + ASSERT_TRUE(LoadTestName("name-ca.pem", &name_ca)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // Not in any permitted subtree. + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_ca))); + // Within the permitted C=US subtree. + EXPECT_TRUE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us))); + // Within the permitted C=US subtree. + EXPECT_TRUE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us_az))); + // Within the permitted C=US subtree, however the excluded C=US,ST=California + // subtree takes priority. + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us_ca))); + // Within the permitted C=US subtree as well as the permitted + // C=US,ST=California,L=Mountain View subtree, however the excluded + // C=US,ST=California subtree still takes priority. + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us_ca_mountain_view))); + // Not in any permitted subtree, and also inside the extraneous excluded C=DE + // subtree. + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_de))); + // Not in any permitted subtree. + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_jp))); + // Within the permitted C=JP,ST=Tokyo subtree. + EXPECT_TRUE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_jp_tokyo))); + + EXPECT_EQ(GENERAL_NAME_DIRECTORY_NAME, + name_constraints->constrained_name_types()); + + // Within the permitted C=US subtree. + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us), + nullptr /* subject_alt_names */)); + // Within the permitted C=US subtree, however the excluded C=US,ST=California + // subtree takes priority. + EXPECT_FALSE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_ca), + nullptr /* subject_alt_names */)); + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-permitted.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-dnsname.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-directoryname.pem", &san, &san_der)); + EXPECT_FALSE( + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-ipaddress.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, DirectoryNamesExcludeOnly) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("directoryname-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + std::string name_empty; + ASSERT_TRUE(LoadTestName("name-empty.pem", &name_empty)); + std::string name_us; + ASSERT_TRUE(LoadTestName("name-us.pem", &name_us)); + std::string name_us_ca; + ASSERT_TRUE(LoadTestName("name-us-california.pem", &name_us_ca)); + std::string name_us_ca_mountain_view; + ASSERT_TRUE(LoadTestName("name-us-california-mountain_view.pem", + &name_us_ca_mountain_view)); + + // Only "C=US,ST=California" is excluded, and since permitted is empty, + // any directoryName outside that is allowed. + EXPECT_TRUE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_empty))); + EXPECT_TRUE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us))); + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us_ca))); + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us_ca_mountain_view))); +} + +TEST_P(ParseNameConstraints, DirectoryNamesExcludeAll) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("directoryname-excludeall.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + std::string name_empty; + ASSERT_TRUE(LoadTestName("name-empty.pem", &name_empty)); + std::string name_us; + ASSERT_TRUE(LoadTestName("name-us.pem", &name_us)); + std::string name_us_ca; + ASSERT_TRUE(LoadTestName("name-us-california.pem", &name_us_ca)); + std::string name_us_ca_mountain_view; + ASSERT_TRUE(LoadTestName("name-us-california-mountain_view.pem", + &name_us_ca_mountain_view)); + std::string name_jp; + ASSERT_TRUE(LoadTestName("name-jp.pem", &name_jp)); + + // "C=US" is in the permitted section, but since an empty + // directoryName is excluded, nothing is permitted. + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_empty))); + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us))); + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_us_ca))); + EXPECT_FALSE(name_constraints->IsPermittedDirectoryName( + SequenceValueFromString(&name_jp))); +} + +TEST_P(ParseNameConstraints, IPAdresses) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // IPv4 tests: + + // Not in any permitted range. + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 169, 0, 1))); + + // Within the permitted 192.168.0.0/255.255.0.0 range. + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 168, 0, 1))); + + // Within the permitted 192.168.0.0/255.255.0.0 range, however the + // excluded 192.168.5.0/255.255.255.0 takes priority. + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 5, 1))); + + // Within the permitted 192.168.0.0/255.255.0.0 range as well as the + // permitted 192.168.5.32/255.255.255.224 range, however the excluded + // 192.168.5.0/255.255.255.0 still takes priority. + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 5, 33))); + + // Not in any permitted range. (Just outside the + // 192.167.5.32/255.255.255.224 range.) + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 167, 5, 31))); + + // Within the permitted 192.167.5.32/255.255.255.224 range. + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 167, 5, 32))); + + // Within the permitted 192.167.5.32/255.255.255.224 range. + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 167, 5, 63))); + + // Not in any permitted range. (Just outside the + // 192.167.5.32/255.255.255.224 range.) + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 167, 5, 64))); + + // Not in any permitted range, and also inside the extraneous excluded + // 192.166.5.32/255.255.255.224 range. + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 166, 5, 32))); + + // IPv6 tests: + + // Not in any permitted range. + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 0, 0, 0, 1))); + + // Within the permitted + // 102:304:506:708:90a:b0c::/ffff:ffff:ffff:ffff:ffff:ffff:: range. + EXPECT_TRUE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1))); + + // Within the permitted + // 102:304:506:708:90a:b0c::/ffff:ffff:ffff:ffff:ffff:ffff:: range, however + // the excluded + // 102:304:506:708:90a:b0c:500:0/ffff:ffff:ffff:ffff:ffff:ffff:ff00:0 takes + // priority. + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 5, 0, 0, 1))); + + // Within the permitted + // 102:304:506:708:90a:b0c::/ffff:ffff:ffff:ffff:ffff:ffff:: range as well + // as the permitted + // 102:304:506:708:90a:b0c:520:0/ffff:ffff:ffff:ffff:ffff:ffff:ff60:0, + // however the excluded + // 102:304:506:708:90a:b0c:500:0/ffff:ffff:ffff:ffff:ffff:ffff:ff00:0 takes + // priority. + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 5, 33, 0, 1))); + + // Not in any permitted range. (Just outside the + // 102:304:506:708:90a:b0b:520:0/ffff:ffff:ffff:ffff:ffff:ffff:ff60:0 + // range.) + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 5, 31, 255, 255))); + + // Within the permitted + // 102:304:506:708:90a:b0b:520:0/ffff:ffff:ffff:ffff:ffff:ffff:ff60:0 range. + EXPECT_TRUE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 5, 32, 0, 0))); + + // Within the permitted + // 102:304:506:708:90a:b0b:520:0/ffff:ffff:ffff:ffff:ffff:ffff:ff60:0 range. + EXPECT_TRUE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 5, 63, 255, 255))); + + // Not in any permitted range. (Just outside the + // 102:304:506:708:90a:b0b:520:0/ffff:ffff:ffff:ffff:ffff:ffff:ff60:0 + // range.) + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 5, 64, 0, 0))); + + // Not in any permitted range, and also inside the extraneous excluded + // 102:304:506:708:90a:b0a:520:0/ffff:ffff:ffff:ffff:ffff:ffff:ff60:0 range. + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 10, 5, 33, 0, 1))); + + EXPECT_EQ(GENERAL_NAME_IP_ADDRESS, + name_constraints->constrained_name_types()); + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-permitted.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-dnsname.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-directoryname.pem", &san, &san_der)); + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), der::Input(), san.get())); + + ASSERT_TRUE( + LoadTestSubjectAltName("san-excluded-ipaddress.pem", &san, &san_der)); + EXPECT_FALSE( + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, IPAdressesExcludeOnly) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-excluded.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // Only 192.168.5.0/255.255.255.0 is excluded, and since permitted is empty, + // any iPAddress outside that is allowed. + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 168, 0, 1))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 5, 1))); + EXPECT_TRUE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 0, 0, 0, 1))); +} + +TEST_P(ParseNameConstraints, IPAdressesExcludeAll) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-excludeall.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + // 192.168.0.0/255.255.0.0 and + // 102:304:506:708:90a:b0c::/ffff:ffff:ffff:ffff:ffff:ffff:: are permitted, + // but since 0.0.0.0/0 and ::/0 are excluded nothing is permitted. + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 0, 1))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(1, 1, 1, 1))); + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1))); + EXPECT_FALSE(name_constraints->IsPermittedIP( + IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 0, 0, 0, 1))); +} + +TEST_P(ParseNameConstraints, IPAdressesNetmaskPermitSingleHost) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-permit_singlehost.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress::IPv4AllZeros())); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 1))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 2))); + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 3))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 4))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(255, 255, 255, 255))); +} + +TEST_P(ParseNameConstraints, IPAdressesNetmaskPermitPrefixLen31) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-permit_prefix31.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress::IPv4AllZeros())); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 1))); + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 2))); + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 3))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 4))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 5))); + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress(255, 255, 255, 255))); +} + +TEST_P(ParseNameConstraints, IPAdressesNetmaskPermitPrefixLen1) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-permit_prefix1.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + EXPECT_FALSE(name_constraints->IsPermittedIP(IPAddress::IPv4AllZeros())); + EXPECT_FALSE( + name_constraints->IsPermittedIP(IPAddress(0x7F, 0xFF, 0xFF, 0xFF))); + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(0x80, 0, 0, 0))); + EXPECT_TRUE( + name_constraints->IsPermittedIP(IPAddress(0xFF, 0xFF, 0xFF, 0xFF))); +} + +TEST_P(ParseNameConstraints, IPAdressesNetmaskPermitAll) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-permit_all.pem", &a)); + + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints( + NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress::IPv4AllZeros())); + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(192, 168, 1, 1))); + EXPECT_TRUE(name_constraints->IsPermittedIP(IPAddress(255, 255, 255, 255))); +} + +TEST_P(ParseNameConstraints, IPAdressesFailOnInvalidAddr) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint("ipaddress-invalid_addr.pem", &a)); + + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&a), is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, IPAdressesFailOnInvalidMaskNotContiguous) { + std::string a; + ASSERT_TRUE(LoadTestNameConstraint( + "ipaddress-invalid_mask_not_contiguous_1.pem", &a)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + + ASSERT_TRUE(LoadTestNameConstraint( + "ipaddress-invalid_mask_not_contiguous_2.pem", &a)); + EXPECT_FALSE(NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + + ASSERT_TRUE(LoadTestNameConstraint( + "ipaddress-invalid_mask_not_contiguous_3.pem", &a)); + EXPECT_FALSE(NameConstraints::Create(der::Input(&a), is_critical(), &errors)); + + ASSERT_TRUE(LoadTestNameConstraint( + "ipaddress-invalid_mask_not_contiguous_4.pem", &a)); + EXPECT_FALSE(NameConstraints::Create(der::Input(&a), is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, OtherNamesInPermitted) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("othername-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_OTHER_NAME, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-othername.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, OtherNamesInExcluded) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("othername-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_OTHER_NAME, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-othername.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, Rfc822NamesInPermitted) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("rfc822name-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_RFC822_NAME, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, Rfc822NamesInExcluded) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("rfc822name-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_RFC822_NAME, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-rfc822name.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, X400AddresssInPermitted) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("x400address-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_X400_ADDRESS, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-x400address.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, X400AddresssInExcluded) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("x400address-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_X400_ADDRESS, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-x400address.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, EdiPartyNamesInPermitted) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("edipartyname-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_EDI_PARTY_NAME, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-edipartyname.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, EdiPartyNamesInExcluded) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("edipartyname-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_EDI_PARTY_NAME, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-edipartyname.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, URIsInPermitted) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("uri-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-uri.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, URIsInExcluded) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("uri-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-uri.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, RegisteredIDsInPermitted) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("registeredid-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_REGISTERED_ID, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-registeredid.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, RegisteredIDsInExcluded) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("registeredid-excluded.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + if (is_critical()) { + EXPECT_EQ(GENERAL_NAME_REGISTERED_ID, + name_constraints->constrained_name_types()); + } else { + EXPECT_EQ(0, name_constraints->constrained_name_types()); + } + + std::string san_der; + std::unique_ptr<GeneralNames> san; + ASSERT_TRUE(LoadTestSubjectAltName("san-registeredid.pem", &san, &san_der)); + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), der::Input(), san.get())); +} + +TEST_P(ParseNameConstraints, + failsOnGeneralSubtreeWithMinimumZeroEncodedUnnecessarily) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("dnsname-with_min_0.pem", &constraints_der)); + // The value should not be in the DER encoding if it is the default. But this + // could be changed to allowed if there are buggy encoders out there that + // include it anyway. + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnGeneralSubtreeWithMinimum) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("dnsname-with_min_1.pem", &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, + failsOnGeneralSubtreeWithMinimumZeroEncodedUnnecessarilyAndMaximum) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-with_min_0_and_max.pem", + &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnGeneralSubtreeWithMinimumAndMaximum) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-with_min_1_and_max.pem", + &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnGeneralSubtreeWithMaximum) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("dnsname-with_max.pem", &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnEmptyExtensionValue) { + std::string constraints_der = ""; + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnNoPermittedAndExcluded) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("invalid-no_subtrees.pem", &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnEmptyPermitted) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("invalid-empty_permitted_subtree.pem", + &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, FailsOnEmptyExcluded) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("invalid-empty_excluded_subtree.pem", + &constraints_der)); + CertErrors errors; + EXPECT_FALSE(NameConstraints::Create(der::Input(&constraints_der), + is_critical(), &errors)); +} + +TEST_P(ParseNameConstraints, IsPermittedCertSubjectEmailAddressIsOk) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("directoryname.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + std::string name_us_arizona_email; + ASSERT_TRUE( + LoadTestName("name-us-arizona-email.pem", &name_us_arizona_email)); + + // Name constraints don't contain rfc822Name, so emailAddress in subject is + // allowed regardless. + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_arizona_email), + nullptr /* subject_alt_names */)); +} + +TEST_P(ParseNameConstraints, IsPermittedCertSubjectEmailAddressIsNotOk) { + std::string constraints_der; + ASSERT_TRUE( + LoadTestNameConstraint("rfc822name-permitted.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + std::string name_us_arizona_email; + ASSERT_TRUE( + LoadTestName("name-us-arizona-email.pem", &name_us_arizona_email)); + + // Name constraints contain rfc822Name, so emailAddress in subject is not + // allowed if the constraints were critical. + EXPECT_EQ(!is_critical(), + IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_arizona_email), + nullptr /* subject_alt_names */)); +} + +// Hostname in commonName is not allowed (crbug.com/308330), so these are tests +// are not particularly interesting, just verifying that the commonName is +// ignored for dNSName constraints. +TEST_P(ParseNameConstraints, IsPermittedCertSubjectDnsNames) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint("directoryname_and_dnsname.pem", + &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + std::string name_us_az_foocom; + ASSERT_TRUE(LoadTestName("name-us-arizona-foo.com.pem", &name_us_az_foocom)); + // The subject is within permitted directoryName constraints, so permitted. + // (The commonName hostname is not within permitted dNSName constraints, so + // this would not be permitted if hostnames in commonName were checked.) + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_az_foocom), + nullptr /* subject_alt_names */)); + + std::string name_us_az_permitted; + ASSERT_TRUE(LoadTestName("name-us-arizona-permitted.example.com.pem", + &name_us_az_permitted)); + // The subject is in permitted directoryName and the commonName is within + // permitted dNSName constraints, so this should be permitted regardless if + // hostnames in commonName are checked or not. + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_az_permitted), + nullptr /* subject_alt_names */)); + + std::string name_us_ca_permitted; + ASSERT_TRUE(LoadTestName("name-us-california-permitted.example.com.pem", + &name_us_ca_permitted)); + // The subject is within the excluded C=US,ST=California directoryName, so + // this should not be allowed, regardless of checking the + // permitted.example.com in commonName. + EXPECT_FALSE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_ca_permitted), + nullptr /* subject_alt_names */)); +} + +// IP addresses in commonName are not allowed (crbug.com/308330), so these are +// tests are not particularly interesting, just verifying that the commonName is +// ignored for iPAddress constraints. +TEST_P(ParseNameConstraints, IsPermittedCertSubjectIpAddresses) { + std::string constraints_der; + ASSERT_TRUE(LoadTestNameConstraint( + "directoryname_and_dnsname_and_ipaddress.pem", &constraints_der)); + CertErrors errors; + std::unique_ptr<NameConstraints> name_constraints(NameConstraints::Create( + der::Input(&constraints_der), is_critical(), &errors)); + ASSERT_TRUE(name_constraints); + + std::string name_us_az_1_1_1_1; + ASSERT_TRUE(LoadTestName("name-us-arizona-1.1.1.1.pem", &name_us_az_1_1_1_1)); + // The subject is within permitted directoryName constraints, so permitted. + // (The commonName IP address is not within permitted iPAddresses constraints, + // so this would not be permitted if IP addresses in commonName were checked.) + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_az_1_1_1_1), + nullptr /* subject_alt_names */)); + + std::string name_us_az_192_168_1_1; + ASSERT_TRUE( + LoadTestName("name-us-arizona-192.168.1.1.pem", &name_us_az_192_168_1_1)); + // The subject is in permitted directoryName and the commonName is within + // permitted iPAddress constraints, so this should be permitted regardless if + // IP addresses in commonName are checked or not. + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_az_192_168_1_1), + nullptr /* subject_alt_names */)); + + std::string name_us_ca_192_168_1_1; + ASSERT_TRUE(LoadTestName("name-us-california-192.168.1.1.pem", + &name_us_ca_192_168_1_1)); + // The subject is within the excluded C=US,ST=California directoryName, so + // this should not be allowed, regardless of checking the + // IP address in commonName. + EXPECT_FALSE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_ca_192_168_1_1), + nullptr /* subject_alt_names */)); + + std::string name_us_az_ipv6; + ASSERT_TRUE(LoadTestName("name-us-arizona-ipv6.pem", &name_us_az_ipv6)); + // The subject is within permitted directoryName constraints, so permitted. + // (The commonName is an ipv6 address which wasn't supported in the past, but + // since commonName checking is ignored entirely, this is permitted.) + EXPECT_TRUE(IsPermittedCert(name_constraints.get(), + SequenceValueFromString(&name_us_az_ipv6), + nullptr /* subject_alt_names */)); +} + +} // namespace net diff --git a/chromium/net/cert/pki/nist_pkits_unittest.cc b/chromium/net/cert/pki/nist_pkits_unittest.cc new file mode 100644 index 00000000000..f2309349fba --- /dev/null +++ b/chromium/net/cert/pki/nist_pkits_unittest.cc @@ -0,0 +1,100 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/nist_pkits_unittest.h" + +#include "net/cert/pki/certificate_policies.h" + +#include <sstream> + +namespace net { + +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 diff --git a/chromium/net/cert/pki/nist_pkits_unittest.h b/chromium/net/cert/pki/nist_pkits_unittest.h new file mode 100644 index 00000000000..bf4d16485c9 --- /dev/null +++ b/chromium/net/cert/pki/nist_pkits_unittest.h @@ -0,0 +1,157 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_NIST_PKITS_UNITTEST_H_ +#define NET_CERT_PKI_NIST_PKITS_UNITTEST_H_ + +#include <set> + +#include "net/cert/pki/test_helpers.h" +#include "net/der/parse_values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +// 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(net::ReadTestFileToString( + "net/third_party/nist-pkits/certs/" + s + ".crt")); + std::vector<std::string> crl_ders; + for (const std::string& s : crl_names) + crl_ders.push_back(net::ReadTestFileToString( + "net/third_party/nist-pkits/crls/" + s + ".crl")); + + base::StringPiece 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 rfc822Names are not + // supported: + // + // 4.13.21 - Valid RFC822 nameConstraints Test21 + // 4.13.23 - Valid RFC822 nameConstraints Test23 + // 4.13.25 - Valid RFC822 nameConstraints Test25 + // 4.13.27 - Valid DN and RFC822 nameConstraints Test27 + // + // 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.21" || test_number == "4.13.23" || + test_number == "4.13.25" || test_number == "4.13.27" || + 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 "net/third_party/nist-pkits/pkits_testcases-inl.h" + +} // namespace net + +#endif // NET_CERT_PKI_NIST_PKITS_UNITTEST_H_ diff --git a/chromium/net/cert/pki/ocsp.cc b/chromium/net/cert/pki/ocsp.cc new file mode 100644 index 00000000000..46fd72f7109 --- /dev/null +++ b/chromium/net/cert/pki/ocsp.cc @@ -0,0 +1,1037 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/ocsp.h" + +#include <algorithm> + +#include "base/base64.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" +#include "net/cert/asn1_util.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/extended_key_usage.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/revocation_util.h" +#include "net/cert/pki/verify_name_match.h" +#include "net/cert/pki/verify_signed_data.h" +#include "net/cert/x509_util.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/sha.h" +#include "url/gurl.h" + +namespace net { + +OCSPCertID::OCSPCertID() = default; +OCSPCertID::~OCSPCertID() = default; + +OCSPSingleResponse::OCSPSingleResponse() = default; +OCSPSingleResponse::~OCSPSingleResponse() = default; + +OCSPResponseData::OCSPResponseData() = default; +OCSPResponseData::~OCSPResponseData() = default; + +OCSPResponse::OCSPResponse() = default; +OCSPResponse::~OCSPResponse() = default; + +// CertID ::= SEQUENCE { +// hashAlgorithm AlgorithmIdentifier, +// issuerNameHash OCTET STRING, -- Hash of issuer's DN +// issuerKeyHash OCTET STRING, -- Hash of issuer's public key +// serialNumber CertificateSerialNumber +// } +bool ParseOCSPCertID(const der::Input& raw_tlv, OCSPCertID* out) { + der::Parser outer_parser(raw_tlv); + der::Parser parser; + if (!outer_parser.ReadSequence(&parser)) + return false; + if (outer_parser.HasMore()) + return false; + + der::Input sigalg_tlv; + if (!parser.ReadRawTLV(&sigalg_tlv)) + return false; + if (!ParseHashAlgorithm(sigalg_tlv, &(out->hash_algorithm))) + return false; + if (!parser.ReadTag(der::kOctetString, &(out->issuer_name_hash))) + return false; + if (!parser.ReadTag(der::kOctetString, &(out->issuer_key_hash))) + return false; + if (!parser.ReadTag(der::kInteger, &(out->serial_number))) + return false; + CertErrors errors; + if (!VerifySerialNumber(out->serial_number, false /*warnings_only*/, &errors)) + return false; + + return !parser.HasMore(); +} + +namespace { + +// Parses |raw_tlv| to extract an OCSP RevokedInfo (RFC 6960) and stores the +// result in the OCSPCertStatus |out|. Returns whether the parsing was +// successful. +// +// RevokedInfo ::= SEQUENCE { +// revocationTime GeneralizedTime, +// revocationReason [0] EXPLICIT CRLReason OPTIONAL +// } +bool ParseRevokedInfo(const der::Input& raw_tlv, OCSPCertStatus* out) { + der::Parser parser(raw_tlv); + if (!parser.ReadGeneralizedTime(&(out->revocation_time))) + return false; + + der::Input reason_input; + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &reason_input, + &(out->has_reason))) { + return false; + } + if (out->has_reason) { + der::Parser reason_parser(reason_input); + der::Input reason_value_input; + uint8_t reason_value; + if (!reason_parser.ReadTag(der::kEnumerated, &reason_value_input)) + return false; + if (!der::ParseUint8(reason_value_input, &reason_value)) + return false; + if (reason_value > + static_cast<uint8_t>(OCSPCertStatus::RevocationReason::LAST)) { + return false; + } + out->revocation_reason = + static_cast<OCSPCertStatus::RevocationReason>(reason_value); + if (out->revocation_reason == OCSPCertStatus::RevocationReason::UNUSED) + return false; + if (reason_parser.HasMore()) + return false; + } + return !parser.HasMore(); +} + +// Parses |raw_tlv| to extract an OCSP CertStatus (RFC 6960) and stores the +// result in the OCSPCertStatus |out|. Returns whether the parsing was +// successful. +// +// CertStatus ::= CHOICE { +// good [0] IMPLICIT NULL, +// revoked [1] IMPLICIT RevokedInfo, +// unknown [2] IMPLICIT UnknownInfo +// } +// +// UnknownInfo ::= NULL +bool ParseCertStatus(const der::Input& raw_tlv, OCSPCertStatus* out) { + der::Parser parser(raw_tlv); + der::Tag status_tag; + der::Input status; + if (!parser.ReadTagAndValue(&status_tag, &status)) + return false; + + out->has_reason = false; + if (status_tag == der::ContextSpecificPrimitive(0)) { + out->status = OCSPRevocationStatus::GOOD; + } else if (status_tag == der::ContextSpecificConstructed(1)) { + out->status = OCSPRevocationStatus::REVOKED; + if (!ParseRevokedInfo(status, out)) + return false; + } else if (status_tag == der::ContextSpecificPrimitive(2)) { + out->status = OCSPRevocationStatus::UNKNOWN; + } else { + return false; + } + + return !parser.HasMore(); +} + +// Writes the hash of |value| as an OCTET STRING to |cbb|, using |hash_type| as +// the algorithm. Returns true on success. +bool AppendHashAsOctetString(const EVP_MD* hash_type, + CBB* cbb, + const der::Input& value) { + CBB octet_string; + unsigned hash_len; + uint8_t hash_buffer[EVP_MAX_MD_SIZE]; + + return CBB_add_asn1(cbb, &octet_string, CBS_ASN1_OCTETSTRING) && + EVP_Digest(value.UnsafeData(), value.Length(), hash_buffer, &hash_len, + hash_type, nullptr) && + CBB_add_bytes(&octet_string, hash_buffer, hash_len) && CBB_flush(cbb); +} + +} // namespace + +// SingleResponse ::= SEQUENCE { +// certID CertID, +// certStatus CertStatus, +// thisUpdate GeneralizedTime, +// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, +// singleExtensions [1] EXPLICIT Extensions OPTIONAL +// } +bool ParseOCSPSingleResponse(const der::Input& raw_tlv, + OCSPSingleResponse* out) { + der::Parser outer_parser(raw_tlv); + der::Parser parser; + if (!outer_parser.ReadSequence(&parser)) + return false; + if (outer_parser.HasMore()) + return false; + + if (!parser.ReadRawTLV(&(out->cert_id_tlv))) + return false; + der::Input status_tlv; + if (!parser.ReadRawTLV(&status_tlv)) + return false; + if (!ParseCertStatus(status_tlv, &(out->cert_status))) + return false; + if (!parser.ReadGeneralizedTime(&(out->this_update))) + return false; + + der::Input next_update_input; + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), + &next_update_input, &(out->has_next_update))) { + return false; + } + if (out->has_next_update) { + der::Parser next_update_parser(next_update_input); + if (!next_update_parser.ReadGeneralizedTime(&(out->next_update))) + return false; + if (next_update_parser.HasMore()) + return false; + } + + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1), + &(out->extensions), &(out->has_extensions))) { + return false; + } + + return !parser.HasMore(); +} + +namespace { + +// Parses |raw_tlv| to extract a ResponderID (RFC 6960) and stores the +// result in the ResponderID |out|. Returns whether the parsing was successful. +// +// ResponderID ::= CHOICE { +// byName [1] Name, +// byKey [2] KeyHash +// } +bool ParseResponderID(const der::Input& raw_tlv, + OCSPResponseData::ResponderID* out) { + der::Parser parser(raw_tlv); + der::Tag id_tag; + der::Input id_input; + if (!parser.ReadTagAndValue(&id_tag, &id_input)) + return false; + + if (id_tag == der::ContextSpecificConstructed(1)) { + out->type = OCSPResponseData::ResponderType::NAME; + out->name = id_input; + } else if (id_tag == der::ContextSpecificConstructed(2)) { + der::Parser key_parser(id_input); + der::Input key_hash; + if (!key_parser.ReadTag(der::kOctetString, &key_hash)) + return false; + if (key_parser.HasMore()) + return false; + if (key_hash.Length() != SHA_DIGEST_LENGTH) + return false; + + out->type = OCSPResponseData::ResponderType::KEY_HASH; + out->key_hash = key_hash; + } else { + return false; + } + return !parser.HasMore(); +} + +} // namespace + +// ResponseData ::= SEQUENCE { +// version [0] EXPLICIT Version DEFAULT v1, +// responderID ResponderID, +// producedAt GeneralizedTime, +// responses SEQUENCE OF SingleResponse, +// responseExtensions [1] EXPLICIT Extensions OPTIONAL +// } +bool ParseOCSPResponseData(const der::Input& raw_tlv, OCSPResponseData* out) { + der::Parser outer_parser(raw_tlv); + der::Parser parser; + if (!outer_parser.ReadSequence(&parser)) + return false; + if (outer_parser.HasMore()) + return false; + + der::Input version_input; + bool version_present; + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), + &version_input, &version_present)) { + return false; + } + + // For compatibilty, we ignore the restriction from X.690 Section 11.5 that + // DEFAULT values should be omitted for values equal to the default value. + // TODO: Add warning about non-strict parsing. + if (version_present) { + der::Parser version_parser(version_input); + if (!version_parser.ReadUint8(&(out->version))) + return false; + if (version_parser.HasMore()) + return false; + } else { + out->version = 0; + } + + if (out->version != 0) + return false; + + der::Input responder_input; + if (!parser.ReadRawTLV(&responder_input)) + return false; + if (!ParseResponderID(responder_input, &(out->responder_id))) + return false; + if (!parser.ReadGeneralizedTime(&(out->produced_at))) + return false; + + der::Parser responses_parser; + if (!parser.ReadSequence(&responses_parser)) + return false; + out->responses.clear(); + while (responses_parser.HasMore()) { + der::Input single_response; + if (!responses_parser.ReadRawTLV(&single_response)) + return false; + out->responses.push_back(single_response); + } + + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1), + &(out->extensions), &(out->has_extensions))) { + return false; + } + + return !parser.HasMore(); +} + +namespace { + +// Parses |raw_tlv| to extract a BasicOCSPResponse (RFC 6960) and stores the +// result in the OCSPResponse |out|. Returns whether the parsing was +// successful. +// +// BasicOCSPResponse ::= SEQUENCE { +// tbsResponseData ResponseData, +// signatureAlgorithm AlgorithmIdentifier, +// signature BIT STRING, +// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL +// } +bool ParseBasicOCSPResponse(const der::Input& raw_tlv, OCSPResponse* out) { + der::Parser outer_parser(raw_tlv); + der::Parser parser; + if (!outer_parser.ReadSequence(&parser)) + return false; + if (outer_parser.HasMore()) + return false; + + if (!parser.ReadRawTLV(&(out->data))) + return false; + der::Input sigalg_tlv; + if (!parser.ReadRawTLV(&sigalg_tlv)) + return false; + // TODO(crbug.com/634443): Propagate the errors. + absl::optional<SignatureAlgorithm> sigalg = + ParseSignatureAlgorithm(sigalg_tlv, /*errors=*/nullptr); + if (!sigalg) + return false; + out->signature_algorithm = sigalg.value(); + absl::optional<der::BitString> signature = parser.ReadBitString(); + if (!signature) + return false; + out->signature = signature.value(); + der::Input certs_input; + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &certs_input, + &(out->has_certs))) { + return false; + } + + out->certs.clear(); + if (out->has_certs) { + der::Parser certs_seq_parser(certs_input); + der::Parser certs_parser; + if (!certs_seq_parser.ReadSequence(&certs_parser)) + return false; + if (certs_seq_parser.HasMore()) + return false; + while (certs_parser.HasMore()) { + der::Input cert_tlv; + if (!certs_parser.ReadRawTLV(&cert_tlv)) + return false; + out->certs.push_back(cert_tlv); + } + } + + return !parser.HasMore(); +} + +} // namespace + +// OCSPResponse ::= SEQUENCE { +// responseStatus OCSPResponseStatus, +// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL +// } +// +// ResponseBytes ::= SEQUENCE { +// responseType OBJECT IDENTIFIER, +// response OCTET STRING +// } +bool ParseOCSPResponse(const der::Input& raw_tlv, OCSPResponse* out) { + der::Parser outer_parser(raw_tlv); + der::Parser parser; + if (!outer_parser.ReadSequence(&parser)) + return false; + if (outer_parser.HasMore()) + return false; + + der::Input response_status_input; + uint8_t response_status; + if (!parser.ReadTag(der::kEnumerated, &response_status_input)) + return false; + if (!der::ParseUint8(response_status_input, &response_status)) + return false; + if (response_status > + static_cast<uint8_t>(OCSPResponse::ResponseStatus::LAST)) { + return false; + } + out->status = static_cast<OCSPResponse::ResponseStatus>(response_status); + if (out->status == OCSPResponse::ResponseStatus::UNUSED) + return false; + + if (out->status == OCSPResponse::ResponseStatus::SUCCESSFUL) { + der::Parser outer_bytes_parser; + der::Parser bytes_parser; + if (!parser.ReadConstructed(der::ContextSpecificConstructed(0), + &outer_bytes_parser)) { + return false; + } + if (!outer_bytes_parser.ReadSequence(&bytes_parser)) + return false; + if (outer_bytes_parser.HasMore()) + return false; + + der::Input type_oid; + if (!bytes_parser.ReadTag(der::kOid, &type_oid)) + return false; + if (type_oid != der::Input(kBasicOCSPResponseOid)) + return false; + + // As per RFC 6960 Section 4.2.1, the value of |response| SHALL be the DER + // encoding of BasicOCSPResponse. + der::Input response; + if (!bytes_parser.ReadTag(der::kOctetString, &response)) + return false; + if (!ParseBasicOCSPResponse(response, out)) + return false; + if (bytes_parser.HasMore()) + return false; + } + + return !parser.HasMore(); +} + +namespace { + +// Checks that the |type| hash of |value| is equal to |hash| +bool VerifyHash(const EVP_MD* type, + const der::Input& hash, + const der::Input& value) { + unsigned value_hash_len; + uint8_t value_hash[EVP_MAX_MD_SIZE]; + if (!EVP_Digest(value.UnsafeData(), value.Length(), value_hash, + &value_hash_len, type, nullptr)) { + return false; + } + + return hash == der::Input(value_hash, value_hash_len); +} + +// Extracts the bytes of the SubjectPublicKey bit string given an SPKI. That is +// to say, the value of subjectPublicKey without the leading unused bit +// count octet. +// +// Returns true on success and fills |*spk_tlv| with the result. +// +// SubjectPublicKeyInfo ::= SEQUENCE { +// algorithm AlgorithmIdentifier, +// subjectPublicKey BIT STRING +// } +bool GetSubjectPublicKeyBytes(const der::Input& spki_tlv, der::Input* spk_tlv) { + base::StringPiece spk_strpiece; + if (!asn1::ExtractSubjectPublicKeyFromSPKI(spki_tlv.AsStringPiece(), + &spk_strpiece)) { + return false; + } + + // ExtractSubjectPublicKeyFromSPKI() includes the unused bit count. For this + // application, the unused bit count must be zero, and is not included in the + // result. + if (!base::StartsWith(spk_strpiece, "\0")) + return false; + spk_strpiece.remove_prefix(1); + + *spk_tlv = der::Input(spk_strpiece); + return true; +} + +// Checks the OCSPCertID |id| identifies |certificate|. +bool CheckCertIDMatchesCertificate( + const OCSPCertID& id, + const ParsedCertificate* certificate, + const ParsedCertificate* issuer_certificate) { + const EVP_MD* type = nullptr; + switch (id.hash_algorithm) { + case DigestAlgorithm::Md2: + case DigestAlgorithm::Md4: + case DigestAlgorithm::Md5: + // Unsupported. + return false; + case DigestAlgorithm::Sha1: + type = EVP_sha1(); + break; + case DigestAlgorithm::Sha256: + type = EVP_sha256(); + break; + case DigestAlgorithm::Sha384: + type = EVP_sha384(); + break; + case DigestAlgorithm::Sha512: + type = EVP_sha512(); + break; + } + + if (!VerifyHash(type, id.issuer_name_hash, certificate->tbs().issuer_tlv)) + return false; + + der::Input key_tlv; + if (!GetSubjectPublicKeyBytes(issuer_certificate->tbs().spki_tlv, &key_tlv)) + return false; + + if (!VerifyHash(type, id.issuer_key_hash, key_tlv)) + return false; + + return id.serial_number == certificate->tbs().serial_number; +} + +// TODO(eroman): Revisit how certificate parsing is used by this file. Ideally +// would either pass in the parsed bits, or have a better abstraction for lazily +// parsing. +scoped_refptr<ParsedCertificate> OCSPParseCertificate(base::StringPiece der) { + ParseCertificateOptions parse_options; + parse_options.allow_invalid_serial_numbers = true; + + // TODO(eroman): Swallows the parsing errors. However uses a permissive + // parsing model. + CertErrors errors; + return ParsedCertificate::Create(x509_util::CreateCryptoBuffer(der), {}, + &errors); +} + +// Checks that the ResponderID |id| matches the certificate |cert| either +// by verifying the name matches that of the certificate or that the hash +// matches the certificate's public key hash (RFC 6960, 4.2.2.3). +[[nodiscard]] bool CheckResponderIDMatchesCertificate( + const OCSPResponseData::ResponderID& id, + const ParsedCertificate* cert) { + switch (id.type) { + case OCSPResponseData::ResponderType::NAME: { + der::Input name_rdn; + der::Input cert_rdn; + if (!der::Parser(id.name).ReadTag(der::kSequence, &name_rdn) || + !der::Parser(cert->tbs().subject_tlv) + .ReadTag(der::kSequence, &cert_rdn)) + return false; + return VerifyNameMatch(name_rdn, cert_rdn); + } + case OCSPResponseData::ResponderType::KEY_HASH: { + der::Input key; + if (!GetSubjectPublicKeyBytes(cert->tbs().spki_tlv, &key)) + return false; + return VerifyHash(EVP_sha1(), id.key_hash, key); + } + } + + return false; +} + +// Verifies that |responder_certificate| has been authority for OCSP signing, +// delegated to it by |issuer_certificate|. +// +// TODO(eroman): No revocation checks are done (see id-pkix-ocsp-nocheck in the +// spec). extension). +// +// TODO(eroman): Not all properties of the certificate are verified, only the +// signature and EKU. Can full RFC 5280 validation be used, or are there +// compatibility concerns? +[[nodiscard]] bool VerifyAuthorizedResponderCert( + const ParsedCertificate* responder_certificate, + const ParsedCertificate* issuer_certificate) { + // The Authorized Responder must be directly signed by the issuer of the + // certificate being checked. + // TODO(eroman): Must check the signature algorithm against policy. + if (!VerifySignedData(responder_certificate->signature_algorithm(), + responder_certificate->tbs_certificate_tlv(), + responder_certificate->signature_value(), + issuer_certificate->tbs().spki_tlv)) { + return false; + } + + // The Authorized Responder must include the value id-kp-OCSPSigning as + // part of the extended key usage extension. + if (!responder_certificate->has_extended_key_usage()) + return false; + const std::vector<der::Input>& ekus = + responder_certificate->extended_key_usage(); + if (std::find(ekus.begin(), ekus.end(), der::Input(kOCSPSigning)) == + ekus.end()) { + return false; + } + + return true; +} + +[[nodiscard]] bool VerifyOCSPResponseSignatureGivenCert( + const OCSPResponse& response, + const ParsedCertificate* cert) { + // TODO(eroman): Must check the signature algorithm against policy. + return VerifySignedData(response.signature_algorithm, response.data, + response.signature, cert->tbs().spki_tlv); +} + +// Verifies that the OCSP response has a valid signature using +// |issuer_certificate|, or an authorized responder issued by +// |issuer_certificate| for OCSP signing. +[[nodiscard]] bool VerifyOCSPResponseSignature( + const OCSPResponse& response, + const OCSPResponseData& response_data, + const ParsedCertificate* issuer_certificate) { + // In order to verify the OCSP signature, a valid responder matching the OCSP + // Responder ID must be located (RFC 6960, 4.2.2.2). The responder is allowed + // to be either the certificate issuer or a delegated authority directly + // signed by the issuer. + if (CheckResponderIDMatchesCertificate(response_data.responder_id, + issuer_certificate) && + VerifyOCSPResponseSignatureGivenCert(response, issuer_certificate)) { + return true; + } + + // Otherwise search through the provided certificates for the Authorized + // Responder. Want a certificate that: + // (1) Matches the OCSP Responder ID. + // (2) Has been given authority for OCSP signing by |issuer_certificate|. + // (3) Has signed the OCSP response using its public key. + for (const auto& responder_cert_tlv : response.certs) { + scoped_refptr<ParsedCertificate> cur_responder_certificate = + OCSPParseCertificate(responder_cert_tlv.AsStringPiece()); + + // If failed parsing the certificate, keep looking. + if (!cur_responder_certificate) + continue; + + // If the certificate doesn't match the OCSP's responder ID, keep looking. + if (!CheckResponderIDMatchesCertificate(response_data.responder_id, + cur_responder_certificate.get())) { + continue; + } + + // If the certificate isn't a valid Authorized Responder certificate, keep + // looking. + if (!VerifyAuthorizedResponderCert(cur_responder_certificate.get(), + issuer_certificate)) { + continue; + } + + // If the certificate signed this OCSP response, have found a match. + // Otherwise keep looking. + if (VerifyOCSPResponseSignatureGivenCert(response, + cur_responder_certificate.get())) { + return true; + } + } + + // Failed to confirm the validity of the OCSP signature using any of the + // candidate certificates. + return false; +} + +// Parse ResponseData and return false if any unhandled critical extensions are +// found. No known critical ResponseData extensions exist. +bool ParseOCSPResponseDataExtensions( + const der::Input& response_extensions, + OCSPVerifyResult::ResponseStatus* response_details) { + std::map<der::Input, ParsedExtension> extensions; + if (!ParseExtensions(response_extensions, &extensions)) { + *response_details = OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR; + return false; + } + + for (const auto& ext : extensions) { + // TODO: handle ResponseData extensions + + if (ext.second.critical) { + *response_details = OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION; + return false; + } + } + + return true; +} + +// Parse SingleResponse and return false if any unhandled critical extensions +// (other than the CT extension) are found. The CT-SCT extension is not required +// to be marked critical, but since it is handled by Chrome, we will overlook +// the flag setting. +bool ParseOCSPSingleResponseExtensions( + const der::Input& single_extensions, + OCSPVerifyResult::ResponseStatus* response_details) { + std::map<der::Input, ParsedExtension> extensions; + if (!ParseExtensions(single_extensions, &extensions)) { + *response_details = OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR; + return false; + } + + // The wire form of the OID 1.3.6.1.4.1.11129.2.4.5 - OCSP SingleExtension for + // X.509v3 Certificate Transparency Signed Certificate Timestamp List, see + // Section 3.3 of RFC6962. + const uint8_t ct_ocsp_ext_oid[] = {0x2B, 0x06, 0x01, 0x04, 0x01, + 0xD6, 0x79, 0x02, 0x04, 0x05}; + der::Input ct_ext_oid(ct_ocsp_ext_oid); + + for (const auto& ext : extensions) { + // The CT OCSP extension is handled in ct::ExtractSCTListFromOCSPResponse + if (ext.second.oid == ct_ext_oid) + continue; + + // TODO: handle SingleResponse extensions + + if (ext.second.critical) { + *response_details = OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION; + return false; + } + } + + return true; +} + +// Loops through the OCSPSingleResponses to find the best match for |cert|. +OCSPRevocationStatus GetRevocationStatusForCert( + const OCSPResponseData& response_data, + const ParsedCertificate* cert, + const ParsedCertificate* issuer_certificate, + const base::Time& verify_time, + const base::TimeDelta& max_age, + OCSPVerifyResult::ResponseStatus* response_details) { + OCSPRevocationStatus result = OCSPRevocationStatus::UNKNOWN; + *response_details = OCSPVerifyResult::NO_MATCHING_RESPONSE; + + for (const auto& single_response_der : response_data.responses) { + // In the common case, there should only be one SingleResponse in the + // ResponseData (matching the certificate requested and used on this + // connection). However, it is possible for the OCSP responder to provide + // multiple responses for multiple certificates. Look through all the + // provided SingleResponses, and check to see if any match the + // certificate. A SingleResponse matches a certificate if it has the same + // serial number, issuer name (hash), and issuer public key (hash). + OCSPSingleResponse single_response; + if (!ParseOCSPSingleResponse(single_response_der, &single_response)) + return OCSPRevocationStatus::UNKNOWN; + + // Reject unhandled critical extensions in SingleResponse + if (single_response.has_extensions && + !ParseOCSPSingleResponseExtensions(single_response.extensions, + response_details)) { + return OCSPRevocationStatus::UNKNOWN; + } + + OCSPCertID cert_id; + if (!ParseOCSPCertID(single_response.cert_id_tlv, &cert_id)) + return OCSPRevocationStatus::UNKNOWN; + if (!CheckCertIDMatchesCertificate(cert_id, cert, issuer_certificate)) + continue; + + // The SingleResponse matches the certificate, but may be out of date. Out + // of date responses are noted seperate from responses with mismatched + // serial numbers. If an OCSP responder provides both an up to date + // response and an expired response, the up to date response takes + // precedence (PROVIDED > INVALID_DATE). + if (!CheckRevocationDateValid(single_response.this_update, + single_response.has_next_update + ? &single_response.next_update + : nullptr, + verify_time, max_age)) { + if (*response_details != OCSPVerifyResult::PROVIDED) + *response_details = OCSPVerifyResult::INVALID_DATE; + continue; + } + + // In the case with multiple matching and up to date responses, keep only + // the strictest status (REVOKED > UNKNOWN > GOOD). + if (*response_details != OCSPVerifyResult::PROVIDED || + result == OCSPRevocationStatus::GOOD || + single_response.cert_status.status == OCSPRevocationStatus::REVOKED) { + result = single_response.cert_status.status; + } + *response_details = OCSPVerifyResult::PROVIDED; + } + + return result; +} + +OCSPRevocationStatus CheckOCSP( + base::StringPiece raw_response, + base::StringPiece certificate_der, + const ParsedCertificate* certificate, + base::StringPiece issuer_certificate_der, + const ParsedCertificate* issuer_certificate, + const base::Time& verify_time, + const base::TimeDelta& max_age, + OCSPVerifyResult::ResponseStatus* response_details) { + *response_details = OCSPVerifyResult::NOT_CHECKED; + + if (raw_response.empty()) { + *response_details = OCSPVerifyResult::MISSING; + return OCSPRevocationStatus::UNKNOWN; + } + + der::Input response_der(raw_response); + OCSPResponse response; + if (!ParseOCSPResponse(response_der, &response)) { + *response_details = OCSPVerifyResult::PARSE_RESPONSE_ERROR; + return OCSPRevocationStatus::UNKNOWN; + } + + // RFC 6960 defines all responses |response_status| != SUCCESSFUL as error + // responses. No revocation information is provided on error responses, and + // the OCSPResponseData structure is not set. + if (response.status != OCSPResponse::ResponseStatus::SUCCESSFUL) { + *response_details = OCSPVerifyResult::ERROR_RESPONSE; + return OCSPRevocationStatus::UNKNOWN; + } + + // Actual revocation information is contained within the BasicOCSPResponse as + // a ResponseData structure. The BasicOCSPResponse was parsed above, and + // contains an unparsed ResponseData. From RFC 6960: + // + // BasicOCSPResponse ::= SEQUENCE { + // tbsResponseData ResponseData, + // signatureAlgorithm AlgorithmIdentifier, + // signature BIT STRING, + // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + // + // ResponseData ::= SEQUENCE { + // version [0] EXPLICIT Version DEFAULT v1, + // responderID ResponderID, + // producedAt GeneralizedTime, + // responses SEQUENCE OF SingleResponse, + // responseExtensions [1] EXPLICIT Extensions OPTIONAL } + OCSPResponseData response_data; + if (!ParseOCSPResponseData(response.data, &response_data)) { + *response_details = OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR; + return OCSPRevocationStatus::UNKNOWN; + } + + // Process the OCSP ResponseData extensions. In particular, must reject if + // there are any critical extensions that are not understood. + if (response_data.has_extensions && + !ParseOCSPResponseDataExtensions(response_data.extensions, + response_details)) { + return OCSPRevocationStatus::UNKNOWN; + } + + scoped_refptr<ParsedCertificate> parsed_certificate; + scoped_refptr<ParsedCertificate> parsed_issuer_certificate; + if (!certificate) { + parsed_certificate = OCSPParseCertificate(certificate_der); + certificate = parsed_certificate.get(); + } + if (!issuer_certificate) { + parsed_issuer_certificate = OCSPParseCertificate(issuer_certificate_der); + issuer_certificate = parsed_issuer_certificate.get(); + } + + if (!certificate || !issuer_certificate) { + *response_details = OCSPVerifyResult::NOT_CHECKED; + return OCSPRevocationStatus::UNKNOWN; + } + + // If producedAt is outside of the certificate validity period, reject the + // response. + if (response_data.produced_at < certificate->tbs().validity_not_before || + response_data.produced_at > certificate->tbs().validity_not_after) { + *response_details = OCSPVerifyResult::BAD_PRODUCED_AT; + return OCSPRevocationStatus::UNKNOWN; + } + + // Look through all of the OCSPSingleResponses for a match (based on CertID + // and time). + OCSPRevocationStatus status = + GetRevocationStatusForCert(response_data, certificate, issuer_certificate, + verify_time, max_age, response_details); + + // Check that the OCSP response has a valid signature. It must either be + // signed directly by the issuing certificate, or a valid authorized + // responder. + if (!VerifyOCSPResponseSignature(response, response_data, + issuer_certificate)) { + return OCSPRevocationStatus::UNKNOWN; + } + + return status; +} + +} // namespace + +OCSPRevocationStatus CheckOCSP( + base::StringPiece raw_response, + base::StringPiece certificate_der, + base::StringPiece issuer_certificate_der, + const base::Time& verify_time, + const base::TimeDelta& max_age, + OCSPVerifyResult::ResponseStatus* response_details) { + return CheckOCSP(raw_response, certificate_der, nullptr, + issuer_certificate_der, nullptr, verify_time, max_age, + response_details); +} + +OCSPRevocationStatus CheckOCSP( + base::StringPiece raw_response, + const ParsedCertificate* certificate, + const ParsedCertificate* issuer_certificate, + const base::Time& verify_time, + const base::TimeDelta& max_age, + OCSPVerifyResult::ResponseStatus* response_details) { + return CheckOCSP(raw_response, base::StringPiece(), certificate, + base::StringPiece(), issuer_certificate, verify_time, + max_age, response_details); +} + +bool CreateOCSPRequest(const ParsedCertificate* cert, + const ParsedCertificate* issuer, + std::vector<uint8_t>* request_der) { + request_der->clear(); + + bssl::ScopedCBB cbb; + + // This initial buffer size is big enough for 20 octet long serial numbers + // (upper bound from RFC 5280) and then a handful of extra bytes. This + // number doesn't matter for correctness. + const size_t kInitialBufferSize = 100; + + if (!CBB_init(cbb.get(), kInitialBufferSize)) + return false; + + // OCSPRequest ::= SEQUENCE { + // tbsRequest TBSRequest, + // optionalSignature [0] EXPLICIT Signature OPTIONAL } + // + // TBSRequest ::= SEQUENCE { + // version [0] EXPLICIT Version DEFAULT v1, + // requestorName [1] EXPLICIT GeneralName OPTIONAL, + // requestList SEQUENCE OF Request, + // requestExtensions [2] EXPLICIT Extensions OPTIONAL } + CBB ocsp_request; + if (!CBB_add_asn1(cbb.get(), &ocsp_request, CBS_ASN1_SEQUENCE)) + return false; + + CBB tbs_request; + if (!CBB_add_asn1(&ocsp_request, &tbs_request, CBS_ASN1_SEQUENCE)) + return false; + + // "version", "requestorName", and "requestExtensions" are omitted. + + CBB request_list; + if (!CBB_add_asn1(&tbs_request, &request_list, CBS_ASN1_SEQUENCE)) + return false; + + CBB request; + if (!CBB_add_asn1(&request_list, &request, CBS_ASN1_SEQUENCE)) + return false; + + // Request ::= SEQUENCE { + // reqCert CertID, + // singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL } + CBB req_cert; + if (!CBB_add_asn1(&request, &req_cert, CBS_ASN1_SEQUENCE)) + return false; + + // CertID ::= SEQUENCE { + // hashAlgorithm AlgorithmIdentifier, + // issuerNameHash OCTET STRING, -- Hash of issuer's DN + // issuerKeyHash OCTET STRING, -- Hash of issuer's public key + // serialNumber CertificateSerialNumber } + + // TODO(eroman): Don't use SHA1. + const EVP_MD* md = EVP_sha1(); + if (!EVP_marshal_digest_algorithm(&req_cert, md)) + return false; + + AppendHashAsOctetString(md, &req_cert, issuer->tbs().subject_tlv); + + der::Input key_tlv; + if (!GetSubjectPublicKeyBytes(issuer->tbs().spki_tlv, &key_tlv)) + return false; + AppendHashAsOctetString(md, &req_cert, key_tlv); + + CBB serial_number; + if (!CBB_add_asn1(&req_cert, &serial_number, CBS_ASN1_INTEGER)) + return false; + if (!CBB_add_bytes(&serial_number, cert->tbs().serial_number.UnsafeData(), + cert->tbs().serial_number.Length())) { + return false; + } + + uint8_t* result_bytes; + size_t result_bytes_length; + if (!CBB_finish(cbb.get(), &result_bytes, &result_bytes_length)) + return false; + bssl::UniquePtr<uint8_t> delete_tbs_cert_bytes(result_bytes); + + request_der->assign(result_bytes, result_bytes + result_bytes_length); + return true; +} + +// From RFC 2560 section A.1.1: +// +// An OCSP request using the GET method is constructed as follows: +// +// GET {url}/{url-encoding of base-64 encoding of the DER encoding of +// the OCSPRequest} +GURL CreateOCSPGetURL(const ParsedCertificate* cert, + const ParsedCertificate* issuer, + base::StringPiece ocsp_responder_url) { + std::vector<uint8_t> ocsp_request_der; + if (!CreateOCSPRequest(cert, issuer, &ocsp_request_der)) { + // Unexpected (means BoringSSL failed an operation). + return GURL(); + } + + // Base64 encode the request data. + std::string b64_encoded; + base::Base64Encode( + base::StringPiece(reinterpret_cast<const char*>(ocsp_request_der.data()), + ocsp_request_der.size()), + &b64_encoded); + + // In theory +, /, and = are valid in paths and don't need to be escaped. + // However from the example in RFC 5019 section 5 it is clear that the intent + // is to escape non-alphanumeric characters (the example conclusively escapes + // '/' and '=', but doesn't clarify '+'). + base::ReplaceSubstringsAfterOffset(&b64_encoded, 0, "+", "%2B"); + base::ReplaceSubstringsAfterOffset(&b64_encoded, 0, "/", "%2F"); + base::ReplaceSubstringsAfterOffset(&b64_encoded, 0, "=", "%3D"); + + // No attempt is made to collapse double slashes for URLs that end in slash, + // since the spec doesn't do that. + return GURL(std::string(ocsp_responder_url) + "/" + b64_encoded); +} + +} // namespace net diff --git a/chromium/net/cert/pki/ocsp.h b/chromium/net/cert/pki/ocsp.h new file mode 100644 index 00000000000..6a2a5e5b7d3 --- /dev/null +++ b/chromium/net/cert/pki/ocsp.h @@ -0,0 +1,328 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_OCSP_H_ +#define NET_CERT_PKI_OCSP_H_ + +#include <memory> +#include <vector> + +#include "base/strings/string_piece_forward.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/cert/ocsp_revocation_status.h" +#include "net/cert/ocsp_verify_result.h" +#include "net/cert/pki/parse_certificate.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +class GURL; + +namespace base { +class Time; +class TimeDelta; +} // namespace base + +namespace net { + +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 NET_EXPORT_PRIVATE 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 NET_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 NET_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 NET_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. +NET_EXPORT_PRIVATE 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. +NET_EXPORT_PRIVATE 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. +NET_EXPORT_PRIVATE 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. +NET_EXPORT_PRIVATE 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|: The time to use when checking revocation status. +// * |max_age|: The maximum age for an OCSP response, implemented as time since +// the |this_update| field in OCSPSingleResponse. Responses older than +// |max_age| will be considered invalid. +// * |response_details|: Additional details about failures. +[[nodiscard]] NET_EXPORT OCSPRevocationStatus +CheckOCSP(base::StringPiece raw_response, + base::StringPiece certificate_der, + base::StringPiece issuer_certificate_der, + const base::Time& verify_time, + const base::TimeDelta& max_age, + 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]] NET_EXPORT OCSPRevocationStatus +CheckOCSP(base::StringPiece raw_response, + const ParsedCertificate* certificate, + const ParsedCertificate* issuer_certificate, + const base::Time& verify_time, + const base::TimeDelta& max_age, + 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. +NET_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|. +NET_EXPORT GURL CreateOCSPGetURL(const ParsedCertificate* cert, + const ParsedCertificate* issuer, + base::StringPiece ocsp_responder_url); + +} // namespace net + +#endif // NET_CERT_PKI_OCSP_H_ diff --git a/chromium/net/cert/pki/ocsp_parse_ocsp_cert_id_fuzzer.cc b/chromium/net/cert/pki/ocsp_parse_ocsp_cert_id_fuzzer.cc new file mode 100644 index 00000000000..1d23453d0b5 --- /dev/null +++ b/chromium/net/cert/pki/ocsp_parse_ocsp_cert_id_fuzzer.cc @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "net/cert/pki/ocsp.h" +#include "net/der/input.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::der::Input cert_id_der(data, size); + net::OCSPCertID cert_id; + net::ParseOCSPCertID(cert_id_der, &cert_id); + + return 0; +} diff --git a/chromium/net/cert/pki/ocsp_parse_ocsp_response_data_fuzzer.cc b/chromium/net/cert/pki/ocsp_parse_ocsp_response_data_fuzzer.cc new file mode 100644 index 00000000000..d312f0fae1b --- /dev/null +++ b/chromium/net/cert/pki/ocsp_parse_ocsp_response_data_fuzzer.cc @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "net/cert/pki/ocsp.h" +#include "net/der/input.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::der::Input response_data_der(data, size); + net::OCSPResponseData response_data; + net::ParseOCSPResponseData(response_data_der, &response_data); + + return 0; +} diff --git a/chromium/net/cert/pki/ocsp_parse_ocsp_response_fuzzer.cc b/chromium/net/cert/pki/ocsp_parse_ocsp_response_fuzzer.cc new file mode 100644 index 00000000000..f3673aeec7a --- /dev/null +++ b/chromium/net/cert/pki/ocsp_parse_ocsp_response_fuzzer.cc @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "net/cert/pki/ocsp.h" +#include "net/der/input.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::der::Input response_der(data, size); + net::OCSPResponse response; + net::ParseOCSPResponse(response_der, &response); + + return 0; +} diff --git a/chromium/net/cert/pki/ocsp_parse_ocsp_single_response_fuzzer.cc b/chromium/net/cert/pki/ocsp_parse_ocsp_single_response_fuzzer.cc new file mode 100644 index 00000000000..872e2680a4e --- /dev/null +++ b/chromium/net/cert/pki/ocsp_parse_ocsp_single_response_fuzzer.cc @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "net/cert/pki/ocsp.h" +#include "net/der/input.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::der::Input single_response_der(data, size); + net::OCSPSingleResponse single_response; + net::ParseOCSPSingleResponse(single_response_der, &single_response); + + return 0; +} diff --git a/chromium/net/cert/pki/ocsp_unittest.cc b/chromium/net/cert/pki/ocsp_unittest.cc new file mode 100644 index 00000000000..6b3ae13a68d --- /dev/null +++ b/chromium/net/cert/pki/ocsp_unittest.cc @@ -0,0 +1,239 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/ocsp.h" + +#include "base/base64.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "net/cert/pki/test_helpers.h" +#include "net/der/encode_values.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/pool.h" +#include "url/gurl.h" + +namespace net { + +namespace { + +const base::TimeDelta kOCSPAgeOneWeek = base::Days(7); + +std::string GetFilePath(const std::string& file_name) { + return std::string("net/data/ocsp_unittest/") + file_name; +} + +scoped_refptr<ParsedCertificate> ParseCertificate(base::StringPiece 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 { + base::StringPiece 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 + base::Time kVerifyTime = base::Time::UnixEpoch() + base::Seconds(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. + scoped_refptr<ParsedCertificate> cert = ParseCertificate(cert_data); + ASSERT_TRUE(cert); + + scoped_refptr<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)); +} + +base::StringPiece 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<base::StringPiece> {}; + +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)); + + scoped_refptr<ParsedCertificate> cert = ParseCertificate(cert_data); + ASSERT_TRUE(cert); + + scoped_refptr<ParsedCertificate> issuer = ParseCertificate(ca_data); + ASSERT_TRUE(issuer); + + GURL 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 implementaiton file. + std::string b64 = url.spec().substr(GetParam().size() + 1); + + // Hex un-escape the data. + base::ReplaceSubstringsAfterOffset(&b64, 0, "%2B", "+"); + base::ReplaceSubstringsAfterOffset(&b64, 0, "%2F", "/"); + base::ReplaceSubstringsAfterOffset(&b64, 0, "%3D", "="); + + // Base64 decode the data. + std::string decoded; + ASSERT_TRUE(base::Base64Decode(b64, &decoded)); + + EXPECT_EQ(request_data, decoded); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/parse_certificate.cc b/chromium/net/cert/pki/parse_certificate.cc new file mode 100644 index 00000000000..d206ec897e6 --- /dev/null +++ b/chromium/net/cert/pki/parse_certificate.cc @@ -0,0 +1,955 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/parse_certificate.h" + +#include <utility> + +#include "base/strings/string_util.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/general_names.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +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); + absl::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, + absl::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 } + absl::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, + absl::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 + absl::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 + absl::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; +} + +NET_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; +} + +NET_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 absl::optional<BitString> when converting +// has_key_usage_ and key_usage_ into single absl::optional field. +bool ParseKeyUsage(const der::Input& key_usage_tlv, der::BitString* key_usage) { + der::Parser parser(key_usage_tlv); + absl::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<base::StringPiece>* out_ca_issuers_uris, + std::vector<base::StringPiece>* 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, + base::StringPiece uri = access_location_value.AsStringPiece(); + if (!base::IsStringASCII(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 diff --git a/chromium/net/cert/pki/parse_certificate.h b/chromium/net/cert/pki/parse_certificate.h new file mode 100644 index 00000000000..d71dda139b5 --- /dev/null +++ b/chromium/net/cert/pki/parse_certificate.h @@ -0,0 +1,621 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_PARSE_CERTIFICATE_H_ +#define NET_CERT_PKI_PARSE_CERTIFICATE_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <vector> + +#include "net/base/net_export.h" +#include "net/cert/pki/general_names.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +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]] NET_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]] NET_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]] NET_EXPORT bool ParseValidity(const der::Input& validity_tlv, + der::GeneralizedTime* not_before, + der::GeneralizedTime* not_after); + +struct NET_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]] NET_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]] NET_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 NET_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 + absl::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 + absl::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. + absl::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 NET_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]] NET_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}; + +// 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]] NET_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]] NET_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]] NET_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]] NET_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]] NET_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]] NET_EXPORT bool ParseAuthorityInfoAccessURIs( + const der::Input& authority_info_access_tlv, + std::vector<base::StringPiece>* out_ca_issuers_uris, + std::vector<base::StringPiece>* 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 NET_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. + absl::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. + absl::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. + absl::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]] NET_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 NET_EXPORT ParsedAuthorityKeyIdentifier { + ParsedAuthorityKeyIdentifier(); + ~ParsedAuthorityKeyIdentifier(); + ParsedAuthorityKeyIdentifier(ParsedAuthorityKeyIdentifier&& other); + ParsedAuthorityKeyIdentifier& operator=(ParsedAuthorityKeyIdentifier&& other); + + // The keyIdentifier, which is an OCTET STRING. + absl::optional<der::Input> key_identifier; + + // The authorityCertIssuer, which should be a GeneralNames, but this is not + // enforced by ParseAuthorityKeyIdentifier. + absl::optional<der::Input> authority_cert_issuer; + + // The DER authorityCertSerialNumber, which should be a + // CertificateSerialNumber (an INTEGER) but this is not enforced by + // ParseAuthorityKeyIdentifier. + absl::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]] NET_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]] NET_EXPORT bool ParseSubjectKeyIdentifier( + const der::Input& extension_value, + der::Input* subject_key_identifier); + +} // namespace net + +#endif // NET_CERT_PKI_PARSE_CERTIFICATE_H_ diff --git a/chromium/net/cert/pki/parse_certificate_fuzzer.cc b/chromium/net/cert/pki/parse_certificate_fuzzer.cc new file mode 100644 index 00000000000..b73eb018a24 --- /dev/null +++ b/chromium/net/cert/pki/parse_certificate_fuzzer.cc @@ -0,0 +1,24 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "base/check_op.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/x509_util.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::CertErrors errors; + scoped_refptr<net::ParsedCertificate> cert = net::ParsedCertificate::Create( + net::x509_util::CreateCryptoBuffer(base::make_span(data, size)), {}, + &errors); + + // Severe errors must be provided iff the parsing failed. + CHECK_EQ(errors.ContainsAnyErrorWithSeverity(net::CertError::SEVERITY_HIGH), + cert == nullptr); + + return 0; +} diff --git a/chromium/net/cert/pki/parse_certificate_unittest.cc b/chromium/net/cert/pki/parse_certificate_unittest.cc new file mode 100644 index 00000000000..7f5c48efe3e --- /dev/null +++ b/chromium/net/cert/pki/parse_certificate_unittest.cc @@ -0,0 +1,1176 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/parse_certificate.h" + +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/general_names.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/test_helpers.h" +#include "net/der/input.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +namespace net { + +namespace { + +// Pretty-prints a GeneralizedTime as a human-readable string for use in test +// expectations (it is more readable to specify the expected results as a +// string). +std::string ToString(const der::GeneralizedTime& time) { + std::ostringstream pretty_time; + pretty_time << "year=" << int{time.year} << ", month=" << int{time.month} + << ", day=" << int{time.day} << ", hours=" << int{time.hours} + << ", minutes=" << int{time.minutes} + << ", seconds=" << int{time.seconds}; + return pretty_time.str(); +} + +std::string GetFilePath(const std::string& file_name) { + return std::string("net/data/parse_certificate_unittest/") + file_name; +} + +// Loads certificate data and expectations from the PEM file |file_name|. +// Verifies that parsing the Certificate matches expectations: +// * If expected to fail, emits the expected errors +// * If expected to succeeds, the parsed fields match expectations +void RunCertificateTest(const std::string& file_name) { + std::string data; + std::string expected_errors; + std::string expected_tbs_certificate; + std::string expected_signature_algorithm; + std::string expected_signature; + + // Read the certificate data and test expectations from a single PEM file. + const PemBlockMapping mappings[] = { + {"CERTIFICATE", &data}, + {"ERRORS", &expected_errors, true /*optional*/}, + {"SIGNATURE", &expected_signature, true /*optional*/}, + {"SIGNATURE ALGORITHM", &expected_signature_algorithm, true /*optional*/}, + {"TBS CERTIFICATE", &expected_tbs_certificate, true /*optional*/}, + }; + std::string test_file_path = GetFilePath(file_name); + ASSERT_TRUE(ReadTestDataFromPemFile(test_file_path, mappings)); + + // Note that empty expected_errors doesn't necessarily mean success. + bool expected_result = !expected_tbs_certificate.empty(); + + // Parsing the certificate. + der::Input tbs_certificate_tlv; + der::Input signature_algorithm_tlv; + der::BitString signature_value; + CertErrors errors; + bool actual_result = + ParseCertificate(der::Input(&data), &tbs_certificate_tlv, + &signature_algorithm_tlv, &signature_value, &errors); + + EXPECT_EQ(expected_result, actual_result); + VerifyCertErrors(expected_errors, errors, test_file_path); + + // Ensure that the parsed certificate matches expectations. + if (expected_result && actual_result) { + EXPECT_EQ(0, signature_value.unused_bits()); + EXPECT_EQ(der::Input(&expected_signature), signature_value.bytes()); + EXPECT_EQ(der::Input(&expected_signature_algorithm), + signature_algorithm_tlv); + EXPECT_EQ(der::Input(&expected_tbs_certificate), tbs_certificate_tlv); + } +} + +// Tests parsing a Certificate. +TEST(ParseCertificateTest, Version3) { + RunCertificateTest("cert_version3.pem"); +} + +// Tests parsing a simplified Certificate-like structure (the sub-fields for +// algorithm and tbsCertificate are not actually valid, but ParseCertificate() +// doesn't check them) +TEST(ParseCertificateTest, Skeleton) { + RunCertificateTest("cert_skeleton.pem"); +} + +// Tests parsing a Certificate that is not a sequence fails. +TEST(ParseCertificateTest, NotSequence) { + RunCertificateTest("cert_not_sequence.pem"); +} + +// Tests that uncomsumed data is not allowed after the main SEQUENCE. +TEST(ParseCertificateTest, DataAfterSignature) { + RunCertificateTest("cert_data_after_signature.pem"); +} + +// Tests that parsing fails if the signature BIT STRING is missing. +TEST(ParseCertificateTest, MissingSignature) { + RunCertificateTest("cert_missing_signature.pem"); +} + +// Tests that parsing fails if the signature is present but not a BIT STRING. +TEST(ParseCertificateTest, SignatureNotBitString) { + RunCertificateTest("cert_signature_not_bit_string.pem"); +} + +// Tests that parsing fails if the main SEQUENCE is empty (missing all the +// fields). +TEST(ParseCertificateTest, EmptySequence) { + RunCertificateTest("cert_empty_sequence.pem"); +} + +// Tests what happens when the signature algorithm is present, but has the wrong +// tag. +TEST(ParseCertificateTest, AlgorithmNotSequence) { + RunCertificateTest("cert_algorithm_not_sequence.pem"); +} + +// Loads tbsCertificate data and expectations from the PEM file |file_name|. +// Verifies that parsing the TBSCertificate succeeds, and each parsed field +// matches the expectations. +// +// TODO(eroman): Get rid of the |expected_version| parameter -- this should be +// encoded in the test expectations file. +void RunTbsCertificateTestGivenVersion(const std::string& file_name, + CertificateVersion expected_version) { + std::string data; + std::string expected_serial_number; + std::string expected_signature_algorithm; + std::string expected_issuer; + std::string expected_validity_not_before; + std::string expected_validity_not_after; + std::string expected_subject; + std::string expected_spki; + std::string expected_issuer_unique_id; + std::string expected_subject_unique_id; + std::string expected_extensions; + std::string expected_errors; + + // Read the certificate data and test expectations from a single PEM file. + const PemBlockMapping mappings[] = { + {"TBS CERTIFICATE", &data}, + {"SIGNATURE ALGORITHM", &expected_signature_algorithm, true}, + {"SERIAL NUMBER", &expected_serial_number, true}, + {"ISSUER", &expected_issuer, true}, + {"VALIDITY NOTBEFORE", &expected_validity_not_before, true}, + {"VALIDITY NOTAFTER", &expected_validity_not_after, true}, + {"SUBJECT", &expected_subject, true}, + {"SPKI", &expected_spki, true}, + {"ISSUER UNIQUE ID", &expected_issuer_unique_id, true}, + {"SUBJECT UNIQUE ID", &expected_subject_unique_id, true}, + {"EXTENSIONS", &expected_extensions, true}, + {"ERRORS", &expected_errors, true}, + }; + std::string test_file_path = GetFilePath(file_name); + ASSERT_TRUE(ReadTestDataFromPemFile(test_file_path, mappings)); + + bool expected_result = !expected_spki.empty(); + + ParsedTbsCertificate parsed; + CertErrors errors; + bool actual_result = + ParseTbsCertificate(der::Input(&data), {}, &parsed, &errors); + + EXPECT_EQ(expected_result, actual_result); + VerifyCertErrors(expected_errors, errors, test_file_path); + + if (!expected_result || !actual_result) + return; + + // Ensure that the ParsedTbsCertificate matches expectations. + EXPECT_EQ(expected_version, parsed.version); + + EXPECT_EQ(der::Input(&expected_serial_number), parsed.serial_number); + EXPECT_EQ(der::Input(&expected_signature_algorithm), + parsed.signature_algorithm_tlv); + + EXPECT_EQ(der::Input(&expected_issuer), parsed.issuer_tlv); + + // In the test expectations PEM file, validity is described as a + // textual string of the parsed value (rather than as DER). + EXPECT_EQ(expected_validity_not_before, ToString(parsed.validity_not_before)); + EXPECT_EQ(expected_validity_not_after, ToString(parsed.validity_not_after)); + + EXPECT_EQ(der::Input(&expected_subject), parsed.subject_tlv); + EXPECT_EQ(der::Input(&expected_spki), parsed.spki_tlv); + + EXPECT_EQ(!expected_issuer_unique_id.empty(), + parsed.issuer_unique_id.has_value()); + if (parsed.issuer_unique_id.has_value()) { + EXPECT_EQ(der::Input(&expected_issuer_unique_id), + parsed.issuer_unique_id->bytes()); + } + EXPECT_EQ(!expected_subject_unique_id.empty(), + parsed.subject_unique_id.has_value()); + if (parsed.subject_unique_id.has_value()) { + EXPECT_EQ(der::Input(&expected_subject_unique_id), + parsed.subject_unique_id->bytes()); + } + + EXPECT_EQ(!expected_extensions.empty(), parsed.extensions_tlv.has_value()); + if (parsed.extensions_tlv) { + EXPECT_EQ(der::Input(&expected_extensions), parsed.extensions_tlv.value()); + } +} + +void RunTbsCertificateTest(const std::string& file_name) { + RunTbsCertificateTestGivenVersion(file_name, CertificateVersion::V3); +} + +// Tests parsing a TBSCertificate for v3 that contains no optional fields. +TEST(ParseTbsCertificateTest, Version3NoOptionals) { + RunTbsCertificateTest("tbs_v3_no_optionals.pem"); +} + +// Tests parsing a TBSCertificate for v3 that contains extensions. +TEST(ParseTbsCertificateTest, Version3WithExtensions) { + RunTbsCertificateTest("tbs_v3_extensions.pem"); +} + +// Tests parsing a TBSCertificate which lacks a version number (causing it to +// default to v1). +TEST(ParseTbsCertificateTest, Version1) { + RunTbsCertificateTestGivenVersion("tbs_v1.pem", CertificateVersion::V1); +} + +// The version was set to v1 explicitly rather than omitting the version field. +TEST(ParseTbsCertificateTest, ExplicitVersion1) { + RunTbsCertificateTest("tbs_explicit_v1.pem"); +} + +// Extensions are not defined in version 1. +TEST(ParseTbsCertificateTest, Version1WithExtensions) { + RunTbsCertificateTest("tbs_v1_extensions.pem"); +} + +// Extensions are not defined in version 2. +TEST(ParseTbsCertificateTest, Version2WithExtensions) { + RunTbsCertificateTest("tbs_v2_extensions.pem"); +} + +// A boring version 2 certificate with none of the optional fields. +TEST(ParseTbsCertificateTest, Version2NoOptionals) { + RunTbsCertificateTestGivenVersion("tbs_v2_no_optionals.pem", + CertificateVersion::V2); +} + +// A version 2 certificate with an issuer unique ID field. +TEST(ParseTbsCertificateTest, Version2IssuerUniqueId) { + RunTbsCertificateTestGivenVersion("tbs_v2_issuer_unique_id.pem", + CertificateVersion::V2); +} + +// A version 2 certificate with both a issuer and subject unique ID field. +TEST(ParseTbsCertificateTest, Version2IssuerAndSubjectUniqueId) { + RunTbsCertificateTestGivenVersion("tbs_v2_issuer_and_subject_unique_id.pem", + CertificateVersion::V2); +} + +// A version 3 certificate with all of the optional fields (issuer unique id, +// subject unique id, and extensions). +TEST(ParseTbsCertificateTest, Version3AllOptionals) { + RunTbsCertificateTest("tbs_v3_all_optionals.pem"); +} + +// The version was set to v4, which is unrecognized. +TEST(ParseTbsCertificateTest, Version4) { + RunTbsCertificateTest("tbs_v4.pem"); +} + +// Tests that extraneous data after extensions in a v3 is rejected. +TEST(ParseTbsCertificateTest, Version3DataAfterExtensions) { + RunTbsCertificateTest("tbs_v3_data_after_extensions.pem"); +} + +// Tests using a real-world certificate (whereas the other tests are fabricated +// (and in fact invalid) data. +TEST(ParseTbsCertificateTest, Version3Real) { + RunTbsCertificateTest("tbs_v3_real.pem"); +} + +// Parses a TBSCertificate whose "validity" field expresses both notBefore +// and notAfter using UTCTime. +TEST(ParseTbsCertificateTest, ValidityBothUtcTime) { + RunTbsCertificateTest("tbs_validity_both_utc_time.pem"); +} + +// Parses a TBSCertificate whose "validity" field expresses both notBefore +// and notAfter using GeneralizedTime. +TEST(ParseTbsCertificateTest, ValidityBothGeneralizedTime) { + RunTbsCertificateTest("tbs_validity_both_generalized_time.pem"); +} + +// Parses a TBSCertificate whose "validity" field expresses notBefore using +// UTCTime and notAfter using GeneralizedTime. +TEST(ParseTbsCertificateTest, ValidityUTCTimeAndGeneralizedTime) { + RunTbsCertificateTest("tbs_validity_utc_time_and_generalized_time.pem"); +} + +// Parses a TBSCertificate whose validity" field expresses notBefore using +// GeneralizedTime and notAfter using UTCTime. Also of interest, notBefore > +// notAfter. Parsing will succeed, however no time can satisfy this constraint. +TEST(ParseTbsCertificateTest, ValidityGeneralizedTimeAndUTCTime) { + RunTbsCertificateTest("tbs_validity_generalized_time_and_utc_time.pem"); +} + +// Parses a TBSCertificate whose "validity" field does not strictly follow +// the DER rules (and fails to be parsed). +TEST(ParseTbsCertificateTest, ValidityRelaxed) { + RunTbsCertificateTest("tbs_validity_relaxed.pem"); +} + +// Parses a KeyUsage with a single 0 bit. +TEST(ParseKeyUsageTest, OneBitAllZeros) { + const uint8_t der[] = { + 0x03, 0x02, // BIT STRING + 0x07, // Number of unused bits + 0x00, // bits + }; + + der::BitString key_usage; + ASSERT_FALSE(ParseKeyUsage(der::Input(der), &key_usage)); +} + +// Parses a KeyUsage with 32 bits that are all 0. +TEST(ParseKeyUsageTest, 32BitsAllZeros) { + const uint8_t der[] = { + 0x03, 0x05, // BIT STRING + 0x00, // Number of unused bits + 0x00, 0x00, 0x00, 0x00, + }; + + der::BitString key_usage; + ASSERT_FALSE(ParseKeyUsage(der::Input(der), &key_usage)); +} + +// Parses a KeyUsage with 32 bits, one of which is 1 (but not in recognized +// set). +TEST(ParseKeyUsageTest, 32BitsOneSet) { + const uint8_t der[] = { + 0x03, 0x05, // BIT STRING + 0x00, // Number of unused bits + 0x00, 0x00, 0x00, 0x02, + }; + + der::BitString key_usage; + ASSERT_TRUE(ParseKeyUsage(der::Input(der), &key_usage)); + + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DIGITAL_SIGNATURE)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_NON_REPUDIATION)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_ENCIPHERMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DATA_ENCIPHERMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_AGREEMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_CRL_SIGN)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_ENCIPHER_ONLY)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DECIPHER_ONLY)); +} + +// Parses a KeyUsage containing bit string 101. +TEST(ParseKeyUsageTest, ThreeBits) { + const uint8_t der[] = { + 0x03, 0x02, // BIT STRING + 0x05, // Number of unused bits + 0xA0, // bits + }; + + der::BitString key_usage; + ASSERT_TRUE(ParseKeyUsage(der::Input(der), &key_usage)); + + EXPECT_TRUE(key_usage.AssertsBit(KEY_USAGE_BIT_DIGITAL_SIGNATURE)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_NON_REPUDIATION)); + EXPECT_TRUE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_ENCIPHERMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DATA_ENCIPHERMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_AGREEMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_CRL_SIGN)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_ENCIPHER_ONLY)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DECIPHER_ONLY)); +} + +// Parses a KeyUsage containing DECIPHER_ONLY, which is the +// only bit that doesn't fit in the first byte. +TEST(ParseKeyUsageTest, DecipherOnly) { + const uint8_t der[] = { + 0x03, 0x03, // BIT STRING + 0x07, // Number of unused bits + 0x00, 0x80, // bits + }; + + der::BitString key_usage; + ASSERT_TRUE(ParseKeyUsage(der::Input(der), &key_usage)); + + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DIGITAL_SIGNATURE)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_NON_REPUDIATION)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_ENCIPHERMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_DATA_ENCIPHERMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_AGREEMENT)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_CRL_SIGN)); + EXPECT_FALSE(key_usage.AssertsBit(KEY_USAGE_BIT_ENCIPHER_ONLY)); + EXPECT_TRUE(key_usage.AssertsBit(KEY_USAGE_BIT_DECIPHER_ONLY)); +} + +// Parses an empty KeyUsage. +TEST(ParseKeyUsageTest, Empty) { + const uint8_t der[] = { + 0x03, 0x01, // BIT STRING + 0x00, // Number of unused bits + }; + + der::BitString key_usage; + ASSERT_FALSE(ParseKeyUsage(der::Input(der), &key_usage)); +} + +TEST(ParseAuthorityInfoAccess, BasicTests) { + // SEQUENCE { + // SEQUENCE { + // # ocsp with directoryName + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.1 } + // [4] { + // SEQUENCE { + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "ocsp" } + // } + // } + // } + // } + // } + // SEQUENCE { + // # caIssuers with directoryName + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.2 } + // [4] { + // SEQUENCE { + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "ca issuer" } + // } + // } + // } + // } + // } + // SEQUENCE { + // # non-standard method with URI + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.3 } + // [6 PRIMITIVE] { "http://nonstandard.example.com" } + // } + // SEQUENCE { + // # ocsp with URI + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.1 } + // [6 PRIMITIVE] { "http://ocsp.example.com" } + // } + // SEQUENCE { + // # caIssuers with URI + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.2 } + // [6 PRIMITIVE] { "http://www.example.com/issuer.crt" } + // } + // } + const uint8_t der[] = { + 0x30, 0x81, 0xc3, 0x30, 0x1d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, + 0x07, 0x30, 0x01, 0xa4, 0x11, 0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x6f, 0x63, 0x73, 0x70, 0x30, 0x22, + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0xa4, 0x16, + 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x09, 0x63, 0x61, 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x30, 0x2a, + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x03, 0x86, 0x1e, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x6f, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, + 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x2d, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x21, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x2e, 0x63, 0x72, 0x74}; + + std::vector<AuthorityInfoAccessDescription> access_descriptions; + ASSERT_TRUE(ParseAuthorityInfoAccess(der::Input(der), &access_descriptions)); + ASSERT_EQ(5u, access_descriptions.size()); + { + const auto& desc = access_descriptions[0]; + EXPECT_EQ(der::Input(kAdOcspOid), desc.access_method_oid); + const uint8_t location_der[] = {0xa4, 0x11, 0x30, 0x0f, 0x31, 0x0d, 0x30, + 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x04, 0x6f, 0x63, 0x73, 0x70}; + EXPECT_EQ(der::Input(location_der), desc.access_location); + } + { + const auto& desc = access_descriptions[1]; + EXPECT_EQ(der::Input(kAdCaIssuersOid), desc.access_method_oid); + const uint8_t location_der[] = { + 0xa4, 0x16, 0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x13, 0x09, 0x63, 0x61, 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72}; + EXPECT_EQ(der::Input(location_der), desc.access_location); + } + { + const auto& desc = access_descriptions[2]; + const uint8_t method_oid[] = {0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x30, 0x03}; + EXPECT_EQ(der::Input(method_oid), desc.access_method_oid); + const uint8_t location_der[] = { + 0x86, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x6f, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d}; + EXPECT_EQ(der::Input(location_der), desc.access_location); + } + { + const auto& desc = access_descriptions[3]; + EXPECT_EQ(der::Input(kAdOcspOid), desc.access_method_oid); + const uint8_t location_der[] = {0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x2e, 0x63, 0x6f, 0x6d}; + EXPECT_EQ(der::Input(location_der), desc.access_location); + } + { + const auto& desc = access_descriptions[4]; + EXPECT_EQ(der::Input(kAdCaIssuersOid), desc.access_method_oid); + const uint8_t location_der[] = { + 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x2e, 0x63, 0x72, 0x74}; + EXPECT_EQ(der::Input(location_der), desc.access_location); + } + + std::vector<base::StringPiece> ca_issuers_uris, ocsp_uris; + ASSERT_TRUE(ParseAuthorityInfoAccessURIs(der::Input(der), &ca_issuers_uris, + &ocsp_uris)); + ASSERT_EQ(1u, ca_issuers_uris.size()); + EXPECT_EQ("http://www.example.com/issuer.crt", ca_issuers_uris.front()); + ASSERT_EQ(1u, ocsp_uris.size()); + EXPECT_EQ("http://ocsp.example.com", ocsp_uris.front()); +} + +TEST(ParseAuthorityInfoAccess, NoOcspOrCaIssuersURIs) { + // SEQUENCE { + // SEQUENCE { + // # non-standard method with directoryName + // OBJECT_IDENTIFIER { 1.2.3 } + // [4] { + // SEQUENCE { + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "foo" } + // } + // } + // } + // } + // } + // } + const uint8_t der[] = {0x30, 0x18, 0x30, 0x16, 0x06, 0x02, 0x2a, 0x03, 0xa4, + 0x10, 0x30, 0x0e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x13, 0x03, 0x66, 0x6f, 0x6f}; + + std::vector<AuthorityInfoAccessDescription> access_descriptions; + ASSERT_TRUE(ParseAuthorityInfoAccess(der::Input(der), &access_descriptions)); + ASSERT_EQ(1u, access_descriptions.size()); + const auto& desc = access_descriptions[0]; + const uint8_t method_oid[] = {0x2a, 0x03}; + EXPECT_EQ(der::Input(method_oid), desc.access_method_oid); + const uint8_t location_der[] = {0xa4, 0x10, 0x30, 0x0e, 0x31, 0x0c, + 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x13, 0x03, 0x66, 0x6f, 0x6f}; + EXPECT_EQ(der::Input(location_der), desc.access_location); + + std::vector<base::StringPiece> ca_issuers_uris, ocsp_uris; + // ParseAuthorityInfoAccessURIs should still return success since it was a + // valid AuthorityInfoAccess extension, even though it did not contain any + // elements we care about, and both output vectors should be empty. + ASSERT_TRUE(ParseAuthorityInfoAccessURIs(der::Input(der), &ca_issuers_uris, + &ocsp_uris)); + EXPECT_EQ(0u, ca_issuers_uris.size()); + EXPECT_EQ(0u, ocsp_uris.size()); +} + +TEST(ParseAuthorityInfoAccess, IncompleteAccessDescription) { + // SEQUENCE { + // # first entry is ok + // SEQUENCE { + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.1 } + // [6 PRIMITIVE] { "http://ocsp.example.com" } + // } + // # second is missing accessLocation field + // SEQUENCE { + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.2 } + // } + // } + const uint8_t der[] = {0x30, 0x31, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, + 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, + 0x63, 0x6f, 0x6d, 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x02}; + + std::vector<AuthorityInfoAccessDescription> access_descriptions; + EXPECT_FALSE(ParseAuthorityInfoAccess(der::Input(der), &access_descriptions)); + + std::vector<base::StringPiece> ca_issuers_uris, ocsp_uris; + EXPECT_FALSE(ParseAuthorityInfoAccessURIs(der::Input(der), &ca_issuers_uris, + &ocsp_uris)); +} + +TEST(ParseAuthorityInfoAccess, ExtraDataInAccessDescription) { + // SEQUENCE { + // SEQUENCE { + // OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.48.1 } + // [6 PRIMITIVE] { "http://ocsp.example.com" } + // # invalid, AccessDescription only has 2 fields + // PrintableString { "henlo" } + // } + // } + const uint8_t der[] = { + 0x30, 0x2c, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, + 0x63, 0x73, 0x70, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, + 0x63, 0x6f, 0x6d, 0x13, 0x05, 0x68, 0x65, 0x6e, 0x6c, 0x6f}; + + std::vector<AuthorityInfoAccessDescription> access_descriptions; + EXPECT_FALSE(ParseAuthorityInfoAccess(der::Input(der), &access_descriptions)); + + std::vector<base::StringPiece> ca_issuers_uris, ocsp_uris; + EXPECT_FALSE(ParseAuthorityInfoAccessURIs(der::Input(der), &ca_issuers_uris, + &ocsp_uris)); +} + +TEST(ParseAuthorityInfoAccess, EmptySequence) { + // SEQUENCE { } + const uint8_t der[] = {0x30, 0x00}; + + std::vector<AuthorityInfoAccessDescription> access_descriptions; + EXPECT_FALSE(ParseAuthorityInfoAccess(der::Input(der), &access_descriptions)); + + std::vector<base::StringPiece> ca_issuers_uris, ocsp_uris; + EXPECT_FALSE(ParseAuthorityInfoAccessURIs(der::Input(der), &ca_issuers_uris, + &ocsp_uris)); +} + +// Test fixture for testing ParseCrlDistributionPoints. +// +// Test data is encoded in certificate files. This fixture is responsible for +// reading and parsing the certificates to get at the extension under test. +class ParseCrlDistributionPointsTest : public ::testing::Test { + public: + protected: + bool GetCrlDps(const char* file_name, + std::vector<ParsedDistributionPoint>* dps) { + std::string cert_bytes; + // Read the test certificate file. + const PemBlockMapping mappings[] = { + {"CERTIFICATE", &cert_bytes}, + }; + std::string test_file_path = GetFilePath(file_name); + EXPECT_TRUE(ReadTestDataFromPemFile(test_file_path, mappings)); + + // Extract the CRLDP from the test Certificate. + CertErrors errors; + scoped_refptr<ParsedCertificate> cert = ParsedCertificate::Create( + bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( + reinterpret_cast<const uint8_t*>(cert_bytes.data()), + cert_bytes.size(), nullptr)), + {}, &errors); + + if (!cert) + return false; + + auto it = cert->extensions().find(der::Input(kCrlDistributionPointsOid)); + if (it == cert->extensions().end()) + return false; + + der::Input crl_dp_tlv = it->second.value; + + // Keep the certificate data alive, since this function will return + // der::Inputs that reference it. Run the function under test (for parsing + // + // TODO(eroman): The use of ParsedCertificate in this test should be removed + // in lieu of lazy parsing. + keep_alive_certs_.push_back(cert); + + return ParseCrlDistributionPoints(crl_dp_tlv, dps); + } + + private: + ParsedCertificateList keep_alive_certs_; +}; + +TEST_F(ParseCrlDistributionPointsTest, OneUriNoIssuer) { + std::vector<ParsedDistributionPoint> dps; + ASSERT_TRUE(GetCrlDps("crldp_1uri_noissuer.pem", &dps)); + + ASSERT_EQ(1u, dps.size()); + const ParsedDistributionPoint& dp1 = dps.front(); + + ASSERT_TRUE(dp1.distribution_point_fullname); + const GeneralNames& fullname = *dp1.distribution_point_fullname; + EXPECT_EQ(GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER, + fullname.present_name_types); + ASSERT_EQ(1u, fullname.uniform_resource_identifiers.size()); + EXPECT_EQ(fullname.uniform_resource_identifiers.front(), + std::string("http://www.example.com/foo.crl")); + + EXPECT_FALSE(dp1.distribution_point_name_relative_to_crl_issuer); + EXPECT_FALSE(dp1.reasons); + EXPECT_FALSE(dp1.crl_issuer); +} + +TEST_F(ParseCrlDistributionPointsTest, ThreeUrisNoIssuer) { + std::vector<ParsedDistributionPoint> dps; + ASSERT_TRUE(GetCrlDps("crldp_3uri_noissuer.pem", &dps)); + + ASSERT_EQ(1u, dps.size()); + const ParsedDistributionPoint& dp1 = dps.front(); + + ASSERT_TRUE(dp1.distribution_point_fullname); + const GeneralNames& fullname = *dp1.distribution_point_fullname; + EXPECT_EQ(GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER, + fullname.present_name_types); + ASSERT_EQ(3u, fullname.uniform_resource_identifiers.size()); + EXPECT_EQ(fullname.uniform_resource_identifiers[0], + std::string("http://www.example.com/foo1.crl")); + EXPECT_EQ(fullname.uniform_resource_identifiers[1], + std::string("http://www.example.com/blah.crl")); + EXPECT_EQ(fullname.uniform_resource_identifiers[2], + std::string("not-even-a-url")); + + EXPECT_FALSE(dp1.distribution_point_name_relative_to_crl_issuer); + EXPECT_FALSE(dp1.reasons); + EXPECT_FALSE(dp1.crl_issuer); +} + +TEST_F(ParseCrlDistributionPointsTest, CrlIssuerAsDirname) { + std::vector<ParsedDistributionPoint> dps; + ASSERT_TRUE(GetCrlDps("crldp_issuer_as_dirname.pem", &dps)); + + ASSERT_EQ(1u, dps.size()); + const ParsedDistributionPoint& dp1 = dps.front(); + ASSERT_TRUE(dp1.distribution_point_fullname); + const GeneralNames& fullname = *dp1.distribution_point_fullname; + EXPECT_EQ(GENERAL_NAME_DIRECTORY_NAME, fullname.present_name_types); + // Generated by `ascii2der | xxd -i` from the Name value in + // crldp_issuer_as_dirname.pem. + const uint8_t kExpectedName[] = { + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x16, + 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x32, 0x30, 0x31, 0x31, 0x31, 0x22, + 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x19, 0x69, 0x6e, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x43, 0x52, 0x4c, 0x20, 0x43, 0x41, 0x33, + 0x20, 0x63, 0x52, 0x4c, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x31, 0x29, + 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x69, 0x6e, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x20, 0x43, 0x52, 0x4c, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x43, 0x52, + 0x4c, 0x20, 0x43, 0x41, 0x33}; + ASSERT_EQ(1u, fullname.directory_names.size()); + EXPECT_EQ(der::Input(kExpectedName), fullname.directory_names[0]); + + EXPECT_FALSE(dp1.distribution_point_name_relative_to_crl_issuer); + EXPECT_FALSE(dp1.reasons); + + ASSERT_TRUE(dp1.crl_issuer); + // Generated by `ascii2der | xxd -i` from the cRLIssuer value in + // crldp_issuer_as_dirname.pem. + const uint8_t kExpectedCrlIssuer[] = { + 0xa4, 0x54, 0x30, 0x52, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1f, 0x30, 0x1d, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x13, 0x16, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x73, 0x20, 0x32, 0x30, 0x31, 0x31, 0x31, 0x22, 0x30, 0x20, 0x06, + 0x03, 0x55, 0x04, 0x0b, 0x13, 0x19, 0x69, 0x6e, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x43, 0x52, 0x4c, 0x20, 0x43, 0x41, 0x33, 0x20, + 0x63, 0x52, 0x4c, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72}; + EXPECT_EQ(der::Input(kExpectedCrlIssuer), dp1.crl_issuer); +} + +TEST_F(ParseCrlDistributionPointsTest, FullnameAsDirname) { + std::vector<ParsedDistributionPoint> dps; + ASSERT_TRUE(GetCrlDps("crldp_full_name_as_dirname.pem", &dps)); + + ASSERT_EQ(1u, dps.size()); + const ParsedDistributionPoint& dp1 = dps.front(); + + ASSERT_TRUE(dp1.distribution_point_fullname); + const GeneralNames& fullname = *dp1.distribution_point_fullname; + EXPECT_EQ(GENERAL_NAME_DIRECTORY_NAME, fullname.present_name_types); + // Generated by `ascii2der | xxd -i` from the Name value in + // crldp_full_name_as_dirname.pem. + const uint8_t kExpectedName[] = { + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x16, + 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x32, 0x30, 0x31, 0x31, 0x31, 0x45, + 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x53, 0x65, 0x6c, + 0x66, 0x2d, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x20, 0x43, 0x65, 0x72, + 0x74, 0x20, 0x44, 0x50, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x42, 0x61, 0x73, + 0x69, 0x63, 0x20, 0x53, 0x65, 0x6c, 0x66, 0x2d, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x64, 0x20, 0x43, 0x52, 0x4c, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x43, 0x41}; + ASSERT_EQ(1u, fullname.directory_names.size()); + EXPECT_EQ(der::Input(kExpectedName), fullname.directory_names[0]); + + EXPECT_FALSE(dp1.distribution_point_name_relative_to_crl_issuer); + EXPECT_FALSE(dp1.reasons); + EXPECT_FALSE(dp1.crl_issuer); +} + +TEST_F(ParseCrlDistributionPointsTest, RelativeNameAndReasonsAndMultipleDPs) { + // SEQUENCE { + // SEQUENCE { + // # distributionPoint + // [0] { + // # nameRelativeToCRLIssuer + // [1] { + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "CRL1" } + // } + // } + // } + // } + // # reasons + // [1 PRIMITIVE] { b`011` } + // } + // SEQUENCE { + // # distributionPoint + // [0] { + // # fullName + // [0] { + // [4] { + // SEQUENCE { + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "CRL2" } + // } + // } + // } + // } + // } + // } + // # reasons + // [1 PRIMITIVE] { b`100111111` } + // } + // } + const uint8_t kInputDer[] = { + 0x30, 0x37, 0x30, 0x17, 0xa0, 0x11, 0xa1, 0x0f, 0x31, 0x0d, 0x30, 0x0b, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x43, 0x52, 0x4c, 0x31, 0x81, + 0x02, 0x05, 0x60, 0x30, 0x1c, 0xa0, 0x15, 0xa0, 0x13, 0xa4, 0x11, 0x30, + 0x0f, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, + 0x43, 0x52, 0x4c, 0x32, 0x81, 0x03, 0x07, 0x9f, 0x80}; + + std::vector<ParsedDistributionPoint> dps; + ASSERT_TRUE(ParseCrlDistributionPoints(der::Input(kInputDer), &dps)); + ASSERT_EQ(2u, dps.size()); + { + const ParsedDistributionPoint& dp = dps[0]; + EXPECT_FALSE(dp.distribution_point_fullname); + + ASSERT_TRUE(dp.distribution_point_name_relative_to_crl_issuer); + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "CRL1" } + // } + // } + const uint8_t kExpectedRDN[] = {0x31, 0x0d, 0x30, 0x0b, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x13, + 0x04, 0x43, 0x52, 0x4c, 0x31}; + EXPECT_EQ(der::Input(kExpectedRDN), + *dp.distribution_point_name_relative_to_crl_issuer); + + ASSERT_TRUE(dp.reasons); + const uint8_t kExpectedReasons[] = {0x05, 0x60}; + EXPECT_EQ(der::Input(kExpectedReasons), *dp.reasons); + + EXPECT_FALSE(dp.crl_issuer); + } + { + const ParsedDistributionPoint& dp = dps[1]; + ASSERT_TRUE(dp.distribution_point_fullname); + const GeneralNames& fullname = *dp.distribution_point_fullname; + EXPECT_EQ(GENERAL_NAME_DIRECTORY_NAME, fullname.present_name_types); + // SET { + // SEQUENCE { + // # commonName + // OBJECT_IDENTIFIER { 2.5.4.3 } + // PrintableString { "CRL2" } + // } + // } + const uint8_t kExpectedName[] = {0x31, 0x0d, 0x30, 0x0b, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x13, + 0x04, 0x43, 0x52, 0x4c, 0x32}; + ASSERT_EQ(1u, fullname.directory_names.size()); + EXPECT_EQ(der::Input(kExpectedName), fullname.directory_names[0]); + + EXPECT_FALSE(dp.distribution_point_name_relative_to_crl_issuer); + + ASSERT_TRUE(dp.reasons); + const uint8_t kExpectedReasons[] = {0x07, 0x9f, 0x80}; + EXPECT_EQ(der::Input(kExpectedReasons), *dp.reasons); + + EXPECT_FALSE(dp.crl_issuer); + } +} + +TEST_F(ParseCrlDistributionPointsTest, NoDistributionPointName) { + // SEQUENCE { + // SEQUENCE { + // # cRLIssuer + // [2] { + // [4] { + // SEQUENCE { + // SET { + // SEQUENCE { + // # organizationUnitName + // OBJECT_IDENTIFIER { 2.5.4.11 } + // PrintableString { "crl issuer" } + // } + // } + // } + // } + // } + // } + // } + const uint8_t kInputDer[] = {0x30, 0x1d, 0x30, 0x1b, 0xa2, 0x19, 0xa4, 0x17, + 0x30, 0x15, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x63, 0x72, 0x6c, + 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72}; + + std::vector<ParsedDistributionPoint> dps; + ASSERT_TRUE(ParseCrlDistributionPoints(der::Input(kInputDer), &dps)); + ASSERT_EQ(1u, dps.size()); + const ParsedDistributionPoint& dp = dps[0]; + EXPECT_FALSE(dp.distribution_point_fullname); + + EXPECT_FALSE(dp.distribution_point_name_relative_to_crl_issuer); + + EXPECT_FALSE(dp.reasons); + + ASSERT_TRUE(dp.crl_issuer); + const uint8_t kExpectedDer[] = {0xa4, 0x17, 0x30, 0x15, 0x31, 0x13, 0x30, + 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, + 0x0a, 0x63, 0x72, 0x6c, 0x20, 0x69, 0x73, + 0x73, 0x75, 0x65, 0x72}; + EXPECT_EQ(der::Input(kExpectedDer), *dp.crl_issuer); +} + +TEST_F(ParseCrlDistributionPointsTest, OnlyReasons) { + // SEQUENCE { + // SEQUENCE { + // # reasons + // [1 PRIMITIVE] { b`011` } + // } + // } + const uint8_t kInputDer[] = {0x30, 0x06, 0x30, 0x04, 0x81, 0x02, 0x05, 0x60}; + + std::vector<ParsedDistributionPoint> dps; + EXPECT_FALSE(ParseCrlDistributionPoints(der::Input(kInputDer), &dps)); +} + +TEST_F(ParseCrlDistributionPointsTest, EmptyDistributionPoint) { + // SEQUENCE { + // SEQUENCE { + // } + // } + const uint8_t kInputDer[] = {0x30, 0x02, 0x30, 0x00}; + + std::vector<ParsedDistributionPoint> dps; + EXPECT_FALSE(ParseCrlDistributionPoints(der::Input(kInputDer), &dps)); +} + +TEST_F(ParseCrlDistributionPointsTest, EmptyDistributionPoints) { + // SEQUENCE { } + const uint8_t kInputDer[] = {0x30, 0x00}; + + std::vector<ParsedDistributionPoint> dps; + EXPECT_FALSE(ParseCrlDistributionPoints(der::Input(kInputDer), &dps)); +} + +bool ParseAuthorityKeyIdentifierTestData( + const char* file_name, + std::string* backing_bytes, + ParsedAuthorityKeyIdentifier* authority_key_identifier) { + // Read the test file. + const PemBlockMapping mappings[] = { + {"AUTHORITY_KEY_IDENTIFIER", backing_bytes}, + }; + std::string test_file_path = + std::string( + "net/data/parse_certificate_unittest/authority_key_identifier/") + + file_name; + EXPECT_TRUE(ReadTestDataFromPemFile(test_file_path, mappings)); + + return ParseAuthorityKeyIdentifier(der::Input(backing_bytes), + authority_key_identifier); +} + +TEST(ParseAuthorityKeyIdentifierTest, EmptyInput) { + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE( + ParseAuthorityKeyIdentifier(der::Input(), &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, EmptySequence) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + // TODO(mattm): should this be an error? RFC 5280 doesn't explicitly say it. + ASSERT_TRUE(ParseAuthorityKeyIdentifierTestData( + "empty_sequence.pem", &backing_bytes, &authority_key_identifier)); + + EXPECT_FALSE(authority_key_identifier.key_identifier); + EXPECT_FALSE(authority_key_identifier.authority_cert_issuer); + EXPECT_FALSE(authority_key_identifier.authority_cert_serial_number); +} + +TEST(ParseAuthorityKeyIdentifierTest, KeyIdentifier) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + ASSERT_TRUE(ParseAuthorityKeyIdentifierTestData( + "key_identifier.pem", &backing_bytes, &authority_key_identifier)); + + ASSERT_TRUE(authority_key_identifier.key_identifier); + const uint8_t kExpectedValue[] = {0xDE, 0xAD, 0xB0, 0x0F}; + EXPECT_EQ(der::Input(kExpectedValue), + authority_key_identifier.key_identifier); +} + +TEST(ParseAuthorityKeyIdentifierTest, IssuerAndSerial) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + ASSERT_TRUE(ParseAuthorityKeyIdentifierTestData( + "issuer_and_serial.pem", &backing_bytes, &authority_key_identifier)); + + EXPECT_FALSE(authority_key_identifier.key_identifier); + + ASSERT_TRUE(authority_key_identifier.authority_cert_issuer); + const uint8_t kExpectedIssuer[] = {0xa4, 0x11, 0x30, 0x0f, 0x31, 0x0d, 0x30, + 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x04, 0x52, 0x6f, 0x6f, 0x74}; + EXPECT_EQ(der::Input(kExpectedIssuer), + authority_key_identifier.authority_cert_issuer); + + ASSERT_TRUE(authority_key_identifier.authority_cert_serial_number); + const uint8_t kExpectedSerial[] = {0x27, 0x4F}; + EXPECT_EQ(der::Input(kExpectedSerial), + authority_key_identifier.authority_cert_serial_number); +} + +TEST(ParseAuthorityKeyIdentifierTest, KeyIdentifierAndIssuerAndSerial) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + ASSERT_TRUE(ParseAuthorityKeyIdentifierTestData( + "key_identifier_and_issuer_and_serial.pem", &backing_bytes, + &authority_key_identifier)); + + ASSERT_TRUE(authority_key_identifier.key_identifier); + const uint8_t kExpectedValue[] = {0xDE, 0xAD, 0xB0, 0x0F}; + EXPECT_EQ(der::Input(kExpectedValue), + authority_key_identifier.key_identifier); + + ASSERT_TRUE(authority_key_identifier.authority_cert_issuer); + const uint8_t kExpectedIssuer[] = {0xa4, 0x11, 0x30, 0x0f, 0x31, 0x0d, 0x30, + 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x04, 0x52, 0x6f, 0x6f, 0x74}; + EXPECT_EQ(der::Input(kExpectedIssuer), + authority_key_identifier.authority_cert_issuer); + + ASSERT_TRUE(authority_key_identifier.authority_cert_serial_number); + const uint8_t kExpectedSerial[] = {0x27, 0x4F}; + EXPECT_EQ(der::Input(kExpectedSerial), + authority_key_identifier.authority_cert_serial_number); +} + +TEST(ParseAuthorityKeyIdentifierTest, IssuerOnly) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "issuer_only.pem", &backing_bytes, &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, SerialOnly) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "serial_only.pem", &backing_bytes, &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, InvalidContents) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "invalid_contents.pem", &backing_bytes, &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, InvalidKeyIdentifier) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "invalid_key_identifier.pem", &backing_bytes, &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, InvalidIssuer) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "invalid_issuer.pem", &backing_bytes, &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, InvalidSerial) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "invalid_serial.pem", &backing_bytes, &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, ExtraContentsAfterIssuerAndSerial) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "extra_contents_after_issuer_and_serial.pem", &backing_bytes, + &authority_key_identifier)); +} + +TEST(ParseAuthorityKeyIdentifierTest, ExtraContentsAfterExtensionSequence) { + std::string backing_bytes; + ParsedAuthorityKeyIdentifier authority_key_identifier; + EXPECT_FALSE(ParseAuthorityKeyIdentifierTestData( + "extra_contents_after_extension_sequence.pem", &backing_bytes, + &authority_key_identifier)); +} + +TEST(ParseSubjectKeyIdentifierTest, EmptyInput) { + der::Input subject_key_identifier; + EXPECT_FALSE( + ParseSubjectKeyIdentifier(der::Input(), &subject_key_identifier)); +} + +TEST(ParseSubjectKeyIdentifierTest, Valid) { + // OCTET_STRING {`abcd`} + const uint8_t kInput[] = {0x04, 0x02, 0xab, 0xcd}; + const uint8_t kExpected[] = {0xab, 0xcd}; + der::Input subject_key_identifier; + EXPECT_TRUE( + ParseSubjectKeyIdentifier(der::Input(kInput), &subject_key_identifier)); + EXPECT_EQ(der::Input(kExpected), subject_key_identifier); +} + +TEST(ParseSubjectKeyIdentifierTest, ExtraData) { + // OCTET_STRING {`abcd`} + // NULL + const uint8_t kInput[] = {0x04, 0x02, 0xab, 0xcd, 0x05}; + der::Input subject_key_identifier; + EXPECT_FALSE( + ParseSubjectKeyIdentifier(der::Input(kInput), &subject_key_identifier)); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/parse_name.cc b/chromium/net/cert/pki/parse_name.cc new file mode 100644 index 00000000000..5cd4516890c --- /dev/null +++ b/chromium/net/cert/pki/parse_name.cc @@ -0,0 +1,222 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/parse_name.h" + +#include "base/check_op.h" +#include "base/notreached.h" +#include "base/strings/string_number_conversions.h" +#include "net/der/parse_values.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/mem.h" + +namespace net { + +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: + 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 = "#" + base::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 += "\\" + base::HexEncode(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 = "#" + base::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 diff --git a/chromium/net/cert/pki/parse_name.h b/chromium/net/cert/pki/parse_name.h new file mode 100644 index 00000000000..e44833a9b30 --- /dev/null +++ b/chromium/net/cert/pki/parse_name.h @@ -0,0 +1,157 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_PARSE_NAME_H_ +#define NET_CERT_PKI_PARSE_NAME_H_ + +#include <vector> + +#include "net/base/net_export.h" +#include "net/der/input.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +namespace net { + +// 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 NET_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]] NET_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]] NET_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]] NET_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]] NET_EXPORT bool ConvertToRFC2253(const RDNSequence& rdn_sequence, + std::string* out); +} // namespace net + +#endif // NET_CERT_PKI_PARSE_NAME_H_ diff --git a/chromium/net/cert/pki/parse_name_unittest.cc b/chromium/net/cert/pki/parse_name_unittest.cc new file mode 100644 index 00000000000..3e29b808c4e --- /dev/null +++ b/chromium/net/cert/pki/parse_name_unittest.cc @@ -0,0 +1,361 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/parse_name.h" + +#include "net/cert/pki/test_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +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 = "net/data/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 diff --git a/chromium/net/cert/pki/parsed_certificate.cc b/chromium/net/cert/pki/parsed_certificate.cc new file mode 100644 index 00000000000..a1268a127b6 --- /dev/null +++ b/chromium/net/cert/pki/parsed_certificate.cc @@ -0,0 +1,302 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/parsed_certificate.h" + +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/certificate_policies.h" +#include "net/cert/pki/extended_key_usage.h" +#include "net/cert/pki/name_constraints.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/cert/pki/verify_name_match.h" +#include "net/der/parser.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +namespace net { + +namespace { + +DEFINE_CERT_ERROR_ID(kFailedParsingCertificate, "Failed parsing Certificate"); +DEFINE_CERT_ERROR_ID(kFailedParsingTbsCertificate, + "Failed parsing TBSCertificate"); +DEFINE_CERT_ERROR_ID(kFailedParsingSignatureAlgorithm, + "Failed parsing SignatureAlgorithm"); +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() = default; +ParsedCertificate::~ParsedCertificate() = default; + +// static +scoped_refptr<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 = base::WrapRefCounted(new ParsedCertificate); + 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. + absl::optional<SignatureAlgorithm> sigalg = + ParseSignatureAlgorithm(result->signature_algorithm_tlv_, errors); + if (!sigalg) { + errors->AddError(kFailedParsingSignatureAlgorithm); + return nullptr; + } + result->signature_algorithm_ = *sigalg; + + 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_ = absl::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_ = + absl::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<scoped_refptr<net::ParsedCertificate>>* chain, + CertErrors* errors) { + scoped_refptr<ParsedCertificate> cert( + Create(std::move(cert_data), options, errors)); + if (!cert) + return false; + chain->push_back(std::move(cert)); + return true; +} + +} // namespace net diff --git a/chromium/net/cert/pki/parsed_certificate.h b/chromium/net/cert/pki/parsed_certificate.h new file mode 100644 index 00000000000..d02c4bf5129 --- /dev/null +++ b/chromium/net/cert/pki/parsed_certificate.h @@ -0,0 +1,335 @@ + +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_PARSED_CERTIFICATE_H_ +#define NET_CERT_PKI_PARSED_CERTIFICATE_H_ + +#include <map> +#include <memory> +#include <vector> + +#include "base/check.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/cert/pki/certificate_policies.h" +#include "net/cert/pki/parse_certificate.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/der/input.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/boringssl/src/include/openssl/base.h" + +namespace net { + +struct GeneralNames; +class NameConstraints; +class ParsedCertificate; +class CertErrors; + +using ParsedCertificateList = std::vector<scoped_refptr<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 NET_EXPORT ParsedCertificate + : public base::RefCountedThreadSafe<ParsedCertificate> { + public: + // 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 scoped_refptr<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<scoped_refptr<net::ParsedCertificate>>* chain, + CertErrors* errors); + + 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). + 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<base::StringPiece>& ca_issuers_uris() const { + return ca_issuers_uris_; + } + + // Returns any OCSP URIs from the AuthorityInfoAccess extension. + const std::vector<base::StringPiece>& 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 absl::optional<ParsedAuthorityKeyIdentifier>& authority_key_identifier() + const { + return authority_key_identifier_; + } + + // Returns the SubjectKeyIdentifier extension, or nullopt if there wasn't + // one. + const absl::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: + friend class base::RefCountedThreadSafe<ParsedCertificate>; + ParsedCertificate(); + ~ParsedCertificate(); + + // 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. + // + // TODO(crbug.com/1321688): This class requires that we recognize the + // signature algorithm, but there are some self-signed root certificates with + // weak signature algorithms like MD2. We never verify those signatures, but + // this means we must include MD2, etc., in the `SignatureAlgorithm` enum. + // Instead, make this an `absl::optional<SignatureAlgorithm>` and make the + // call sites handle recognized and unrecognized algorithms. + 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<base::StringPiece> ca_issuers_uris_; + std::vector<base::StringPiece> ocsp_uris_; + + // Policies extension. + 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. + absl::optional<ParsedAuthorityKeyIdentifier> authority_key_identifier_; + + // SubjectKeyIdentifier extension. + absl::optional<der::Input> subject_key_identifier_; + + // All of the extensions. + ExtensionsMap extensions_; +}; + +} // namespace net + +#endif // NET_CERT_PKI_PARSED_CERTIFICATE_H_ diff --git a/chromium/net/cert/pki/parsed_certificate_unittest.cc b/chromium/net/cert/pki/parsed_certificate_unittest.cc new file mode 100644 index 00000000000..b33520910b3 --- /dev/null +++ b/chromium/net/cert/pki/parsed_certificate_unittest.cc @@ -0,0 +1,586 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/parsed_certificate.h" + +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/parse_certificate.h" +#include "net/cert/pki/test_helpers.h" +#include "net/der/input.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +// TODO(eroman): Add tests for parsing of policy mappings. + +namespace net { + +namespace { + +std::string GetFilePath(const std::string& file_name) { + return std::string("net/data/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. +scoped_refptr<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; + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + ASSERT_FALSE(ParseCertificateFromFile("bad_signature_algorithm_oid.pem", {})); +} + +// 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) { + ASSERT_FALSE(ParseCertificateFromFile("failed_signature_algorithm.pem", {})); +} + +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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<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) { + scoped_refptr<ParsedCertificate> cert = + ParseCertificateFromFile("serial_not_number.pem", {}); + ASSERT_FALSE(cert); +} + +// Tests a serial number which uses a non-minimal INTEGER encoding +TEST(ParsedCertificateTest, SerialNotMinimal) { + scoped_refptr<ParsedCertificate> cert = + ParseCertificateFromFile("serial_not_minimal.pem", {}); + ASSERT_FALSE(cert); +} + +// Tests parsing a certificate that has an inhibitAnyPolicy extension. +TEST(ParsedCertificateTest, InhibitAnyPolicy) { + scoped_refptr<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) { + scoped_refptr<ParsedCertificate> cert = ParseCertificateFromFile( + "subject_key_identifier_not_octet_string.pem", {}); + ASSERT_FALSE(cert); +} + +// Tests an authorityKeyIdentifier that is not a SEQUENCE. +TEST(ParsedCertificateTest, AuthourityKeyIdentifierNotSequence) { + scoped_refptr<ParsedCertificate> cert = + ParseCertificateFromFile("authority_key_identifier_not_sequence.pem", {}); + ASSERT_FALSE(cert); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/path_builder.cc b/chromium/net/cert/pki/path_builder.cc new file mode 100644 index 00000000000..cdb9ede48dd --- /dev/null +++ b/chromium/net/cert/pki/path_builder.cc @@ -0,0 +1,851 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/path_builder.h" + +#include <memory> +#include <set> +#include <unordered_set> + +#include "base/logging.h" +#include "base/memory/raw_ptr.h" +#include "base/metrics/histogram_functions.h" +#include "base/notreached.h" +#include "base/strings/string_number_conversions.h" +#include "crypto/sha2.h" +#include "net/base/net_errors.h" +#include "net/cert/pki/cert_issuer_source.h" +#include "net/cert/pki/certificate_policies.h" +#include "net/cert/pki/common_cert_errors.h" +#include "net/cert/pki/parse_certificate.h" +#include "net/cert/pki/parse_name.h" // For CertDebugString. +#include "net/cert/pki/trust_store.h" +#include "net/cert/pki/verify_certificate_chain.h" +#include "net/cert/pki/verify_name_match.h" +#include "net/der/parser.h" +#include "net/der/tag.h" + +namespace net { + +namespace { + +using CertIssuerSources = std::vector<CertIssuerSource*>; + +// Returns a hex-encoded sha256 of the DER-encoding of |cert|. +std::string FingerPrintParsedCertificate(const net::ParsedCertificate* cert) { + std::string hash = crypto::SHA256HashString(cert->der_cert().AsStringPiece()); + return base::HexEncode(hash.data(), hash.size()); +} + +// TODO(mattm): decide how much debug logging to keep. +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; +} + +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; +} + +void RecordIterationCountHistogram(uint32_t iteration_count) { + base::UmaHistogramCounts10000("Net.CertVerifier.PathBuilderIterationCount", + iteration_count); +} + +// This structure describes a certificate and its trust level. Note that |cert| +// may be null to indicate an "empty" entry. +struct IssuerEntry { + scoped_refptr<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_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + switch (key_id_match) { + case kMatch: + return kTrustedAndKeyIdMatch; + case kNoData: + return kTrustedAndKeyIdNoData; + case kMismatch: + return kTrustedAndKeyIdMismatch; + } + case CertificateTrustType::UNSPECIFIED: + switch (key_id_match) { + case kMatch: + return kKeyIdMatch; + case kNoData: + return kKeyIdNoData; + case kMismatch: + return kKeyIdMismatch; + } + case CertificateTrustType::DISTRUSTED: + switch (key_id_match) { + case kMatch: + return kDistrustedAndKeyIdMatch; + case kNoData: + return kDistrustedAndKeyIdNoData; + case kMismatch: + return kDistrustedAndKeyIdMismatch; + } + } +} + +// 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(scoped_refptr<ParsedCertificate> cert, + CertIssuerSources* cert_issuer_sources, + const TrustStore* trust_store, + base::SupportsUserData* 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(); } + scoped_refptr<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(); + + scoped_refptr<ParsedCertificate> cert_; + raw_ptr<CertIssuerSources> cert_issuer_sources_; + raw_ptr<const 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<base::StringPiece, base::StringPieceHash> 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_; + + raw_ptr<base::SupportsUserData> debug_data_; +}; + +CertIssuersIter::CertIssuersIter(scoped_refptr<ParsedCertificate> in_cert, + CertIssuerSources* cert_issuer_sources, + const TrustStore* trust_store, + base::SupportsUserData* debug_data) + : cert_(in_cert), + cert_issuer_sources_(cert_issuer_sources), + trust_store_(trust_store), + debug_data_(debug_data) { + DVLOG(2) << "CertIssuersIter created for " << CertDebugString(cert()); +} + +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); + 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(); + + DVLOG(2) << "CertIssuersIter returning issuer " << cur_issuer_ << " of " + << issuers_.size() << " for " << CertDebugString(cert()); + // 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; + } + + DVLOG(2) << "CertIssuersIter reached the end of all available issuers for " + << CertDebugString(cert()); + // Reached the end of all available issuers. + *out = IssuerEntry(); +} + +void CertIssuersIter::AddIssuers(ParsedCertificateList new_issuers) { + for (scoped_refptr<ParsedCertificate>& issuer : new_issuers) { + if (present_issuers_.find(issuer->der_cert().AsStringPiece()) != + present_issuers_.end()) + continue; + present_issuers_.insert(issuer->der_cert().AsStringPiece()); + + // 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) { + DVLOG(1) << "AsyncGetIssuersOf pending for " << CertDebugString(cert()); + 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<base::StringPiece, base::StringPiece, base::StringPiece>; + + 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().AsStringPiece(), + cert->subject_alt_names_extension().value.AsStringPiece(), + cert->tbs().spki_tlv.AsStringPiece()); + } + + 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_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + return certs.back().get(); + case CertificateTrustType::UNSPECIFIED: + case CertificateTrustType::DISTRUSTED: + return nullptr; + } + + 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(scoped_refptr<ParsedCertificate> cert, + const TrustStore* trust_store, + base::SupportsUserData* 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, + const base::TimeTicks deadline, + 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. + raw_ptr<const TrustStore> trust_store_; + + raw_ptr<base::SupportsUserData> debug_data_; +}; + +CertPathIter::CertPathIter(scoped_refptr<ParsedCertificate> cert, + const TrustStore* trust_store, + base::SupportsUserData* 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, + const base::TimeTicks deadline, + 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 (!deadline.is_null() && base::TimeTicks::Now() > deadline) { + 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); + DVLOG(1) << "CertPathIter reached depth limit. Returning partial path " + "and backtracking:\n" + << PathDebugString(*out_certs); + cur_path_.Pop(); + return true; + } + + if (!next_issuer_.cert) { + if (cur_path_.Empty()) { + DVLOG(1) << "CertPathIter exhausted all paths..."; + 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); + DVLOG(1) << "CertPathIter returning partial path and backtracking:\n" + << PathDebugString(*out_certs); + 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. + DVLOG(1) << "CertPathIter backtracking..."; + cur_path_.Pop(); + continue; + } + } + } + + // If the cert is trusted but is the leaf, 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). + switch (next_issuer_.trust.type) { + case CertificateTrustType::TRUSTED_ANCHOR: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + if (cur_path_.Empty()) { + DVLOG(1) << "Leaf is a trust anchor, considering as UNSPECIFIED"; + next_issuer_.trust = CertificateTrust::ForUnspecified(); + } + break; + 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_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: { + // If the issuer has a known trust level, can stop building the path. + DVLOG(2) << "CertPathIter got anchor: " + << CertDebugString(next_issuer_.cert.get()); + cur_path_.CopyPath(out_certs); + out_certs->push_back(std::move(next_issuer_.cert)); + DVLOG(1) << "CertPathIter returning path:\n" + << PathDebugString(*out_certs); + *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(); + DVLOG(1) << "CertPathIter skipping dupe cert: " + << CertDebugString(next_issuer_.cert.get()); + 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(); + DVLOG(1) << "CertPathIter cur_path_ =\n" << cur_path_.PathDebugString(); + // 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( + scoped_refptr<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::SetDeadline(base::TimeTicks deadline) { + deadline_ = deadline; +} + +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, deadline_, &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; + RecordIterationCountHistogram(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); + } + + DVLOG(1) << "CertPathBuilder VerifyCertificateChain errors:\n" + << result_path->errors.ToDebugString(result_path->certs); + + // 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; + RecordIterationCountHistogram(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 diff --git a/chromium/net/cert/pki/path_builder.h b/chromium/net/cert/pki/path_builder.h new file mode 100644 index 00000000000..c4bd8a72581 --- /dev/null +++ b/chromium/net/cert/pki/path_builder.h @@ -0,0 +1,247 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_PATH_BUILDER_H_ +#define NET_CERT_PKI_PATH_BUILDER_H_ + +#include <memory> +#include <vector> + +#include "base/memory/raw_ptr.h" +#include "base/supports_user_data.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/trust_store.h" +#include "net/cert/pki/verify_certificate_chain.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" + +namespace net { + +namespace der { +struct GeneralizedTime; +} + +class CertPathBuilder; +class CertPathIter; +class CertIssuerSource; + +// Base class for custom data that CertPathBuilderDelegate can attach to paths. +class NET_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 NET_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 NET_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; +}; + +// 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 NET_EXPORT CertPathBuilder { + public: + // Provides the overall result of path building. This includes the paths that + // were attempted. + struct NET_EXPORT Result : public base::SupportsUserData { + Result(); + Result(Result&&); + + Result(const Result&) = delete; + Result& operator=(const Result&) = delete; + + ~Result() override; + 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 it exceeded the deadline configured + // with |SetDeadline|. + 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(scoped_refptr<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 deadline for completing path building. If |deadline| has passed and + // path building has not completed, path building will stop. Note that this + // is not a hard limit, there is no guarantee how far past |deadline| time + // will be when path building is aborted. + void SetDeadline(base::TimeTicks deadline); + + // 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); + + // Returns the deadline for path building, if any. If no deadline is set, + // |deadline().is_null()| will be true. + base::TimeTicks deadline() const { return deadline_; } + + // 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_; + raw_ptr<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; + base::TimeTicks deadline_; + uint32_t max_path_building_depth_ = 0; + bool explore_all_paths_ = false; +}; + +} // namespace net + +#endif // NET_CERT_PKI_PATH_BUILDER_H_ diff --git a/chromium/net/cert/pki/path_builder_pkits_unittest.cc b/chromium/net/cert/pki/path_builder_pkits_unittest.cc new file mode 100644 index 00000000000..e082f7d55fc --- /dev/null +++ b/chromium/net/cert/pki/path_builder_pkits_unittest.cc @@ -0,0 +1,300 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/path_builder.h" + +#include "base/time/time.h" +#include "net/base/net_errors.h" +#include "net/cert/pki/cert_issuer_source_static.h" +#include "net/cert/pki/common_cert_errors.h" +#include "net/cert/pki/crl.h" +#include "net/cert/pki/parse_certificate.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/simple_path_builder_delegate.h" +#include "net/cert/pki/trust_store_in_memory.h" +#include "net/cert/pki/verify_certificate_chain.h" +#include "net/der/encode_values.h" +#include "net/der/input.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +#include "net/cert/pki/nist_pkits_unittest.h" + +namespace net { + +namespace { + +class CrlCheckingPathBuilderDelegate : public SimplePathBuilderDelegate { + public: + CrlCheckingPathBuilderDelegate(const std::vector<std::string>& der_crls, + const base::Time& verify_time, + const base::TimeDelta& 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_; + const base::Time verify_time_; + const base::TimeDelta 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]); + + scoped_refptr<ParsedCertificate> target_cert(certs.back()); + + base::Time verify_time; + ASSERT_TRUE(der::GeneralizedTimeToTime(info.time, &verify_time)); + CrlCheckingPathBuilderDelegate path_builder_delegate( + crl_ders, verify_time, /*max_age=*/base::Days(365 * 2), 1024, + SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + + base::StringPiece 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) { + const net::CertPathBuilderResultPath* result_path = + result.paths[i].get(); + LOG(ERROR) << "path " << i << " errors:\n" + << result_path->errors.ToDebugString(result_path->certs); + } + } + + 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 diff --git a/chromium/net/cert/pki/path_builder_unittest.cc b/chromium/net/cert/pki/path_builder_unittest.cc new file mode 100644 index 00000000000..80c5baa5eae --- /dev/null +++ b/chromium/net/cert/pki/path_builder_unittest.cc @@ -0,0 +1,2555 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/path_builder.h" + +#include "base/base_paths.h" +#include "base/callback_forward.h" +#include "base/containers/span.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/test/bind.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/task_environment.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "net/cert/pem.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_issuer_source_static.h" +#include "net/cert/pki/common_cert_errors.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/simple_path_builder_delegate.h" +#include "net/cert/pki/test_helpers.h" +#include "net/cert/pki/trust_store_collection.h" +#include "net/cert/pki/trust_store_in_memory.h" +#include "net/cert/pki/verify_certificate_chain.h" +#include "net/der/input.h" +#include "net/net_buildflags.h" +#include "net/test/test_certificate_data.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +#if BUILDFLAG(IS_WIN) +#include "base/win/wincrypt_shim.h" +#include "crypto/scoped_capi_types.h" +#include "net/cert/internal/trust_store_win.h" +#endif // BUILDFLAG(IS_WIN) + +namespace net { + +// TODO(crbug.com/634443): Assert the errors for each ResultPath. + +namespace { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +// AsyncCertIssuerSourceStatic always returns its certs asynchronously. +class AsyncCertIssuerSourceStatic : public CertIssuerSource { + public: + class StaticAsyncRequest : public Request { + public: + explicit StaticAsyncRequest(ParsedCertificateList&& issuers) { + issuers_.swap(issuers); + issuers_iter_ = issuers_.begin(); + } + + StaticAsyncRequest(const StaticAsyncRequest&) = delete; + StaticAsyncRequest& operator=(const StaticAsyncRequest&) = delete; + + ~StaticAsyncRequest() override = default; + + void GetNext(ParsedCertificateList* out_certs) override { + if (issuers_iter_ != issuers_.end()) + out_certs->push_back(std::move(*issuers_iter_++)); + } + + ParsedCertificateList issuers_; + ParsedCertificateList::iterator issuers_iter_; + }; + + ~AsyncCertIssuerSourceStatic() override = default; + + void SetAsyncGetCallback(base::RepeatingClosure closure) { + async_get_callback_ = std::move(closure); + } + + void AddCert(scoped_refptr<ParsedCertificate> cert) { + static_cert_issuer_source_.AddCert(std::move(cert)); + } + + void SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) override {} + void AsyncGetIssuersOf(const ParsedCertificate* cert, + std::unique_ptr<Request>* out_req) override { + num_async_gets_++; + ParsedCertificateList issuers; + static_cert_issuer_source_.SyncGetIssuersOf(cert, &issuers); + auto req = std::make_unique<StaticAsyncRequest>(std::move(issuers)); + *out_req = std::move(req); + if (!async_get_callback_.is_null()) + async_get_callback_.Run(); + } + int num_async_gets() const { return num_async_gets_; } + + private: + CertIssuerSourceStatic static_cert_issuer_source_; + + int num_async_gets_ = 0; + base::RepeatingClosure async_get_callback_; +}; + +::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, + scoped_refptr<ParsedCertificate>* result) { + std::string der; + ::testing::AssertionResult r = ReadTestPem( + "net/data/ssl/certificates/" + 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() + << "ParseCertificate::Create() failed:\n" + << errors.ToDebugString(); + } + return ::testing::AssertionSuccess(); +} + +const void* kKey = &kKey; +class TrustStoreThatStoresUserData : public TrustStore { + public: + class Data : public base::SupportsUserData::Data { + public: + explicit Data(int value) : value(value) {} + + int value = 0; + }; + + // TrustStore implementation: + void SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) override {} + CertificateTrust GetTrust(const ParsedCertificate* cert, + base::SupportsUserData* debug_data) const override { + debug_data->SetUserData(kKey, std::make_unique<Data>(1234)); + return CertificateTrust::ForUnspecified(); + } +}; + +TEST(PathBuilderResultUserDataTest, ModifyUserDataInConstructor) { + scoped_refptr<ParsedCertificate> a_by_b; + ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b)); + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; + TrustStoreThatStoresUserData trust_store; + + // |trust_store| will unconditionally store user data in the + // CertPathBuilder::Result. This ensures that the Result object has been + // initialized before the first GetTrust call occurs (otherwise the test will + // crash or fail on ASAN bots). + CertPathBuilder path_builder( + a_by_b, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, + InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, + InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); + CertPathBuilder::Result result = path_builder.Run(); + auto* data = static_cast<TrustStoreThatStoresUserData::Data*>( + result.GetUserData(kKey)); + ASSERT_TRUE(data); + EXPECT_EQ(1234, data->value); +} + +class PathBuilderMultiRootTest : public ::testing::Test { + public: + PathBuilderMultiRootTest() + : delegate_(1024, + SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {} + + void SetUp() override { + ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b_)); + ASSERT_TRUE(ReadTestCert("multi-root-B-by-C.pem", &b_by_c_)); + ASSERT_TRUE(ReadTestCert("multi-root-B-by-F.pem", &b_by_f_)); + ASSERT_TRUE(ReadTestCert("multi-root-C-by-D.pem", &c_by_d_)); + ASSERT_TRUE(ReadTestCert("multi-root-C-by-E.pem", &c_by_e_)); + ASSERT_TRUE(ReadTestCert("multi-root-D-by-D.pem", &d_by_d_)); + ASSERT_TRUE(ReadTestCert("multi-root-E-by-E.pem", &e_by_e_)); + ASSERT_TRUE(ReadTestCert("multi-root-F-by-E.pem", &f_by_e_)); + } + + protected: + scoped_refptr<ParsedCertificate> a_by_b_, b_by_c_, b_by_f_, c_by_d_, c_by_e_, + d_by_d_, e_by_e_, f_by_e_; + + SimplePathBuilderDelegate delegate_; + der::GeneralizedTime time_ = {2017, 3, 1, 0, 0, 0}; + + const InitialExplicitPolicy initial_explicit_policy_ = + InitialExplicitPolicy::kFalse; + const std::set<der::Input> user_initial_policy_set_ = { + der::Input(kAnyPolicyOid)}; + const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_ = + InitialPolicyMappingInhibit::kFalse; + const InitialAnyPolicyInhibit initial_any_policy_inhibit_ = + InitialAnyPolicyInhibit::kFalse; +}; + +// Tests when the target cert has the same name and key as a trust anchor, +// however is signed by a different trust anchor. This should successfully build +// a path, however the trust anchor will be the signer of this cert. +// +// (This test is very similar to TestEndEntityHasSameNameAndSpkiAsTrustAnchor +// but with different data; also in this test the target cert itself is in the +// trust store). +TEST_F(PathBuilderMultiRootTest, TargetHasNameAndSpkiOfTrustAnchor) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(a_by_b_); + trust_store.AddTrustAnchor(b_by_f_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(2U, path.certs.size()); + EXPECT_EQ(a_by_b_, path.certs[0]); + EXPECT_EQ(b_by_f_, path.certs[1]); +} + +// If the target cert is has the same name and key as a trust anchor, however +// is NOT itself signed by a trust anchor, it fails. Although the provided SPKI +// is trusted, the certificate contents cannot be verified. +TEST_F(PathBuilderMultiRootTest, TargetWithSameNameAsTrustAnchorFails) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(a_by_b_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + EXPECT_EQ(1U, result.max_depth_seen); +} + +// Test a failed path building when the trust anchor is provided as a +// supplemental certificate. Conceptually the following paths could be built: +// +// B(C) <- C(D) <- [Trust anchor D] +// B(C) <- C(D) <- D(D) <- [Trust anchor D] +// +// However the second one is extraneous given the shorter path. +TEST_F(PathBuilderMultiRootTest, SelfSignedTrustAnchorSupplementalCert) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + + // The (extraneous) trust anchor D(D) is supplied as a certificate, as is the + // intermediate needed for path building C(D). + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(d_by_d_); + sync_certs.AddCert(c_by_d_); + + // C(D) is not valid at this time, so path building will fail. + der::GeneralizedTime expired_time = {2016, 1, 1, 0, 0, 0}; + + CertPathBuilder path_builder( + b_by_c_, &trust_store, &delegate_, expired_time, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + ASSERT_EQ(1U, result.paths.size()); + + EXPECT_FALSE(result.paths[0]->IsValid()); + const auto& path0 = *result.paths[0]; + ASSERT_EQ(3U, path0.certs.size()); + EXPECT_EQ(b_by_c_, path0.certs[0]); + EXPECT_EQ(c_by_d_, path0.certs[1]); + EXPECT_EQ(d_by_d_, path0.certs[2]); +} + +// Test verifying a certificate that is a trust anchor. +TEST_F(PathBuilderMultiRootTest, TargetIsSelfSignedTrustAnchor) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(e_by_e_); + // This is not necessary for the test, just an extra... + trust_store.AddTrustAnchor(f_by_e_); + + CertPathBuilder path_builder( + e_by_e_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + + // Verifying a trusted leaf certificate is not permitted, however this + // certificate is self-signed, and can chain to itself. + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(2U, path.certs.size()); + EXPECT_EQ(e_by_e_, path.certs[0]); + EXPECT_EQ(e_by_e_, path.certs[1]); +} + +// If the target cert is directly issued by a trust anchor, it should verify +// without any intermediate certs being provided. +TEST_F(PathBuilderMultiRootTest, TargetDirectlySignedByTrustAnchor) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(b_by_f_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(2U, path.certs.size()); + EXPECT_EQ(a_by_b_, path.certs[0]); + EXPECT_EQ(b_by_f_, path.certs[1]); +} + +// Test that async cert queries are not made if the path can be successfully +// built with synchronously available certs. +TEST_F(PathBuilderMultiRootTest, TriesSyncFirst) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(e_by_e_); + + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_f_); + sync_certs.AddCert(f_by_e_); + + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(b_by_c_); + async_certs.AddCert(c_by_e_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&async_certs); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + EXPECT_EQ(0, async_certs.num_async_gets()); +} + +// If async queries are needed, all async sources will be queried +// simultaneously. +TEST_F(PathBuilderMultiRootTest, TestAsyncSimultaneous) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(e_by_e_); + + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + sync_certs.AddCert(b_by_f_); + + AsyncCertIssuerSourceStatic async_certs1; + async_certs1.AddCert(c_by_e_); + + AsyncCertIssuerSourceStatic async_certs2; + async_certs2.AddCert(f_by_e_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&async_certs1); + path_builder.AddCertIssuerSource(&async_certs2); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + EXPECT_EQ(1, async_certs1.num_async_gets()); + EXPECT_EQ(1, async_certs2.num_async_gets()); +} + +// Test that PathBuilder does not generate longer paths than necessary if one of +// the supplied certs is itself a trust anchor. +TEST_F(PathBuilderMultiRootTest, TestLongChain) { + // Both D(D) and C(D) are trusted roots. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + trust_store.AddTrustAnchor(c_by_d_); + + // Certs B(C), and C(D) are all supplied. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + sync_certs.AddCert(c_by_d_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + + // The result path should be A(B) <- B(C) <- C(D) + // not the longer but also valid A(B) <- B(C) <- C(D) <- D(D) + EXPECT_EQ(3U, result.GetBestValidPath()->certs.size()); +} + +// Test that PathBuilder will backtrack and try a different path if the first +// one doesn't work out. +TEST_F(PathBuilderMultiRootTest, TestBacktracking) { + // Only D(D) is a trusted root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + + // Certs B(F) and F(E) are supplied synchronously, thus the path + // A(B) <- B(F) <- F(E) should be built first, though it won't verify. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_f_); + sync_certs.AddCert(f_by_e_); + + // Certs B(C), and C(D) are supplied asynchronously, so the path + // A(B) <- B(C) <- C(D) <- D(D) should be tried second. + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(b_by_c_); + async_certs.AddCert(c_by_d_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + path_builder.AddCertIssuerSource(&async_certs); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + + // The partial path should be returned even though it didn't reach a trust + // anchor. + ASSERT_EQ(2U, result.paths.size()); + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); + EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); + + // The result path should be A(B) <- B(C) <- C(D) <- D(D) + EXPECT_EQ(1U, result.best_result_index); + EXPECT_TRUE(result.paths[1]->IsValid()); + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(4U, path.certs.size()); + EXPECT_EQ(a_by_b_, path.certs[0]); + EXPECT_EQ(b_by_c_, path.certs[1]); + EXPECT_EQ(c_by_d_, path.certs[2]); + EXPECT_EQ(d_by_d_, path.certs[3]); +} + +// Test that if no path to a trust anchor was found, the partial path is +// returned. +TEST_F(PathBuilderMultiRootTest, TestOnlyPartialPathResult) { + TrustStoreInMemory trust_store; + + // Certs B(F) and F(E) are supplied synchronously, thus the path + // A(B) <- B(F) <- F(E) should be built first, though it won't verify. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_f_); + sync_certs.AddCert(f_by_e_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + + // The partial path should be returned even though it didn't reach a trust + // anchor. + ASSERT_EQ(1U, result.paths.size()); + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); + EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); +} + +// Test that if two partial paths are returned, the first is marked as the best +// path. +TEST_F(PathBuilderMultiRootTest, TestTwoPartialPathResults) { + TrustStoreInMemory trust_store; + + // Certs B(F) and F(E) are supplied synchronously, thus the path + // A(B) <- B(F) <- F(E) should be built first, though it won't verify. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_f_); + sync_certs.AddCert(f_by_e_); + + // Certs B(C), and C(D) are supplied asynchronously, so the path + // A(B) <- B(C) <- C(D) <- D(D) should be tried second. + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(b_by_c_); + async_certs.AddCert(c_by_d_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + path_builder.AddCertIssuerSource(&async_certs); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + + // First partial path found should be marked as the best one. + EXPECT_EQ(0U, result.best_result_index); + + ASSERT_EQ(2U, result.paths.size()); + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); + EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); + + EXPECT_FALSE(result.paths[1]->IsValid()); + ASSERT_EQ(3U, result.paths[1]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[1]->certs[0]); + EXPECT_EQ(b_by_c_, result.paths[1]->certs[1]); + EXPECT_EQ(c_by_d_, result.paths[1]->certs[2]); +} + +// Test that if no valid path is found, and the first invalid path is a partial +// path, but the 2nd invalid path ends with a cert with a trust record, the 2nd +// path should be preferred. +TEST_F(PathBuilderMultiRootTest, TestDistrustedPathPreferredOverPartialPath) { + // Only D(D) has a trust record, but it is distrusted. + TrustStoreInMemory trust_store; + trust_store.AddDistrustedCertificateForTest(d_by_d_); + + // Certs B(F) and F(E) are supplied synchronously, thus the path + // A(B) <- B(F) <- F(E) should be built first, though it won't verify. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_f_); + sync_certs.AddCert(f_by_e_); + + // Certs B(C), and C(D) are supplied asynchronously, so the path + // A(B) <- B(C) <- C(D) <- D(D) should be tried second. + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(b_by_c_); + async_certs.AddCert(c_by_d_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + path_builder.AddCertIssuerSource(&async_certs); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + + // The partial path should be returned even though it didn't reach a trust + // anchor. + ASSERT_EQ(2U, result.paths.size()); + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); + EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); + + // The result path should be A(B) <- B(C) <- C(D) <- D(D) + EXPECT_EQ(1U, result.best_result_index); + EXPECT_FALSE(result.paths[1]->IsValid()); + const auto& path = *result.GetBestPathPossiblyInvalid(); + ASSERT_EQ(4U, path.certs.size()); + EXPECT_EQ(a_by_b_, path.certs[0]); + EXPECT_EQ(b_by_c_, path.certs[1]); + EXPECT_EQ(c_by_d_, path.certs[2]); + EXPECT_EQ(d_by_d_, path.certs[3]); +} + +// Test that whichever order CertIssuerSource returns the issuers, the path +// building still succeeds. +TEST_F(PathBuilderMultiRootTest, TestCertIssuerOrdering) { + // Only D(D) is a trusted root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + + for (bool reverse_order : {false, true}) { + SCOPED_TRACE(reverse_order); + std::vector<scoped_refptr<ParsedCertificate>> certs = { + b_by_c_, b_by_f_, f_by_e_, c_by_d_, c_by_e_}; + CertIssuerSourceStatic sync_certs; + if (reverse_order) { + for (auto it = certs.rbegin(); it != certs.rend(); ++it) + sync_certs.AddCert(*it); + } else { + for (const auto& cert : certs) + sync_certs.AddCert(cert); + } + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + + // The result path should be A(B) <- B(C) <- C(D) <- D(D) + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(4U, path.certs.size()); + EXPECT_EQ(a_by_b_, path.certs[0]); + EXPECT_EQ(b_by_c_, path.certs[1]); + EXPECT_EQ(c_by_d_, path.certs[2]); + EXPECT_EQ(d_by_d_, path.certs[3]); + } +} + +TEST_F(PathBuilderMultiRootTest, TestIterationLimit) { + // D(D) is the trust root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + + // Certs B(C) and C(D) are supplied. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + sync_certs.AddCert(c_by_d_); + + for (const bool insufficient_limit : {true, false}) { + SCOPED_TRACE(insufficient_limit); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + if (insufficient_limit) { + // A limit of one is insufficient to build a path in this case. Therefore + // building is expected to fail in this case. + path_builder.SetIterationLimit(1); + } else { + // The other tests in this file exercise the case that |SetIterationLimit| + // isn't called. Therefore set a sufficient limit for the path to be + // found. + path_builder.SetIterationLimit(5); + } + + base::HistogramTester histogram_tester; + auto result = path_builder.Run(); + + EXPECT_EQ(!insufficient_limit, result.HasValidPath()); + EXPECT_EQ(insufficient_limit, result.exceeded_iteration_limit); + + if (insufficient_limit) { + EXPECT_EQ(2U, result.iteration_count); + EXPECT_THAT(histogram_tester.GetAllSamples( + "Net.CertVerifier.PathBuilderIterationCount"), + ElementsAre(base::Bucket(/*sample=*/2, /*count=*/1))); + } else { + EXPECT_EQ(3U, result.iteration_count); + EXPECT_THAT(histogram_tester.GetAllSamples( + "Net.CertVerifier.PathBuilderIterationCount"), + ElementsAre(base::Bucket(/*sample=*/3, /*count=*/1))); + } + } +} + +TEST_F(PathBuilderMultiRootTest, TestTrivialDeadline) { + // C(D) is the trust root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(c_by_d_); + + // Cert B(C) is supplied. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + + for (const bool insufficient_limit : {true, false}) { + SCOPED_TRACE(insufficient_limit); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + base::TimeTicks deadline; + if (insufficient_limit) { + // Set a deadline one millisecond in the past. Path building should fail + // since the deadline is already past. + deadline = base::TimeTicks::Now() - base::Milliseconds(1); + } else { + // The other tests in this file exercise the case that |SetDeadline| + // isn't called. Therefore set a sufficient limit for the path to be + // found. + deadline = base::TimeTicks::Now() + base::Days(1); + } + path_builder.SetDeadline(deadline); + + auto result = path_builder.Run(); + + EXPECT_EQ(!insufficient_limit, result.HasValidPath()); + EXPECT_EQ(insufficient_limit, result.exceeded_deadline); + EXPECT_EQ(deadline, path_builder.deadline()); + + if (insufficient_limit) { + ASSERT_EQ(1U, result.paths.size()); + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(1U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_TRUE(result.paths[0]->errors.ContainsError( + cert_errors::kDeadlineExceeded)); + } else { + ASSERT_EQ(1U, result.paths.size()); + EXPECT_TRUE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_EQ(b_by_c_, result.paths[0]->certs[1]); + EXPECT_EQ(c_by_d_, result.paths[0]->certs[2]); + } + } +} + +TEST_F(PathBuilderMultiRootTest, TestDeadline) { + base::test::TaskEnvironment task_environment{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + + // Cert B(C) is supplied statically. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + + // Cert C(D) is supplied asynchronously and will advance time before returning + // the async result. + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(c_by_d_); + async_certs.SetAsyncGetCallback(base::BindLambdaForTesting( + [&] { task_environment.FastForwardBy(base::Seconds(2)); })); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + path_builder.AddCertIssuerSource(&async_certs); + + base::TimeTicks deadline; + deadline = base::TimeTicks::Now() + base::Seconds(1); + path_builder.SetDeadline(deadline); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + EXPECT_TRUE(result.exceeded_deadline); + EXPECT_EQ(deadline, path_builder.deadline()); + + // The chain returned should end in c_by_d_, since the deadline would only be + // checked again after the async results had been checked (since + // AsyncCertIssuerSourceStatic makes the async results available immediately.) + ASSERT_EQ(1U, result.paths.size()); + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); + EXPECT_EQ(b_by_c_, result.paths[0]->certs[1]); + EXPECT_EQ(c_by_d_, result.paths[0]->certs[2]); + EXPECT_TRUE( + result.paths[0]->errors.ContainsError(cert_errors::kDeadlineExceeded)); +} + +TEST_F(PathBuilderMultiRootTest, TestDepthLimit) { + // D(D) is the trust root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(d_by_d_); + + // Certs B(C) and C(D) are supplied. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + sync_certs.AddCert(c_by_d_); + + for (const bool insufficient_limit : {true, false}) { + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + if (insufficient_limit) { + // A limit of depth equal to 2 is insufficient to build the path. + // Therefore, building is expected to fail. + path_builder.SetDepthLimit(2); + } else { + // The other tests in this file exercise the case that |SetDepthLimit| + // isn't called. Therefore, set a sufficient limit for the path to be + // found. + path_builder.SetDepthLimit(5); + } + + auto result = path_builder.Run(); + + EXPECT_EQ(!insufficient_limit, result.HasValidPath()); + EXPECT_EQ(insufficient_limit, + result.AnyPathContainsError(cert_errors::kDepthLimitExceeded)); + if (insufficient_limit) { + EXPECT_EQ(2U, result.max_depth_seen); + } else { + EXPECT_EQ(4U, result.max_depth_seen); + } + } +} + +TEST_F(PathBuilderMultiRootTest, TestDepthLimitMultiplePaths) { + // This case tests path building backracking due to reaching the path depth + // limit. Given the root and issuer certificates below, there can be two paths + // from between the leaf to a trusted root, one has length of 3 and the other + // has length of 4. These certificates are specifically chosen because path + // building will first explore the 4-certificate long path then the + // 3-certificate long path. So with a depth limit of 3, we can test the + // backtracking code path. + + // E(E) and C(D) are the trust roots. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(e_by_e_); + trust_store.AddTrustAnchor(c_by_d_); + + // Certs B(C). B(F) and F(E) are supplied. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(b_by_c_); + sync_certs.AddCert(b_by_f_); + sync_certs.AddCert(f_by_e_); + + CertPathBuilder path_builder( + a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + path_builder.SetDepthLimit(3); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + EXPECT_TRUE(result.AnyPathContainsError(cert_errors::kDepthLimitExceeded)); + + ASSERT_EQ(result.paths.size(), 2u); + + const CertPathBuilderResultPath* truncated_path = result.paths[0].get(); + EXPECT_FALSE(truncated_path->IsValid()); + EXPECT_TRUE( + truncated_path->errors.ContainsError(cert_errors::kDepthLimitExceeded)); + ASSERT_EQ(truncated_path->certs.size(), 3u); + EXPECT_EQ(a_by_b_, truncated_path->certs[0]); + EXPECT_EQ(b_by_f_, truncated_path->certs[1]); + EXPECT_EQ(f_by_e_, truncated_path->certs[2]); + + const CertPathBuilderResultPath* valid_path = result.paths[1].get(); + EXPECT_TRUE(valid_path->IsValid()); + EXPECT_FALSE( + valid_path->errors.ContainsError(cert_errors::kDepthLimitExceeded)); + ASSERT_EQ(valid_path->certs.size(), 3u); + EXPECT_EQ(a_by_b_, valid_path->certs[0]); + EXPECT_EQ(b_by_c_, valid_path->certs[1]); + EXPECT_EQ(c_by_d_, valid_path->certs[2]); +} + +#if BUILDFLAG(IS_WIN) && BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) + +void AddToStoreWithEKURestriction(HCERTSTORE store, + const scoped_refptr<ParsedCertificate>& cert, + LPCSTR usage_identifier) { + crypto::ScopedPCCERT_CONTEXT os_cert(CertCreateCertificateContext( + X509_ASN_ENCODING, cert->der_cert().UnsafeData(), + cert->der_cert().Length())); + + CERT_ENHKEY_USAGE usage; + memset(&usage, 0, sizeof(usage)); + CertSetEnhancedKeyUsage(os_cert.get(), &usage); + if (usage_identifier) { + CertAddEnhancedKeyUsageIdentifier(os_cert.get(), usage_identifier); + } + CertAddCertificateContextToStore(store, os_cert.get(), CERT_STORE_ADD_ALWAYS, + nullptr); +} + +bool AreCertsEq(const scoped_refptr<ParsedCertificate> cert_1, + const scoped_refptr<ParsedCertificate> cert_2) { + return cert_1 && cert_2 && cert_1->der_cert() == cert_2->der_cert(); +} + +// Test to ensure that path building stops when an intermediate cert is +// encountered that is not usable for TLS because of EKU restrictions. +TEST_F(PathBuilderMultiRootTest, TrustStoreWinOnlyFindTrustedTLSPath) { + crypto::ScopedHCERTSTORE root_store(CertOpenStore( + CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr)); + crypto::ScopedHCERTSTORE intermediate_store(CertOpenStore( + CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr)); + crypto::ScopedHCERTSTORE disallowed_store(CertOpenStore( + CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr)); + + AddToStoreWithEKURestriction(root_store.get(), d_by_d_, + szOID_PKIX_KP_SERVER_AUTH); + AddToStoreWithEKURestriction(root_store.get(), e_by_e_, + szOID_PKIX_KP_SERVER_AUTH); + AddToStoreWithEKURestriction(intermediate_store.get(), c_by_e_, + szOID_PKIX_KP_SERVER_AUTH); + AddToStoreWithEKURestriction(intermediate_store.get(), c_by_d_, nullptr); + + std::unique_ptr<TrustStoreWin> trust_store = TrustStoreWin::CreateForTesting( + std::move(root_store), std::move(intermediate_store), + std::move(disallowed_store)); + + CertPathBuilder path_builder( + b_by_c_, trust_store.get(), &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + // Check all paths. + path_builder.SetExploreAllPaths(true); + + auto result = path_builder.Run(); + ASSERT_TRUE(result.HasValidPath()); + ASSERT_EQ(2U, result.paths.size()); + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(3U, path.certs.size()); + EXPECT_TRUE(AreCertsEq(b_by_c_, path.certs[0])); + EXPECT_TRUE(AreCertsEq(c_by_e_, path.certs[1])); + EXPECT_TRUE(AreCertsEq(e_by_e_, path.certs[2])); + + // Should only be one valid path, the one above. + int valid_paths = 0; + for (auto&& path : result.paths) { + valid_paths += path->IsValid() ? 1 : 0; + } + ASSERT_EQ(1, valid_paths); +} + +// Test that if an intermediate is disabled for TLS, and it is the only +// path, then path building should fail, even if the root is enabled for +// TLS. +TEST_F(PathBuilderMultiRootTest, TrustStoreWinNoPathEKURestrictions) { + crypto::ScopedHCERTSTORE root_store(CertOpenStore( + CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr)); + crypto::ScopedHCERTSTORE intermediate_store(CertOpenStore( + CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr)); + crypto::ScopedHCERTSTORE disallowed_store(CertOpenStore( + CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr)); + + AddToStoreWithEKURestriction(root_store.get(), d_by_d_, + szOID_PKIX_KP_SERVER_AUTH); + AddToStoreWithEKURestriction(intermediate_store.get(), c_by_d_, nullptr); + std::unique_ptr<TrustStoreWin> trust_store = TrustStoreWin::CreateForTesting( + std::move(root_store), std::move(intermediate_store), + std::move(disallowed_store)); + + CertPathBuilder path_builder( + b_by_c_, trust_store.get(), &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + ASSERT_FALSE(result.HasValidPath()); +} +#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) + +class PathBuilderKeyRolloverTest : public ::testing::Test { + public: + PathBuilderKeyRolloverTest() + : delegate_(1024, + SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {} + + void SetUp() override { + ParsedCertificateList path; + + VerifyCertChainTest test; + ASSERT_TRUE(ReadVerifyCertChainTestFromFile( + "net/data/verify_certificate_chain_unittest/key-rollover/oldchain.test", + &test)); + path = test.chain; + ASSERT_EQ(3U, path.size()); + target_ = path[0]; + oldintermediate_ = path[1]; + oldroot_ = path[2]; + time_ = test.time; + + ASSERT_TRUE(target_); + ASSERT_TRUE(oldintermediate_); + + ASSERT_TRUE(ReadVerifyCertChainTestFromFile( + "net/data/verify_certificate_chain_unittest/" + "key-rollover/longrolloverchain.test", + &test)); + path = test.chain; + + ASSERT_EQ(5U, path.size()); + newintermediate_ = path[1]; + newroot_ = path[2]; + newrootrollover_ = path[3]; + ASSERT_TRUE(newintermediate_); + ASSERT_TRUE(newroot_); + ASSERT_TRUE(newrootrollover_); + } + + protected: + // oldroot-------->newrootrollover newroot + // | | | + // v v v + // oldintermediate newintermediate + // | | + // +------------+-------------+ + // | + // v + // target + scoped_refptr<ParsedCertificate> target_; + scoped_refptr<ParsedCertificate> oldintermediate_; + scoped_refptr<ParsedCertificate> newintermediate_; + scoped_refptr<ParsedCertificate> oldroot_; + scoped_refptr<ParsedCertificate> newroot_; + scoped_refptr<ParsedCertificate> newrootrollover_; + + SimplePathBuilderDelegate delegate_; + der::GeneralizedTime time_; + + const InitialExplicitPolicy initial_explicit_policy_ = + InitialExplicitPolicy::kFalse; + const std::set<der::Input> user_initial_policy_set_ = { + der::Input(kAnyPolicyOid)}; + const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_ = + InitialPolicyMappingInhibit::kFalse; + const InitialAnyPolicyInhibit initial_any_policy_inhibit_ = + InitialAnyPolicyInhibit::kFalse; +}; + +// Tests that if only the old root cert is trusted, the path builder can build a +// path through the new intermediate and rollover cert to the old root. +TEST_F(PathBuilderKeyRolloverTest, TestRolloverOnlyOldRootTrusted) { + // Only oldroot is trusted. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(oldroot_); + + // Old intermediate cert is not provided, so the pathbuilder will need to go + // through the rollover cert. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(newintermediate_); + sync_certs.AddCert(newrootrollover_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + + // Due to authorityKeyIdentifier prioritization, path builder will first + // attempt: target <- newintermediate <- newrootrollover <- oldroot + // which will succeed. + ASSERT_EQ(1U, result.paths.size()); + const auto& path0 = *result.paths[0]; + EXPECT_EQ(0U, result.best_result_index); + EXPECT_TRUE(path0.IsValid()); + ASSERT_EQ(4U, path0.certs.size()); + EXPECT_EQ(target_, path0.certs[0]); + EXPECT_EQ(newintermediate_, path0.certs[1]); + EXPECT_EQ(newrootrollover_, path0.certs[2]); + EXPECT_EQ(oldroot_, path0.certs[3]); +} + +// Tests that if both old and new roots are trusted it builds a path through +// the new intermediate. +TEST_F(PathBuilderKeyRolloverTest, TestRolloverBothRootsTrusted) { + // Both oldroot and newroot are trusted. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(oldroot_); + trust_store.AddTrustAnchor(newroot_); + + // Both old and new intermediates + rollover cert are provided. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(oldintermediate_); + sync_certs.AddCert(newintermediate_); + sync_certs.AddCert(newrootrollover_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + + ASSERT_EQ(1U, result.paths.size()); + const auto& path = *result.paths[0]; + EXPECT_TRUE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, path.certs.size()); + EXPECT_EQ(target_, path.certs[0]); + // The newer intermediate should be used as newer certs are prioritized in + // path building. + EXPECT_EQ(newintermediate_, path.certs[1]); + EXPECT_EQ(newroot_, path.certs[2]); +} + +// If trust anchor query returned no results, and there are no issuer +// sources, path building should fail at that point. +TEST_F(PathBuilderKeyRolloverTest, TestAnchorsNoMatchAndNoIssuerSources) { + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newroot_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + + ASSERT_EQ(1U, result.paths.size()); + const auto& path = *result.paths[0]; + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(1U, path.certs.size()); + EXPECT_EQ(target_, path.certs[0]); +} + +// If a path to a trust anchor could not be found, and the last issuer(s) in +// the chain were culled by the loop checker, the partial path up to that point +// should be returned. +TEST_F(PathBuilderKeyRolloverTest, TestReturnsPartialPathEndedByLoopChecker) { + TrustStoreInMemory trust_store; + + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(newintermediate_); + sync_certs.AddCert(newroot_); + sync_certs.AddCert(newrootrollover_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + ASSERT_EQ(2U, result.paths.size()); + + // Since none of the certs are trusted, the path builder should build 4 + // candidate paths, all of which are disallowed due to the loop checker: + // target->newintermediate->newroot->newroot + // target->newintermediate->newroot->newrootrollover + // target->newintermediate->newrootrollover->newroot + // target->newintermediate->newrootrollover->newrootrollover + // This should end up returning the 2 partial paths which are the longest + // paths for which no acceptable issuers could be found: + // target->newintermediate->newroot + // target->newintermediate->newrootrollover + + { + const auto& path = *result.paths[0]; + EXPECT_FALSE(path.IsValid()); + ASSERT_EQ(3U, path.certs.size()); + EXPECT_EQ(target_, path.certs[0]); + EXPECT_EQ(newintermediate_, path.certs[1]); + EXPECT_EQ(newroot_, path.certs[2]); + EXPECT_TRUE(path.errors.ContainsError(cert_errors::kNoIssuersFound)); + } + + { + const auto& path = *result.paths[1]; + EXPECT_FALSE(path.IsValid()); + ASSERT_EQ(3U, path.certs.size()); + EXPECT_EQ(target_, path.certs[0]); + EXPECT_EQ(newintermediate_, path.certs[1]); + EXPECT_EQ(newrootrollover_, path.certs[2]); + EXPECT_TRUE(path.errors.ContainsError(cert_errors::kNoIssuersFound)); + } +} + +// Tests that multiple trust root matches on a single path will be considered. +// Both roots have the same subject but different keys. Only one of them will +// verify. +TEST_F(PathBuilderKeyRolloverTest, TestMultipleRootMatchesOnlyOneWorks) { + TrustStoreCollection trust_store_collection; + TrustStoreInMemory trust_store1; + TrustStoreInMemory trust_store2; + trust_store_collection.AddTrustStore(&trust_store1); + trust_store_collection.AddTrustStore(&trust_store2); + // Add two trust anchors (newroot_ and oldroot_). Path building will attempt + // them in this same order, as trust_store1 was added to + // trust_store_collection first. + trust_store1.AddTrustAnchor(newroot_); + trust_store2.AddTrustAnchor(oldroot_); + + // Only oldintermediate is supplied, so the path with newroot should fail, + // oldroot should succeed. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(oldintermediate_); + + CertPathBuilder path_builder( + target_, &trust_store_collection, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + ASSERT_EQ(1U, result.paths.size()); + + // Due to authorityKeyIdentifier prioritization, path builder will first + // attempt: target <- old intermediate <- oldroot + // which should succeed. + EXPECT_TRUE(result.paths[result.best_result_index]->IsValid()); + const auto& path = *result.paths[result.best_result_index]; + ASSERT_EQ(3U, path.certs.size()); + EXPECT_EQ(target_, path.certs[0]); + EXPECT_EQ(oldintermediate_, path.certs[1]); + EXPECT_EQ(oldroot_, path.certs[2]); +} + +// Tests that the path builder doesn't build longer than necessary paths, +// by skipping certs where the same Name+SAN+SPKI is already in the current +// path. +TEST_F(PathBuilderKeyRolloverTest, TestRolloverLongChain) { + // Only oldroot is trusted. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(oldroot_); + + // New intermediate and new root are provided synchronously. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(newintermediate_); + sync_certs.AddCert(newroot_); + + // Rollover cert is only provided asynchronously. This will force the + // pathbuilder to first try building a longer than necessary path. + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(newrootrollover_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + path_builder.AddCertIssuerSource(&async_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + ASSERT_EQ(3U, result.paths.size()); + + // Path builder will first attempt: + // target <- newintermediate <- newroot <- oldroot + // but it will fail since newroot is self-signed. + EXPECT_FALSE(result.paths[0]->IsValid()); + const auto& path0 = *result.paths[0]; + ASSERT_EQ(4U, path0.certs.size()); + EXPECT_EQ(target_, path0.certs[0]); + EXPECT_EQ(newintermediate_, path0.certs[1]); + EXPECT_EQ(newroot_, path0.certs[2]); + EXPECT_EQ(oldroot_, path0.certs[3]); + + // Path builder will next attempt: target <- newintermediate <- oldroot + // but it will fail since newintermediate is signed by newroot. + EXPECT_FALSE(result.paths[1]->IsValid()); + const auto& path1 = *result.paths[1]; + ASSERT_EQ(3U, path1.certs.size()); + EXPECT_EQ(target_, path1.certs[0]); + EXPECT_EQ(newintermediate_, path1.certs[1]); + EXPECT_EQ(oldroot_, path1.certs[2]); + + // Path builder will skip: + // target <- newintermediate <- newroot <- newrootrollover <- ... + // Since newroot and newrootrollover have the same Name+SAN+SPKI. + + // Finally path builder will use: + // target <- newintermediate <- newrootrollover <- oldroot + EXPECT_EQ(2U, result.best_result_index); + EXPECT_TRUE(result.paths[2]->IsValid()); + const auto& path2 = *result.paths[2]; + ASSERT_EQ(4U, path2.certs.size()); + EXPECT_EQ(target_, path2.certs[0]); + EXPECT_EQ(newintermediate_, path2.certs[1]); + EXPECT_EQ(newrootrollover_, path2.certs[2]); + EXPECT_EQ(oldroot_, path2.certs[3]); +} + +// Tests that when SetExploreAllPaths is combined with SetIterationLimit the +// path builder will return all the paths that were able to be built before the +// iteration limit was reached. +TEST_F(PathBuilderKeyRolloverTest, ExploreAllPathsWithIterationLimit) { + struct Expectation { + int iteration_limit; + size_t expected_num_paths; + std::vector<scoped_refptr<ParsedCertificate>> partial_path; + } kExpectations[] = { + // No iteration limit. All possible paths should be built. + {0, 4, {}}, + // Limit 1 is only enough to reach the intermediate, no complete path + // should be built. + {1, 0, {target_, newintermediate_}}, + // Limit 2 allows reaching the root on the first path. + {2, 1, {target_, newintermediate_}}, + // Next iteration uses oldroot instead of newroot. + {3, 2, {target_, newintermediate_}}, + // Backtracking to the target cert. + {4, 2, {target_}}, + // Adding oldintermediate. + {5, 2, {target_, oldintermediate_}}, + // Trying oldroot. + {6, 3, {target_, oldintermediate_}}, + // Trying newroot. + {7, 4, {target_, oldintermediate_}}, + }; + + // Trust both old and new roots. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(oldroot_); + trust_store.AddTrustAnchor(newroot_); + + // Intermediates and root rollover are all provided synchronously. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(oldintermediate_); + sync_certs.AddCert(newintermediate_); + + for (const auto& expectation : kExpectations) { + SCOPED_TRACE(expectation.iteration_limit); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + // Explore all paths, rather than stopping at the first valid path. + path_builder.SetExploreAllPaths(true); + + // Limit the number of iterations. + path_builder.SetIterationLimit(expectation.iteration_limit); + + auto result = path_builder.Run(); + + EXPECT_EQ(expectation.expected_num_paths > 0, result.HasValidPath()); + if (expectation.partial_path.empty()) { + ASSERT_EQ(expectation.expected_num_paths, result.paths.size()); + } else { + ASSERT_EQ(1 + expectation.expected_num_paths, result.paths.size()); + const auto& path = *result.paths[result.paths.size() - 1]; + EXPECT_FALSE(path.IsValid()); + EXPECT_EQ(expectation.partial_path, path.certs); + EXPECT_TRUE( + path.errors.ContainsError(cert_errors::kIterationLimitExceeded)); + } + + if (expectation.expected_num_paths > 0) { + // Path builder will first build path: target <- newintermediate <- + // newroot + const auto& path0 = *result.paths[0]; + EXPECT_TRUE(path0.IsValid()); + ASSERT_EQ(3U, path0.certs.size()); + EXPECT_EQ(target_, path0.certs[0]); + EXPECT_EQ(newintermediate_, path0.certs[1]); + EXPECT_EQ(newroot_, path0.certs[2]); + EXPECT_EQ(3U, result.max_depth_seen); + } + + if (expectation.expected_num_paths > 1) { + // Next path: target <- newintermediate <- oldroot + const auto& path1 = *result.paths[1]; + EXPECT_FALSE(path1.IsValid()); + ASSERT_EQ(3U, path1.certs.size()); + EXPECT_EQ(target_, path1.certs[0]); + EXPECT_EQ(newintermediate_, path1.certs[1]); + EXPECT_EQ(oldroot_, path1.certs[2]); + EXPECT_EQ(3U, result.max_depth_seen); + } + + if (expectation.expected_num_paths > 2) { + // Next path: target <- oldintermediate <- oldroot + const auto& path2 = *result.paths[2]; + EXPECT_TRUE(path2.IsValid()); + ASSERT_EQ(3U, path2.certs.size()); + EXPECT_EQ(target_, path2.certs[0]); + EXPECT_EQ(oldintermediate_, path2.certs[1]); + EXPECT_EQ(oldroot_, path2.certs[2]); + EXPECT_EQ(3U, result.max_depth_seen); + } + + if (expectation.expected_num_paths > 3) { + // Final path: target <- oldintermediate <- newroot + const auto& path3 = *result.paths[3]; + EXPECT_FALSE(path3.IsValid()); + ASSERT_EQ(3U, path3.certs.size()); + EXPECT_EQ(target_, path3.certs[0]); + EXPECT_EQ(oldintermediate_, path3.certs[1]); + EXPECT_EQ(newroot_, path3.certs[2]); + EXPECT_EQ(3U, result.max_depth_seen); + } + } +} + +// If the target cert is a trust anchor, however is not itself *signed* by a +// trust anchor, then it is not considered valid (the SPKI and name of the +// trust anchor matches the SPKI and subject of the targe certificate, but the +// rest of the certificate cannot be verified). +TEST_F(PathBuilderKeyRolloverTest, TestEndEntityIsTrustRoot) { + // Trust newintermediate. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newintermediate_); + + // Newintermediate is also the target cert. + CertPathBuilder path_builder( + newintermediate_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); +} + +// If target has same Name+SAN+SPKI as a necessary intermediate, test if a path +// can still be built. +// Since LoopChecker will prevent the intermediate from being included, this +// currently does NOT verify. This case shouldn't occur in the web PKI. +TEST_F(PathBuilderKeyRolloverTest, + TestEndEntityHasSameNameAndSpkiAsIntermediate) { + // Trust oldroot. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(oldroot_); + + // New root rollover is provided synchronously. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(newrootrollover_); + + // Newroot is the target cert. + CertPathBuilder path_builder( + newroot_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + // This could actually be OK, but CertPathBuilder does not build the + // newroot <- newrootrollover <- oldroot path. + EXPECT_FALSE(result.HasValidPath()); +} + +// If target has same Name+SAN+SPKI as the trust root, test that a (trivial) +// path can still be built. +TEST_F(PathBuilderKeyRolloverTest, + TestEndEntityHasSameNameAndSpkiAsTrustAnchor) { + // Trust newrootrollover. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newrootrollover_); + + // Newroot is the target cert. + CertPathBuilder path_builder( + newroot_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + + auto result = path_builder.Run(); + + ASSERT_TRUE(result.HasValidPath()); + + const CertPathBuilderResultPath* best_result = result.GetBestValidPath(); + + // Newroot has same name+SPKI as newrootrollover, thus the path is valid and + // only contains newroot. + EXPECT_TRUE(best_result->IsValid()); + ASSERT_EQ(2U, best_result->certs.size()); + EXPECT_EQ(newroot_, best_result->certs[0]); + EXPECT_EQ(newrootrollover_, best_result->certs[1]); +} + +// Test that PathBuilder will not try the same path twice if multiple +// CertIssuerSources provide the same certificate. +TEST_F(PathBuilderKeyRolloverTest, TestDuplicateIntermediates) { + // Create a separate copy of oldintermediate. + scoped_refptr<ParsedCertificate> oldintermediate_dupe( + ParsedCertificate::Create( + bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( + oldintermediate_->der_cert().UnsafeData(), + oldintermediate_->der_cert().Length(), nullptr)), + {}, nullptr)); + + // Only newroot is a trusted root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newroot_); + + // The oldintermediate is supplied synchronously by |sync_certs1| and + // another copy of oldintermediate is supplied synchronously by |sync_certs2|. + // The path target <- oldintermediate <- newroot should be built first, + // though it won't verify. It should not be attempted again even though + // oldintermediate was supplied twice. + CertIssuerSourceStatic sync_certs1; + sync_certs1.AddCert(oldintermediate_); + CertIssuerSourceStatic sync_certs2; + sync_certs2.AddCert(oldintermediate_dupe); + + // The newintermediate is supplied asynchronously, so the path + // target <- newintermediate <- newroot should be tried second. + AsyncCertIssuerSourceStatic async_certs; + async_certs.AddCert(newintermediate_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs1); + path_builder.AddCertIssuerSource(&sync_certs2); + path_builder.AddCertIssuerSource(&async_certs); + + auto result = path_builder.Run(); + + EXPECT_TRUE(result.HasValidPath()); + ASSERT_EQ(2U, result.paths.size()); + + // Path builder will first attempt: target <- oldintermediate <- newroot + // but it will fail since oldintermediate is signed by oldroot. + EXPECT_FALSE(result.paths[0]->IsValid()); + const auto& path0 = *result.paths[0]; + + ASSERT_EQ(3U, path0.certs.size()); + EXPECT_EQ(target_, path0.certs[0]); + // Compare the DER instead of ParsedCertificate pointer, don't care which copy + // of oldintermediate was used in the path. + EXPECT_EQ(oldintermediate_->der_cert(), path0.certs[1]->der_cert()); + EXPECT_EQ(newroot_, path0.certs[2]); + + // Path builder will next attempt: target <- newintermediate <- newroot + // which will succeed. + EXPECT_EQ(1U, result.best_result_index); + EXPECT_TRUE(result.paths[1]->IsValid()); + const auto& path1 = *result.paths[1]; + ASSERT_EQ(3U, path1.certs.size()); + EXPECT_EQ(target_, path1.certs[0]); + EXPECT_EQ(newintermediate_, path1.certs[1]); + EXPECT_EQ(newroot_, path1.certs[2]); +} + +// Test when PathBuilder is given a cert via CertIssuerSources that has the same +// SPKI as a trust anchor. +TEST_F(PathBuilderKeyRolloverTest, TestDuplicateIntermediateAndRoot) { + // Create a separate copy of newroot. + scoped_refptr<ParsedCertificate> newroot_dupe(ParsedCertificate::Create( + bssl::UniquePtr<CRYPTO_BUFFER>( + CRYPTO_BUFFER_new(newroot_->der_cert().UnsafeData(), + newroot_->der_cert().Length(), nullptr)), + {}, nullptr)); + + // Only newroot is a trusted root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newroot_); + + // The oldintermediate and newroot are supplied synchronously by |sync_certs|. + CertIssuerSourceStatic sync_certs; + sync_certs.AddCert(oldintermediate_); + sync_certs.AddCert(newroot_dupe); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&sync_certs); + + auto result = path_builder.Run(); + + EXPECT_FALSE(result.HasValidPath()); + ASSERT_EQ(1U, result.paths.size()); + + // Path builder attempt: target <- oldintermediate <- newroot + // but it will fail since oldintermediate is signed by oldroot. + EXPECT_FALSE(result.paths[0]->IsValid()); + const auto& path = *result.paths[0]; + ASSERT_EQ(3U, path.certs.size()); + EXPECT_EQ(target_, path.certs[0]); + EXPECT_EQ(oldintermediate_, path.certs[1]); + // Compare the DER instead of ParsedCertificate pointer, don't care which copy + // of newroot was used in the path. + EXPECT_EQ(newroot_->der_cert(), path.certs[2]->der_cert()); +} + +class MockCertIssuerSourceRequest : public CertIssuerSource::Request { + public: + MOCK_METHOD1(GetNext, void(ParsedCertificateList*)); +}; + +class MockCertIssuerSource : public CertIssuerSource { + public: + MOCK_METHOD2(SyncGetIssuersOf, + void(const ParsedCertificate*, ParsedCertificateList*)); + MOCK_METHOD2(AsyncGetIssuersOf, + void(const ParsedCertificate*, std::unique_ptr<Request>*)); +}; + +// Helper class to pass the Request to the PathBuilder when it calls +// AsyncGetIssuersOf. (GoogleMock has a ByMove helper, but it apparently can +// only be used with Return, not SetArgPointee.) +class CertIssuerSourceRequestMover { + public: + explicit CertIssuerSourceRequestMover( + std::unique_ptr<CertIssuerSource::Request> req) + : request_(std::move(req)) {} + void MoveIt(const ParsedCertificate* cert, + std::unique_ptr<CertIssuerSource::Request>* out_req) { + *out_req = std::move(request_); + } + + private: + std::unique_ptr<CertIssuerSource::Request> request_; +}; + +// Functor that when called with a ParsedCertificateList* will append the +// specified certificate. +class AppendCertToList { + public: + explicit AppendCertToList(const scoped_refptr<ParsedCertificate>& cert) + : cert_(cert) {} + + void operator()(ParsedCertificateList* out) { out->push_back(cert_); } + + private: + scoped_refptr<ParsedCertificate> cert_; +}; + +// 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. +TEST_F(PathBuilderKeyRolloverTest, TestMultipleAsyncIssuersFromSingleSource) { + StrictMock<MockCertIssuerSource> cert_issuer_source; + + // Only newroot is a trusted root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newroot_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&cert_issuer_source); + + // Create the mock CertIssuerSource::Request... + auto target_issuers_req_owner = + std::make_unique<StrictMock<MockCertIssuerSourceRequest>>(); + // Keep a raw pointer to the Request... + StrictMock<MockCertIssuerSourceRequest>* target_issuers_req = + target_issuers_req_owner.get(); + // Setup helper class to pass ownership of the Request to the PathBuilder when + // it calls AsyncGetIssuersOf. + CertIssuerSourceRequestMover req_mover(std::move(target_issuers_req_owner)); + { + ::testing::InSequence s; + EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(target_.get(), _)); + EXPECT_CALL(cert_issuer_source, AsyncGetIssuersOf(target_.get(), _)) + .WillOnce(Invoke(&req_mover, &CertIssuerSourceRequestMover::MoveIt)); + } + + EXPECT_CALL(*target_issuers_req, GetNext(_)) + // First async batch: return oldintermediate_. + .WillOnce(Invoke(AppendCertToList(oldintermediate_))) + // Second async batch: return newintermediate_. + .WillOnce(Invoke(AppendCertToList(newintermediate_))); + { + ::testing::InSequence s; + // oldintermediate_ does not create a valid path, so both sync and async + // lookups are expected. + EXPECT_CALL(cert_issuer_source, + SyncGetIssuersOf(oldintermediate_.get(), _)); + EXPECT_CALL(cert_issuer_source, + AsyncGetIssuersOf(oldintermediate_.get(), _)); + } + + // newroot_ is in the trust store, so this path will be completed + // synchronously. AsyncGetIssuersOf will not be called on newintermediate_. + EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(newintermediate_.get(), _)); + + // Ensure pathbuilder finished and filled result. + auto result = path_builder.Run(); + + // Note that VerifyAndClearExpectations(target_issuers_req) is not called + // here. PathBuilder could have destroyed it already, so just let the + // expectations get checked by the destructor. + ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); + + EXPECT_TRUE(result.HasValidPath()); + ASSERT_EQ(2U, result.paths.size()); + + // Path builder first attempts: target <- oldintermediate <- newroot + // but it will fail since oldintermediate is signed by oldroot. + EXPECT_FALSE(result.paths[0]->IsValid()); + const auto& path0 = *result.paths[0]; + ASSERT_EQ(3U, path0.certs.size()); + EXPECT_EQ(target_, path0.certs[0]); + EXPECT_EQ(oldintermediate_, path0.certs[1]); + EXPECT_EQ(newroot_, path0.certs[2]); + + // After the second batch of async results, path builder will attempt: + // target <- newintermediate <- newroot which will succeed. + EXPECT_TRUE(result.paths[1]->IsValid()); + const auto& path1 = *result.paths[1]; + ASSERT_EQ(3U, path1.certs.size()); + EXPECT_EQ(target_, path1.certs[0]); + EXPECT_EQ(newintermediate_, path1.certs[1]); + EXPECT_EQ(newroot_, path1.certs[2]); +} + +// Test that PathBuilder will not try the same path twice if CertIssuerSources +// asynchronously provide the same certificate multiple times. +TEST_F(PathBuilderKeyRolloverTest, TestDuplicateAsyncIntermediates) { + StrictMock<MockCertIssuerSource> cert_issuer_source; + + // Only newroot is a trusted root. + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(newroot_); + + CertPathBuilder path_builder( + target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, + initial_explicit_policy_, user_initial_policy_set_, + initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); + path_builder.AddCertIssuerSource(&cert_issuer_source); + + // Create the mock CertIssuerSource::Request... + auto target_issuers_req_owner = + std::make_unique<StrictMock<MockCertIssuerSourceRequest>>(); + // Keep a raw pointer to the Request... + StrictMock<MockCertIssuerSourceRequest>* target_issuers_req = + target_issuers_req_owner.get(); + // Setup helper class to pass ownership of the Request to the PathBuilder when + // it calls AsyncGetIssuersOf. + CertIssuerSourceRequestMover req_mover(std::move(target_issuers_req_owner)); + { + ::testing::InSequence s; + EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(target_.get(), _)); + EXPECT_CALL(cert_issuer_source, AsyncGetIssuersOf(target_.get(), _)) + .WillOnce(Invoke(&req_mover, &CertIssuerSourceRequestMover::MoveIt)); + } + + scoped_refptr<ParsedCertificate> oldintermediate_dupe( + ParsedCertificate::Create( + bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( + oldintermediate_->der_cert().UnsafeData(), + oldintermediate_->der_cert().Length(), nullptr)), + {}, nullptr)); + + EXPECT_CALL(*target_issuers_req, GetNext(_)) + // First async batch: return oldintermediate_. + .WillOnce(Invoke(AppendCertToList(oldintermediate_))) + // Second async batch: return a different copy of oldintermediate_ again. + .WillOnce(Invoke(AppendCertToList(oldintermediate_dupe))) + // Third async batch: return newintermediate_. + .WillOnce(Invoke(AppendCertToList(newintermediate_))); + + { + ::testing::InSequence s; + // oldintermediate_ does not create a valid path, so both sync and async + // lookups are expected. + EXPECT_CALL(cert_issuer_source, + SyncGetIssuersOf(oldintermediate_.get(), _)); + EXPECT_CALL(cert_issuer_source, + AsyncGetIssuersOf(oldintermediate_.get(), _)); + } + + // newroot_ is in the trust store, so this path will be completed + // synchronously. AsyncGetIssuersOf will not be called on newintermediate_. + EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(newintermediate_.get(), _)); + + // Ensure pathbuilder finished and filled result. + auto result = path_builder.Run(); + + ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); + + EXPECT_TRUE(result.HasValidPath()); + ASSERT_EQ(2U, result.paths.size()); + + // Path builder first attempts: target <- oldintermediate <- newroot + // but it will fail since oldintermediate is signed by oldroot. + EXPECT_FALSE(result.paths[0]->IsValid()); + const auto& path0 = *result.paths[0]; + ASSERT_EQ(3U, path0.certs.size()); + EXPECT_EQ(target_, path0.certs[0]); + EXPECT_EQ(oldintermediate_, path0.certs[1]); + EXPECT_EQ(newroot_, path0.certs[2]); + + // The second async result does not generate any path. + + // After the third batch of async results, path builder will attempt: + // target <- newintermediate <- newroot which will succeed. + EXPECT_TRUE(result.paths[1]->IsValid()); + const auto& path1 = *result.paths[1]; + ASSERT_EQ(3U, path1.certs.size()); + EXPECT_EQ(target_, path1.certs[0]); + EXPECT_EQ(newintermediate_, path1.certs[1]); + EXPECT_EQ(newroot_, path1.certs[2]); +} + +class PathBuilderSimpleChainTest : public ::testing::Test { + public: + PathBuilderSimpleChainTest() = default; + + protected: + void SetUp() override { + // Read a simple test chain comprised of a target, intermediate, and root. + ASSERT_TRUE(ReadVerifyCertChainTestFromFile( + "net/data/verify_certificate_chain_unittest/target-and-intermediate/" + "main.test", + &test_)); + ASSERT_EQ(3u, test_.chain.size()); + } + + // Runs the path builder for the target certificate while |distrusted_cert| is + // blocked, and |delegate| if non-null. + CertPathBuilder::Result RunPathBuilder( + const scoped_refptr<ParsedCertificate>& distrusted_cert, + CertPathBuilderDelegate* optional_delegate) { + // Set up the trust store such that |distrusted_cert| is blocked, and + // the root is trusted (except if it was |distrusted_cert|). + TrustStoreInMemory trust_store; + if (distrusted_cert != test_.chain.back()) + trust_store.AddTrustAnchor(test_.chain.back()); + if (distrusted_cert) + trust_store.AddDistrustedCertificateForTest(distrusted_cert); + + // Add the single intermediate. + CertIssuerSourceStatic intermediates; + intermediates.AddCert(test_.chain[1]); + + SimplePathBuilderDelegate default_delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + CertPathBuilderDelegate* delegate = + optional_delegate ? optional_delegate : &default_delegate; + + const InitialExplicitPolicy initial_explicit_policy = + InitialExplicitPolicy::kFalse; + const std::set<der::Input> user_initial_policy_set = { + der::Input(kAnyPolicyOid)}; + const InitialPolicyMappingInhibit initial_policy_mapping_inhibit = + InitialPolicyMappingInhibit::kFalse; + const InitialAnyPolicyInhibit initial_any_policy_inhibit = + InitialAnyPolicyInhibit::kFalse; + + CertPathBuilder path_builder( + test_.chain.front(), &trust_store, delegate, test_.time, + KeyPurpose::ANY_EKU, initial_explicit_policy, user_initial_policy_set, + initial_policy_mapping_inhibit, initial_any_policy_inhibit); + path_builder.AddCertIssuerSource(&intermediates); + return path_builder.Run(); + } + + protected: + VerifyCertChainTest test_; +}; + +// Test fixture for running the path builder over a simple chain, while varying +// the trustedness of certain certificates. +class PathBuilderDistrustTest : public PathBuilderSimpleChainTest { + public: + PathBuilderDistrustTest() = default; + + protected: + // Runs the path builder for the target certificate while |distrusted_cert| is + // blocked. + CertPathBuilder::Result RunPathBuilderWithDistrustedCert( + const scoped_refptr<ParsedCertificate>& distrusted_cert) { + return RunPathBuilder(distrusted_cert, nullptr); + } +}; + +// Tests that path building fails when the target, intermediate, or root are +// distrusted (but the path is otherwise valid). +TEST_F(PathBuilderDistrustTest, TargetIntermediateRoot) { + // First do a control test -- path building without any blocked + // certificates should work. + CertPathBuilder::Result result = RunPathBuilderWithDistrustedCert(nullptr); + { + ASSERT_TRUE(result.HasValidPath()); + // The built path should be identical the the one read from disk. + const auto& path = *result.GetBestValidPath(); + ASSERT_EQ(test_.chain.size(), path.certs.size()); + for (size_t i = 0; i < test_.chain.size(); ++i) + EXPECT_EQ(test_.chain[i], path.certs[i]); + } + + // Try path building when only the target is blocked - should fail. + result = RunPathBuilderWithDistrustedCert(test_.chain[0]); + { + EXPECT_FALSE(result.HasValidPath()); + ASSERT_LT(result.best_result_index, result.paths.size()); + const auto& best_path = result.paths[result.best_result_index]; + + // The built chain has length 1 since path building stopped once + // it encountered the blocked certificate (target). + ASSERT_EQ(1u, best_path->certs.size()); + EXPECT_EQ(best_path->certs[0], test_.chain[0]); + EXPECT_TRUE(best_path->errors.ContainsHighSeverityErrors()); + best_path->errors.ContainsError(cert_errors::kDistrustedByTrustStore); + } + + // Try path building when only the intermediate is blocked - should fail. + result = RunPathBuilderWithDistrustedCert(test_.chain[1]); + { + EXPECT_FALSE(result.HasValidPath()); + ASSERT_LT(result.best_result_index, result.paths.size()); + const auto& best_path = result.paths[result.best_result_index]; + + // The built chain has length 2 since path building stopped once + // it encountered the blocked certificate (intermediate). + ASSERT_EQ(2u, best_path->certs.size()); + EXPECT_EQ(best_path->certs[0], test_.chain[0]); + EXPECT_EQ(best_path->certs[1], test_.chain[1]); + EXPECT_TRUE(best_path->errors.ContainsHighSeverityErrors()); + best_path->errors.ContainsError(cert_errors::kDistrustedByTrustStore); + } + + // Try path building when only the root is blocked - should fail. + result = RunPathBuilderWithDistrustedCert(test_.chain[2]); + { + EXPECT_FALSE(result.HasValidPath()); + ASSERT_LT(result.best_result_index, result.paths.size()); + const auto& best_path = result.paths[result.best_result_index]; + + // The built chain has length 3 since path building stopped once + // it encountered the blocked certificate (root). + ASSERT_EQ(3u, best_path->certs.size()); + EXPECT_EQ(best_path->certs[0], test_.chain[0]); + EXPECT_EQ(best_path->certs[1], test_.chain[1]); + EXPECT_EQ(best_path->certs[2], test_.chain[2]); + EXPECT_TRUE(best_path->errors.ContainsHighSeverityErrors()); + best_path->errors.ContainsError(cert_errors::kDistrustedByTrustStore); + } +} + +// Test fixture for running the path builder over a simple chain, while varying +// what CheckPathAfterVerification() does. +class PathBuilderCheckPathAfterVerificationTest + : public PathBuilderSimpleChainTest {}; + +class CertPathBuilderDelegateBase : public SimplePathBuilderDelegate { + public: + CertPathBuilderDelegateBase() + : SimplePathBuilderDelegate( + 1024, + SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {} + void CheckPathAfterVerification(const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path) override { + ADD_FAILURE() << "Tests must override this"; + } +}; + +class MockPathBuilderDelegate : public CertPathBuilderDelegateBase { + public: + MOCK_METHOD2(CheckPathAfterVerification, + void(const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path)); +}; + +TEST_F(PathBuilderCheckPathAfterVerificationTest, NoOpToValidPath) { + StrictMock<MockPathBuilderDelegate> delegate; + // Just verify that the hook is called. + EXPECT_CALL(delegate, CheckPathAfterVerification(_, _)); + + CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); + EXPECT_TRUE(result.HasValidPath()); +} + +DEFINE_CERT_ERROR_ID(kWarningFromDelegate, "Warning from delegate"); + +class AddWarningPathBuilderDelegate : public CertPathBuilderDelegateBase { + public: + void CheckPathAfterVerification(const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path) override { + path->errors.GetErrorsForCert(1)->AddWarning(kWarningFromDelegate, nullptr); + } +}; + +TEST_F(PathBuilderCheckPathAfterVerificationTest, AddsWarningToValidPath) { + AddWarningPathBuilderDelegate delegate; + CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); + ASSERT_TRUE(result.HasValidPath()); + + // A warning should have been added to certificate at index 1 in the path. + const CertErrors* cert1_errors = + result.GetBestValidPath()->errors.GetErrorsForCert(1); + ASSERT_TRUE(cert1_errors); + EXPECT_TRUE(cert1_errors->ContainsError(kWarningFromDelegate)); +} + +DEFINE_CERT_ERROR_ID(kErrorFromDelegate, "Error from delegate"); + +class AddErrorPathBuilderDelegate : public CertPathBuilderDelegateBase { + public: + void CheckPathAfterVerification(const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path) override { + path->errors.GetErrorsForCert(2)->AddError(kErrorFromDelegate, nullptr); + } +}; + +TEST_F(PathBuilderCheckPathAfterVerificationTest, AddsErrorToValidPath) { + AddErrorPathBuilderDelegate delegate; + CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); + + // Verification failed. + ASSERT_FALSE(result.HasValidPath()); + + ASSERT_LT(result.best_result_index, result.paths.size()); + const CertPathBuilderResultPath* failed_path = + result.paths[result.best_result_index].get(); + ASSERT_TRUE(failed_path); + + // An error should have been added to certificate at index 2 in the path. + const CertErrors* cert2_errors = failed_path->errors.GetErrorsForCert(2); + ASSERT_TRUE(cert2_errors); + EXPECT_TRUE(cert2_errors->ContainsError(kErrorFromDelegate)); +} + +TEST_F(PathBuilderCheckPathAfterVerificationTest, NoopToAlreadyInvalidPath) { + StrictMock<MockPathBuilderDelegate> delegate; + // Just verify that the hook is called (on an invalid path). + EXPECT_CALL(delegate, CheckPathAfterVerification(_, _)); + + // Run the pathbuilder with certificate at index 1 actively distrusted. + CertPathBuilder::Result result = RunPathBuilder(test_.chain[1], &delegate); + EXPECT_FALSE(result.HasValidPath()); +} + +struct DelegateData : public CertPathBuilderDelegateData { + int value = 0xB33F; +}; + +class SetsDelegateDataPathBuilderDelegate : public CertPathBuilderDelegateBase { + public: + void CheckPathAfterVerification(const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path) override { + path->delegate_data = std::make_unique<DelegateData>(); + } +}; + +TEST_F(PathBuilderCheckPathAfterVerificationTest, SetsDelegateData) { + SetsDelegateDataPathBuilderDelegate delegate; + CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); + ASSERT_TRUE(result.HasValidPath()); + + DelegateData* data = reinterpret_cast<DelegateData*>( + result.GetBestValidPath()->delegate_data.get()); + + EXPECT_EQ(0xB33F, data->value); +} + +TEST(PathBuilderPrioritizationTest, DatePrioritization) { + std::string test_dir = + "net/data/path_builder_unittest/validity_date_prioritization/"; + scoped_refptr<ParsedCertificate> root = + ReadCertFromFile(test_dir + "root.pem"); + ASSERT_TRUE(root); + scoped_refptr<ParsedCertificate> int_ac = + ReadCertFromFile(test_dir + "int_ac.pem"); + ASSERT_TRUE(int_ac); + scoped_refptr<ParsedCertificate> int_ad = + ReadCertFromFile(test_dir + "int_ad.pem"); + ASSERT_TRUE(int_ad); + scoped_refptr<ParsedCertificate> int_bc = + ReadCertFromFile(test_dir + "int_bc.pem"); + ASSERT_TRUE(int_bc); + scoped_refptr<ParsedCertificate> int_bd = + ReadCertFromFile(test_dir + "int_bd.pem"); + ASSERT_TRUE(int_bd); + scoped_refptr<ParsedCertificate> target = + ReadCertFromFile(test_dir + "target.pem"); + ASSERT_TRUE(target); + + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; + + // Distrust the root certificate. This will force the path builder to attempt + // all possible paths. + TrustStoreInMemory trust_store; + trust_store.AddDistrustedCertificateForTest(root); + + for (bool reverse_input_order : {false, true}) { + SCOPED_TRACE(reverse_input_order); + + CertIssuerSourceStatic intermediates; + // Test with the intermediates supplied in two different orders to ensure + // the results don't depend on input ordering. + if (reverse_input_order) { + intermediates.AddCert(int_bd); + intermediates.AddCert(int_bc); + intermediates.AddCert(int_ad); + intermediates.AddCert(int_ac); + } else { + intermediates.AddCert(int_ac); + intermediates.AddCert(int_ad); + intermediates.AddCert(int_bc); + intermediates.AddCert(int_bd); + } + + CertPathBuilder path_builder( + target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, + InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, + InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); + path_builder.AddCertIssuerSource(&intermediates); + + CertPathBuilder::Result result = path_builder.Run(); + EXPECT_FALSE(result.HasValidPath()); + ASSERT_EQ(4U, result.paths.size()); + + // Path builder should have attempted paths using the intermediates in + // order: bd, bc, ad, ac + + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(target, result.paths[0]->certs[0]); + EXPECT_EQ(int_bd, result.paths[0]->certs[1]); + EXPECT_EQ(root, result.paths[0]->certs[2]); + + EXPECT_FALSE(result.paths[1]->IsValid()); + ASSERT_EQ(3U, result.paths[1]->certs.size()); + EXPECT_EQ(target, result.paths[1]->certs[0]); + EXPECT_EQ(int_bc, result.paths[1]->certs[1]); + EXPECT_EQ(root, result.paths[1]->certs[2]); + + EXPECT_FALSE(result.paths[2]->IsValid()); + ASSERT_EQ(3U, result.paths[2]->certs.size()); + EXPECT_EQ(target, result.paths[2]->certs[0]); + EXPECT_EQ(int_ad, result.paths[2]->certs[1]); + EXPECT_EQ(root, result.paths[2]->certs[2]); + + EXPECT_FALSE(result.paths[3]->IsValid()); + ASSERT_EQ(3U, result.paths[3]->certs.size()); + EXPECT_EQ(target, result.paths[3]->certs[0]); + EXPECT_EQ(int_ac, result.paths[3]->certs[1]); + EXPECT_EQ(root, result.paths[3]->certs[2]); + } +} + +TEST(PathBuilderPrioritizationTest, KeyIdPrioritization) { + std::string test_dir = + "net/data/path_builder_unittest/key_id_prioritization/"; + scoped_refptr<ParsedCertificate> root = + ReadCertFromFile(test_dir + "root.pem"); + ASSERT_TRUE(root); + scoped_refptr<ParsedCertificate> int_matching_ski_a = + ReadCertFromFile(test_dir + "int_matching_ski_a.pem"); + ASSERT_TRUE(int_matching_ski_a); + scoped_refptr<ParsedCertificate> int_matching_ski_b = + ReadCertFromFile(test_dir + "int_matching_ski_b.pem"); + ASSERT_TRUE(int_matching_ski_b); + scoped_refptr<ParsedCertificate> int_no_ski_a = + ReadCertFromFile(test_dir + "int_no_ski_a.pem"); + ASSERT_TRUE(int_no_ski_a); + scoped_refptr<ParsedCertificate> int_no_ski_b = + ReadCertFromFile(test_dir + "int_no_ski_b.pem"); + ASSERT_TRUE(int_no_ski_b); + scoped_refptr<ParsedCertificate> int_different_ski_a = + ReadCertFromFile(test_dir + "int_different_ski_a.pem"); + ASSERT_TRUE(int_different_ski_a); + scoped_refptr<ParsedCertificate> int_different_ski_b = + ReadCertFromFile(test_dir + "int_different_ski_b.pem"); + ASSERT_TRUE(int_different_ski_b); + scoped_refptr<ParsedCertificate> target = + ReadCertFromFile(test_dir + "target.pem"); + ASSERT_TRUE(target); + + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; + + // Distrust the root certificate. This will force the path builder to attempt + // all possible paths. + TrustStoreInMemory trust_store; + trust_store.AddDistrustedCertificateForTest(root); + + for (bool reverse_input_order : {false, true}) { + SCOPED_TRACE(reverse_input_order); + + CertIssuerSourceStatic intermediates; + // Test with the intermediates supplied in two different orders to ensure + // the results don't depend on input ordering. + if (reverse_input_order) { + intermediates.AddCert(int_different_ski_b); + intermediates.AddCert(int_different_ski_a); + intermediates.AddCert(int_no_ski_b); + intermediates.AddCert(int_no_ski_a); + intermediates.AddCert(int_matching_ski_b); + intermediates.AddCert(int_matching_ski_a); + } else { + intermediates.AddCert(int_matching_ski_a); + intermediates.AddCert(int_matching_ski_b); + intermediates.AddCert(int_no_ski_a); + intermediates.AddCert(int_no_ski_b); + intermediates.AddCert(int_different_ski_a); + intermediates.AddCert(int_different_ski_b); + } + + CertPathBuilder path_builder( + target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, + InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, + InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); + path_builder.AddCertIssuerSource(&intermediates); + + CertPathBuilder::Result result = path_builder.Run(); + EXPECT_FALSE(result.HasValidPath()); + ASSERT_EQ(6U, result.paths.size()); + + // Path builder should have attempted paths using the intermediates in + // order: matching_ski_b, matching_ski_a, no_ski_b, no_ski_a, + // different_ski_b, different_ski_a + + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(target, result.paths[0]->certs[0]); + EXPECT_EQ(int_matching_ski_b, result.paths[0]->certs[1]); + EXPECT_EQ(root, result.paths[0]->certs[2]); + + EXPECT_FALSE(result.paths[1]->IsValid()); + ASSERT_EQ(3U, result.paths[1]->certs.size()); + EXPECT_EQ(target, result.paths[1]->certs[0]); + EXPECT_EQ(int_matching_ski_a, result.paths[1]->certs[1]); + EXPECT_EQ(root, result.paths[1]->certs[2]); + + EXPECT_FALSE(result.paths[2]->IsValid()); + ASSERT_EQ(3U, result.paths[2]->certs.size()); + EXPECT_EQ(target, result.paths[2]->certs[0]); + EXPECT_EQ(int_no_ski_b, result.paths[2]->certs[1]); + EXPECT_EQ(root, result.paths[2]->certs[2]); + + EXPECT_FALSE(result.paths[3]->IsValid()); + ASSERT_EQ(3U, result.paths[3]->certs.size()); + EXPECT_EQ(target, result.paths[3]->certs[0]); + EXPECT_EQ(int_no_ski_a, result.paths[3]->certs[1]); + EXPECT_EQ(root, result.paths[3]->certs[2]); + + EXPECT_FALSE(result.paths[4]->IsValid()); + ASSERT_EQ(3U, result.paths[4]->certs.size()); + EXPECT_EQ(target, result.paths[4]->certs[0]); + EXPECT_EQ(int_different_ski_b, result.paths[4]->certs[1]); + EXPECT_EQ(root, result.paths[4]->certs[2]); + + EXPECT_FALSE(result.paths[5]->IsValid()); + ASSERT_EQ(3U, result.paths[5]->certs.size()); + EXPECT_EQ(target, result.paths[5]->certs[0]); + EXPECT_EQ(int_different_ski_a, result.paths[5]->certs[1]); + EXPECT_EQ(root, result.paths[5]->certs[2]); + } +} + +TEST(PathBuilderPrioritizationTest, TrustAndKeyIdPrioritization) { + std::string test_dir = + "net/data/path_builder_unittest/key_id_prioritization/"; + scoped_refptr<ParsedCertificate> root = + ReadCertFromFile(test_dir + "root.pem"); + ASSERT_TRUE(root); + scoped_refptr<ParsedCertificate> trusted_and_matching = + ReadCertFromFile(test_dir + "int_matching_ski_a.pem"); + ASSERT_TRUE(trusted_and_matching); + scoped_refptr<ParsedCertificate> matching = + ReadCertFromFile(test_dir + "int_matching_ski_b.pem"); + ASSERT_TRUE(matching); + scoped_refptr<ParsedCertificate> distrusted_and_matching = + ReadCertFromFile(test_dir + "int_matching_ski_c.pem"); + ASSERT_TRUE(distrusted_and_matching); + scoped_refptr<ParsedCertificate> trusted_and_no_match_data = + ReadCertFromFile(test_dir + "int_no_ski_a.pem"); + ASSERT_TRUE(trusted_and_no_match_data); + scoped_refptr<ParsedCertificate> no_match_data = + ReadCertFromFile(test_dir + "int_no_ski_b.pem"); + ASSERT_TRUE(no_match_data); + scoped_refptr<ParsedCertificate> distrusted_and_no_match_data = + ReadCertFromFile(test_dir + "int_no_ski_c.pem"); + ASSERT_TRUE(distrusted_and_no_match_data); + scoped_refptr<ParsedCertificate> trusted_and_mismatch = + ReadCertFromFile(test_dir + "int_different_ski_a.pem"); + ASSERT_TRUE(trusted_and_mismatch); + scoped_refptr<ParsedCertificate> mismatch = + ReadCertFromFile(test_dir + "int_different_ski_b.pem"); + ASSERT_TRUE(mismatch); + scoped_refptr<ParsedCertificate> distrusted_and_mismatch = + ReadCertFromFile(test_dir + "int_different_ski_c.pem"); + ASSERT_TRUE(distrusted_and_mismatch); + scoped_refptr<ParsedCertificate> target = + ReadCertFromFile(test_dir + "target.pem"); + ASSERT_TRUE(target); + + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; + + for (bool reverse_input_order : {false, true}) { + SCOPED_TRACE(reverse_input_order); + + TrustStoreInMemory trust_store; + // Test with the intermediates supplied in two different orders to ensure + // the results don't depend on input ordering. + if (reverse_input_order) { + trust_store.AddTrustAnchor(trusted_and_matching); + trust_store.AddCertificateWithUnspecifiedTrust(matching); + trust_store.AddDistrustedCertificateForTest(distrusted_and_matching); + trust_store.AddTrustAnchor(trusted_and_no_match_data); + trust_store.AddCertificateWithUnspecifiedTrust(no_match_data); + trust_store.AddDistrustedCertificateForTest(distrusted_and_no_match_data); + trust_store.AddTrustAnchor(trusted_and_mismatch); + trust_store.AddCertificateWithUnspecifiedTrust(mismatch); + trust_store.AddDistrustedCertificateForTest(distrusted_and_mismatch); + } else { + trust_store.AddDistrustedCertificateForTest(distrusted_and_matching); + trust_store.AddCertificateWithUnspecifiedTrust(no_match_data); + trust_store.AddTrustAnchor(trusted_and_no_match_data); + trust_store.AddTrustAnchor(trusted_and_matching); + trust_store.AddCertificateWithUnspecifiedTrust(matching); + trust_store.AddCertificateWithUnspecifiedTrust(mismatch); + trust_store.AddDistrustedCertificateForTest(distrusted_and_no_match_data); + trust_store.AddTrustAnchor(trusted_and_mismatch); + trust_store.AddDistrustedCertificateForTest(distrusted_and_mismatch); + } + // Also distrust the root certificate. This will force the path builder to + // report paths that included an unspecified trust intermediate. + trust_store.AddDistrustedCertificateForTest(root); + + CertPathBuilder path_builder( + target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, + InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, + InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); + path_builder.SetExploreAllPaths(true); + + CertPathBuilder::Result result = path_builder.Run(); + EXPECT_TRUE(result.HasValidPath()); + ASSERT_EQ(9U, result.paths.size()); + + // Path builder should have attempted paths using the intermediates in + // order: trusted_and_matching, trusted_and_no_match_data, matching, + // no_match_data, trusted_and_mismatch, mismatch, distrusted_and_matching, + // distrusted_and_no_match_data, distrusted_and_mismatch. + + EXPECT_TRUE(result.paths[0]->IsValid()); + ASSERT_EQ(2U, result.paths[0]->certs.size()); + EXPECT_EQ(target, result.paths[0]->certs[0]); + EXPECT_EQ(trusted_and_matching, result.paths[0]->certs[1]); + + EXPECT_TRUE(result.paths[1]->IsValid()); + ASSERT_EQ(2U, result.paths[1]->certs.size()); + EXPECT_EQ(target, result.paths[1]->certs[0]); + EXPECT_EQ(trusted_and_no_match_data, result.paths[1]->certs[1]); + + EXPECT_FALSE(result.paths[2]->IsValid()); + ASSERT_EQ(3U, result.paths[2]->certs.size()); + EXPECT_EQ(target, result.paths[2]->certs[0]); + EXPECT_EQ(matching, result.paths[2]->certs[1]); + EXPECT_EQ(root, result.paths[2]->certs[2]); + + EXPECT_FALSE(result.paths[3]->IsValid()); + ASSERT_EQ(3U, result.paths[3]->certs.size()); + EXPECT_EQ(target, result.paths[3]->certs[0]); + EXPECT_EQ(no_match_data, result.paths[3]->certs[1]); + EXPECT_EQ(root, result.paths[3]->certs[2]); + + // Although this intermediate is trusted, it has the wrong key, so + // the path should not be valid. + EXPECT_FALSE(result.paths[4]->IsValid()); + ASSERT_EQ(2U, result.paths[4]->certs.size()); + EXPECT_EQ(target, result.paths[4]->certs[0]); + EXPECT_EQ(trusted_and_mismatch, result.paths[4]->certs[1]); + + EXPECT_FALSE(result.paths[5]->IsValid()); + ASSERT_EQ(3U, result.paths[5]->certs.size()); + EXPECT_EQ(target, result.paths[5]->certs[0]); + EXPECT_EQ(mismatch, result.paths[5]->certs[1]); + EXPECT_EQ(root, result.paths[5]->certs[2]); + + EXPECT_FALSE(result.paths[6]->IsValid()); + ASSERT_EQ(2U, result.paths[6]->certs.size()); + EXPECT_EQ(target, result.paths[6]->certs[0]); + EXPECT_EQ(distrusted_and_matching, result.paths[6]->certs[1]); + + EXPECT_FALSE(result.paths[7]->IsValid()); + ASSERT_EQ(2U, result.paths[7]->certs.size()); + EXPECT_EQ(target, result.paths[7]->certs[0]); + EXPECT_EQ(distrusted_and_no_match_data, result.paths[7]->certs[1]); + + EXPECT_FALSE(result.paths[8]->IsValid()); + ASSERT_EQ(2U, result.paths[8]->certs.size()); + EXPECT_EQ(target, result.paths[8]->certs[0]); + EXPECT_EQ(distrusted_and_mismatch, result.paths[8]->certs[1]); + } +} + +// PathBuilder does not support prioritization based on the issuer name & +// serial in authorityKeyIdentifier, so this test just ensures that it does not +// affect prioritization order and that it is generally just ignored +// completely. +TEST(PathBuilderPrioritizationTest, KeyIdNameAndSerialPrioritization) { + std::string test_dir = + "net/data/path_builder_unittest/key_id_name_and_serial_prioritization/"; + scoped_refptr<ParsedCertificate> root = + ReadCertFromFile(test_dir + "root.pem"); + ASSERT_TRUE(root); + scoped_refptr<ParsedCertificate> root2 = + ReadCertFromFile(test_dir + "root2.pem"); + ASSERT_TRUE(root2); + scoped_refptr<ParsedCertificate> int_matching = + ReadCertFromFile(test_dir + "int_matching.pem"); + ASSERT_TRUE(int_matching); + scoped_refptr<ParsedCertificate> int_match_name_only = + ReadCertFromFile(test_dir + "int_match_name_only.pem"); + ASSERT_TRUE(int_match_name_only); + scoped_refptr<ParsedCertificate> int_mismatch = + ReadCertFromFile(test_dir + "int_mismatch.pem"); + ASSERT_TRUE(int_mismatch); + scoped_refptr<ParsedCertificate> target = + ReadCertFromFile(test_dir + "target.pem"); + ASSERT_TRUE(target); + + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; + + // Distrust the root certificates. This will force the path builder to attempt + // all possible paths. + TrustStoreInMemory trust_store; + trust_store.AddDistrustedCertificateForTest(root); + trust_store.AddDistrustedCertificateForTest(root2); + + for (bool reverse_input_order : {false, true}) { + SCOPED_TRACE(reverse_input_order); + + CertIssuerSourceStatic intermediates; + // Test with the intermediates supplied in two different orders to ensure + // the results don't depend on input ordering. + if (reverse_input_order) { + intermediates.AddCert(int_mismatch); + intermediates.AddCert(int_match_name_only); + intermediates.AddCert(int_matching); + } else { + intermediates.AddCert(int_matching); + intermediates.AddCert(int_match_name_only); + intermediates.AddCert(int_mismatch); + } + + CertPathBuilder path_builder( + target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, + InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, + InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); + path_builder.AddCertIssuerSource(&intermediates); + + CertPathBuilder::Result result = path_builder.Run(); + EXPECT_FALSE(result.HasValidPath()); + ASSERT_EQ(3U, result.paths.size()); + + // The serial & issuer method is not used in prioritization, so the certs + // should have been prioritized based on dates. The test certs have the + // date priority order in the reverse of what authorityKeyIdentifier + // prioritization would have done if it were supported. + // Path builder should have attempted paths using the intermediates in + // order: mismatch, match_name_only, matching + + EXPECT_FALSE(result.paths[0]->IsValid()); + ASSERT_EQ(3U, result.paths[0]->certs.size()); + EXPECT_EQ(target, result.paths[0]->certs[0]); + EXPECT_EQ(int_mismatch, result.paths[0]->certs[1]); + EXPECT_EQ(root2, result.paths[0]->certs[2]); + + EXPECT_FALSE(result.paths[1]->IsValid()); + ASSERT_EQ(3U, result.paths[1]->certs.size()); + EXPECT_EQ(target, result.paths[1]->certs[0]); + EXPECT_EQ(int_match_name_only, result.paths[1]->certs[1]); + EXPECT_EQ(root, result.paths[1]->certs[2]); + + EXPECT_FALSE(result.paths[2]->IsValid()); + ASSERT_EQ(3U, result.paths[2]->certs.size()); + EXPECT_EQ(target, result.paths[2]->certs[0]); + EXPECT_EQ(int_matching, result.paths[2]->certs[1]); + EXPECT_EQ(root, result.paths[2]->certs[2]); + } +} + +TEST(PathBuilderPrioritizationTest, SelfIssuedPrioritization) { + std::string test_dir = + "net/data/path_builder_unittest/self_issued_prioritization/"; + scoped_refptr<ParsedCertificate> root1 = + ReadCertFromFile(test_dir + "root1.pem"); + ASSERT_TRUE(root1); + scoped_refptr<ParsedCertificate> root1_cross = + ReadCertFromFile(test_dir + "root1_cross.pem"); + ASSERT_TRUE(root1_cross); + scoped_refptr<ParsedCertificate> target = + ReadCertFromFile(test_dir + "target.pem"); + ASSERT_TRUE(target); + + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; + + TrustStoreInMemory trust_store; + trust_store.AddTrustAnchor(root1); + trust_store.AddTrustAnchor(root1_cross); + CertPathBuilder path_builder( + target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, + InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, + InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); + path_builder.SetExploreAllPaths(true); + + CertPathBuilder::Result result = path_builder.Run(); + EXPECT_TRUE(result.HasValidPath()); + + // Path builder should have built paths to both trusted roots. + ASSERT_EQ(2U, result.paths.size()); + + // |root1| should have been preferred because it is self-issued, even though + // the notBefore date is older than |root1_cross|. + EXPECT_TRUE(result.paths[0]->IsValid()); + ASSERT_EQ(2U, result.paths[0]->certs.size()); + EXPECT_EQ(target, result.paths[0]->certs[0]); + EXPECT_EQ(root1, result.paths[0]->certs[1]); + + EXPECT_TRUE(result.paths[1]->IsValid()); + ASSERT_EQ(2U, result.paths[1]->certs.size()); + EXPECT_EQ(target, result.paths[1]->certs[0]); + EXPECT_EQ(root1_cross, result.paths[1]->certs[1]); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/path_builder_verify_certificate_chain_unittest.cc b/chromium/net/cert/pki/path_builder_verify_certificate_chain_unittest.cc new file mode 100644 index 00000000000..1db806bb67a --- /dev/null +++ b/chromium/net/cert/pki/path_builder_verify_certificate_chain_unittest.cc @@ -0,0 +1,67 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/path_builder.h" + +#include "net/cert/pki/cert_issuer_source_static.h" +#include "net/cert/pki/simple_path_builder_delegate.h" +#include "net/cert/pki/trust_store_in_memory.h" +#include "net/cert/pki/verify_certificate_chain_typed_unittest.h" + +namespace net { + +namespace { + +class PathBuilderTestDelegate { + public: + static void Verify(const VerifyCertChainTest& test, + const std::string& test_file_path) { + SimplePathBuilderDelegate path_builder_delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + ASSERT_FALSE(test.chain.empty()); + + TrustStoreInMemory trust_store; + + switch (test.last_cert_trust.type) { + case CertificateTrustType::TRUSTED_ANCHOR: + trust_store.AddTrustAnchor(test.chain.back()); + break; + case CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION: + trust_store.AddTrustAnchorWithExpiration(test.chain.back()); + break; + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + trust_store.AddTrustAnchorWithConstraints(test.chain.back()); + break; + case CertificateTrustType::UNSPECIFIED: + trust_store.AddCertificateWithUnspecifiedTrust(test.chain.back()); + break; + case CertificateTrustType::DISTRUSTED: + trust_store.AddDistrustedCertificateForTest(test.chain.back()); + break; + } + + 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()); + } +}; + +} // namespace + +INSTANTIATE_TYPED_TEST_SUITE_P(PathBuilder, + VerifyCertificateChainSingleRootTest, + PathBuilderTestDelegate); + +} // namespace net diff --git a/chromium/net/cert/pki/revocation_util.cc b/chromium/net/cert/pki/revocation_util.cc new file mode 100644 index 00000000000..17a75b03c8e --- /dev/null +++ b/chromium/net/cert/pki/revocation_util.cc @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/revocation_util.h" + +#include "base/time/time.h" +#include "net/der/encode_values.h" +#include "net/der/parse_values.h" + +namespace net { + +bool CheckRevocationDateValid(const der::GeneralizedTime& this_update, + const der::GeneralizedTime* next_update, + const base::Time& verify_time, + const base::TimeDelta& max_age) { + der::GeneralizedTime verify_time_der; + if (!der::EncodeTimeAsGeneralizedTime(verify_time, &verify_time_der)) + return false; + + if (this_update > verify_time_der) + return false; // Response is not yet valid. + + if (next_update && (*next_update <= verify_time_der)) + return false; // Response is no longer valid. + + der::GeneralizedTime earliest_this_update; + if (!der::EncodeTimeAsGeneralizedTime(verify_time - max_age, + &earliest_this_update)) { + return false; + } + if (this_update < earliest_this_update) + return false; // Response is too old. + + return true; +} + +} // namespace net diff --git a/chromium/net/cert/pki/revocation_util.h b/chromium/net/cert/pki/revocation_util.h new file mode 100644 index 00000000000..2966a0542de --- /dev/null +++ b/chromium/net/cert/pki/revocation_util.h @@ -0,0 +1,33 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_REVOCATION_UTIL_H_ +#define NET_CERT_PKI_REVOCATION_UTIL_H_ + +#include "net/base/net_export.h" + +namespace base { +class Time; +class TimeDelta; +} // namespace base + +namespace net { + +namespace der { +struct GeneralizedTime; +} + +// Returns true if a revocation status with |this_update| field and potentially +// a |next_update| field, is valid at |verify_time| and not older than +// |max_age|. Expressed differently, returns true if |this_update <= +// verify_time < next_update|, and |this_update >= verify_time - max_age|. +[[nodiscard]] NET_EXPORT_PRIVATE bool CheckRevocationDateValid( + const der::GeneralizedTime& this_update, + const der::GeneralizedTime* next_update, + const base::Time& verify_time, + const base::TimeDelta& max_age); + +} // namespace net + +#endif // NET_CERT_PKI_REVOCATION_UTIL_H_ diff --git a/chromium/net/cert/pki/signature_algorithm.cc b/chromium/net/cert/pki/signature_algorithm.cc new file mode 100644 index 00000000000..a7ff1852587 --- /dev/null +++ b/chromium/net/cert/pki/signature_algorithm.cc @@ -0,0 +1,487 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/signature_algorithm.h" + +#include "base/check.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" + +namespace net { + +namespace { + +// md2WithRSAEncryption +// In dotted notation: 1.2.840.113549.1.1.2 +const uint8_t kOidMd2WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x02}; + +// md4WithRSAEncryption +// In dotted notation: 1.2.840.113549.1.1.3 +const uint8_t kOidMd4WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x03}; + +// md5WithRSAEncryption +// In dotted notation: 1.2.840.113549.1.1.4 +const uint8_t kOidMd5WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x04}; + +// From RFC 5912: +// +// sha1WithRSAEncryption OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) +// pkcs-1(1) 5 } +// +// In dotted notation: 1.2.840.113549.1.1.5 +const uint8_t kOidSha1WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x05}; + +// sha1WithRSASignature is a deprecated equivalent of +// sha1WithRSAEncryption. +// +// It originates from the NIST Open Systems Environment (OSE) +// Implementor's Workshop (OIW). +// +// It is supported for compatibility with Microsoft's certificate APIs and +// tools, particularly makecert.exe, which default(ed/s) to this OID for SHA-1. +// +// See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1042479 +// +// In dotted notation: 1.3.14.3.2.29 +const uint8_t kOidSha1WithRsaSignature[] = {0x2b, 0x0e, 0x03, 0x02, 0x1d}; + +// From RFC 5912: +// +// pkcs-1 OBJECT IDENTIFIER ::= +// { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } + +// From RFC 5912: +// +// sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } +// +// In dotted notation: 1.2.840.113549.1.1.11 +const uint8_t kOidSha256WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0b}; + +// From RFC 5912: +// +// sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } +// +// In dotted notation: 1.2.840.113549.1.1.11 +const uint8_t kOidSha384WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0c}; + +// From RFC 5912: +// +// sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } +// +// In dotted notation: 1.2.840.113549.1.1.13 +const uint8_t kOidSha512WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0d}; + +// From RFC 5912: +// +// ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-X9-62(10045) +// signatures(4) 1 } +// +// In dotted notation: 1.2.840.10045.4.1 +const uint8_t kOidEcdsaWithSha1[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01}; + +// From RFC 5912: +// +// ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +// ecdsa-with-SHA2(3) 2 } +// +// In dotted notation: 1.2.840.10045.4.3.2 +const uint8_t kOidEcdsaWithSha256[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02}; + +// From RFC 5912: +// +// ecdsa-with-SHA384 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +// ecdsa-with-SHA2(3) 3 } +// +// In dotted notation: 1.2.840.10045.4.3.3 +const uint8_t kOidEcdsaWithSha384[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x03}; + +// From RFC 5912: +// +// ecdsa-with-SHA512 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +// ecdsa-with-SHA2(3) 4 } +// +// In dotted notation: 1.2.840.10045.4.3.4 +const uint8_t kOidEcdsaWithSha512[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x04}; + +// From RFC 5912: +// +// id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 } +// +// In dotted notation: 1.2.840.113549.1.1.10 +const uint8_t kOidRsaSsaPss[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0a}; + +// From RFC 5912: +// +// dsa-with-sha1 OBJECT IDENTIFIER ::= { +// iso(1) member-body(2) us(840) x9-57(10040) x9algorithm(4) 3 } +// +// In dotted notation: 1.2.840.10040.4.3 +const uint8_t kOidDsaWithSha1[] = {0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03}; + +// From RFC 5912: +// +// dsa-with-sha256 OBJECT IDENTIFIER ::= { +// joint-iso-ccitt(2) country(16) us(840) organization(1) gov(101) +// csor(3) algorithms(4) id-dsa-with-sha2(3) 2 } +// +// In dotted notation: 2.16.840.1.101.3.4.3.2 +const uint8_t kOidDsaWithSha256[] = {0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x03, 0x02}; + +// From RFC 5912: +// +// id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 } +// +// In dotted notation: 1.2.840.113549.1.1.8 +const uint8_t kOidMgf1[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x08}; + +// Returns true if |input| is empty. +[[nodiscard]] bool IsEmpty(const der::Input& input) { + return input.Length() == 0; +} + +// Returns true if the entirety of the input is a NULL value. +[[nodiscard]] bool IsNull(const der::Input& input) { + der::Parser parser(input); + der::Input null_value; + if (!parser.ReadTag(der::kNull, &null_value)) + return false; + + // NULL values are TLV encoded; the value is expected to be empty. + if (!IsEmpty(null_value)) + return false; + + // By definition of this function, the entire input must be a NULL. + return !parser.HasMore(); +} + +[[nodiscard]] bool IsNullOrEmpty(const der::Input& input) { + return IsNull(input) || IsEmpty(input); +} + +// Parses a MaskGenAlgorithm as defined by RFC 5912: +// +// MaskGenAlgorithm ::= AlgorithmIdentifier{ALGORITHM, +// {PKCS1MGFAlgorithms}} +// +// mgf1SHA1 MaskGenAlgorithm ::= { +// algorithm id-mgf1, +// parameters HashAlgorithm : sha1Identifier +// } +// +// -- +// -- Define the set of mask generation functions +// -- +// -- If the identifier is id-mgf1, any of the listed hash +// -- algorithms may be used. +// -- +// +// PKCS1MGFAlgorithms ALGORITHM ::= { +// { IDENTIFIER id-mgf1 PARAMS TYPE HashAlgorithm ARE required }, +// ... +// } +// +// Note that the possible mask gen algorithms is extensible. However at present +// the only function supported is MGF1, as that is the singular mask gen +// function defined by RFC 4055 / RFC 5912. +[[nodiscard]] bool ParseMaskGenAlgorithm(const der::Input input, + DigestAlgorithm* mgf1_hash) { + der::Input oid; + der::Input params; + if (!ParseAlgorithmIdentifier(input, &oid, ¶ms)) + return false; + + // MGF1 is the only supported mask generation algorithm. + if (oid != der::Input(kOidMgf1)) + return false; + + return ParseHashAlgorithm(params, mgf1_hash); +} + +// Parses the parameters for an RSASSA-PSS signature algorithm, as defined by +// RFC 5912: +// +// sa-rsaSSA-PSS SIGNATURE-ALGORITHM ::= { +// IDENTIFIER id-RSASSA-PSS +// PARAMS TYPE RSASSA-PSS-params ARE required +// HASHES { mda-sha1 | mda-sha224 | mda-sha256 | mda-sha384 +// | mda-sha512 } +// PUBLIC-KEYS { pk-rsa | pk-rsaSSA-PSS } +// SMIME-CAPS { IDENTIFIED BY id-RSASSA-PSS } +// } +// +// RSASSA-PSS-params ::= SEQUENCE { +// hashAlgorithm [0] HashAlgorithm DEFAULT sha1Identifier, +// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, +// saltLength [2] INTEGER DEFAULT 20, +// trailerField [3] INTEGER DEFAULT 1 +// } +// +// Which is to say the parameters MUST be present, and of type +// RSASSA-PSS-params. Additionally, we only support the RSA-PSS parameter +// combinations representable by TLS 1.3 (RFC 8446). +// +// Note also that DER encoding (ITU-T X.690 section 11.5) prohibits +// specifying default values explicitly. The parameter should instead be +// omitted to indicate a default value. +absl::optional<SignatureAlgorithm> ParseRsaPss(const der::Input& params) { + der::Parser parser(params); + der::Parser params_parser; + if (!parser.ReadSequence(¶ms_parser)) + return absl::nullopt; + + // There shouldn't be anything after the sequence (by definition the + // parameters is a single sequence). + if (parser.HasMore()) + return absl::nullopt; + + // The default values for hashAlgorithm, maskGenAlgorithm, and saltLength + // correspond to SHA-1, which we do not support with RSA-PSS, so treat them as + // required fields. Explicitly-specified defaults will be rejected later, when + // we limit combinations. Additionally, as the trailerField is required to be + // the default, we simply ignore it and reject it as any other trailing data. + // + // hashAlgorithm [0] HashAlgorithm DEFAULT sha1Identifier, + // maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, + // saltLength [2] INTEGER DEFAULT 20, + // trailerField [3] INTEGER DEFAULT 1 + der::Input field; + DigestAlgorithm hash, mgf1_hash; + der::Parser salt_length_parser; + uint64_t salt_length; + if (!params_parser.ReadTag(der::ContextSpecificConstructed(0), &field) || + !ParseHashAlgorithm(field, &hash) || + !params_parser.ReadTag(der::ContextSpecificConstructed(1), &field) || + !ParseMaskGenAlgorithm(field, &mgf1_hash) || + !params_parser.ReadConstructed(der::ContextSpecificConstructed(2), + &salt_length_parser) || + !salt_length_parser.ReadUint64(&salt_length) || + salt_length_parser.HasMore() || params_parser.HasMore()) { + return absl::nullopt; + } + + // Only combinations of RSASSA-PSS-params specified by TLS 1.3 (RFC 8446) are + // supported. + if (hash != mgf1_hash) { + return absl::nullopt; // TLS 1.3 always matches MGF-1 and message hash. + } + if (hash == DigestAlgorithm::Sha256 && salt_length == 32) { + return SignatureAlgorithm::kRsaPssSha256; + } + if (hash == DigestAlgorithm::Sha384 && salt_length == 48) { + return SignatureAlgorithm::kRsaPssSha384; + } + if (hash == DigestAlgorithm::Sha512 && salt_length == 64) { + return SignatureAlgorithm::kRsaPssSha512; + } + + return absl::nullopt; +} + +DEFINE_CERT_ERROR_ID(kUnknownSignatureAlgorithm, "Unknown signature algorithm"); + +} // namespace + +[[nodiscard]] bool ParseAlgorithmIdentifier(const der::Input& input, + der::Input* algorithm, + der::Input* parameters) { + der::Parser parser(input); + + der::Parser algorithm_identifier_parser; + if (!parser.ReadSequence(&algorithm_identifier_parser)) + return false; + + // There shouldn't be anything after the sequence. This is by definition, + // as the input to this function is expected to be a single + // AlgorithmIdentifier. + if (parser.HasMore()) + return false; + + if (!algorithm_identifier_parser.ReadTag(der::kOid, algorithm)) + return false; + + // Read the optional parameters to a der::Input. The parameters can be at + // most one TLV (for instance NULL or a sequence). + // + // Note that nothing is allowed after the single optional "parameters" TLV. + // This is because RFC 5912's notation for AlgorithmIdentifier doesn't + // explicitly list an extension point after "parameters". + *parameters = der::Input(); + if (algorithm_identifier_parser.HasMore() && + !algorithm_identifier_parser.ReadRawTLV(parameters)) { + return false; + } + return !algorithm_identifier_parser.HasMore(); +} + +[[nodiscard]] bool ParseHashAlgorithm(const der::Input& input, + DigestAlgorithm* out) { + CBS cbs; + CBS_init(&cbs, input.UnsafeData(), input.Length()); + const EVP_MD* md = EVP_parse_digest_algorithm(&cbs); + + if (md == EVP_sha1()) { + *out = DigestAlgorithm::Sha1; + } else if (md == EVP_sha256()) { + *out = DigestAlgorithm::Sha256; + } else if (md == EVP_sha384()) { + *out = DigestAlgorithm::Sha384; + } else if (md == EVP_sha512()) { + *out = DigestAlgorithm::Sha512; + } else { + // TODO(eroman): Support MD2, MD4, MD5 for completeness? + // Unsupported digest algorithm. + return false; + } + + return true; +} + +absl::optional<SignatureAlgorithm> ParseSignatureAlgorithm( + const der::Input& algorithm_identifier, + CertErrors* errors) { + der::Input oid; + der::Input params; + if (!ParseAlgorithmIdentifier(algorithm_identifier, &oid, ¶ms)) + return absl::nullopt; + + // TODO(eroman): Each OID is tested for equality in order, which is not + // particularly efficient. + + // RFC 5912 requires that the parameters for RSA PKCS#1 v1.5 algorithms be + // NULL ("PARAMS TYPE NULL ARE required"), however an empty parameter is also + // allowed for compatibility with non-compliant OCSP responders. + // + // TODO(svaldez): Add warning about non-strict parsing. + if (oid == der::Input(kOidSha1WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Sha1; + } + if (oid == der::Input(kOidSha256WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Sha256; + } + if (oid == der::Input(kOidSha384WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Sha384; + } + if (oid == der::Input(kOidSha512WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Sha512; + } + if (oid == der::Input(kOidSha1WithRsaSignature) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Sha1; + } + if (oid == der::Input(kOidMd2WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Md2; + } + if (oid == der::Input(kOidMd4WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Md4; + } + if (oid == der::Input(kOidMd5WithRsaEncryption) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kRsaPkcs1Md5; + } + + // RFC 5912 requires that the parameters for ECDSA algorithms be absent + // ("PARAMS TYPE NULL ARE absent"): + if (oid == der::Input(kOidEcdsaWithSha1) && IsEmpty(params)) { + return SignatureAlgorithm::kEcdsaSha1; + } + if (oid == der::Input(kOidEcdsaWithSha256) && IsEmpty(params)) { + return SignatureAlgorithm::kEcdsaSha256; + } + if (oid == der::Input(kOidEcdsaWithSha384) && IsEmpty(params)) { + return SignatureAlgorithm::kEcdsaSha384; + } + if (oid == der::Input(kOidEcdsaWithSha512) && IsEmpty(params)) { + return SignatureAlgorithm::kEcdsaSha512; + } + + if (oid == der::Input(kOidRsaSsaPss)) { + return ParseRsaPss(params); + } + + // RFC 5912 requires that the parameters for DSA algorithms be absent. + // + // TODO(svaldez): Add warning about non-strict parsing. + if (oid == der::Input(kOidDsaWithSha1) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kDsaSha1; + } + if (oid == der::Input(kOidDsaWithSha256) && IsNullOrEmpty(params)) { + return SignatureAlgorithm::kDsaSha256; + } + + // Unknown signature algorithm. + if (errors) { + errors->AddError(kUnknownSignatureAlgorithm, + CreateCertErrorParams2Der("oid", oid, "params", params)); + } + return absl::nullopt; +} + +absl::optional<DigestAlgorithm> GetTlsServerEndpointDigestAlgorithm( + SignatureAlgorithm alg) { + // See RFC 5929, section 4.1. RFC 5929 breaks the signature algorithm + // abstraction by trying to extract individual digest algorithms. (While + // common, this is not a universal property of signature algorithms.) We + // implement this within the library, so callers do not need to condition over + // all algorithms. + switch (alg) { + // If the single digest algorithm is MD5 or SHA-1, use SHA-256. + case SignatureAlgorithm::kRsaPkcs1Md5: + case SignatureAlgorithm::kRsaPkcs1Sha1: + case SignatureAlgorithm::kEcdsaSha1: + return DigestAlgorithm::Sha256; + + case SignatureAlgorithm::kRsaPkcs1Sha256: + case SignatureAlgorithm::kEcdsaSha256: + return DigestAlgorithm::Sha256; + + case SignatureAlgorithm::kRsaPkcs1Sha384: + case SignatureAlgorithm::kEcdsaSha384: + return DigestAlgorithm::Sha384; + + case SignatureAlgorithm::kRsaPkcs1Sha512: + case SignatureAlgorithm::kEcdsaSha512: + return DigestAlgorithm::Sha512; + + // It is ambiguous whether hash-matching RSASSA-PSS instantiations count as + // using one or multiple digests, but the corresponding digest is the only + // reasonable interpretation. + case SignatureAlgorithm::kRsaPssSha256: + return DigestAlgorithm::Sha256; + case SignatureAlgorithm::kRsaPssSha384: + return DigestAlgorithm::Sha384; + case SignatureAlgorithm::kRsaPssSha512: + return DigestAlgorithm::Sha512; + + // Do not return anything for these legacy algorithms. + case SignatureAlgorithm::kDsaSha1: + case SignatureAlgorithm::kDsaSha256: + case SignatureAlgorithm::kRsaPkcs1Md2: + case SignatureAlgorithm::kRsaPkcs1Md4: + return absl::nullopt; + } + return absl::nullopt; +} + +} // namespace net diff --git a/chromium/net/cert/pki/signature_algorithm.h b/chromium/net/cert/pki/signature_algorithm.h new file mode 100644 index 00000000000..e6e2569bbae --- /dev/null +++ b/chromium/net/cert/pki/signature_algorithm.h @@ -0,0 +1,95 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_SIGNATURE_ALGORITHM_H_ +#define NET_CERT_PKI_SIGNATURE_ALGORITHM_H_ + +#include <stdint.h> + +#include "net/base/net_export.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +class CertErrors; + +namespace der { +class Input; +} // namespace der + +// The digest algorithm used within a signature. +enum class DigestAlgorithm { + Md2, + Md4, + Md5, + Sha1, + Sha256, + Sha384, + Sha512, +}; + +// The signature algorithm used within a certificate. +enum class SignatureAlgorithm { + kRsaPkcs1Sha1, + kRsaPkcs1Sha256, + kRsaPkcs1Sha384, + kRsaPkcs1Sha512, + kEcdsaSha1, + kEcdsaSha256, + kEcdsaSha384, + kEcdsaSha512, + // These RSA-PSS constants match RFC 8446 and refer to RSASSA-PSS with MGF-1, + // using the specified hash as both the signature and MGF-1 hash, and the hash + // length as the salt length. + kRsaPssSha256, + kRsaPssSha384, + kRsaPssSha512, + // These algorithms can be parsed but are not supported. + // TODO(https://crbug.com/1321688): Remove these. + kRsaPkcs1Md2, + kRsaPkcs1Md4, + kRsaPkcs1Md5, + kDsaSha1, + kDsaSha256, +}; + +// Parses AlgorithmIdentifier as defined by RFC 5280 section 4.1.1.2: +// +// AlgorithmIdentifier ::= SEQUENCE { +// algorithm OBJECT IDENTIFIER, +// parameters ANY DEFINED BY algorithm OPTIONAL } +[[nodiscard]] NET_EXPORT bool ParseAlgorithmIdentifier(const der::Input& input, + der::Input* algorithm, + der::Input* parameters); + +// Parses a HashAlgorithm as defined by RFC 5912: +// +// HashAlgorithm ::= AlgorithmIdentifier{DIGEST-ALGORITHM, +// {HashAlgorithms}} +// +// HashAlgorithms DIGEST-ALGORITHM ::= { +// { IDENTIFIER id-sha1 PARAMS TYPE NULL ARE preferredPresent } | +// { IDENTIFIER id-sha224 PARAMS TYPE NULL ARE preferredPresent } | +// { IDENTIFIER id-sha256 PARAMS TYPE NULL ARE preferredPresent } | +// { IDENTIFIER id-sha384 PARAMS TYPE NULL ARE preferredPresent } | +// { IDENTIFIER id-sha512 PARAMS TYPE NULL ARE preferredPresent } +// } +[[nodiscard]] bool ParseHashAlgorithm(const der::Input& input, + DigestAlgorithm* out); + +// Parses an AlgorithmIdentifier into a signature algorithm and returns it, or +// returns `absl::nullopt` if `algorithm_identifer` either cannot be parsed or +// is not a recognized signature algorithm. +NET_EXPORT absl::optional<SignatureAlgorithm> ParseSignatureAlgorithm( + const der::Input& algorithm_identifier, + CertErrors* errors); + +// Returns the hash to be used with the tls-server-end-point channel binding +// (RFC 5929) or `absl::nullopt`, if not supported for this signature algorithm. +absl::optional<DigestAlgorithm> GetTlsServerEndpointDigestAlgorithm( + SignatureAlgorithm alg); + +} // namespace net + +#endif // NET_CERT_PKI_SIGNATURE_ALGORITHM_H_ diff --git a/chromium/net/cert/pki/signature_algorithm_unittest.cc b/chromium/net/cert/pki/signature_algorithm_unittest.cc new file mode 100644 index 00000000000..2247675ca76 --- /dev/null +++ b/chromium/net/cert/pki/signature_algorithm_unittest.cc @@ -0,0 +1,1468 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/signature_algorithm.h" + +#include <memory> + +#include "base/containers/span.h" +#include "base/files/file_util.h" +#include "base/strings/string_number_conversions.h" +#include "net/cert/pem.h" +#include "net/cert/pki/cert_errors.h" +#include "net/der/input.h" +#include "net/der/parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +// Parses a SignatureAlgorithm given an empty DER input. +TEST(SignatureAlgorithmTest, ParseDerEmpty) { + CertErrors errors; + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(), &errors)); + // TODO(crbug.com/634443): Test the errors. + // EXPECT_FALSE(errors.empty()); +} + +// Parses a SignatureAlgorithm given invalid DER input. +TEST(SignatureAlgorithmTest, ParseDerBogus) { + const uint8_t kData[] = {0x00}; + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a SignatureAlgorithm with an unsupported algorithm OID. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 66 (bogus) +TEST(SignatureAlgorithmTest, ParseDerRsaPssUnsupportedAlgorithmOid) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x03, // SEQUENCE (3 bytes) + 0x06, 0x01, // OBJECT IDENTIFIER (1 bytes) + 0x42, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a sha1WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.5 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha1WithRSAEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha1); +} + +// Parses a sha1WithRSAEncryption which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.5 +TEST(SignatureAlgorithmTest, ParseDerSha1WithRSAEncryptionNoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha1); +} + +// Parses a sha1WithRSAEncryption which contains an unexpected parameters +// field. Instead of being NULL it is an integer. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.5 +// INTEGER 0 +TEST(SignatureAlgorithmTest, ParseDerSha1WithRSAEncryptionNonNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0E, // SEQUENCE (14 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + 0x02, 0x01, 0x00, // INTEGER (1 byte) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a sha1WithRSASignature which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.3.14.3.2.29 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha1WithRSASignatureNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x05, // OBJECT IDENTIFIER (5 bytes) + 0x2b, 0x0e, 0x03, 0x02, 0x1d, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha1); +} + +// Parses a sha1WithRSASignature which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.3.14.3.2.29 +TEST(SignatureAlgorithmTest, ParseDerSha1WithRSASignatureNoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x07, // SEQUENCE (7 bytes) + 0x06, 0x05, // OBJECT IDENTIFIER (5 bytes) + 0x2b, 0x0e, 0x03, 0x02, 0x1d, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha1); +} + +// Parses a sha1WithRSAEncryption which contains values after the sequence. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.5 +// NULL +// INTEGER 0 +TEST(SignatureAlgorithmTest, ParseDerSha1WithRsaEncryptionDataAfterSequence) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + 0x05, 0x00, // NULL (0 bytes) + 0x02, 0x01, 0x00, // INTEGER (1 byte) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a sha1WithRSAEncryption which contains a bad NULL parameters field. +// Normally NULL is encoded as {0x05, 0x00} (tag for NULL and length of 0). Here +// NULL is encoded as having a length of 1 instead, followed by data 0x09. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.5 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha1WithRSAEncryptionBadNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0E, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + 0x05, 0x01, 0x09, // NULL (1 byte) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a sha1WithRSAEncryption which contains a NULL parameters field, +// followed by an integer. +// +// SEQUENCE (3 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.5 +// NULL +// INTEGER 0 +TEST(SignatureAlgorithmTest, + ParseDerSha1WithRSAEncryptionNullParamsThenInteger) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x10, // SEQUENCE (16 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, + 0x05, 0x00, // NULL (0 bytes) + 0x02, 0x01, 0x00, // INTEGER (1 byte) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a SignatureAlgorithm given DER which does not encode a sequence. +// +// INTEGER 0 +TEST(SignatureAlgorithmTest, ParseDerNotASequence) { + // clang-format off + const uint8_t kData[] = { + 0x02, 0x01, 0x00, // INTEGER (1 byte) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a sha256WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.11 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha256WithRSAEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha256); +} + +// Parses a sha256WithRSAEncryption which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.11 +TEST(SignatureAlgorithmTest, ParseDerSha256WithRSAEncryptionNoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha256); +} + +// Parses a sha384WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.12 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha384WithRSAEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha384); +} + +// Parses a sha384WithRSAEncryption which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.12 +TEST(SignatureAlgorithmTest, ParseDerSha384WithRSAEncryptionNoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha384); +} + +// Parses a sha512WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.13 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha512WithRSAEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha512); +} + +// Parses a sha512WithRSAEncryption which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.13 +TEST(SignatureAlgorithmTest, ParseDerSha512WithRSAEncryptionNoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Sha512); +} + +// Parses a sha224WithRSAEncryption which contains a NULL parameters field. +// This fails because the parsing code does not enumerate this OID (even though +// it is in fact valid). +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.14 +// NULL +TEST(SignatureAlgorithmTest, ParseDerSha224WithRSAEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0e, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a ecdsa-with-SHA1 which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.1 +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA1NoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x07, // OBJECT IDENTIFIER (7 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kEcdsaSha1); +} + +// Parses a ecdsa-with-SHA1 which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.1 +// NULL +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA1NullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x07, // OBJECT IDENTIFIER (7 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a ecdsa-with-SHA256 which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.3.2 +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA256NoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0A, // SEQUENCE (10 bytes) + 0x06, 0x08, // OBJECT IDENTIFIER (8 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kEcdsaSha256); +} + +// Parses a ecdsa-with-SHA256 which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.3.2 +// NULL +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA256NullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0C, // SEQUENCE (12 bytes) + 0x06, 0x08, // OBJECT IDENTIFIER (8 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a ecdsa-with-SHA384 which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.3.3 +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA384NoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0A, // SEQUENCE (10 bytes) + 0x06, 0x08, // OBJECT IDENTIFIER (8 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kEcdsaSha384); +} + +// Parses a ecdsa-with-SHA384 which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.3.3 +// NULL +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA384NullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0C, // SEQUENCE (12 bytes) + 0x06, 0x08, // OBJECT IDENTIFIER (8 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a ecdsa-with-SHA512 which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.3.4 +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA512NoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0A, // SEQUENCE (10 bytes) + 0x06, 0x08, // OBJECT IDENTIFIER (8 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kEcdsaSha512); +} + +// Parses a ecdsa-with-SHA512 which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.10045.4.3.4 +// NULL +TEST(SignatureAlgorithmTest, ParseDerEcdsaWithSHA512NullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0C, // SEQUENCE (12 bytes) + 0x06, 0x08, // OBJECT IDENTIFIER (8 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses SHA256 and a salt length of 32. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (4 elem) +// [0] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 +// NULL +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 +// NULL +// [2] (1 elem) +// INTEGER 32 +TEST(SignatureAlgorithmTest, ParseDerRsaPss) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x41, // SEQUENCE (65 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x34, // SEQUENCE (52 bytes) + 0xA0, 0x0F, // [0] (15 bytes) + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, // NULL (0 bytes) + 0xA1, 0x1C, // [1] (28 bytes) + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08, + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, // NULL (0 bytes) + 0xA2, 0x03, // [2] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x20, + + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPssSha256); +} + +// Parses a rsaPss algorithm that has an empty parameters. This encodes the +// default, SHA-1, which we do not support. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (0 elem) +TEST(SignatureAlgorithmTest, ParseDerRsaPssEmptyParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x00, // SEQUENCE (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that has NULL parameters. This fails. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that has no parameters. This fails. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +TEST(SignatureAlgorithmTest, ParseDerRsaPssNoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that has data after the parameters sequence. +// +// SEQUENCE (3 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (0 elem) +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssDataAfterParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0F, // SEQUENCE (15 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x00, // SEQUENCE (0 bytes) + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that has unrecognized data (NULL) within the +// parameters sequence. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (2 elem) +// [2] (1 elem) +// INTEGER 23 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssNullInsideParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x14, // SEQUENCE (62 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x07, // SEQUENCE (5 bytes) + 0xA2, 0x03, // [2] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x17, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that has an unsupported trailer value (2). Only +// trailer values of 1 are allowed by RFC 4055. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [3] (1 elem) +// INTEGER 2 +TEST(SignatureAlgorithmTest, ParseDerRsaPssUnsupportedTrailer) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x12, // SEQUENCE (18 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x05, // SEQUENCE (5 bytes) + 0xA3, 0x03, // [3] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x02, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that has extra data appearing after the trailer in +// the [3] section. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [3] (2 elem) +// INTEGER 1 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssBadTrailer) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x14, // SEQUENCE (20 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x07, // SEQUENCE (7 bytes) + 0xA3, 0x05, // [3] (5 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x01, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses SHA384 for the hash, and leaves the rest +// as defaults, specifying a SHA-1 MGF-1 hash. This fails because we require +// the hashes match. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [0] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.2 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssNonDefaultHash) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x1E, // SEQUENCE (30 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x11, // SEQUENCE (17 bytes) + 0xA0, 0x0F, // [0] (15 bytes) + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses an invalid hash algorithm (twiddled the +// bytes for the SHA-384 OID a bit). +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [0] (1 elem) +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 2.16.840.2.103.19.4.2.2 +TEST(SignatureAlgorithmTest, ParseDerRsaPssUnsupportedHashOid) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x1C, // SEQUENCE (28 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x0F, // SEQUENCE (15 bytes) + 0xA0, 0x0D, // [0] (13 bytes) + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x02, 0x67, 0x13, 0x04, 0x02, 0x02, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses SHA512 MGF1 for the mask gen, and +// defaults (SHA-1) for the rest. This fails because we require the hashes +// match. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.3 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssNonDefaultMaskGen) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x2B, // SEQUENCE (43 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x1E, // SEQUENCE (30 bytes) + 0xA1, 0x1C, // [1] (28 bytes) + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08, + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses a mask gen with an unrecognized OID +// (twiddled some of the bits). +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113618.1.2.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.3 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssUnsupportedMaskGen) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x2B, // SEQUENCE (43 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x1E, // SEQUENCE (30 bytes) + 0xA1, 0x1C, // [1] (28 bytes) + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x52, 0x01, 0x02, 0x08, + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses SHA256 for the hash, and SHA512 for the +// MGF1. This fails because we require the hashes match. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (2 elem) +// [0] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 +// NULL +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.3 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssNonDefaultHashAndMaskGen) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x3C, // SEQUENCE (60 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x2F, // SEQUENCE (47 bytes) + 0xA0, 0x0F, // [0] (15 bytes) + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, // NULL (0 bytes) + 0xA1, 0x1C, // [1] (28 bytes) + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08, + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that uses SHA256 for the hash, and SHA256 for the +// MGF1, and a salt length of 10. This fails because we require a standard salt +// length. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (3 elem) +// [0] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 +// NULL +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 +// NULL +// [2] (1 elem) +// INTEGER 10 +TEST(SignatureAlgorithmTest, ParseDerRsaPssNonDefaultHashAndMaskGenAndSalt) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x41, // SEQUENCE (65 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x34, // SEQUENCE (52 bytes) + 0xA0, 0x0F, // [0] (15 bytes) + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, // NULL (0 bytes) + 0xA1, 0x1C, // [1] (28 bytes) + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08, + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, // NULL (0 bytes) + 0xA2, 0x03, // [2] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x0A, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that specifies default hash (SHA1). +// It is invalid to specify the default. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [0] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.3.14.3.2.26 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssSpecifiedDefaultHash) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0xA0, 0x0B, // [0] (11 bytes) + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x05, // OBJECT IDENTIFIER (5 bytes) + 0x2B, 0x0E, 0x03, 0x02, 0x1A, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that specifies default mask gen algorithm (SHA1). +// It is invalid to specify the default. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.3.14.3.2.26 +// NULL +TEST(SignatureAlgorithmTest, ParseDerRsaPssSpecifiedDefaultMaskGen) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x27, // SEQUENCE (39 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x1A, // SEQUENCE (26 bytes) + 0xA1, 0x18, // [1] (24 bytes) + 0x30, 0x16, // SEQUENCE (22 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08, + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x05, // OBJECT IDENTIFIER (5 bytes) + 0x2B, 0x0E, 0x03, 0x02, 0x1A, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that specifies default salt length. +// It is invalid to specify the default. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (1 elem) +// [2] (1 elem) +// INTEGER 20 +TEST(SignatureAlgorithmTest, ParseDerRsaPssSpecifiedDefaultSaltLength) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x12, // SEQUENCE (18 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x05, // SEQUENCE (5 bytes) + 0xA2, 0x03, // [2] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x14, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that specifies default trailer field. +// It is invalid to specify the default. +TEST(SignatureAlgorithmTest, ParseDerRsaPssSpecifiedDefaultTrailerField) { + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 32 } + // } + // [3] { + // INTEGER { 1 } + // } + // } + // } + const uint8_t kData[] = { + 0x30, 0x46, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x39, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20, 0xa3, 0x03, 0x02, 0x01, 0x01}; + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +// Parses a rsaPss algorithm that specifies multiple default parameter values. +// It is invalid to specify a default value. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.10 +// SEQUENCE (3 elem) +// [0] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.3.14.3.2.26 +// NULL +// [1] (1 elem) +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.8 +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.3.14.3.2.26 +// NULL +// [2] (1 elem) +// INTEGER 20 +// [3] (1 elem) +// INTEGER 1 +TEST(SignatureAlgorithmTest, ParseDerRsaPssMultipleDefaultParameterValues) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x3E, // SEQUENCE (62 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A, + 0x30, 0x31, // SEQUENCE (49 bytes) + 0xA0, 0x0B, // [0] (11 bytes) + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x05, // OBJECT IDENTIFIER (5 bytes) + 0x2B, 0x0E, 0x03, 0x02, 0x1A, + 0x05, 0x00, // NULL (0 bytes) + 0xA1, 0x18, // [1] (24 bytes) + 0x30, 0x16, // SEQUENCE (22 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08, + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x05, // OBJECT IDENTIFIER (5 bytes) + 0x2B, 0x0E, 0x03, 0x02, 0x1A, + 0x05, 0x00, // NULL (0 bytes) + 0xA2, 0x03, // [2] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x14, + 0xA3, 0x03, // [3] (3 bytes) + 0x02, 0x01, // INTEGER (1 byte) + 0x01, + }; + // clang-format on + EXPECT_FALSE(ParseSignatureAlgorithm(der::Input(kData), nullptr)); +} + +TEST(SignatureAlgorithmTest, ParseRsaPss) { + // Test data generated with https://github.com/google/der-ascii. + struct { + std::vector<uint8_t> data; + SignatureAlgorithm expected; + } kValidTests[] = { + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 32 } + // } + // } + // } + {{0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20}, + SignatureAlgorithm::kRsaPssSha256}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha384 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha384 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 48 } + // } + // } + // } + {{0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x30}, + SignatureAlgorithm::kRsaPssSha384}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha512 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.3 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha512 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.3 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 64 } + // } + // } + // } + {{0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x40}, + SignatureAlgorithm::kRsaPssSha512}, + + // The same inputs as above, but the NULLs in the digest algorithms are + // omitted. + {{0x30, 0x3d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0a, 0x30, 0x30, 0xa0, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0xa1, 0x1a, 0x30, + 0x18, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x08, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x01, 0xa2, 0x03, 0x02, 0x01, 0x20}, + SignatureAlgorithm::kRsaPssSha256}, + {{0x30, 0x3d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0a, 0x30, 0x30, 0xa0, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0xa1, 0x1a, 0x30, + 0x18, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x08, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x02, 0xa2, 0x03, 0x02, 0x01, 0x30}, + SignatureAlgorithm::kRsaPssSha384}, + {{0x30, 0x3d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0a, 0x30, 0x30, 0xa0, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0xa1, 0x1a, 0x30, + 0x18, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x08, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x03, 0xa2, 0x03, 0x02, 0x01, 0x40}, + SignatureAlgorithm::kRsaPssSha512}}; + for (const auto& t : kValidTests) { + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(t.data.data(), t.data.size()), + nullptr), + t.expected); + } + + struct { + std::vector<uint8_t> data; + } kInvalidTests[] = { + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha384 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } + // NULL {} + // } + // } + // } + // } + // } + {{0x30, 0x3c, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0a, 0x30, 0x2f, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa1, + 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00}}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # md5 + // OBJECT_IDENTIFIER { 1.2.840.113549.2.5 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # md5 + // OBJECT_IDENTIFIER { 1.2.840.113549.2.5 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 16 } + // } + // } + // } + {{0x30, 0x3f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0a, 0x30, 0x32, 0xa0, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0xa1, 0x1b, + 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x08, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x02, 0x05, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x10}}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // # SHA-1 with salt length 20 is the default. + // SEQUENCE {} + // } + {{0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x00}}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [2] { + // INTEGER { 21 } + // } + // } + // } + {{0x30, 0x12, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0a, 0x30, 0x05, 0xa2, 0x03, 0x02, 0x01, 0x15}}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha256 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 33 } + // } + // } + // } + {{0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x21}}, + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha384 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha384 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 49 } + // } + // } + // } + {{0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x31}}, + + // SEQUENCE { + // # rsassa-pss + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } + // SEQUENCE { + // [0] { + // SEQUENCE { + // # sha512 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.3 } + // NULL {} + // } + // } + // [1] { + // SEQUENCE { + // # mgf1 + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } + // SEQUENCE { + // # sha512 + // OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.3 } + // NULL {} + // } + // } + // } + // [2] { + // INTEGER { 65 } + // } + // } + // } + {{0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0a, 0x30, 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, + 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x41}}, + }; + for (const auto& t : kInvalidTests) { + EXPECT_FALSE(ParseSignatureAlgorithm( + der::Input(t.data.data(), t.data.size()), nullptr)); + } +} + +// Parses a md5WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.4 +// NULL +TEST(SignatureAlgorithmTest, ParseDerMd5WithRsaEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Md5); +} + +// Parses a md4WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.3 +// NULL +TEST(SignatureAlgorithmTest, ParseDerMd4WithRsaEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x03, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Md4); +} + +// Parses a md2WithRSAEncryption which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.113549.1.1.2 +// NULL +TEST(SignatureAlgorithmTest, ParseDerMd2WithRsaEncryptionNullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0D, // SEQUENCE (13 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kRsaPkcs1Md2); +} + +// Parses a dsaWithSha1 which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 1.2.840.10040.4.3 +TEST(SignatureAlgorithmTest, ParseDerDsaWithSha1NoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x09, // SEQUENCE (9 bytes) + 0x06, 0x07, // OBJECT IDENTIFIER (7 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03, + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kDsaSha1); +} + +// Parses a dsaWithSha1 which contains a NULL parameters field. +// +// SEQUENCE (2 elem) +// OBJECT IDENTIFIER 1.2.840.10040.4.3 +// NULL +TEST(SignatureAlgorithmTest, ParseDerDsaWithSha1NullParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (9 bytes) + 0x06, 0x07, // OBJECT IDENTIFIER (7 bytes) + 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03, + 0x05, 0x00, // NULL (0 bytes) + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kDsaSha1); +} + +// Parses a dsaWithSha256 which contains no parameters field. +// +// SEQUENCE (1 elem) +// OBJECT IDENTIFIER 2.16.840.1.101.3.4.3.2 +TEST(SignatureAlgorithmTest, ParseDerDsaWithSha256NoParams) { + // clang-format off + const uint8_t kData[] = { + 0x30, 0x0B, // SEQUENCE (11 bytes) + 0x06, 0x09, // OBJECT IDENTIFIER (9 bytes) + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02 + }; + // clang-format on + EXPECT_EQ(ParseSignatureAlgorithm(der::Input(kData), nullptr), + SignatureAlgorithm::kDsaSha256); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/simple_path_builder_delegate.cc b/chromium/net/cert/pki/simple_path_builder_delegate.cc new file mode 100644 index 00000000000..aa961254d3a --- /dev/null +++ b/chromium/net/cert/pki/simple_path_builder_delegate.cc @@ -0,0 +1,126 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/simple_path_builder_delegate.h" + +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/cert/pki/verify_signed_data.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec.h" +#include "third_party/boringssl/src/include/openssl/ec_key.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/nid.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" + +namespace net { + +DEFINE_CERT_ERROR_ID(SimplePathBuilderDelegate::kRsaModulusTooSmall, + "RSA modulus too small"); + +namespace { + +DEFINE_CERT_ERROR_ID(kUnacceptableCurveForEcdsa, + "Only P-256, P-384, P-521 are supported for ECDSA"); + +bool IsAcceptableCurveForEcdsa(int curve_nid) { + switch (curve_nid) { + case NID_X9_62_prime256v1: + case NID_secp384r1: + case NID_secp521r1: + return true; + } + + return false; +} + +} // namespace + +SimplePathBuilderDelegate::SimplePathBuilderDelegate( + size_t min_rsa_modulus_length_bits, + DigestPolicy digest_policy) + : min_rsa_modulus_length_bits_(min_rsa_modulus_length_bits), + digest_policy_(digest_policy) {} + +void SimplePathBuilderDelegate::CheckPathAfterVerification( + const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path) { + // Do nothing - consider all candidate paths valid. +} + +bool SimplePathBuilderDelegate::IsSignatureAlgorithmAcceptable( + SignatureAlgorithm algorithm, + CertErrors* errors) { + switch (algorithm) { + case SignatureAlgorithm::kRsaPkcs1Sha1: + case SignatureAlgorithm::kEcdsaSha1: + return digest_policy_ == DigestPolicy::kWeakAllowSha1; + + case SignatureAlgorithm::kRsaPkcs1Sha256: + case SignatureAlgorithm::kRsaPkcs1Sha384: + case SignatureAlgorithm::kRsaPkcs1Sha512: + case SignatureAlgorithm::kEcdsaSha256: + case SignatureAlgorithm::kEcdsaSha384: + case SignatureAlgorithm::kEcdsaSha512: + case SignatureAlgorithm::kRsaPssSha256: + case SignatureAlgorithm::kRsaPssSha384: + case SignatureAlgorithm::kRsaPssSha512: + return true; + + case SignatureAlgorithm::kRsaPkcs1Md2: + case SignatureAlgorithm::kRsaPkcs1Md4: + case SignatureAlgorithm::kRsaPkcs1Md5: + case SignatureAlgorithm::kDsaSha1: + case SignatureAlgorithm::kDsaSha256: + // TODO(https://crbug.com/1321688): We do not implement DSA, MD2, MD4, or + // MD5 anyway. Remove them from the parser altogether, so code does not + // need to handle them. + return false; + } +} + +bool SimplePathBuilderDelegate::IsPublicKeyAcceptable(EVP_PKEY* public_key, + CertErrors* errors) { + int pkey_id = EVP_PKEY_id(public_key); + if (pkey_id == EVP_PKEY_RSA) { + // Extract the modulus length from the key. + RSA* rsa = EVP_PKEY_get0_RSA(public_key); + if (!rsa) + return false; + unsigned int modulus_length_bits = RSA_bits(rsa); + + if (modulus_length_bits < min_rsa_modulus_length_bits_) { + errors->AddError( + kRsaModulusTooSmall, + CreateCertErrorParams2SizeT("actual", modulus_length_bits, "minimum", + min_rsa_modulus_length_bits_)); + return false; + } + + return true; + } + + if (pkey_id == EVP_PKEY_EC) { + // Extract the curve name. + EC_KEY* ec = EVP_PKEY_get0_EC_KEY(public_key); + if (!ec) + return false; // Unexpected. + int curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + + if (!IsAcceptableCurveForEcdsa(curve_nid)) { + errors->AddError(kUnacceptableCurveForEcdsa); + return false; + } + + return true; + } + + // Unexpected key type. + return false; +} + +} // namespace net diff --git a/chromium/net/cert/pki/simple_path_builder_delegate.h b/chromium/net/cert/pki/simple_path_builder_delegate.h new file mode 100644 index 00000000000..db1b368c215 --- /dev/null +++ b/chromium/net/cert/pki/simple_path_builder_delegate.h @@ -0,0 +1,65 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_SIMPLE_PATH_BUILDER_DELEGATE_H_ +#define NET_CERT_PKI_SIMPLE_PATH_BUILDER_DELEGATE_H_ + +#include <stddef.h> + +#include "net/base/net_export.h" +#include "net/cert/pki/path_builder.h" +#include "net/cert/pki/signature_algorithm.h" + +namespace net { + +class CertErrors; + +// SimplePathBuilderDelegate is an implementation of CertPathBuilderDelegate +// that uses some default policies: +// +// * RSA public keys must be >= |min_rsa_modulus_length_bits|. +// * Signature algorithm can be RSA PKCS#1, RSASSA-PSS or ECDSA +// * Digest algorithm can be SHA256, SHA348 or SHA512. +// * If the |digest_policy| was set to kAllowSha1, then SHA1 is +// additionally accepted. +// * EC named curve can be P-256, P-384, P-521. +class NET_EXPORT SimplePathBuilderDelegate : public CertPathBuilderDelegate { + public: + enum class DigestPolicy { + // Accepts digests of SHA256, SHA348 or SHA512 + kStrong, + + // Accepts everything that kStrong does, plus SHA1. + kWeakAllowSha1, + + kMaxValue = kWeakAllowSha1 + }; + + // Error emitted when a public key is rejected because it is an RSA key with a + // modulus size that is too small. + static const CertErrorId kRsaModulusTooSmall; + + SimplePathBuilderDelegate(size_t min_rsa_modulus_length_bits, + DigestPolicy digest_policy); + + // Accepts RSA PKCS#1, RSASSA-PSS or ECDA using any of the SHA* digests + // (including SHA1). + bool IsSignatureAlgorithmAcceptable(SignatureAlgorithm signature_algorithm, + CertErrors* errors) override; + + // Requires RSA keys be >= |min_rsa_modulus_length_bits_|. + bool IsPublicKeyAcceptable(EVP_PKEY* public_key, CertErrors* errors) override; + + // No-op implementation. + void CheckPathAfterVerification(const CertPathBuilder& path_builder, + CertPathBuilderResultPath* path) override; + + private: + const size_t min_rsa_modulus_length_bits_; + const DigestPolicy digest_policy_; +}; + +} // namespace net + +#endif // NET_CERT_PKI_SIMPLE_PATH_BUILDER_DELEGATE_H_ diff --git a/chromium/net/cert/pki/simple_path_builder_delegate_unittest.cc b/chromium/net/cert/pki/simple_path_builder_delegate_unittest.cc new file mode 100644 index 00000000000..e9613a1e61f --- /dev/null +++ b/chromium/net/cert/pki/simple_path_builder_delegate_unittest.cc @@ -0,0 +1,109 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "net/cert/pki/simple_path_builder_delegate.h" + +#include <memory> +#include <set> + +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/cert/pki/test_helpers.h" +#include "net/cert/pki/verify_signed_data.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/nid.h" + +namespace net { + +namespace { + +// Reads the public key and algorithm from the test data at |file_name|. +void ReadTestCase(const char* file_name, + SignatureAlgorithm* signature_algorithm, + bssl::UniquePtr<EVP_PKEY>* public_key) { + std::string path = + std::string("net/data/verify_signed_data_unittest/") + file_name; + + std::string public_key_str; + std::string algorithm_str; + + const PemBlockMapping mappings[] = { + {"PUBLIC KEY", &public_key_str}, + {"ALGORITHM", &algorithm_str}, + }; + + ASSERT_TRUE(ReadTestDataFromPemFile(path, mappings)); + + CertErrors algorithm_errors; + absl::optional<SignatureAlgorithm> sigalg_opt = + ParseSignatureAlgorithm(der::Input(&algorithm_str), &algorithm_errors); + ASSERT_TRUE(sigalg_opt) << algorithm_errors.ToDebugString(); + *signature_algorithm = *sigalg_opt; + + ASSERT_TRUE(ParsePublicKey(der::Input(&public_key_str), public_key)); +} + +class SimplePathBuilderDelegate1024SuccessTest + : public ::testing::TestWithParam<const char*> {}; + +const char* kSuccess1024Filenames[] = { + "rsa-pkcs1-sha1.pem", "rsa-pkcs1-sha256.pem", + "rsa2048-pkcs1-sha512.pem", "ecdsa-secp384r1-sha256.pem", + "ecdsa-prime256v1-sha512.pem", "rsa-pss-sha256.pem", + "ecdsa-secp384r1-sha256.pem", "ecdsa-prime256v1-sha512.pem", +}; + +INSTANTIATE_TEST_SUITE_P(All, + SimplePathBuilderDelegate1024SuccessTest, + ::testing::ValuesIn(kSuccess1024Filenames)); + +TEST_P(SimplePathBuilderDelegate1024SuccessTest, IsAcceptableSignatureAndKey) { + SignatureAlgorithm signature_algorithm; + bssl::UniquePtr<EVP_PKEY> public_key; + ASSERT_NO_FATAL_FAILURE( + ReadTestCase(GetParam(), &signature_algorithm, &public_key)); + ASSERT_TRUE(public_key); + + CertErrors errors; + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + + EXPECT_TRUE( + delegate.IsSignatureAlgorithmAcceptable(signature_algorithm, &errors)); + + EXPECT_TRUE(delegate.IsPublicKeyAcceptable(public_key.get(), &errors)); +} + +class SimplePathBuilderDelegate2048FailTest + : public ::testing::TestWithParam<const char*> {}; + +const char* kFail2048Filenames[] = {"rsa-pkcs1-sha1.pem", + "rsa-pkcs1-sha256.pem"}; + +INSTANTIATE_TEST_SUITE_P(All, + SimplePathBuilderDelegate2048FailTest, + ::testing::ValuesIn(kFail2048Filenames)); + +TEST_P(SimplePathBuilderDelegate2048FailTest, RsaKeySmallerThan2048) { + SignatureAlgorithm signature_algorithm; + bssl::UniquePtr<EVP_PKEY> public_key; + ASSERT_NO_FATAL_FAILURE( + ReadTestCase(GetParam(), &signature_algorithm, &public_key)); + ASSERT_TRUE(public_key); + + CertErrors errors; + SimplePathBuilderDelegate delegate( + 2048, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + + EXPECT_TRUE( + delegate.IsSignatureAlgorithmAcceptable(signature_algorithm, &errors)); + + EXPECT_FALSE(delegate.IsPublicKeyAcceptable(public_key.get(), &errors)); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/test_helpers.cc b/chromium/net/cert/pki/test_helpers.cc new file mode 100644 index 00000000000..914b6a3921a --- /dev/null +++ b/chromium/net/cert/pki/test_helpers.cc @@ -0,0 +1,377 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/test_helpers.h" + +#include "base/base64.h" +#include "base/base_paths.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "net/cert/pem.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/der/parser.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +#include <sstream> + +namespace net { + +namespace { + +bool GetValue(base::StringPiece prefix, + base::StringPiece line, + std::string* value, + bool* has_value) { + if (!base::StartsWith(line, prefix)) + return false; + + if (*has_value) { + ADD_FAILURE() << "Duplicated " << prefix; + return false; + } + + *has_value = true; + *value = std::string(line.substr(prefix.size())); + return true; +} + +} // namespace + +namespace der { + +void PrintTo(const Input& data, ::std::ostream* os) { + std::string b64; + base::Base64Encode( + base::StringPiece(reinterpret_cast<const char*>(data.UnsafeData()), + data.Length()), + &b64); + + *os << "[" << b64 << "]"; +} + +} // namespace der + +der::Input SequenceValueFromString(const std::string* s) { + der::Parser parser((der::Input(s))); + der::Input data; + if (!parser.ReadTag(der::kSequence, &data)) { + ADD_FAILURE(); + return der::Input(); + } + if (parser.HasMore()) { + ADD_FAILURE(); + return der::Input(); + } + return data; +} + +::testing::AssertionResult ReadTestDataFromPemFile( + const std::string& file_path_ascii, + const PemBlockMapping* mappings, + size_t mappings_length) { + // Compute the full path, relative to the src/ directory. + base::FilePath src_root; + base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root); + base::FilePath filepath = src_root.AppendASCII(file_path_ascii); + + // Read the full contents of the PEM file. + std::string file_data; + if (!base::ReadFileToString(filepath, &file_data)) { + return ::testing::AssertionFailure() + << "Couldn't read file: " << filepath.value(); + } + + // mappings_copy is used to keep track of which mappings have already been + // satisfied (by nulling the |value| field). This is used to track when + // blocks are mulitply defined. + std::vector<PemBlockMapping> mappings_copy(mappings, + mappings + mappings_length); + + // Build the |pem_headers| vector needed for PEMTokenzier. + std::vector<std::string> pem_headers; + for (const auto& mapping : mappings_copy) { + pem_headers.push_back(mapping.block_name); + } + + PEMTokenizer pem_tokenizer(file_data, pem_headers); + while (pem_tokenizer.GetNext()) { + for (auto& mapping : mappings_copy) { + // Find the mapping for this block type. + if (pem_tokenizer.block_type() == mapping.block_name) { + if (!mapping.value) { + return ::testing::AssertionFailure() + << "PEM block defined multiple times: " << mapping.block_name; + } + + // Copy the data to the result. + mapping.value->assign(pem_tokenizer.data()); + + // Mark the mapping as having been satisfied. + mapping.value = nullptr; + } + } + } + + // Ensure that all specified blocks were found. + for (const auto& mapping : mappings_copy) { + if (mapping.value && !mapping.optional) { + return ::testing::AssertionFailure() + << "PEM block missing: " << mapping.block_name; + } + } + + return ::testing::AssertionSuccess(); +} + +VerifyCertChainTest::VerifyCertChainTest() + : user_initial_policy_set{der::Input(kAnyPolicyOid)} {} +VerifyCertChainTest::~VerifyCertChainTest() = default; + +bool VerifyCertChainTest::HasHighSeverityErrors() const { + // This function assumes that high severity warnings are prefixed with + // "ERROR: " and warnings are prefixed with "WARNING: ". This is an + // implementation detail of CertError::ToDebugString). + // + // Do a quick sanity-check to confirm this. + CertError error(CertError::SEVERITY_HIGH, "unused", nullptr); + EXPECT_EQ("ERROR: unused\n", error.ToDebugString()); + CertError warning(CertError::SEVERITY_WARNING, "unused", nullptr); + EXPECT_EQ("WARNING: unused\n", warning.ToDebugString()); + + // Do a simple substring test (not perfect, but good enough for our test + // corpus). + return expected_errors.find("ERROR: ") != std::string::npos; +} + +bool ReadCertChainFromFile(const std::string& file_path_ascii, + ParsedCertificateList* chain) { + // Reset all the out parameters to their defaults. + *chain = ParsedCertificateList(); + + std::string file_data = ReadTestFileToString(file_path_ascii); + if (file_data.empty()) + return false; + + std::vector<std::string> pem_headers = {"CERTIFICATE"}; + + PEMTokenizer pem_tokenizer(file_data, pem_headers); + while (pem_tokenizer.GetNext()) { + const std::string& block_data = pem_tokenizer.data(); + + CertErrors errors; + if (!ParsedCertificate::CreateAndAddToVector( + bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( + reinterpret_cast<const uint8_t*>(block_data.data()), + block_data.size(), nullptr)), + {}, chain, &errors)) { + ADD_FAILURE() << errors.ToDebugString(); + return false; + } + } + + return true; +} + +scoped_refptr<ParsedCertificate> ReadCertFromFile( + const std::string& file_path_ascii) { + ParsedCertificateList chain; + if (!ReadCertChainFromFile(file_path_ascii, &chain)) + return nullptr; + if (chain.size() != 1) + return nullptr; + return chain[0]; +} + +bool ReadVerifyCertChainTestFromFile(const std::string& file_path_ascii, + VerifyCertChainTest* test) { + // Reset all the out parameters to their defaults. + *test = {}; + + std::string file_data = ReadTestFileToString(file_path_ascii); + if (file_data.empty()) + return false; + + bool has_chain = false; + bool has_trust = false; + bool has_time = false; + bool has_errors = false; + bool has_key_purpose = false; + + base::StringPiece kExpectedErrors = "expected_errors:"; + + std::istringstream stream(file_data); + for (std::string line; std::getline(stream, line, '\n');) { + 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; + } + line = line.substr(start, end + 1); + if (line.empty()) { + continue; + } + base::StringPiece line_piece(line); + + std::string value; + + // For details on the file format refer to: + // net/data/verify_certificate_chain_unittest/README. + if (GetValue("chain: ", line_piece, &value, &has_chain)) { + // Interpret the |chain| path as being relative to the .test file. + size_t slash = file_path_ascii.rfind('/'); + if (slash == std::string::npos) { + ADD_FAILURE() << "Bad path - expecting slashes"; + return false; + } + std::string chain_path = file_path_ascii.substr(0, slash) + "/" + value; + + ReadCertChainFromFile(chain_path, &test->chain); + } else if (GetValue("utc_time: ", line_piece, &value, &has_time)) { + if (value == "DEFAULT") { + value = "211005120000Z"; + } + if (!der::ParseUTCTime(der::Input(&value), &test->time)) { + ADD_FAILURE() << "Failed parsing UTC time"; + return false; + } + } else if (GetValue("key_purpose: ", line_piece, &value, + &has_key_purpose)) { + if (value == "ANY_EKU") { + test->key_purpose = KeyPurpose::ANY_EKU; + } else if (value == "SERVER_AUTH") { + test->key_purpose = KeyPurpose::SERVER_AUTH; + } else if (value == "CLIENT_AUTH") { + test->key_purpose = KeyPurpose::CLIENT_AUTH; + } else { + ADD_FAILURE() << "Unrecognized key_purpose: " << value; + return false; + } + } else if (GetValue("last_cert_trust: ", line_piece, &value, &has_trust)) { + if (value == "TRUSTED_ANCHOR") { + test->last_cert_trust = CertificateTrust::ForTrustAnchor(); + } else if (value == "TRUSTED_ANCHOR_WITH_EXPIRATION") { + test->last_cert_trust = + CertificateTrust::ForTrustAnchorEnforcingExpiration(); + } else if (value == "TRUSTED_ANCHOR_WITH_CONSTRAINTS") { + test->last_cert_trust = + CertificateTrust::ForTrustAnchorEnforcingConstraints(); + } else if (value == "DISTRUSTED") { + test->last_cert_trust = CertificateTrust::ForDistrusted(); + } else if (value == "UNSPECIFIED") { + test->last_cert_trust = CertificateTrust::ForUnspecified(); + } else { + ADD_FAILURE() << "Unrecognized last_cert_trust: " << value; + return false; + } + } else if (base::StartsWith(line_piece, "#")) { + // Skip comments. + continue; + } else if (line_piece == kExpectedErrors) { + has_errors = true; + // The errors start on the next line, and extend until the end of the + // file. + std::string prefix = + std::string("\n") + std::string(kExpectedErrors) + std::string("\n"); + size_t errors_start = file_data.find(prefix); + if (errors_start == std::string::npos) { + ADD_FAILURE() << "expected_errors not found"; + return false; + } + test->expected_errors = file_data.substr(errors_start + prefix.size()); + break; + } else { + ADD_FAILURE() << "Unknown line: " << line_piece; + return false; + } + } + + if (!has_chain) { + ADD_FAILURE() << "Missing chain: "; + return false; + } + + if (!has_trust) { + ADD_FAILURE() << "Missing last_cert_trust: "; + return false; + } + + if (!has_time) { + ADD_FAILURE() << "Missing time: "; + return false; + } + + if (!has_key_purpose) { + ADD_FAILURE() << "Missing key_purpose: "; + return false; + } + + if (!has_errors) { + ADD_FAILURE() << "Missing errors:"; + return false; + } + + return true; +} + +std::string ReadTestFileToString(const std::string& file_path_ascii) { + // Compute the full path, relative to the src/ directory. + base::FilePath src_root; + base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root); + base::FilePath filepath = src_root.AppendASCII(file_path_ascii); + + // Read the full contents of the file. + std::string file_data; + if (!base::ReadFileToString(filepath, &file_data)) { + ADD_FAILURE() << "Couldn't read file: " << filepath.value(); + return std::string(); + } + + return file_data; +} + +void VerifyCertPathErrors(const std::string& expected_errors_str, + const CertPathErrors& actual_errors, + const ParsedCertificateList& chain, + const std::string& errors_file_path) { + std::string actual_errors_str = actual_errors.ToDebugString(chain); + + if (expected_errors_str != actual_errors_str) { + ADD_FAILURE() << "Cert path errors don't match expectations (" + << errors_file_path << ")\n\n" + << "EXPECTED:\n\n" + << expected_errors_str << "\n" + << "ACTUAL:\n\n" + << actual_errors_str << "\n" + << "===> Use " + "net/data/verify_certificate_chain_unittest/" + "rebase-errors.py to rebaseline.\n"; + } +} + +void VerifyCertErrors(const std::string& expected_errors_str, + const CertErrors& actual_errors, + const std::string& errors_file_path) { + std::string actual_errors_str = actual_errors.ToDebugString(); + + if (expected_errors_str != actual_errors_str) { + ADD_FAILURE() << "Cert errors don't match expectations (" + << errors_file_path << ")\n\n" + << "EXPECTED:\n\n" + << expected_errors_str << "\n" + << "ACTUAL:\n\n" + << actual_errors_str << "\n" + << "===> Use " + "net/data/parse_certificate_unittest/" + "rebase-errors.py to rebaseline.\n"; + } +} + +} // namespace net diff --git a/chromium/net/cert/pki/test_helpers.h b/chromium/net/cert/pki/test_helpers.h new file mode 100644 index 00000000000..0fe301af316 --- /dev/null +++ b/chromium/net/cert/pki/test_helpers.h @@ -0,0 +1,155 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_TEST_HELPERS_H_ +#define NET_CERT_PKI_TEST_HELPERS_H_ + +#include <stddef.h> + +#include <ostream> +#include <string> +#include <vector> + +#include "base/memory/raw_ptr.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/trust_store.h" +#include "net/cert/pki/verify_certificate_chain.h" +#include "net/der/input.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace der { + +// This function is used by GTest to support EXPECT_EQ() for der::Input. +void PrintTo(const Input& data, ::std::ostream* os); + +} // namespace der + +// Parses |s| as a DER SEQUENCE TLV and returns a der::Input corresponding to +// the value portion. On error returns an empty der::Input and adds a gtest +// failure. +// +// The returned der::Input() is only valid so long as the input string is alive +// and is not mutated. +der::Input SequenceValueFromString(const std::string* s); + +// Helper structure that maps a PEM block header (for instance "CERTIFICATE") to +// the destination where the value for that block should be written. +struct PemBlockMapping { + // The name of the PEM header. Example "CERTIFICATE". + const char* block_name; + + // The destination where the read value should be written to. + raw_ptr<std::string> value; + + // True to indicate that the block is not required to be present. If the + // block is optional and is not present, then |value| will not be modified. + bool optional; +}; + +// ReadTestDataFromPemFile() is a helper function that reads a PEM test file +// rooted in the "src/" directory. +// +// * file_path_ascii: +// The path to the PEM file, relative to src. For instance +// "net/data/verify_signed_data_unittest/foopy.pem" +// +// * mappings: +// An array of length |mappings_length| which maps the expected PEM +// headers to the destination to write its data. +// +// The function ensures that each of the chosen mappings is satisfied exactly +// once. In other words, the header must be present (unless marked as +// optional=true), have valid data, and appear no more than once. +::testing::AssertionResult ReadTestDataFromPemFile( + const std::string& file_path_ascii, + const PemBlockMapping* mappings, + size_t mappings_length); + +// This is the same as the variant above, however it uses template magic so an +// mappings array can be passed in directly (and the correct length is +// inferred). +template <size_t N> +::testing::AssertionResult ReadTestDataFromPemFile( + const std::string& file_path_ascii, + const PemBlockMapping (&mappings)[N]) { + return ReadTestDataFromPemFile(file_path_ascii, mappings, N); +} + +// Test cases are comprised of all the parameters to certificate +// verification, as well as the expected outputs. +struct VerifyCertChainTest { + VerifyCertChainTest(); + ~VerifyCertChainTest(); + + // The chain of certificates (with the zero-th being the target). + ParsedCertificateList chain; + + // Details on the trustedness of the last certificate. + CertificateTrust last_cert_trust; + + // The time to use when verifying the chain. + der::GeneralizedTime time; + + // The Key Purpose to use when verifying the chain. + KeyPurpose key_purpose = KeyPurpose::ANY_EKU; + + InitialExplicitPolicy initial_explicit_policy = InitialExplicitPolicy::kFalse; + + std::set<der::Input> user_initial_policy_set; + + InitialPolicyMappingInhibit initial_policy_mapping_inhibit = + InitialPolicyMappingInhibit::kFalse; + + InitialAnyPolicyInhibit initial_any_policy_inhibit = + InitialAnyPolicyInhibit::kFalse; + + // The expected errors/warnings from verification (as a string). + std::string expected_errors; + + // Returns true if |expected_errors| contains any high severity errors (a + // non-empty expected_errors doesn't necessarily mean verification is + // expected to fail, as it may have contained warnings). + bool HasHighSeverityErrors() const; +}; + +// Reads a test case from |file_path_ascii| (which is relative to //src). +// Generally |file_path_ascii| will start with: +// net/data/verify_certificate_chain_unittest/ +bool ReadVerifyCertChainTestFromFile(const std::string& file_path_ascii, + VerifyCertChainTest* test); + +// Reads a certificate chain from |file_path_ascii| +bool ReadCertChainFromFile(const std::string& file_path_ascii, + ParsedCertificateList* chain); + +// Reads a certificate from |file_path_ascii|. Returns nullptr if the file +// contained more that one certificate. +scoped_refptr<ParsedCertificate> ReadCertFromFile( + const std::string& file_path_ascii); + +// Reads a data file relative to the src root directory. +std::string ReadTestFileToString(const std::string& file_path_ascii); + +// Asserts that |actual_errors| matches |expected_errors_str|. +// +// This is a helper function to simplify rebasing the error expectations when +// they originate from a test file. +void VerifyCertPathErrors(const std::string& expected_errors_str, + const CertPathErrors& actual_errors, + const ParsedCertificateList& chain, + const std::string& errors_file_path); + +// Asserts that |actual_errors| matches |expected_errors_str|. +// +// This is a helper function to simplify rebasing the error expectations when +// they originate from a test file. +void VerifyCertErrors(const std::string& expected_errors_str, + const CertErrors& actual_errors, + const std::string& errors_file_path); + +} // namespace net + +#endif // NET_CERT_PKI_TEST_HELPERS_H_ diff --git a/chromium/net/cert/pki/trust_store.cc b/chromium/net/cert/pki/trust_store.cc new file mode 100644 index 00000000000..ee504bff53f --- /dev/null +++ b/chromium/net/cert/pki/trust_store.cc @@ -0,0 +1,93 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/trust_store.h" + +#include "base/notreached.h" + +namespace net { + +CertificateTrust CertificateTrust::ForTrustAnchor() { + CertificateTrust result; + result.type = CertificateTrustType::TRUSTED_ANCHOR; + return result; +} + +CertificateTrust CertificateTrust::ForTrustAnchorEnforcingExpiration() { + CertificateTrust result; + result.type = CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION; + return result; +} + +CertificateTrust CertificateTrust::ForTrustAnchorEnforcingConstraints() { + CertificateTrust result; + result.type = CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS; + return result; +} + +CertificateTrust CertificateTrust::ForUnspecified() { + CertificateTrust result; + result.type = CertificateTrustType::UNSPECIFIED; + return result; +} + +CertificateTrust CertificateTrust::ForDistrusted() { + CertificateTrust result; + result.type = CertificateTrustType::DISTRUSTED; + return result; +} + +bool CertificateTrust::IsTrustAnchor() const { + switch (type) { + case CertificateTrustType::DISTRUSTED: + case CertificateTrustType::UNSPECIFIED: + return false; + case CertificateTrustType::TRUSTED_ANCHOR: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + return true; + } + + NOTREACHED(); + return false; +} + +bool CertificateTrust::IsDistrusted() const { + switch (type) { + case CertificateTrustType::DISTRUSTED: + return true; + case CertificateTrustType::UNSPECIFIED: + case CertificateTrustType::TRUSTED_ANCHOR: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + return false; + } + + NOTREACHED(); + return false; +} + +bool CertificateTrust::HasUnspecifiedTrust() const { + switch (type) { + case CertificateTrustType::UNSPECIFIED: + return true; + case CertificateTrustType::DISTRUSTED: + case CertificateTrustType::TRUSTED_ANCHOR: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION: + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + return false; + } + + NOTREACHED(); + return true; +} + +TrustStore::TrustStore() = default; + +void TrustStore::AsyncGetIssuersOf(const ParsedCertificate* cert, + std::unique_ptr<Request>* out_req) { + out_req->reset(); +} + +} // namespace net diff --git a/chromium/net/cert/pki/trust_store.h b/chromium/net/cert/pki/trust_store.h new file mode 100644 index 00000000000..1c3a721ea29 --- /dev/null +++ b/chromium/net/cert/pki/trust_store.h @@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_TRUST_STORE_H_ +#define NET_CERT_PKI_TRUST_STORE_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/supports_user_data.h" +#include "net/base/net_export.h" +#include "net/cert/pki/cert_issuer_source.h" +#include "net/cert/pki/parsed_certificate.h" + +namespace net { + +enum class CertificateTrustType { + // This certificate is explicitly blocked (distrusted). + DISTRUSTED, + + // The trustedness of this certificate is unknown (inherits trust from + // its issuer). + UNSPECIFIED, + + // This certificate is a trust anchor (as defined by RFC 5280). The only + // fields in the certificate that are meaningful are its name and SPKI. + TRUSTED_ANCHOR, + + // This certificate is a trust anchor which additionally has expiration + // enforced. The only fields in the certificate that are meaningful are its + // name, SPKI, and validity period. + TRUSTED_ANCHOR_WITH_EXPIRATION, + + // This certificate is a trust anchor for which some of the fields in the + // certificate (in addition to the name and SPKI) should be used during the + // verification process. See VerifyCertificateChain() for details on how + // constraints are applied. + TRUSTED_ANCHOR_WITH_CONSTRAINTS, + + LAST = TRUSTED_ANCHOR_WITH_CONSTRAINTS +}; + +// Describes the level of trust in a certificate. See CertificateTrustType for +// details. +// +// TODO(eroman): Right now this is just a glorified wrapper around an enum... +struct NET_EXPORT CertificateTrust { + static CertificateTrust ForTrustAnchor(); + static CertificateTrust ForTrustAnchorEnforcingExpiration(); + static CertificateTrust ForTrustAnchorEnforcingConstraints(); + static CertificateTrust ForUnspecified(); + static CertificateTrust ForDistrusted(); + + bool IsTrustAnchor() const; + bool IsDistrusted() const; + bool HasUnspecifiedTrust() const; + + CertificateTrustType type = CertificateTrustType::UNSPECIFIED; +}; + +// Interface for finding intermediates / trust anchors, and testing the +// trustedness of certificates. +class NET_EXPORT TrustStore : public CertIssuerSource { + public: + TrustStore(); + + TrustStore(const TrustStore&) = delete; + TrustStore& operator=(const TrustStore&) = delete; + + // Returns the trusted of |cert|, which must be non-null. + // + // Optionally, if |debug_data| is non-null, debug information may be added + // (any added Data must implement the Clone method.) The same |debug_data| + // object may be passed to multiple GetTrust calls for a single verification, + // so implementations should check whether they already added data with a + // certain key and update it instead of overwriting it. + virtual CertificateTrust GetTrust( + const ParsedCertificate* cert, + base::SupportsUserData* debug_data) const = 0; + + // Disable async issuers for TrustStore, as it isn't needed. + // TODO(mattm): Pass debug_data here too. + void AsyncGetIssuersOf(const ParsedCertificate* cert, + std::unique_ptr<Request>* out_req) final; +}; + +} // namespace net + +#endif // NET_CERT_PKI_TRUST_STORE_H_ diff --git a/chromium/net/cert/pki/trust_store_collection.cc b/chromium/net/cert/pki/trust_store_collection.cc new file mode 100644 index 00000000000..03657c4d4a0 --- /dev/null +++ b/chromium/net/cert/pki/trust_store_collection.cc @@ -0,0 +1,46 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/trust_store_collection.h" + +namespace net { + +TrustStoreCollection::TrustStoreCollection() = default; +TrustStoreCollection::~TrustStoreCollection() = default; + +void TrustStoreCollection::AddTrustStore(TrustStore* store) { + DCHECK(store); + stores_.push_back(store); +} + +void TrustStoreCollection::SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) { + for (auto* store : stores_) { + store->SyncGetIssuersOf(cert, issuers); + } +} + +CertificateTrust TrustStoreCollection::GetTrust( + const ParsedCertificate* cert, + base::SupportsUserData* debug_data) const { + // The current aggregate result. + CertificateTrust result = CertificateTrust::ForUnspecified(); + + for (auto* store : stores_) { + CertificateTrust cur_trust = store->GetTrust(cert, debug_data); + + // * If any stores distrust the certificate, consider it untrusted. + // * If multiple stores consider it trusted, use the trust result from the + // last one + if (!cur_trust.HasUnspecifiedTrust()) { + result = cur_trust; + if (result.IsDistrusted()) + break; + } + } + + return result; +} + +} // namespace net diff --git a/chromium/net/cert/pki/trust_store_collection.h b/chromium/net/cert/pki/trust_store_collection.h new file mode 100644 index 00000000000..4d168aa6cfb --- /dev/null +++ b/chromium/net/cert/pki/trust_store_collection.h @@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_TRUST_STORE_COLLECTION_H_ +#define NET_CERT_PKI_TRUST_STORE_COLLECTION_H_ + +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/cert/pki/trust_store.h" + +namespace net { + +// TrustStoreCollection is an implementation of TrustStore which combines the +// results from multiple TrustStores. +// +// The order of the matches will correspond to a concatenation of matches in +// the order the stores were added. +class NET_EXPORT TrustStoreCollection : public TrustStore { + public: + TrustStoreCollection(); + + TrustStoreCollection(const TrustStoreCollection&) = delete; + TrustStoreCollection& operator=(const TrustStoreCollection&) = delete; + + ~TrustStoreCollection() override; + + // Includes results from |store| in the combined output. |store| must + // outlive the TrustStoreCollection. + void AddTrustStore(TrustStore* store); + + // TrustStore implementation: + void SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) override; + CertificateTrust GetTrust(const ParsedCertificate* cert, + base::SupportsUserData* debug_data) const override; + + private: + std::vector<TrustStore*> stores_; +}; + +} // namespace net + +#endif // NET_CERT_PKI_TRUST_STORE_COLLECTION_H_ diff --git a/chromium/net/cert/pki/trust_store_collection_unittest.cc b/chromium/net/cert/pki/trust_store_collection_unittest.cc new file mode 100644 index 00000000000..8b17c5a8d8d --- /dev/null +++ b/chromium/net/cert/pki/trust_store_collection_unittest.cc @@ -0,0 +1,182 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/trust_store_collection.h" + +#include "net/cert/pki/test_helpers.h" +#include "net/cert/pki/trust_store_in_memory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class TrustStoreCollectionTest : public testing::Test { + public: + void SetUp() override { + ParsedCertificateList chain; + ASSERT_TRUE(ReadCertChainFromFile( + "net/data/verify_certificate_chain_unittest/key-rollover/oldchain.pem", + &chain)); + + ASSERT_EQ(3U, chain.size()); + target_ = chain[0]; + oldintermediate_ = chain[1]; + oldroot_ = chain[2]; + ASSERT_TRUE(target_); + ASSERT_TRUE(oldintermediate_); + ASSERT_TRUE(oldroot_); + + ASSERT_TRUE( + ReadCertChainFromFile("net/data/verify_certificate_chain_unittest/" + "key-rollover/longrolloverchain.pem", + &chain)); + + ASSERT_EQ(5U, chain.size()); + newintermediate_ = chain[1]; + newroot_ = chain[2]; + newrootrollover_ = chain[3]; + ASSERT_TRUE(newintermediate_); + ASSERT_TRUE(newroot_); + ASSERT_TRUE(newrootrollover_); + } + + protected: + scoped_refptr<ParsedCertificate> oldroot_; + scoped_refptr<ParsedCertificate> newroot_; + scoped_refptr<ParsedCertificate> newrootrollover_; + + scoped_refptr<ParsedCertificate> target_; + scoped_refptr<ParsedCertificate> oldintermediate_; + scoped_refptr<ParsedCertificate> newintermediate_; +}; + +// Collection contains no stores, should return no results. +TEST_F(TrustStoreCollectionTest, NoStores) { + ParsedCertificateList issuers; + + TrustStoreCollection collection; + collection.SyncGetIssuersOf(target_.get(), &issuers); + + EXPECT_TRUE(issuers.empty()); +} + +// Collection contains only one store. +TEST_F(TrustStoreCollectionTest, OneStore) { + ParsedCertificateList issuers; + + TrustStoreCollection collection; + TrustStoreInMemory in_memory; + in_memory.AddTrustAnchor(newroot_); + collection.AddTrustStore(&in_memory); + collection.SyncGetIssuersOf(newintermediate_.get(), &issuers); + + ASSERT_EQ(1U, issuers.size()); + EXPECT_EQ(newroot_.get(), issuers[0].get()); + + // newroot_ is trusted. + CertificateTrust trust = + collection.GetTrust(newroot_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::TRUSTED_ANCHOR, trust.type); + + // oldroot_ is not. + trust = collection.GetTrust(oldroot_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type); +} + +// SyncGetIssuersOf() should append to its output parameters rather than assign +// them. +TEST_F(TrustStoreCollectionTest, OutputVectorsAppendedTo) { + ParsedCertificateList issuers; + + // Populate the out-parameter with some values. + issuers.resize(3); + + TrustStoreCollection collection; + TrustStoreInMemory in_memory; + in_memory.AddTrustAnchor(newroot_); + collection.AddTrustStore(&in_memory); + collection.SyncGetIssuersOf(newintermediate_.get(), &issuers); + + ASSERT_EQ(4U, issuers.size()); + EXPECT_EQ(newroot_.get(), issuers[3].get()); + + // newroot_ is trusted. + CertificateTrust trust = + collection.GetTrust(newroot_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::TRUSTED_ANCHOR, trust.type); + + // newrootrollover_ is not. + trust = collection.GetTrust(newrootrollover_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type); +} + +// Collection contains two stores. +TEST_F(TrustStoreCollectionTest, TwoStores) { + ParsedCertificateList issuers; + + TrustStoreCollection collection; + TrustStoreInMemory in_memory1; + TrustStoreInMemory in_memory2; + in_memory1.AddTrustAnchor(newroot_); + in_memory2.AddTrustAnchor(oldroot_); + collection.AddTrustStore(&in_memory1); + collection.AddTrustStore(&in_memory2); + collection.SyncGetIssuersOf(newintermediate_.get(), &issuers); + + ASSERT_EQ(2U, issuers.size()); + EXPECT_EQ(newroot_.get(), issuers[0].get()); + EXPECT_EQ(oldroot_.get(), issuers[1].get()); + + // newroot_ is trusted. + CertificateTrust trust = + collection.GetTrust(newroot_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::TRUSTED_ANCHOR, trust.type); + + // oldroot_ is trusted. + trust = collection.GetTrust(oldroot_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::TRUSTED_ANCHOR, trust.type); + + // newrootrollover_ is not. + trust = collection.GetTrust(newrootrollover_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type); +} + +// Collection contains two stores. The certificate is marked as trusted in one, +// but distrusted in the other. +TEST_F(TrustStoreCollectionTest, DistrustTakesPriority) { + ParsedCertificateList issuers; + + TrustStoreCollection collection; + TrustStoreInMemory in_memory1; + TrustStoreInMemory in_memory2; + + // newroot_ is trusted in store1, distrusted in store2. + in_memory1.AddTrustAnchor(newroot_); + in_memory2.AddDistrustedCertificateForTest(newroot_); + + // oldintermediate is distrusted in store1, trusted in store2. + in_memory1.AddDistrustedCertificateForTest(oldintermediate_); + in_memory2.AddTrustAnchor(oldintermediate_); + + collection.AddTrustStore(&in_memory1); + collection.AddTrustStore(&in_memory2); + + // newroot_ is distrusted.. + CertificateTrust trust = + collection.GetTrust(newroot_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::DISTRUSTED, trust.type); + + // oldintermediate_ is distrusted. + trust = collection.GetTrust(oldintermediate_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::DISTRUSTED, trust.type); + + // newrootrollover_ is unspecified. + trust = collection.GetTrust(newrootrollover_.get(), /*debug_data=*/nullptr); + EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/cert/pki/trust_store_in_memory.cc b/chromium/net/cert/pki/trust_store_in_memory.cc new file mode 100644 index 00000000000..7769b992429 --- /dev/null +++ b/chromium/net/cert/pki/trust_store_in_memory.cc @@ -0,0 +1,92 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/trust_store_in_memory.h" + +namespace net { + +TrustStoreInMemory::TrustStoreInMemory() = default; +TrustStoreInMemory::~TrustStoreInMemory() = default; + +bool TrustStoreInMemory::IsEmpty() const { + return entries_.empty(); +} + +void TrustStoreInMemory::Clear() { + entries_.clear(); +} + +void TrustStoreInMemory::AddTrustAnchor(scoped_refptr<ParsedCertificate> cert) { + AddCertificate(std::move(cert), CertificateTrust::ForTrustAnchor()); +} + +void TrustStoreInMemory::AddTrustAnchorWithExpiration( + scoped_refptr<ParsedCertificate> cert) { + AddCertificate(std::move(cert), + CertificateTrust::ForTrustAnchorEnforcingExpiration()); +} + +void TrustStoreInMemory::AddTrustAnchorWithConstraints( + scoped_refptr<ParsedCertificate> cert) { + AddCertificate(std::move(cert), + CertificateTrust::ForTrustAnchorEnforcingConstraints()); +} + +void TrustStoreInMemory::AddDistrustedCertificateForTest( + scoped_refptr<ParsedCertificate> cert) { + AddCertificate(std::move(cert), CertificateTrust::ForDistrusted()); +} + +void TrustStoreInMemory::AddCertificateWithUnspecifiedTrust( + scoped_refptr<ParsedCertificate> cert) { + AddCertificate(std::move(cert), CertificateTrust::ForUnspecified()); +} + +void TrustStoreInMemory::SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) { + auto range = entries_.equal_range(cert->normalized_issuer().AsStringPiece()); + for (auto it = range.first; it != range.second; ++it) + issuers->push_back(it->second.cert); +} + +CertificateTrust TrustStoreInMemory::GetTrust( + const ParsedCertificate* cert, + base::SupportsUserData* debug_data) const { + const Entry* entry = GetEntry(cert); + return entry ? entry->trust : CertificateTrust::ForUnspecified(); +} + +bool TrustStoreInMemory::Contains(const ParsedCertificate* cert) const { + return GetEntry(cert) != nullptr; +} + +TrustStoreInMemory::Entry::Entry() = default; +TrustStoreInMemory::Entry::Entry(const Entry& other) = default; +TrustStoreInMemory::Entry::~Entry() = default; + +void TrustStoreInMemory::AddCertificate(scoped_refptr<ParsedCertificate> cert, + const CertificateTrust& trust) { + Entry entry; + entry.cert = std::move(cert); + entry.trust = trust; + + // TODO(mattm): should this check for duplicate certificates? + entries_.insert( + std::make_pair(entry.cert->normalized_subject().AsStringPiece(), entry)); +} + +const TrustStoreInMemory::Entry* TrustStoreInMemory::GetEntry( + const ParsedCertificate* cert) const { + auto range = entries_.equal_range(cert->normalized_subject().AsStringPiece()); + for (auto it = range.first; it != range.second; ++it) { + if (cert == it->second.cert.get() || + cert->der_cert() == it->second.cert->der_cert()) { + // NOTE: ambiguity when there are duplicate entries. + return &it->second; + } + } + return nullptr; +} + +} // namespace net diff --git a/chromium/net/cert/pki/trust_store_in_memory.h b/chromium/net/cert/pki/trust_store_in_memory.h new file mode 100644 index 00000000000..1d6a7c69257 --- /dev/null +++ b/chromium/net/cert/pki/trust_store_in_memory.h @@ -0,0 +1,91 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_TRUST_STORE_IN_MEMORY_H_ +#define NET_CERT_PKI_TRUST_STORE_IN_MEMORY_H_ + +#include <unordered_map> + +#include "base/memory/ref_counted.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/cert/pki/trust_store.h" + +namespace net { + +// A very simple implementation of a TrustStore, which contains a set of +// certificates and their trustedness. +class NET_EXPORT TrustStoreInMemory : public TrustStore { + public: + TrustStoreInMemory(); + + TrustStoreInMemory(const TrustStoreInMemory&) = delete; + TrustStoreInMemory& operator=(const TrustStoreInMemory&) = delete; + + ~TrustStoreInMemory() override; + + // Returns whether the TrustStore is in the initial empty state. + bool IsEmpty() const; + + // Empties the trust store, resetting it to original state. + void Clear(); + + // Adds a certificate as a trust anchor (only the SPKI and subject will be + // used during verification). + void AddTrustAnchor(scoped_refptr<ParsedCertificate> cert); + + // Adds a certificate as a trust anchor which will have expiration enforced. + // See VerifyCertificateChain for details. + void AddTrustAnchorWithExpiration(scoped_refptr<ParsedCertificate> cert); + + // Adds a certificate as a trust anchor and extracts anchor constraints from + // the certificate. See VerifyCertificateChain for details. + void AddTrustAnchorWithConstraints(scoped_refptr<ParsedCertificate> cert); + + // TODO(eroman): This is marked "ForTest" as the current implementation + // requires an exact match on the certificate DER (a wider match by say + // issuer/serial is probably what we would want for a real implementation). + void AddDistrustedCertificateForTest(scoped_refptr<ParsedCertificate> cert); + + // Adds a certificate to the store, that is neither trusted nor untrusted. + void AddCertificateWithUnspecifiedTrust( + scoped_refptr<ParsedCertificate> cert); + + // TrustStore implementation: + void SyncGetIssuersOf(const ParsedCertificate* cert, + ParsedCertificateList* issuers) override; + CertificateTrust GetTrust(const ParsedCertificate* cert, + base::SupportsUserData* debug_data) const override; + + // Returns true if the trust store contains the given ParsedCertificate + // (matches by DER). + bool Contains(const ParsedCertificate* cert) const; + + private: + struct Entry { + Entry(); + Entry(const Entry& other); + ~Entry(); + + scoped_refptr<ParsedCertificate> cert; + CertificateTrust trust; + }; + + // Multimap from normalized subject -> Entry. + std::unordered_multimap<base::StringPiece, Entry, base::StringPieceHash> + entries_; + + // Adds a certificate with the specified trust settings. Both trusted and + // distrusted certificates require a full DER match. + void AddCertificate(scoped_refptr<ParsedCertificate> cert, + const CertificateTrust& trust); + + // Returns the `Entry` matching `cert`, or `nullptr` if not in the trust + // store. + const Entry* GetEntry(const ParsedCertificate* cert) const; +}; + +} // namespace net + +#endif // NET_CERT_PKI_TRUST_STORE_IN_MEMORY_H_ diff --git a/chromium/net/cert/pki/verify_certificate_chain.cc b/chromium/net/cert/pki/verify_certificate_chain.cc new file mode 100644 index 00000000000..5fea3878087 --- /dev/null +++ b/chromium/net/cert/pki/verify_certificate_chain.cc @@ -0,0 +1,1357 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_certificate_chain.h" + +#include <algorithm> + +#include "base/check.h" +#include "base/memory/raw_ptr.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/common_cert_errors.h" +#include "net/cert/pki/extended_key_usage.h" +#include "net/cert/pki/name_constraints.h" +#include "net/cert/pki/parse_certificate.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/cert/pki/trust_store.h" +#include "net/cert/pki/verify_signed_data.h" +#include "net/der/input.h" +#include "net/der/parser.h" + +namespace net { + +namespace { + +bool IsHandledCriticalExtension(const ParsedExtension& extension) { + if (extension.oid == der::Input(kBasicConstraintsOid)) + return true; + // Key Usage is NOT processed for end-entity certificates (this is the + // responsibility of callers), however it is considered "handled" here in + // order to allow being marked as critical. + if (extension.oid == der::Input(kKeyUsageOid)) + return true; + if (extension.oid == der::Input(kExtKeyUsageOid)) + return true; + if (extension.oid == der::Input(kNameConstraintsOid)) + return true; + if (extension.oid == der::Input(kSubjectAltNameOid)) + return true; + if (extension.oid == der::Input(kCertificatePoliciesOid)) { + // Policy qualifiers are skipped during processing, so if the + // extension is marked critical need to ensure there weren't any + // qualifiers other than User Notice / CPS. + // + // This follows from RFC 5280 section 4.2.1.4: + // + // If this extension is critical, the path validation software MUST + // be able to interpret this extension (including the optional + // qualifier), or MUST reject the certificate. + std::vector<der::Input> unused_policies; + CertErrors unused_errors; + return ParseCertificatePoliciesExtensionOids( + extension.value, true /*fail_parsing_unknown_qualifier_oids*/, + &unused_policies, &unused_errors); + + // TODO(eroman): Give a better error message. + } + if (extension.oid == der::Input(kPolicyMappingsOid)) + return true; + if (extension.oid == der::Input(kPolicyConstraintsOid)) + return true; + if (extension.oid == der::Input(kInhibitAnyPolicyOid)) + return true; + + return false; +} + +// Adds errors to |errors| if the certificate contains unconsumed _critical_ +// extensions. +void VerifyNoUnconsumedCriticalExtensions(const ParsedCertificate& cert, + CertErrors* errors) { + for (const auto& it : cert.extensions()) { + const ParsedExtension& extension = it.second; + if (extension.critical && !IsHandledCriticalExtension(extension)) { + errors->AddError(cert_errors::kUnconsumedCriticalExtension, + CreateCertErrorParams2Der("oid", extension.oid, "value", + extension.value)); + } + } +} + +// Returns true if |cert| was self-issued. The definition of self-issuance +// comes from RFC 5280 section 6.1: +// +// A certificate is self-issued if the same DN appears in the subject +// and issuer fields (the two DNs are the same if they match according +// to the rules specified in Section 7.1). In general, the issuer and +// subject of the certificates that make up a path are different for +// each certificate. However, a CA may issue a certificate to itself to +// support key rollover or changes in certificate policies. These +// self-issued certificates are not counted when evaluating path length +// or name constraints. +[[nodiscard]] bool IsSelfIssued(const ParsedCertificate& cert) { + return cert.normalized_subject() == cert.normalized_issuer(); +} + +// Adds errors to |errors| if |cert| is not valid at time |time|. +// +// The certificate's validity requirements are described by RFC 5280 section +// 4.1.2.5: +// +// The validity period for a certificate is the period of time from +// notBefore through notAfter, inclusive. +void VerifyTimeValidity(const ParsedCertificate& cert, + const der::GeneralizedTime& time, + CertErrors* errors) { + if (time < cert.tbs().validity_not_before) + errors->AddError(cert_errors::kValidityFailedNotBefore); + + if (cert.tbs().validity_not_after < time) + errors->AddError(cert_errors::kValidityFailedNotAfter); +} + +// Adds errors to |errors| if |cert| has internally inconsistent signature +// algorithms. +// +// X.509 certificates contain two different signature algorithms: +// (1) The signatureAlgorithm field of Certificate +// (2) The signature field of TBSCertificate +// +// According to RFC 5280 section 4.1.1.2 and 4.1.2.3 these two fields must be +// equal: +// +// This field MUST contain the same algorithm identifier as the +// signature field in the sequence tbsCertificate (Section 4.1.2.3). +// +// The spec is not explicit about what "the same algorithm identifier" means. +// Our interpretation is that the two DER-encoded fields must be byte-for-byte +// identical. +// +// In practice however there are certificates which use different encodings for +// specifying RSA with SHA1 (different OIDs). This is special-cased for +// compatibility sake. +bool VerifySignatureAlgorithmsMatch(const ParsedCertificate& cert, + CertErrors* errors) { + const der::Input& alg1_tlv = cert.signature_algorithm_tlv(); + const der::Input& alg2_tlv = cert.tbs().signature_algorithm_tlv; + + // Ensure that the two DER-encoded signature algorithms are byte-for-byte + // equal. + if (alg1_tlv == alg2_tlv) + return true; + + // But make a compatibility concession if alternate encodings are used + // TODO(eroman): Turn this warning into an error. + // TODO(eroman): Add a unit-test that exercises this case. + absl::optional<SignatureAlgorithm> alg1 = + ParseSignatureAlgorithm(alg1_tlv, errors); + if (!alg1) { + errors->AddError(cert_errors::kUnacceptableSignatureAlgorithm); + return false; + } + absl::optional<SignatureAlgorithm> alg2 = + ParseSignatureAlgorithm(alg2_tlv, errors); + if (!alg2) { + errors->AddError(cert_errors::kUnacceptableSignatureAlgorithm); + return false; + } + + if (*alg1 == *alg2) { + errors->AddWarning( + cert_errors::kSignatureAlgorithmsDifferentEncoding, + CreateCertErrorParams2Der("Certificate.algorithm", alg1_tlv, + "TBSCertificate.signature", alg2_tlv)); + return true; + } + + errors->AddError( + cert_errors::kSignatureAlgorithmMismatch, + CreateCertErrorParams2Der("Certificate.algorithm", alg1_tlv, + "TBSCertificate.signature", alg2_tlv)); + return false; +} + +// Verify that |cert| can be used for |required_key_purpose|. +void VerifyExtendedKeyUsage(const ParsedCertificate& cert, + KeyPurpose required_key_purpose, + CertErrors* errors) { + switch (required_key_purpose) { + case KeyPurpose::ANY_EKU: + return; + case KeyPurpose::SERVER_AUTH: { + // TODO(eroman): Is it OK for the target certificate to omit the EKU? + if (!cert.has_extended_key_usage()) + return; + + for (const auto& key_purpose_oid : cert.extended_key_usage()) { + if (key_purpose_oid == der::Input(kAnyEKU)) + return; + if (key_purpose_oid == der::Input(kServerAuth)) + return; + } + + // Check if the certificate contains Netscape Server Gated Crypto. + // nsSGC is a deprecated mechanism, and not part of RFC 5280's + // profile. Some unexpired certificate chains still rely on it though + // (there are intermediates valid until 2020 that use it). + bool has_nsgc = false; + + for (const auto& key_purpose_oid : cert.extended_key_usage()) { + if (key_purpose_oid == der::Input(kNetscapeServerGatedCrypto)) { + has_nsgc = true; + break; + } + } + + if (has_nsgc) { + errors->AddWarning(cert_errors::kEkuLacksServerAuthButHasGatedCrypto); + + // Allow NSGC for legacy RSA SHA1 intermediates, for compatibility with + // platform verifiers. + // + // In practice the chain will be rejected with or without this + // compatibility hack. The difference is whether the final error will be + // ERR_CERT_WEAK_SIGNATURE_ALGORITHM (with compatibility hack) vs + // ERR_CERT_INVALID (without hack). + // + // TODO(https://crbug.com/843735): Remove this once error-for-error + // equivalence between builtin verifier and platform verifier is less + // important. + if ((cert.has_basic_constraints() && cert.basic_constraints().is_ca) && + cert.signature_algorithm() == SignatureAlgorithm::kRsaPkcs1Sha1) { + return; + } + } + + errors->AddError(cert_errors::kEkuLacksServerAuth); + break; + } + case KeyPurpose::CLIENT_AUTH: { + // TODO(eroman): Is it OK for the target certificate to omit the EKU? + if (!cert.has_extended_key_usage()) + return; + + for (const auto& key_purpose_oid : cert.extended_key_usage()) { + if (key_purpose_oid == der::Input(kAnyEKU)) + return; + if (key_purpose_oid == der::Input(kClientAuth)) + return; + } + + errors->AddError(cert_errors::kEkuLacksClientAuth); + break; + } + } +} + +// Returns |true| if |policies| contains the OID |search_oid|. +bool SetContains(const std::set<der::Input>& policies, + const der::Input& search_oid) { + return policies.count(search_oid) > 0; +} + +// Representation of RFC 5280's "valid_policy_tree", used to keep track of the +// valid policies and policy re-mappings. +// +// ValidPolicyTree differs slightly from RFC 5280's description in that: +// +// (1) It does not track "qualifier_set". This is not needed as it is not +// output by this implementation. +// +// (2) It only stores the most recent level of the policy tree rather than +// the full tree of nodes. +class ValidPolicyTree { + public: + ValidPolicyTree() = default; + + ValidPolicyTree(const ValidPolicyTree&) = delete; + ValidPolicyTree& operator=(const ValidPolicyTree&) = delete; + + struct Node { + // |root_policy| is equivalent to |valid_policy|, but in the domain of the + // caller. + // + // The reason for this distinction is the Policy Mappings extension. + // + // So whereas |valid_policy| is in the remapped domain defined by the + // issuing certificate, |root_policy| is in the fixed domain of the caller. + // + // OIDs in "user_initial_policy_set" and "user_constrained_policy_set" are + // directly comparable to |root_policy| values, but not necessarily to + // |valid_policy|. + // + // In terms of the valid policy tree, |root_policy| can be found by + // starting at the node's root ancestor, and finding the first node with a + // valid_policy other than anyPolicy. This is effectively the same process + // as used during policy tree intersection in RFC 5280 6.1.5.g.iii.1 + der::Input root_policy; + + // The same as RFC 5280's "valid_policy" variable. + der::Input valid_policy; + + // The same as RFC 5280s "expected_policy_set" variable. + std::set<der::Input> expected_policy_set; + + // Note that RFC 5280's "qualifier_set" is omitted. + }; + + // Level represents all the nodes at depth "i" in the valid_policy_tree. + using Level = std::vector<Node>; + + // Initializes the ValidPolicyTree for the given "user_initial_policy_set". + // + // In RFC 5280, the valid_policy_tree is initialized to a root node at depth + // 0 of "anyPolicy"; the intersection with the "user_initial_policy_set" is + // done at the end (Wrap Up) as described in section 6.1.5 step g. + // + // Whereas in this implementation, the restriction on policies is added here, + // and intersecting the valid policy tree during Wrap Up is no longer needed. + // + // The final "user_constrained_policy_set" obtained will be the same. The + // advantages of this approach is simpler code. + void Init(const std::set<der::Input>& user_initial_policy_set) { + Clear(); + for (const der::Input& policy_oid : user_initial_policy_set) + AddRootNode(policy_oid); + } + + // Returns the current level (i.e. all nodes at depth i in the valid + // policy tree). + const Level& current_level() const { return current_level_; } + Level& current_level() { return current_level_; } + + // In RFC 5280 valid_policy_tree may be set to null. That is represented here + // by emptiness. + bool IsNull() const { return current_level_.empty(); } + void SetNull() { Clear(); } + + // This implementation keeps only the last level of the valid policy + // tree. Calling StartLevel() returns the nodes for the previous + // level, and starts a new level. + Level StartLevel() { + Level prev_level; + std::swap(prev_level, current_level_); + return prev_level; + } + + // Gets the set of policies (in terms of root authority's policy domain) that + // are valid at the curent level of the policy tree. + // + // For example: + // + // * If the valid policy tree was initialized with anyPolicy, then this + // function returns what X.509 calls "authorities-constrained-policy-set". + // + // * If the valid policy tree was instead initialized with the + // "user-initial-policy_set", then this function returns what X.509 + // calls "user-constrained-policy-set" + // ("authorities-constrained-policy-set" intersected with the + // "user-initial-policy-set"). + void GetValidRootPolicySet(std::set<der::Input>* policy_set) { + policy_set->clear(); + for (const Node& node : current_level_) + policy_set->insert(node.root_policy); + + // If the result includes anyPolicy, simplify it to a set of size 1. + if (policy_set->size() > 1 && + SetContains(*policy_set, der::Input(kAnyPolicyOid))) { + *policy_set = {der::Input(kAnyPolicyOid)}; + } + } + + // Adds a node |n| to the current level which is a child of |parent| + // such that: + // * n.valid_policy = policy_oid + // * n.expected_policy_set = {policy_oid} + void AddNode(const Node& parent, const der::Input& policy_oid) { + AddNodeWithExpectedPolicySet(parent, policy_oid, {policy_oid}); + } + + // Adds a node |n| to the current level which is a child of |parent| + // such that: + // * n.valid_policy = policy_oid + // * n.expected_policy_set = expected_policy_set + void AddNodeWithExpectedPolicySet( + const Node& parent, + const der::Input& policy_oid, + const std::set<der::Input>& expected_policy_set) { + Node new_node; + new_node.valid_policy = policy_oid; + new_node.expected_policy_set = expected_policy_set; + + // Consider the root policy as the first policy other than anyPolicy (or + // anyPolicy if it hasn't been restricted yet). + new_node.root_policy = (parent.root_policy == der::Input(kAnyPolicyOid)) + ? policy_oid + : parent.root_policy; + + current_level_.push_back(std::move(new_node)); + } + + // Returns the first node having valid_policy == anyPolicy in |level|, or + // nullptr if there is none. + static const Node* FindAnyPolicyNode(const Level& level) { + for (const Node& node : level) { + if (node.valid_policy == der::Input(kAnyPolicyOid)) + return &node; + } + return nullptr; + } + + // Deletes all nodes |n| in |level| where |n.valid_policy| matches the given + // |valid_policy|. This may re-order the nodes in |level|. + static void DeleteNodesMatchingValidPolicy(const der::Input& valid_policy, + Level* level) { + // This works by swapping nodes to the end of the vector, and then doing a + // single resize to delete them all. + auto cur = level->begin(); + auto end = level->end(); + while (cur != end) { + bool should_delete_node = cur->valid_policy == valid_policy; + if (should_delete_node) { + end = std::prev(end); + if (cur != end) + std::iter_swap(cur, end); + } else { + ++cur; + } + } + level->erase(end, level->end()); + } + + private: + // Deletes all nodes in the valid policy tree. + void Clear() { current_level_.clear(); } + + // Adds a node to the current level for OID |policy_oid|. The current level + // is assumed to be the root level. + void AddRootNode(const der::Input& policy_oid) { + Node new_node; + new_node.root_policy = policy_oid; + new_node.valid_policy = policy_oid; + new_node.expected_policy_set = {policy_oid}; + current_level_.push_back(std::move(new_node)); + } + + Level current_level_; +}; + +// Class that encapsulates the state variables used by certificate path +// validation. +class PathVerifier { + public: + // Same parameters and meaning as VerifyCertificateChain(). + void Run(const ParsedCertificateList& certs, + const CertificateTrust& last_cert_trust, + VerifyCertificateChainDelegate* delegate, + const der::GeneralizedTime& time, + KeyPurpose required_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, + std::set<der::Input>* user_constrained_policy_set, + CertPathErrors* errors); + + private: + // Verifies and updates the valid policies. This corresponds with RFC 5280 + // section 6.1.3 steps d-f. + void VerifyPolicies(const ParsedCertificate& cert, + bool is_target_cert, + CertErrors* errors); + + // Applies the policy mappings. This corresponds with RFC 5280 section 6.1.4 + // steps a-b. + void VerifyPolicyMappings(const ParsedCertificate& cert, CertErrors* errors); + + // This function corresponds to RFC 5280 section 6.1.3's "Basic Certificate + // Processing" procedure. + void BasicCertificateProcessing(const ParsedCertificate& cert, + bool is_target_cert, + const der::GeneralizedTime& time, + KeyPurpose required_key_purpose, + CertErrors* errors, + bool* shortcircuit_chain_validation); + + // This function corresponds to RFC 5280 section 6.1.4's "Preparation for + // Certificate i+1" procedure. |cert| is expected to be an intermediate. + void PrepareForNextCertificate(const ParsedCertificate& cert, + CertErrors* errors); + + // This function corresponds with RFC 5280 section 6.1.5's "Wrap-Up + // Procedure". It does processing for the final certificate (the target cert). + void WrapUp(const ParsedCertificate& cert, CertErrors* errors); + + // Enforces trust anchor constraints compatibile with RFC 5937. + // + // Note that the anchor constraints are encoded via the attached certificate + // itself. + void ApplyTrustAnchorConstraints(const ParsedCertificate& cert, + KeyPurpose required_key_purpose, + CertErrors* errors); + + // Initializes the path validation algorithm given anchor constraints. This + // follows the description in RFC 5937 + void ProcessRootCertificate(const ParsedCertificate& cert, + const CertificateTrust& trust, + const der::GeneralizedTime& time, + KeyPurpose required_key_purpose, + CertErrors* errors, + bool* shortcircuit_chain_validation); + + // Parses |spki| to an EVP_PKEY and checks whether the public key is accepted + // by |delegate_|. On failure parsing returns nullptr. If either parsing the + // key or key policy failed, adds a high-severity error to |errors|. + bssl::UniquePtr<EVP_PKEY> ParseAndCheckPublicKey(const der::Input& spki, + CertErrors* errors); + + ValidPolicyTree valid_policy_tree_; + + // Will contain a NameConstraints for each previous cert in the chain which + // had nameConstraints. This corresponds to the permitted_subtrees and + // excluded_subtrees state variables from RFC 5280. + std::vector<const NameConstraints*> name_constraints_list_; + + // |explicit_policy_| corresponds with the same named variable from RFC 5280 + // section 6.1.2: + // + // explicit_policy: an integer that indicates if a non-NULL + // valid_policy_tree is required. The integer indicates the + // number of non-self-issued certificates to be processed before + // this requirement is imposed. Once set, this variable may be + // decreased, but may not be increased. That is, if a certificate in the + // path requires a non-NULL valid_policy_tree, a later certificate cannot + // remove this requirement. If initial-explicit-policy is set, then the + // initial value is 0, otherwise the initial value is n+1. + size_t explicit_policy_; + + // |inhibit_any_policy_| corresponds with the same named variable from RFC + // 5280 section 6.1.2: + // + // inhibit_anyPolicy: an integer that indicates whether the + // anyPolicy policy identifier is considered a match. The + // integer indicates the number of non-self-issued certificates + // to be processed before the anyPolicy OID, if asserted in a + // certificate other than an intermediate self-issued + // certificate, is ignored. Once set, this variable may be + // decreased, but may not be increased. That is, if a + // certificate in the path inhibits processing of anyPolicy, a + // later certificate cannot permit it. If initial-any-policy- + // inhibit is set, then the initial value is 0, otherwise the + // initial value is n+1. + size_t inhibit_any_policy_; + + // |policy_mapping_| corresponds with the same named variable from RFC 5280 + // section 6.1.2: + // + // policy_mapping: an integer that indicates if policy mapping + // is permitted. The integer indicates the number of non-self- + // issued certificates to be processed before policy mapping is + // inhibited. Once set, this variable may be decreased, but may + // not be increased. That is, if a certificate in the path + // specifies that policy mapping is not permitted, it cannot be + // overridden by a later certificate. If initial-policy- + // mapping-inhibit is set, then the initial value is 0, + // otherwise the initial value is n+1. + size_t policy_mapping_; + + // |working_public_key_| is an amalgamation of 3 separate variables from RFC + // 5280: + // * working_public_key + // * working_public_key_algorithm + // * working_public_key_parameters + // + // They are combined for simplicity since the signature verification takes an + // EVP_PKEY, and the parameter inheritence is not applicable for the supported + // key types. |working_public_key_| may be null if parsing failed. + // + // An approximate explanation of |working_public_key_| is this description + // from RFC 5280 section 6.1.2: + // + // working_public_key: the public key used to verify the + // signature of a certificate. + bssl::UniquePtr<EVP_PKEY> working_public_key_; + + // |working_normalized_issuer_name_| is the normalized value of the + // working_issuer_name variable in RFC 5280 section 6.1.2: + // + // working_issuer_name: the issuer distinguished name expected + // in the next certificate in the chain. + der::Input working_normalized_issuer_name_; + + // |max_path_length_| corresponds with the same named variable in RFC 5280 + // section 6.1.2. + // + // max_path_length: this integer is initialized to n, is + // decremented for each non-self-issued certificate in the path, + // and may be reduced to the value in the path length constraint + // field within the basic constraints extension of a CA + // certificate. + size_t max_path_length_; + + raw_ptr<VerifyCertificateChainDelegate> delegate_; +}; + +void PathVerifier::VerifyPolicies(const ParsedCertificate& cert, + bool is_target_cert, + CertErrors* errors) { + // From RFC 5280 section 6.1.3: + // + // (d) If the certificate policies extension is present in the + // certificate and the valid_policy_tree is not NULL, process + // the policy information by performing the following steps in + // order: + if (cert.has_policy_oids() && !valid_policy_tree_.IsNull()) { + ValidPolicyTree::Level previous_level = valid_policy_tree_.StartLevel(); + + // Identify if there was a node with valid_policy == anyPolicy at depth i-1. + const ValidPolicyTree::Node* any_policy_node_prev_level = + ValidPolicyTree::FindAnyPolicyNode(previous_level); + + // (1) For each policy P not equal to anyPolicy in the + // certificate policies extension, let P-OID denote the OID + // for policy P and P-Q denote the qualifier set for policy + // P. Perform the following steps in order: + bool cert_has_any_policy = false; + for (const der::Input& p_oid : cert.policy_oids()) { + if (p_oid == der::Input(kAnyPolicyOid)) { + cert_has_any_policy = true; + continue; + } + + // (i) For each node of depth i-1 in the valid_policy_tree + // where P-OID is in the expected_policy_set, create a + // child node as follows: set the valid_policy to P-OID, + // set the qualifier_set to P-Q, and set the + // expected_policy_set to {P-OID}. + bool found_match = false; + for (const ValidPolicyTree::Node& prev_node : previous_level) { + if (SetContains(prev_node.expected_policy_set, p_oid)) { + valid_policy_tree_.AddNode(prev_node, p_oid); + found_match = true; + } + } + + // (ii) If there was no match in step (i) and the + // valid_policy_tree includes a node of depth i-1 with + // the valid_policy anyPolicy, generate a child node with + // the following values: set the valid_policy to P-OID, + // set the qualifier_set to P-Q, and set the + // expected_policy_set to {P-OID}. + if (!found_match && any_policy_node_prev_level) + valid_policy_tree_.AddNode(*any_policy_node_prev_level, p_oid); + } + + // (2) If the certificate policies extension includes the policy + // anyPolicy with the qualifier set AP-Q and either (a) + // inhibit_anyPolicy is greater than 0 or (b) i<n and the + // certificate is self-issued, then: + // + // For each node in the valid_policy_tree of depth i-1, for + // each value in the expected_policy_set (including + // anyPolicy) that does not appear in a child node, create a + // child node with the following values: set the valid_policy + // to the value from the expected_policy_set in the parent + // node, set the qualifier_set to AP-Q, and set the + // expected_policy_set to the value in the valid_policy from + // this node. + if (cert_has_any_policy && ((inhibit_any_policy_ > 0) || + (!is_target_cert && IsSelfIssued(cert)))) { + // Keep track of the existing policies at depth i. + std::set<der::Input> child_node_policies; + for (const ValidPolicyTree::Node& node : + valid_policy_tree_.current_level()) + child_node_policies.insert(node.valid_policy); + + for (const ValidPolicyTree::Node& prev_node : previous_level) { + for (const der::Input& expected_policy : + prev_node.expected_policy_set) { + if (!SetContains(child_node_policies, expected_policy)) { + child_node_policies.insert(expected_policy); + valid_policy_tree_.AddNode(prev_node, expected_policy); + } + } + } + } + + // (3) If there is a node in the valid_policy_tree of depth i-1 + // or less without any child nodes, delete that node. Repeat + // this step until there are no nodes of depth i-1 or less + // without children. + // + // Nothing needs to be done for this step, since this implementation only + // stores the nodes at depth i, and the entire level has already been + // calculated. + } + + // (e) If the certificate policies extension is not present, set the + // valid_policy_tree to NULL. + if (!cert.has_policy_oids()) + valid_policy_tree_.SetNull(); + + // (f) Verify that either explicit_policy is greater than 0 or the + // valid_policy_tree is not equal to NULL; + if (!((explicit_policy_ > 0) || !valid_policy_tree_.IsNull())) + errors->AddError(cert_errors::kNoValidPolicy); +} + +void PathVerifier::VerifyPolicyMappings(const ParsedCertificate& cert, + CertErrors* errors) { + if (!cert.has_policy_mappings()) + return; + + // From RFC 5280 section 6.1.4: + // + // (a) If a policy mappings extension is present, verify that the + // special value anyPolicy does not appear as an + // issuerDomainPolicy or a subjectDomainPolicy. + for (const ParsedPolicyMapping& mapping : cert.policy_mappings()) { + if (mapping.issuer_domain_policy == der::Input(kAnyPolicyOid) || + mapping.subject_domain_policy == der::Input(kAnyPolicyOid)) { + // Because this implementation continues processing certificates after + // this error, clear the valid policy tree to ensure the + // "user_constrained_policy_set" output upon failure is empty. + valid_policy_tree_.SetNull(); + errors->AddError(cert_errors::kPolicyMappingAnyPolicy); + } + } + + // (b) If a policy mappings extension is present, then for each + // issuerDomainPolicy ID-P in the policy mappings extension: + // + // (1) If the policy_mapping variable is greater than 0, for each + // node in the valid_policy_tree of depth i where ID-P is the + // valid_policy, set expected_policy_set to the set of + // subjectDomainPolicy values that are specified as + // equivalent to ID-P by the policy mappings extension. + // + // If no node of depth i in the valid_policy_tree has a + // valid_policy of ID-P but there is a node of depth i with a + // valid_policy of anyPolicy, then generate a child node of + // the node of depth i-1 that has a valid_policy of anyPolicy + // as follows: + // + // (i) set the valid_policy to ID-P; + // + // (ii) set the qualifier_set to the qualifier set of the + // policy anyPolicy in the certificate policies + // extension of certificate i; and + // + // (iii) set the expected_policy_set to the set of + // subjectDomainPolicy values that are specified as + // equivalent to ID-P by the policy mappings extension. + // + if (policy_mapping_ > 0) { + const ValidPolicyTree::Node* any_policy_node = + ValidPolicyTree::FindAnyPolicyNode(valid_policy_tree_.current_level()); + + // Group mappings by issuer domain policy. + std::map<der::Input, std::set<der::Input>> mappings; + for (const ParsedPolicyMapping& mapping : cert.policy_mappings()) { + mappings[mapping.issuer_domain_policy].insert( + mapping.subject_domain_policy); + } + + for (const auto& it : mappings) { + const der::Input& issuer_domain_policy = it.first; + const std::set<der::Input>& subject_domain_policies = it.second; + bool found_node = false; + + for (ValidPolicyTree::Node& node : valid_policy_tree_.current_level()) { + if (node.valid_policy == issuer_domain_policy) { + node.expected_policy_set = subject_domain_policies; + found_node = true; + } + } + + if (!found_node && any_policy_node) { + valid_policy_tree_.AddNodeWithExpectedPolicySet( + *any_policy_node, issuer_domain_policy, subject_domain_policies); + } + } + } + + // (b) If a policy mappings extension is present, then for each + // issuerDomainPolicy ID-P in the policy mappings extension: + // + // ... + // + // (2) If the policy_mapping variable is equal to 0: + // + // (i) delete each node of depth i in the valid_policy_tree + // where ID-P is the valid_policy. + // + // (ii) If there is a node in the valid_policy_tree of depth + // i-1 or less without any child nodes, delete that + // node. Repeat this step until there are no nodes of + // depth i-1 or less without children. + if (policy_mapping_ == 0) { + for (const ParsedPolicyMapping& mapping : cert.policy_mappings()) { + ValidPolicyTree::DeleteNodesMatchingValidPolicy( + mapping.issuer_domain_policy, &valid_policy_tree_.current_level()); + } + } +} + +void PathVerifier::BasicCertificateProcessing( + const ParsedCertificate& cert, + bool is_target_cert, + const der::GeneralizedTime& time, + KeyPurpose required_key_purpose, + CertErrors* errors, + bool* shortcircuit_chain_validation) { + *shortcircuit_chain_validation = false; + // Check that the signature algorithms in Certificate vs TBSCertificate + // match. This isn't part of RFC 5280 section 6.1.3, but is mandated by + // sections 4.1.1.2 and 4.1.2.3. + if (!VerifySignatureAlgorithmsMatch(cert, errors)) { + CHECK(errors->ContainsAnyErrorWithSeverity(CertError::SEVERITY_HIGH)); + *shortcircuit_chain_validation = true; + } + + // Check whether this signature algorithm is allowed. + if (!delegate_->IsSignatureAlgorithmAcceptable(cert.signature_algorithm(), + errors)) { + *shortcircuit_chain_validation = true; + errors->AddError(cert_errors::kUnacceptableSignatureAlgorithm); + } + + if (working_public_key_) { + // Verify the digital signature using the previous certificate's key (RFC + // 5280 section 6.1.3 step a.1). + if (!VerifySignedData(cert.signature_algorithm(), + cert.tbs_certificate_tlv(), cert.signature_value(), + working_public_key_.get())) { + *shortcircuit_chain_validation = true; + errors->AddError(cert_errors::kVerifySignedDataFailed); + } + } + if (*shortcircuit_chain_validation) + return; + + // Check the time range for the certificate's validity, ensuring it is valid + // at |time|. + // (RFC 5280 section 6.1.3 step a.2) + VerifyTimeValidity(cert, time, errors); + + // RFC 5280 section 6.1.3 step a.3 calls for checking the certificate's + // revocation status here. In this implementation revocation checking is + // implemented separately from path validation. + + // Verify the certificate's issuer name matches the issuing certificate's + // subject name. (RFC 5280 section 6.1.3 step a.4) + if (cert.normalized_issuer() != working_normalized_issuer_name_) + errors->AddError(cert_errors::kSubjectDoesNotMatchIssuer); + + // Name constraints (RFC 5280 section 6.1.3 step b & c) + // If certificate i is self-issued and it is not the final certificate in the + // path, skip this step for certificate i. + if (!name_constraints_list_.empty() && + (!IsSelfIssued(cert) || is_target_cert)) { + for (const NameConstraints* nc : name_constraints_list_) { + nc->IsPermittedCert(cert.normalized_subject(), cert.subject_alt_names(), + errors); + } + } + + // RFC 5280 section 6.1.3 step d - f. + VerifyPolicies(cert, is_target_cert, errors); + + // The key purpose is checked not just for the end-entity certificate, but + // also interpreted as a constraint when it appears in intermediates. This + // goes beyond what RFC 5280 describes, but is the de-facto standard. See + // https://wiki.mozilla.org/CA:CertificatePolicyV2.1#Frequently_Asked_Questions + VerifyExtendedKeyUsage(cert, required_key_purpose, errors); +} + +void PathVerifier::PrepareForNextCertificate(const ParsedCertificate& cert, + CertErrors* errors) { + // RFC 5280 section 6.1.4 step a-b + VerifyPolicyMappings(cert, errors); + + // From RFC 5280 section 6.1.4 step c: + // + // Assign the certificate subject name to working_normalized_issuer_name. + working_normalized_issuer_name_ = cert.normalized_subject(); + + // From RFC 5280 section 6.1.4 step d: + // + // Assign the certificate subjectPublicKey to working_public_key. + working_public_key_ = ParseAndCheckPublicKey(cert.tbs().spki_tlv, errors); + + // Note that steps e and f are omitted as they are handled by + // the assignment to |working_spki| above. See the definition + // of |working_spki|. + + // From RFC 5280 section 6.1.4 step g: + if (cert.has_name_constraints()) + name_constraints_list_.push_back(&cert.name_constraints()); + + // (h) If certificate i is not self-issued: + if (!IsSelfIssued(cert)) { + // (1) If explicit_policy is not 0, decrement explicit_policy by + // 1. + if (explicit_policy_ > 0) + explicit_policy_ -= 1; + + // (2) If policy_mapping is not 0, decrement policy_mapping by 1. + if (policy_mapping_ > 0) + policy_mapping_ -= 1; + + // (3) If inhibit_anyPolicy is not 0, decrement inhibit_anyPolicy + // by 1. + if (inhibit_any_policy_ > 0) + inhibit_any_policy_ -= 1; + } + + // (i) If a policy constraints extension is included in the + // certificate, modify the explicit_policy and policy_mapping + // state variables as follows: + if (cert.has_policy_constraints()) { + // (1) If requireExplicitPolicy is present and is less than + // explicit_policy, set explicit_policy to the value of + // requireExplicitPolicy. + if (cert.policy_constraints().require_explicit_policy && + cert.policy_constraints().require_explicit_policy.value() < + explicit_policy_) { + explicit_policy_ = + cert.policy_constraints().require_explicit_policy.value(); + } + + // (2) If inhibitPolicyMapping is present and is less than + // policy_mapping, set policy_mapping to the value of + // inhibitPolicyMapping. + if (cert.policy_constraints().inhibit_policy_mapping && + cert.policy_constraints().inhibit_policy_mapping.value() < + policy_mapping_) { + policy_mapping_ = + cert.policy_constraints().inhibit_policy_mapping.value(); + } + } + + // (j) If the inhibitAnyPolicy extension is included in the + // certificate and is less than inhibit_anyPolicy, set + // inhibit_anyPolicy to the value of inhibitAnyPolicy. + if (cert.has_inhibit_any_policy() && + cert.inhibit_any_policy() < inhibit_any_policy_) { + inhibit_any_policy_ = cert.inhibit_any_policy(); + } + + // From RFC 5280 section 6.1.4 step k: + // + // If certificate i is a version 3 certificate, verify that the + // basicConstraints extension is present and that cA is set to + // TRUE. (If certificate i is a version 1 or version 2 + // certificate, then the application MUST either verify that + // certificate i is a CA certificate through out-of-band means + // or reject the certificate. Conforming implementations may + // choose to reject all version 1 and version 2 intermediate + // certificates.) + // + // This code implicitly rejects non version 3 intermediates, since they + // can't contain a BasicConstraints extension. + if (!cert.has_basic_constraints()) { + errors->AddError(cert_errors::kMissingBasicConstraints); + } else if (!cert.basic_constraints().is_ca) { + errors->AddError(cert_errors::kBasicConstraintsIndicatesNotCa); + } + + // From RFC 5280 section 6.1.4 step l: + // + // If the certificate was not self-issued, verify that + // max_path_length is greater than zero and decrement + // max_path_length by 1. + if (!IsSelfIssued(cert)) { + if (max_path_length_ == 0) { + errors->AddError(cert_errors::kMaxPathLengthViolated); + } else { + --max_path_length_; + } + } + + // From RFC 5280 section 6.1.4 step m: + // + // If pathLenConstraint is present in the certificate and is + // less than max_path_length, set max_path_length to the value + // of pathLenConstraint. + if (cert.has_basic_constraints() && cert.basic_constraints().has_path_len && + cert.basic_constraints().path_len < max_path_length_) { + max_path_length_ = cert.basic_constraints().path_len; + } + + // From RFC 5280 section 6.1.4 step n: + // + // If a key usage extension is present, verify that the + // keyCertSign bit is set. + if (cert.has_key_usage() && + !cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)) { + errors->AddError(cert_errors::kKeyCertSignBitNotSet); + } + + // From RFC 5280 section 6.1.4 step o: + // + // Recognize and process any other critical extension present in + // the certificate. Process any other recognized non-critical + // extension present in the certificate that is relevant to path + // processing. + VerifyNoUnconsumedCriticalExtensions(cert, errors); +} + +// Checks that if the target certificate has properties that only a CA should +// have (keyCertSign, CA=true, pathLenConstraint), then its other properties +// are consistent with being a CA. If it does, adds errors to |errors|. +// +// This follows from some requirements in RFC 5280 section 4.2.1.9. In +// particular: +// +// CAs MUST NOT include the pathLenConstraint field unless the cA +// boolean is asserted and the key usage extension asserts the +// keyCertSign bit. +// +// And: +// +// If the cA boolean is not asserted, then the keyCertSign bit in the key +// usage extension MUST NOT be asserted. +// +// TODO(eroman): Strictly speaking the first requirement is on CAs and not the +// certificate client, so could be skipped. +// +// TODO(eroman): I don't believe Firefox enforces the keyCertSign restriction +// for compatibility reasons. Investigate if we need to similarly relax this +// constraint. +void VerifyTargetCertHasConsistentCaBits(const ParsedCertificate& cert, + CertErrors* errors) { + // Check if the certificate contains any property specific to CAs. + bool has_ca_property = + (cert.has_basic_constraints() && + (cert.basic_constraints().is_ca || + cert.basic_constraints().has_path_len)) || + (cert.has_key_usage() && + cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); + + // If it "looks" like a CA because it has a CA-only property, then check that + // it sets ALL the properties expected of a CA. + if (has_ca_property) { + bool success = cert.has_basic_constraints() && + cert.basic_constraints().is_ca && + (!cert.has_key_usage() || + cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); + if (!success) { + // TODO(eroman): Add DER for basic constraints and key usage. + errors->AddError(cert_errors::kTargetCertInconsistentCaBits); + } + } +} + +void PathVerifier::WrapUp(const ParsedCertificate& cert, CertErrors* errors) { + // From RFC 5280 section 6.1.5: + // (a) If explicit_policy is not 0, decrement explicit_policy by 1. + if (explicit_policy_ > 0) + explicit_policy_ -= 1; + + // (b) If a policy constraints extension is included in the + // certificate and requireExplicitPolicy is present and has a + // value of 0, set the explicit_policy state variable to 0. + if (cert.has_policy_constraints() && + cert.policy_constraints().require_explicit_policy.has_value() && + cert.policy_constraints().require_explicit_policy == 0) { + explicit_policy_ = 0; + } + + // Note step c-e are omitted as the verification function does + // not output the working public key. + + // From RFC 5280 section 6.1.5 step f: + // + // Recognize and process any other critical extension present in + // the certificate n. Process any other recognized non-critical + // extension present in certificate n that is relevant to path + // processing. + // + // Note that this is duplicated by PrepareForNextCertificate() so as to + // directly match the procedures in RFC 5280's section 6.1. + VerifyNoUnconsumedCriticalExtensions(cert, errors); + + // RFC 5280 section 6.1.5 step g is skipped, as the intersection of valid + // policies was computed during previous steps. + // + // If either (1) the value of explicit_policy variable is greater than + // zero or (2) the valid_policy_tree is not NULL, then path processing + // has succeeded. + if (!(explicit_policy_ > 0 || !valid_policy_tree_.IsNull())) { + errors->AddError(cert_errors::kNoValidPolicy); + } + + // The following check is NOT part of RFC 5280 6.1.5's "Wrap-Up Procedure", + // however is implied by RFC 5280 section 4.2.1.9. + VerifyTargetCertHasConsistentCaBits(cert, errors); + + // Check the public key for the target certificate. The public key for the + // other certificates is already checked by PrepareForNextCertificate(). + // Note that this step is not part of RFC 5280 6.1.5. + ParseAndCheckPublicKey(cert.tbs().spki_tlv, errors); +} + +void PathVerifier::ApplyTrustAnchorConstraints(const ParsedCertificate& cert, + KeyPurpose required_key_purpose, + CertErrors* errors) { + // This is not part of RFC 5937 nor RFC 5280, but matches the EKU handling + // done for intermediates (described in Web PKI's Baseline Requirements). + VerifyExtendedKeyUsage(cert, required_key_purpose, errors); + + // The following enforcements follow from RFC 5937 (primarily section 3.2): + + // Initialize name constraints initial-permitted/excluded-subtrees. + if (cert.has_name_constraints()) + name_constraints_list_.push_back(&cert.name_constraints()); + + // TODO(eroman): Initialize user-initial-policy-set based on anchor + // constraints. + + // TODO(eroman): Initialize inhibit any policy based on anchor constraints. + + // TODO(eroman): Initialize require explicit policy based on anchor + // constraints. + + // TODO(eroman): Initialize inhibit policy mapping based on anchor + // constraints. + + // From RFC 5937 section 3.2: + // + // If a basic constraints extension is associated with the trust + // anchor and contains a pathLenConstraint value, set the + // max_path_length state variable equal to the pathLenConstraint + // value from the basic constraints extension. + // + // NOTE: RFC 5937 does not say to enforce the CA=true part of basic + // constraints. + if (cert.has_basic_constraints() && cert.basic_constraints().has_path_len) + max_path_length_ = cert.basic_constraints().path_len; + + // From RFC 5937 section 2: + // + // Extensions may be marked critical or not critical. When trust anchor + // constraints are enforced, clients MUST reject certification paths + // containing a trust anchor with unrecognized critical extensions. + VerifyNoUnconsumedCriticalExtensions(cert, errors); +} + +void PathVerifier::ProcessRootCertificate(const ParsedCertificate& cert, + const CertificateTrust& trust, + const der::GeneralizedTime& time, + KeyPurpose required_key_purpose, + CertErrors* errors, + bool* shortcircuit_chain_validation) { + *shortcircuit_chain_validation = false; + switch (trust.type) { + case CertificateTrustType::UNSPECIFIED: + // Doesn't chain to a trust anchor - implicitly distrusted + errors->AddError(cert_errors::kCertIsNotTrustAnchor); + *shortcircuit_chain_validation = true; + break; + case CertificateTrustType::DISTRUSTED: + // Chains to an actively distrusted certificate. + errors->AddError(cert_errors::kDistrustedByTrustStore); + *shortcircuit_chain_validation = true; + break; + case CertificateTrustType::TRUSTED_ANCHOR: + break; + case CertificateTrustType::TRUSTED_ANCHOR_WITH_EXPIRATION: + VerifyTimeValidity(cert, time, errors); + break; + case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: + ApplyTrustAnchorConstraints(cert, required_key_purpose, errors); + break; + } + if (*shortcircuit_chain_validation) + return; + + // Use the certificate's SPKI and subject when verifying the next certificate. + working_public_key_ = ParseAndCheckPublicKey(cert.tbs().spki_tlv, errors); + working_normalized_issuer_name_ = cert.normalized_subject(); +} + +bssl::UniquePtr<EVP_PKEY> PathVerifier::ParseAndCheckPublicKey( + const der::Input& spki, + CertErrors* errors) { + // Parse the public key. + bssl::UniquePtr<EVP_PKEY> pkey; + if (!ParsePublicKey(spki, &pkey)) { + errors->AddError(cert_errors::kFailedParsingSpki); + return nullptr; + } + + // Check if the key is acceptable by the delegate. + if (!delegate_->IsPublicKeyAcceptable(pkey.get(), errors)) + errors->AddError(cert_errors::kUnacceptablePublicKey); + + return pkey; +} + +void PathVerifier::Run( + const ParsedCertificateList& certs, + const CertificateTrust& last_cert_trust, + VerifyCertificateChainDelegate* delegate, + const der::GeneralizedTime& time, + KeyPurpose required_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, + std::set<der::Input>* user_constrained_policy_set, + CertPathErrors* errors) { + // This implementation is structured to mimic the description of certificate + // path verification given by RFC 5280 section 6.1. + DCHECK(delegate); + DCHECK(errors); + + delegate_ = delegate; + + // An empty chain is necessarily invalid. + if (certs.empty()) { + errors->GetOtherErrors()->AddError(cert_errors::kChainIsEmpty); + return; + } + + // Verifying a trusted leaf certificate is not permitted. (It isn't a + // well-specified operation.) See https://crbug.com/814994. + if (certs.size() == 1) { + errors->GetOtherErrors()->AddError(cert_errors::kChainIsLength1); + return; + } + + // RFC 5280's "n" variable is the length of the path, which does not count + // the trust anchor. (Although in practice it doesn't really change behaviors + // if n is used in place of n+1). + const size_t n = certs.size() - 1; + + valid_policy_tree_.Init(user_initial_policy_set); + + // RFC 5280 section section 6.1.2: + // + // If initial-explicit-policy is set, then the initial value + // [of explicit_policy] is 0, otherwise the initial value is n+1. + explicit_policy_ = + initial_explicit_policy == InitialExplicitPolicy::kTrue ? 0 : n + 1; + + // RFC 5280 section section 6.1.2: + // + // If initial-any-policy-inhibit is set, then the initial value + // [of inhibit_anyPolicy] is 0, otherwise the initial value is n+1. + inhibit_any_policy_ = + initial_any_policy_inhibit == InitialAnyPolicyInhibit::kTrue ? 0 : n + 1; + + // RFC 5280 section section 6.1.2: + // + // If initial-policy-mapping-inhibit is set, then the initial value + // [of policy_mapping] is 0, otherwise the initial value is n+1. + policy_mapping_ = + initial_policy_mapping_inhibit == InitialPolicyMappingInhibit::kTrue + ? 0 + : n + 1; + + // RFC 5280 section section 6.1.2: + // + // max_path_length: this integer is initialized to n, ... + max_path_length_ = n; + + // Iterate over all the certificates in the reverse direction: starting from + // the root certificate and progressing towards the target certificate. + // + // * i=0 : Root certificate (i.e. trust anchor) + // * i=1 : Certificate issued by root + // * i=x : Certificate i=x is issued by certificate i=x-1 + // * i=n : Target certificate. + for (size_t i = 0; i < certs.size(); ++i) { + const size_t index_into_certs = certs.size() - i - 1; + + // |is_target_cert| is true if the current certificate is the target + // certificate being verified. The target certificate isn't necessarily an + // end-entity certificate. + const bool is_target_cert = index_into_certs == 0; + const bool is_root_cert = i == 0; + + const ParsedCertificate& cert = *certs[index_into_certs]; + + // Output errors for the current certificate into an error bucket that is + // associated with that certificate. + CertErrors* cert_errors = errors->GetErrorsForCert(index_into_certs); + + if (is_root_cert) { + bool shortcircuit_chain_validation = false; + ProcessRootCertificate(cert, last_cert_trust, time, required_key_purpose, + cert_errors, &shortcircuit_chain_validation); + if (shortcircuit_chain_validation) { + // Chains that don't start from a trusted root should short-circuit the + // rest of the verification, as accumulating more errors from untrusted + // certificates would not be meaningful. + CHECK(cert_errors->ContainsAnyErrorWithSeverity( + CertError::SEVERITY_HIGH)); + return; + } + + // Don't do any other checks for root certificates. + continue; + } + + bool shortcircuit_chain_validation = false; + // Per RFC 5280 section 6.1: + // * Do basic processing for each certificate + // * If it is the last certificate in the path (target certificate) + // - Then run "Wrap up" + // - Otherwise run "Prepare for Next cert" + BasicCertificateProcessing(cert, is_target_cert, time, required_key_purpose, + cert_errors, &shortcircuit_chain_validation); + if (shortcircuit_chain_validation) { + // Signature errors should short-circuit the rest of the verification, as + // accumulating more errors from untrusted certificates would not be + // meaningful. + CHECK( + cert_errors->ContainsAnyErrorWithSeverity(CertError::SEVERITY_HIGH)); + return; + } + if (!is_target_cert) { + PrepareForNextCertificate(cert, cert_errors); + } else { + WrapUp(cert, cert_errors); + } + } + + if (user_constrained_policy_set) { + // valid_policy_tree_ already contains the intersection of valid policies + // with user_initial_policy_set. + valid_policy_tree_.GetValidRootPolicySet(user_constrained_policy_set); + } + + // TODO(eroman): RFC 5280 forbids duplicate certificates per section 6.1: + // + // A certificate MUST NOT appear more than once in a prospective + // certification path. +} + +} // namespace + +VerifyCertificateChainDelegate::~VerifyCertificateChainDelegate() = default; + +void VerifyCertificateChain( + const ParsedCertificateList& certs, + const CertificateTrust& last_cert_trust, + VerifyCertificateChainDelegate* delegate, + const der::GeneralizedTime& time, + KeyPurpose required_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, + std::set<der::Input>* user_constrained_policy_set, + CertPathErrors* errors) { + PathVerifier verifier; + verifier.Run(certs, last_cert_trust, delegate, time, required_key_purpose, + initial_explicit_policy, user_initial_policy_set, + initial_policy_mapping_inhibit, initial_any_policy_inhibit, + user_constrained_policy_set, errors); +} + +} // namespace net diff --git a/chromium/net/cert/pki/verify_certificate_chain.h b/chromium/net/cert/pki/verify_certificate_chain.h new file mode 100644 index 00000000000..3dd187e6ff2 --- /dev/null +++ b/chromium/net/cert/pki/verify_certificate_chain.h @@ -0,0 +1,248 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_VERIFY_CERTIFICATE_CHAIN_H_ +#define NET_CERT_PKI_VERIFY_CERTIFICATE_CHAIN_H_ + +#include <set> + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/der/input.h" +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace net { + +namespace der { +struct GeneralizedTime; +} + +struct CertificateTrust; + +// The key purpose (extended key usage) to check for during verification. +enum class KeyPurpose { + ANY_EKU, + SERVER_AUTH, + CLIENT_AUTH, +}; + +enum class InitialExplicitPolicy { + kFalse, + kTrue, +}; + +enum class InitialPolicyMappingInhibit { + kFalse, + kTrue, +}; + +enum class InitialAnyPolicyInhibit { + kFalse, + kTrue, +}; + +// VerifyCertificateChainDelegate exposes delegate methods used when verifying a +// chain. +class NET_EXPORT VerifyCertificateChainDelegate { + public: + // Implementations should return true if |signature_algorithm| is allowed for + // certificate signing, false otherwise. When returning false implementations + // can optionally add high-severity errors to |errors| with details on why it + // was rejected. + virtual bool IsSignatureAlgorithmAcceptable( + SignatureAlgorithm signature_algorithm, + CertErrors* errors) = 0; + + // Implementations should return true if |public_key| is acceptable. This is + // called for each certificate in the chain, including the target certificate. + // When returning false implementations can optionally add high-severity + // errors to |errors| with details on why it was rejected. + // + // |public_key| can be assumed to be non-null. + virtual bool IsPublicKeyAcceptable(EVP_PKEY* public_key, + CertErrors* errors) = 0; + + virtual ~VerifyCertificateChainDelegate(); +}; + +// VerifyCertificateChain() verifies an ordered certificate path in accordance +// with RFC 5280's "Certification Path Validation" algorithm (section 6). +// +// ----------------------------------------- +// Deviations from RFC 5280 +// ----------------------------------------- +// +// * If Extended Key Usage appears on intermediates, it is treated as +// a restriction on subordinate certificates. +// * No revocation checking is performed. +// +// ----------------------------------------- +// Additional responsibilities of the caller +// ----------------------------------------- +// +// After successful path verification, the caller is responsible for +// subsequently checking: +// +// * The end-entity's KeyUsage before using its SPKI. +// * The end-entity's name/subjectAltName. Name constraints from intermediates +// will have already been applied, so it is sufficient to check the +// end-entity for a match. The caller MUST NOT check hostnames on the +// commonName field because this implementation does not apply dnsName +// constraints on commonName. +// +// --------- +// Inputs +// --------- +// +// certs: +// A non-empty chain of DER-encoded certificates, listed in the +// "forward" direction. The first certificate is the target +// certificate to verify, and the last certificate has trustedness +// given by |last_cert_trust| (generally a trust anchor). +// +// * certs[0] is the target certificate to verify. +// * certs[i+1] holds the certificate that issued cert_chain[i]. +// * certs[N-1] the root certificate +// +// Note that THIS IS NOT identical in meaning to the same named +// "certs" input defined in RFC 5280 section 6.1.1.a. The differences +// are: +// +// * The order of certificates is reversed +// * In RFC 5280 "certs" DOES NOT include the trust anchor +// +// last_cert_trust: +// Trustedness of |certs.back()|. The trustedness of |certs.back()| +// MUST BE decided by the caller -- this function takes it purely as +// an input. Moreover, the CertificateTrust can be used to specify +// trust anchor constraints. +// +// This combined with |certs.back()| (the root certificate) fills a +// similar role to "trust anchor information" defined in RFC 5280 +// section 6.1.1.d. +// +// delegate: +// |delegate| must be non-null. It is used to answer policy questions such +// as whether a signature algorithm is acceptable, or a public key is strong +// enough. +// +// time: +// The UTC time to use for expiration checks. This is equivalent to +// the input from RFC 5280 section 6.1.1: +// +// (b) the current date/time. +// +// required_key_purpose: +// The key purpose that the target certificate needs to be valid for. +// +// user_initial_policy_set: +// This is equivalent to the same named input in RFC 5280 section +// 6.1.1: +// +// (c) user-initial-policy-set: A set of certificate policy +// identifiers naming the policies that are acceptable to the +// certificate user. The user-initial-policy-set contains the +// special value any-policy if the user is not concerned about +// certificate policy. +// +// initial_policy_mapping_inhibit: +// This is equivalent to the same named input in RFC 5280 section +// 6.1.1: +// +// (e) initial-policy-mapping-inhibit, which indicates if policy +// mapping is allowed in the certification path. +// +// initial_explicit_policy: +// This is equivalent to the same named input in RFC 5280 section +// 6.1.1: +// +// (f) initial-explicit-policy, which indicates if the path must be +// valid for at least one of the certificate policies in the +// user-initial-policy-set. +// +// initial_any_policy_inhibit: +// This is equivalent to the same named input in RFC 5280 section +// 6.1.1: +// +// (g) initial-any-policy-inhibit, which indicates whether the +// anyPolicy OID should be processed if it is included in a +// certificate. +// +// --------- +// Outputs +// --------- +// +// user_constrained_policy_set: +// Can be null. If non-null, |user_constrained_policy_set| will be filled +// with the matching policies (intersected with user_initial_policy_set). +// This is equivalent to the same named output in X.509 section 10.2. +// Note that it is OK for this to point to input user_initial_policy_set. +// +// errors: +// Must be non-null. The set of errors/warnings encountered while +// validating the path are appended to this structure. If verification +// failed, then there is guaranteed to be at least 1 high severity error +// written to |errors|. +// +// ------------------------- +// Trust Anchor constraints +// ------------------------- +// +// Conceptually, VerifyCertificateChain() sets RFC 5937's +// "enforceTrustAnchorConstraints" to true. +// +// One specifies trust anchor constraints using the |last_cert_trust| +// parameter in conjunction with extensions appearing in |certs.back()|. +// +// The trust anchor |certs.back()| is always passed as a certificate to +// this function, however the manner in which that certificate is +// interpreted depends on |last_cert_trust|: +// +// TRUSTED_ANCHOR: +// +// No properties from the root certificate, other than its Subject and +// SPKI, are checked during verification. This is the usual +// interpretation for a "trust anchor". +// +// TRUSTED_ANCHOR_WITH_EXPIRATION: +// +// The validity period of the root is checked, in addition to Subject and SPKI. +// +// TRUSTED_ANCHOR_WITH_CONSTRAINTS: +// +// Only a subset of extensions and properties from the certificate are checked, +// as described by RFC 5937. +// +// * Signature: No +// * Validity (expiration): No +// * Key usage: No +// * Extended key usage: Yes (not part of RFC 5937) +// * Basic constraints: Yes, but only the pathlen (CA=false is accepted) +// * Name constraints: Yes +// * Certificate policies: Not currently, TODO(crbug.com/634453) +// * Policy Mappings: No +// * inhibitAnyPolicy: Not currently, TODO(crbug.com/634453) +// * PolicyConstraints: Not currently, TODO(crbug.com/634452) +// +// The presence of any other unrecognized extension marked as critical fails +// validation. +NET_EXPORT void VerifyCertificateChain( + const ParsedCertificateList& certs, + const CertificateTrust& last_cert_trust, + VerifyCertificateChainDelegate* delegate, + const der::GeneralizedTime& time, + KeyPurpose required_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, + std::set<der::Input>* user_constrained_policy_set, + CertPathErrors* errors); + +} // namespace net + +#endif // NET_CERT_PKI_VERIFY_CERTIFICATE_CHAIN_H_ diff --git a/chromium/net/cert/pki/verify_certificate_chain_pkits_unittest.cc b/chromium/net/cert/pki/verify_certificate_chain_pkits_unittest.cc new file mode 100644 index 00000000000..7a2a4aa32ec --- /dev/null +++ b/chromium/net/cert/pki/verify_certificate_chain_pkits_unittest.cc @@ -0,0 +1,129 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_certificate_chain.h" + +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/simple_path_builder_delegate.h" +#include "net/cert/pki/trust_store.h" +#include "net/der/input.h" +#include "third_party/boringssl/src/include/openssl/pool.h" + +// These require CRL support, which is not implemented at the +// VerifyCertificateChain level. +#define Section7InvalidkeyUsageCriticalcRLSignFalseTest4 \ + DISABLED_Section7InvalidkeyUsageCriticalcRLSignFalseTest4 +#define Section7InvalidkeyUsageNotCriticalcRLSignFalseTest5 \ + DISABLED_Section7InvalidkeyUsageNotCriticalcRLSignFalseTest5 + +#include "net/cert/pki/nist_pkits_unittest.h" + +namespace net { + +namespace { + +class VerifyCertificateChainPkitsTestDelegate { + public: + static void RunTest(std::vector<std::string> cert_ders, + std::vector<std::string> crl_ders, + const PkitsTestInfo& info) { + ASSERT_FALSE(cert_ders.empty()); + + // PKITS lists chains from trust anchor to target, whereas + // VerifyCertificateChain takes them starting with the target and ending + // with the trust anchor. + std::vector<scoped_refptr<ParsedCertificate>> input_chain; + CertErrors parsing_errors; + for (auto i = cert_ders.rbegin(); i != cert_ders.rend(); ++i) { + ASSERT_TRUE(ParsedCertificate::CreateAndAddToVector( + bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( + reinterpret_cast<const uint8_t*>(i->data()), i->size(), nullptr)), + {}, &input_chain, &parsing_errors)) + << parsing_errors.ToDebugString(); + } + + SimplePathBuilderDelegate path_builder_delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + + std::set<der::Input> user_constrained_policy_set; + + CertPathErrors path_errors; + VerifyCertificateChain( + input_chain, CertificateTrust::ForTrustAnchor(), &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, &user_constrained_policy_set, + &path_errors); + bool did_succeed = !path_errors.ContainsHighSeverityErrors(); + + EXPECT_EQ(info.should_validate, did_succeed); + EXPECT_EQ(info.user_constrained_policy_set, user_constrained_policy_set); + + // Check that the errors match expectations. The errors are saved in a + // parallel file, as they don't apply generically to the third_party + // PKITS data. + if (!info.should_validate && !did_succeed) { + std::string errors_file_path = + std::string( + "net/data/verify_certificate_chain_unittest/pkits_errors/") + + info.test_number + std::string(".txt"); + + std::string expected_errors = ReadTestFileToString(errors_file_path); + + // Check that the errors match. + VerifyCertPathErrors(expected_errors, path_errors, input_chain, + errors_file_path); + } else if (!did_succeed) { + // If it failed and wasn't supposed to fail, print the errors. + EXPECT_EQ("", path_errors.ToDebugString(input_chain)); + } + } +}; + +} // namespace + +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest01SignatureVerification, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest02ValidityPeriods, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest03VerifyingNameChaining, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest06VerifyingBasicConstraints, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest07KeyUsage, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest08CertificatePolicies, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest09RequireExplicitPolicy, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest10PolicyMappings, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest11InhibitPolicyMapping, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest12InhibitAnyPolicy, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest13NameConstraints, + VerifyCertificateChainPkitsTestDelegate); +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + PkitsTest16PrivateCertificateExtensions, + VerifyCertificateChainPkitsTestDelegate); + +// These require CRL support, which is not implemented at the +// VerifyCertificateChain level: +// PkitsTest04BasicCertificateRevocationTests, +// PkitsTest05VerifyingPathswithSelfIssuedCertificates, +// PkitsTest14DistributionPoints, PkitsTest15DeltaCRLs + +} // namespace net diff --git a/chromium/net/cert/pki/verify_certificate_chain_typed_unittest.h b/chromium/net/cert/pki/verify_certificate_chain_typed_unittest.h new file mode 100644 index 00000000000..c563f17ffa0 --- /dev/null +++ b/chromium/net/cert/pki/verify_certificate_chain_typed_unittest.h @@ -0,0 +1,228 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_VERIFY_CERTIFICATE_CHAIN_TYPED_UNITTEST_H_ +#define NET_CERT_PKI_VERIFY_CERTIFICATE_CHAIN_TYPED_UNITTEST_H_ + +#include "net/cert/pem.h" +#include "net/cert/pki/parsed_certificate.h" +#include "net/cert/pki/test_helpers.h" +#include "net/cert/pki/trust_store.h" +#include "net/cert/pki/verify_certificate_chain.h" +#include "net/der/input.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +template <typename TestDelegate> +class VerifyCertificateChainTest : public ::testing::Test { + public: + void RunTest(const char* file_name) { + VerifyCertChainTest test; + + std::string path = + std::string("net/data/verify_certificate_chain_unittest/") + file_name; + + SCOPED_TRACE("Test file: " + path); + + if (!ReadVerifyCertChainTestFromFile(path, &test)) { + ADD_FAILURE() << "Couldn't load test case: " << path; + return; + } + + TestDelegate::Verify(test, path); + } +}; + +// Tests that have only one root. These can be tested without requiring any +// path-building ability. +template <typename TestDelegate> +class VerifyCertificateChainSingleRootTest + : public VerifyCertificateChainTest<TestDelegate> {}; + +TYPED_TEST_SUITE_P(VerifyCertificateChainSingleRootTest); + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, Simple) { + this->RunTest("target-and-intermediate/main.test"); + this->RunTest("target-and-intermediate/ta-with-expiration.test"); + this->RunTest("target-and-intermediate/ta-with-constraints.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, BasicConstraintsCa) { + this->RunTest("intermediate-lacks-basic-constraints/main.test"); + this->RunTest("intermediate-basic-constraints-ca-false/main.test"); + this->RunTest("intermediate-basic-constraints-not-critical/main.test"); + this->RunTest("root-lacks-basic-constraints/main.test"); + this->RunTest("root-lacks-basic-constraints/ta-with-constraints.test"); + this->RunTest("root-basic-constraints-ca-false/main.test"); + this->RunTest("root-basic-constraints-ca-false/ta-with-constraints.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, BasicConstraintsPathlen) { + this->RunTest("violates-basic-constraints-pathlen-0/main.test"); + this->RunTest("basic-constraints-pathlen-0-self-issued/main.test"); + this->RunTest("target-has-pathlen-but-not-ca/main.test"); + this->RunTest("violates-pathlen-1-from-root/main.test"); + this->RunTest("violates-pathlen-1-from-root/ta-with-constraints.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, UnknownExtension) { + this->RunTest("intermediate-unknown-critical-extension/main.test"); + this->RunTest("intermediate-unknown-non-critical-extension/main.test"); + this->RunTest("target-unknown-critical-extension/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, WeakSignature) { + this->RunTest("target-signed-with-md5/main.test"); + this->RunTest("intermediate-signed-with-md5/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, WrongSignature) { + this->RunTest("target-wrong-signature/main.test"); + this->RunTest("intermediate-and-target-wrong-signature/main.test"); + this->RunTest("incorrect-trust-anchor/main.test"); + this->RunTest("target-wrong-signature-no-authority-key-identifier/main.test"); + this->RunTest( + "intermediate-wrong-signature-no-authority-key-identifier/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, LastCertificateNotTrusted) { + this->RunTest("target-and-intermediate/distrusted-root.test"); + this->RunTest("target-and-intermediate/distrusted-root-expired.test"); + this->RunTest("target-and-intermediate/unspecified-trust-root.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, WeakPublicKey) { + this->RunTest("target-signed-by-512bit-rsa/main.test"); + this->RunTest("target-has-512bit-rsa-key/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, TargetSignedUsingEcdsa) { + this->RunTest("target-signed-using-ecdsa/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, Expired) { + this->RunTest("expired-target/not-before.test"); + this->RunTest("expired-target/not-after.test"); + this->RunTest("expired-intermediate/not-before.test"); + this->RunTest("expired-intermediate/not-after.test"); + this->RunTest("expired-root/not-before.test"); + this->RunTest("expired-root/not-before-ta-with-expiration.test"); + this->RunTest("expired-root/not-after.test"); + this->RunTest("expired-root/not-after-ta-with-expiration.test"); + this->RunTest("expired-root/not-after-ta-with-constraints.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, TargetNotEndEntity) { + this->RunTest("target-not-end-entity/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, KeyUsage) { + this->RunTest("intermediate-lacks-signing-key-usage/main.test"); + this->RunTest("target-has-keycertsign-but-not-ca/main.test"); + + this->RunTest("target-serverauth-various-keyusages/rsa-decipherOnly.test"); + this->RunTest( + "target-serverauth-various-keyusages/rsa-digitalSignature.test"); + this->RunTest("target-serverauth-various-keyusages/rsa-keyAgreement.test"); + this->RunTest("target-serverauth-various-keyusages/rsa-keyEncipherment.test"); + + this->RunTest("target-serverauth-various-keyusages/ec-decipherOnly.test"); + this->RunTest("target-serverauth-various-keyusages/ec-digitalSignature.test"); + this->RunTest("target-serverauth-various-keyusages/ec-keyAgreement.test"); + this->RunTest("target-serverauth-various-keyusages/ec-keyEncipherment.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, ExtendedKeyUsage) { + this->RunTest("intermediate-eku-clientauth/any.test"); + this->RunTest("intermediate-eku-clientauth/serverauth.test"); + this->RunTest("intermediate-eku-clientauth/clientauth.test"); + this->RunTest("intermediate-eku-any-and-clientauth/any.test"); + this->RunTest("intermediate-eku-any-and-clientauth/serverauth.test"); + this->RunTest("intermediate-eku-any-and-clientauth/clientauth.test"); + this->RunTest("target-eku-clientauth/any.test"); + this->RunTest("target-eku-clientauth/serverauth.test"); + this->RunTest("target-eku-clientauth/clientauth.test"); + this->RunTest("target-eku-none/any.test"); + this->RunTest("target-eku-none/serverauth.test"); + this->RunTest("target-eku-none/clientauth.test"); + this->RunTest("root-eku-clientauth/serverauth.test"); + this->RunTest("root-eku-clientauth/serverauth-ta-with-constraints.test"); + this->RunTest("intermediate-eku-server-gated-crypto/sha1-eku-any.test"); + this->RunTest( + "intermediate-eku-server-gated-crypto/sha1-eku-clientAuth.test"); + this->RunTest( + "intermediate-eku-server-gated-crypto/sha1-eku-serverAuth.test"); + this->RunTest("intermediate-eku-server-gated-crypto/sha256-eku-any.test"); + this->RunTest( + "intermediate-eku-server-gated-crypto/sha256-eku-clientAuth.test"); + this->RunTest( + "intermediate-eku-server-gated-crypto/sha256-eku-serverAuth.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, + IssuerAndSubjectNotByteForByteEqual) { + this->RunTest("issuer-and-subject-not-byte-for-byte-equal/target.test"); + this->RunTest("issuer-and-subject-not-byte-for-byte-equal/anchor.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, TrustAnchorNotSelfSigned) { + this->RunTest("non-self-signed-root/main.test"); + this->RunTest("non-self-signed-root/ta-with-constraints.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, KeyRollover) { + this->RunTest("key-rollover/oldchain.test"); + this->RunTest("key-rollover/rolloverchain.test"); + this->RunTest("key-rollover/longrolloverchain.test"); + this->RunTest("key-rollover/newchain.test"); +} + +// Test coverage of policies comes primarily from the PKITS tests. The +// tests here only cover aspects not already tested by PKITS. +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, Policies) { + this->RunTest("unknown-critical-policy-qualifier/main.test"); + this->RunTest("unknown-non-critical-policy-qualifier/main.test"); +} + +TYPED_TEST_P(VerifyCertificateChainSingleRootTest, ManyNames) { + this->RunTest("many-names/ok-all-types.test"); + this->RunTest("many-names/ok-different-types-dns.test"); + this->RunTest("many-names/ok-different-types-ips.test"); + this->RunTest("many-names/ok-different-types-dirnames.test"); + this->RunTest("many-names/toomany-all-types.test"); + this->RunTest("many-names/toomany-dns-excluded.test"); + this->RunTest("many-names/toomany-dns-permitted.test"); + this->RunTest("many-names/toomany-ips-excluded.test"); + this->RunTest("many-names/toomany-ips-permitted.test"); + this->RunTest("many-names/toomany-dirnames-excluded.test"); + this->RunTest("many-names/toomany-dirnames-permitted.test"); +} + +// TODO(eroman): Add test that invalid validity dates where the day or month +// ordinal not in range, like "March 39, 2016" are rejected. + +REGISTER_TYPED_TEST_SUITE_P(VerifyCertificateChainSingleRootTest, + Simple, + BasicConstraintsCa, + BasicConstraintsPathlen, + UnknownExtension, + WeakSignature, + WrongSignature, + LastCertificateNotTrusted, + WeakPublicKey, + TargetSignedUsingEcdsa, + Expired, + TargetNotEndEntity, + KeyUsage, + ExtendedKeyUsage, + IssuerAndSubjectNotByteForByteEqual, + TrustAnchorNotSelfSigned, + KeyRollover, + Policies, + ManyNames); + +} // namespace net + +#endif // NET_CERT_PKI_VERIFY_CERTIFICATE_CHAIN_TYPED_UNITTEST_H_ diff --git a/chromium/net/cert/pki/verify_certificate_chain_unittest.cc b/chromium/net/cert/pki/verify_certificate_chain_unittest.cc new file mode 100644 index 00000000000..a98532ebc0a --- /dev/null +++ b/chromium/net/cert/pki/verify_certificate_chain_unittest.cc @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_certificate_chain.h" + +#include "net/cert/pki/simple_path_builder_delegate.h" +#include "net/cert/pki/test_helpers.h" +#include "net/cert/pki/trust_store.h" +#include "net/cert/pki/verify_certificate_chain_typed_unittest.h" + +namespace net { + +namespace { + +class VerifyCertificateChainTestDelegate { + public: + static void Verify(const VerifyCertChainTest& test, + const std::string& test_file_path) { + SimplePathBuilderDelegate delegate( + 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); + + CertPathErrors errors; + // TODO(eroman): Check user_constrained_policy_set. + VerifyCertificateChain( + test.chain, test.last_cert_trust, &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, + nullptr /*user_constrained_policy_set*/, &errors); + VerifyCertPathErrors(test.expected_errors, errors, test.chain, + test_file_path); + } +}; + +} // namespace + +INSTANTIATE_TYPED_TEST_SUITE_P(VerifyCertificateChain, + VerifyCertificateChainSingleRootTest, + VerifyCertificateChainTestDelegate); + +} // namespace net diff --git a/chromium/net/cert/pki/verify_name_match.cc b/chromium/net/cert/pki/verify_name_match.cc new file mode 100644 index 00000000000..b17ab7e2296 --- /dev/null +++ b/chromium/net/cert/pki/verify_name_match.cc @@ -0,0 +1,418 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_name_match.h" + +#include "base/check.h" +#include "base/notreached.h" +#include "base/strings/string_util.h" +#include "net/cert/pki/cert_error_params.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/parse_name.h" +#include "net/der/input.h" +#include "net/der/parser.h" +#include "net/der/tag.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" + +namespace net { + +DEFINE_CERT_ERROR_ID(kFailedConvertingAttributeValue, + "Failed converting AttributeValue to string"); +DEFINE_CERT_ERROR_ID(kFailedNormalizingString, "Failed normalizing string"); + +namespace { + +// Types of character set checking that NormalizeDirectoryString can perform. +enum CharsetEnforcement { + NO_ENFORCEMENT, + ENFORCE_PRINTABLE_STRING, + ENFORCE_ASCII, +}; + +// Normalizes |output|, a UTF-8 encoded string, as if it contained +// only ASCII characters. +// +// This could be considered a partial subset of RFC 5280 rules, and +// is compatible with RFC 2459/3280. +// +// In particular, RFC 5280, Section 7.1 describes how UTF8String +// and PrintableString should be compared - using the LDAP StringPrep +// profile of RFC 4518, with case folding and whitespace compression. +// However, because it is optional for 2459/3280 implementations and because +// it's desirable to avoid the size cost of the StringPrep tables, +// this function treats |output| as if it was composed of ASCII. +// +// That is, rather than folding all whitespace characters, it only +// folds ' '. Rather than case folding using locale-aware handling, +// it only folds A-Z to a-z. +// +// This gives better results than outright rejecting (due to mismatched +// encodings), or from doing a strict binary comparison (the minimum +// required by RFC 3280), and is sufficient for those certificates +// publicly deployed. +// +// If |charset_enforcement| is not NO_ENFORCEMENT and |output| contains any +// characters not allowed in the specified charset, returns false. +// +// NOTE: |output| will be modified regardless of the return. +[[nodiscard]] bool NormalizeDirectoryString( + CharsetEnforcement charset_enforcement, + std::string* output) { + // Normalized version will always be equal or shorter than input. + // Normalize in place and then truncate the output if necessary. + std::string::const_iterator read_iter = output->begin(); + std::string::iterator write_iter = output->begin(); + + for (; read_iter != output->end() && *read_iter == ' '; ++read_iter) { + // Ignore leading whitespace. + } + + for (; read_iter != output->end(); ++read_iter) { + const unsigned char c = *read_iter; + if (c == ' ') { + // If there are non-whitespace characters remaining in input, compress + // multiple whitespace chars to a single space, otherwise ignore trailing + // whitespace. + std::string::const_iterator next_iter = read_iter + 1; + if (next_iter != output->end() && *next_iter != ' ') + *(write_iter++) = ' '; + } else if (base::IsAsciiUpper(c)) { + // Fold case. + *(write_iter++) = c + ('a' - 'A'); + } else { + // Note that these checks depend on the characters allowed by earlier + // conditions also being valid for the enforced charset. + switch (charset_enforcement) { + case ENFORCE_PRINTABLE_STRING: + // See NormalizePrintableStringValue comment for the acceptable list + // of characters. + if (!(base::IsAsciiLower(c) || (c >= '\'' && c <= ':') || c == '=' || + c == '?')) + return false; + break; + case ENFORCE_ASCII: + if (c > 0x7F) + return false; + break; + case NO_ENFORCEMENT: + break; + } + *(write_iter++) = c; + } + } + if (write_iter != output->end()) + output->erase(write_iter, output->end()); + return true; +} + +// Converts the value of X509NameAttribute |attribute| to UTF-8, normalizes it, +// and stores in |output|. The type of |attribute| must be one of the types for +// which IsNormalizableDirectoryString is true. +// +// If the value of |attribute| can be normalized, returns true and sets +// |output| to the case folded, normalized value. If the value of |attribute| +// is invalid, returns false. +// NOTE: |output| will be modified regardless of the return. +[[nodiscard]] bool NormalizeValue(X509NameAttribute attribute, + std::string* output, + CertErrors* errors) { + DCHECK(errors); + + if (!attribute.ValueAsStringUnsafe(output)) { + errors->AddError(kFailedConvertingAttributeValue, + CreateCertErrorParams1SizeT("tag", attribute.value_tag)); + return false; + } + + bool success = false; + switch (attribute.value_tag) { + case der::kPrintableString: + success = NormalizeDirectoryString(ENFORCE_PRINTABLE_STRING, output); + break; + case der::kBmpString: + case der::kUniversalString: + case der::kUtf8String: + success = NormalizeDirectoryString(NO_ENFORCEMENT, output); + break; + case der::kIA5String: + success = NormalizeDirectoryString(ENFORCE_ASCII, output); + break; + default: + NOTREACHED(); + success = false; + break; + } + + if (!success) { + errors->AddError(kFailedNormalizingString, + CreateCertErrorParams1SizeT("tag", attribute.value_tag)); + } + + return success; +} + +// Returns true if |tag| is a string type that NormalizeValue can handle. +bool IsNormalizableDirectoryString(der::Tag tag) { + switch (tag) { + case der::kPrintableString: + case der::kUtf8String: + // RFC 5280 only requires handling IA5String for comparing domainComponent + // values, but handling it here avoids the need to special case anything. + case der::kIA5String: + case der::kUniversalString: + case der::kBmpString: + return true; + // TeletexString isn't normalized. Section 8 of RFC 5280 briefly + // describes the historical confusion between treating TeletexString + // as Latin1String vs T.61, and there are even incompatibilities within + // T.61 implementations. As this time is virtually unused, simply + // treat it with a binary comparison, as permitted by RFC 3280/5280. + default: + return false; + } +} + +// Returns true if the value of X509NameAttribute |a| matches |b|. +bool VerifyValueMatch(X509NameAttribute a, X509NameAttribute b) { + if (IsNormalizableDirectoryString(a.value_tag) && + IsNormalizableDirectoryString(b.value_tag)) { + std::string a_normalized, b_normalized; + // TODO(eroman): Plumb this down. + CertErrors unused_errors; + if (!NormalizeValue(a, &a_normalized, &unused_errors) || + !NormalizeValue(b, &b_normalized, &unused_errors)) + return false; + return a_normalized == b_normalized; + } + // Attributes encoded with different types may be assumed to be unequal. + if (a.value_tag != b.value_tag) + return false; + // All other types use binary comparison. + return a.value == b.value; +} + +// Verifies that |a_parser| and |b_parser| are the same length and that every +// AttributeTypeAndValue in |a_parser| has a matching AttributeTypeAndValue in +// |b_parser|. +bool VerifyRdnMatch(der::Parser* a_parser, der::Parser* b_parser) { + RelativeDistinguishedName a_type_and_values, b_type_and_values; + if (!ReadRdn(a_parser, &a_type_and_values) || + !ReadRdn(b_parser, &b_type_and_values)) + return false; + + // RFC 5280 section 7.1: + // Two relative distinguished names RDN1 and RDN2 match if they have the same + // number of naming attributes and for each naming attribute in RDN1 there is + // a matching naming attribute in RDN2. + if (a_type_and_values.size() != b_type_and_values.size()) + return false; + + // The ordering of elements may differ due to denormalized values sorting + // differently in the DER encoding. Since the number of elements should be + // small, a naive linear search for each element should be fine. (Hostile + // certificates already have ways to provoke pathological behavior.) + for (const auto& a : a_type_and_values) { + auto b_iter = b_type_and_values.begin(); + for (; b_iter != b_type_and_values.end(); ++b_iter) { + const auto& b = *b_iter; + if (a.type == b.type && VerifyValueMatch(a, b)) { + break; + } + } + if (b_iter == b_type_and_values.end()) + return false; + // Remove the matched element from b_type_and_values to ensure duplicate + // elements in a_type_and_values can't match the same element in + // b_type_and_values multiple times. + b_type_and_values.erase(b_iter); + } + + // Every element in |a_type_and_values| had a matching element in + // |b_type_and_values|. + return true; +} + +enum NameMatchType { + EXACT_MATCH, + SUBTREE_MATCH, +}; + +// Verify that |a| matches |b|. If |match_type| is EXACT_MATCH, returns true if +// they are an exact match as defined by RFC 5280 7.1. If |match_type| is +// SUBTREE_MATCH, returns true if |a| is within the subtree defined by |b| as +// defined by RFC 5280 7.1. +// +// |a| and |b| are ASN.1 RDNSequence values (not including the Sequence tag), +// defined in RFC 5280 section 4.1.2.4: +// +// Name ::= CHOICE { -- only one possibility for now -- +// rdnSequence RDNSequence } +// +// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +// +// RelativeDistinguishedName ::= +// SET SIZE (1..MAX) OF AttributeTypeAndValue +bool VerifyNameMatchInternal(const der::Input& a, + const der::Input& b, + NameMatchType match_type) { + // Empty Names are allowed. RFC 5280 section 4.1.2.4 requires "The issuer + // field MUST contain a non-empty distinguished name (DN)", while section + // 4.1.2.6 allows for the Subject to be empty in certain cases. The caller is + // assumed to have verified those conditions. + + // RFC 5280 section 7.1: + // Two distinguished names DN1 and DN2 match if they have the same number of + // RDNs, for each RDN in DN1 there is a matching RDN in DN2, and the matching + // RDNs appear in the same order in both DNs. + + // As an optimization, first just compare the number of RDNs: + der::Parser a_rdn_sequence_counter(a); + der::Parser b_rdn_sequence_counter(b); + while (a_rdn_sequence_counter.HasMore() && b_rdn_sequence_counter.HasMore()) { + if (!a_rdn_sequence_counter.SkipTag(der::kSet) || + !b_rdn_sequence_counter.SkipTag(der::kSet)) { + return false; + } + } + // If doing exact match and either of the sequences has more elements than the + // other, not a match. If doing a subtree match, the first Name may have more + // RDNs than the second. + if (b_rdn_sequence_counter.HasMore()) + return false; + if (match_type == EXACT_MATCH && a_rdn_sequence_counter.HasMore()) + return false; + + // Verify that RDNs in |a| and |b| match. + der::Parser a_rdn_sequence(a); + der::Parser b_rdn_sequence(b); + while (a_rdn_sequence.HasMore() && b_rdn_sequence.HasMore()) { + der::Parser a_rdn, b_rdn; + if (!a_rdn_sequence.ReadConstructed(der::kSet, &a_rdn) || + !b_rdn_sequence.ReadConstructed(der::kSet, &b_rdn)) { + return false; + } + if (!VerifyRdnMatch(&a_rdn, &b_rdn)) + return false; + } + + return true; +} + +} // namespace + +bool NormalizeName(const der::Input& name_rdn_sequence, + std::string* normalized_rdn_sequence, + CertErrors* errors) { + DCHECK(errors); + + // RFC 5280 section 4.1.2.4 + // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + der::Parser rdn_sequence_parser(name_rdn_sequence); + + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) + return false; + + while (rdn_sequence_parser.HasMore()) { + // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue + 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; + + CBB rdn_cbb; + if (!CBB_add_asn1(cbb.get(), &rdn_cbb, CBS_ASN1_SET)) + return false; + + for (const auto& type_and_value : type_and_values) { + // AttributeTypeAndValue ::= SEQUENCE { + // type AttributeType, + // value AttributeValue } + CBB attribute_type_and_value_cbb, type_cbb, value_cbb; + if (!CBB_add_asn1(&rdn_cbb, &attribute_type_and_value_cbb, + CBS_ASN1_SEQUENCE)) { + return false; + } + + // AttributeType ::= OBJECT IDENTIFIER + if (!CBB_add_asn1(&attribute_type_and_value_cbb, &type_cbb, + CBS_ASN1_OBJECT) || + !CBB_add_bytes(&type_cbb, type_and_value.type.UnsafeData(), + type_and_value.type.Length())) { + return false; + } + + // AttributeValue ::= ANY -- DEFINED BY AttributeType + if (IsNormalizableDirectoryString(type_and_value.value_tag)) { + std::string normalized_value; + if (!NormalizeValue(type_and_value, &normalized_value, errors)) + return false; + if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb, + CBS_ASN1_UTF8STRING) || + !CBB_add_bytes( + &value_cbb, + reinterpret_cast<const uint8_t*>(normalized_value.data()), + normalized_value.size())) + return false; + } else { + if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb, + type_and_value.value_tag) || + !CBB_add_bytes(&value_cbb, type_and_value.value.UnsafeData(), + type_and_value.value.Length())) + return false; + } + + if (!CBB_flush(&rdn_cbb)) + return false; + } + + // Ensure the encoded AttributeTypeAndValue values in the SET OF are sorted. + if (!CBB_flush_asn1_set_of(&rdn_cbb) || !CBB_flush(cbb.get())) + return false; + } + + normalized_rdn_sequence->assign(CBB_data(cbb.get()), + CBB_data(cbb.get()) + CBB_len(cbb.get())); + return true; +} + +bool VerifyNameMatch(const der::Input& a_rdn_sequence, + const der::Input& b_rdn_sequence) { + return VerifyNameMatchInternal(a_rdn_sequence, b_rdn_sequence, EXACT_MATCH); +} + +bool VerifyNameInSubtree(const der::Input& name_rdn_sequence, + const der::Input& parent_rdn_sequence) { + return VerifyNameMatchInternal(name_rdn_sequence, parent_rdn_sequence, + SUBTREE_MATCH); +} + +bool NameContainsEmailAddress(const der::Input& name_rdn_sequence, + bool* contained_email_address) { + der::Parser rdn_sequence_parser(name_rdn_sequence); + + 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; + + for (const auto& type_and_value : type_and_values) { + if (type_and_value.type == der::Input(kTypeEmailAddressOid)) { + *contained_email_address = true; + return true; + } + } + } + + *contained_email_address = false; + return true; +} + +} // namespace net diff --git a/chromium/net/cert/pki/verify_name_match.h b/chromium/net/cert/pki/verify_name_match.h new file mode 100644 index 00000000000..4e49d435df5 --- /dev/null +++ b/chromium/net/cert/pki/verify_name_match.h @@ -0,0 +1,57 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_VERIFY_NAME_MATCH_H_ +#define NET_CERT_PKI_VERIFY_NAME_MATCH_H_ + +#include <string> + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" + +namespace net { + +class CertErrors; + +namespace der { +class Input; +} // namespace der + +// Normalizes DER-encoded X.501 Name |name_rdn_sequence| (which should not +// include the Sequence tag). If successful, returns true and stores the +// normalized DER-encoded Name into |normalized_rdn_sequence| (not including an +// outer Sequence tag). Returns false if there was an error parsing or +// normalizing the input, and adds error information to |errors|. |errors| must +// be non-null. +NET_EXPORT bool NormalizeName(const der::Input& name_rdn_sequence, + std::string* normalized_rdn_sequence, + CertErrors* errors); + +// Compares DER-encoded X.501 Name values according to RFC 5280 rules. +// |a_rdn_sequence| and |b_rdn_sequence| should be the DER-encoded RDNSequence +// values (not including the Sequence tag). +// Returns true if |a_rdn_sequence| and |b_rdn_sequence| match. +NET_EXPORT bool VerifyNameMatch(const der::Input& a_rdn_sequence, + const der::Input& b_rdn_sequence); + +// Compares |name_rdn_sequence| and |parent_rdn_sequence| and return true if +// |name_rdn_sequence| is within the subtree defined by |parent_rdn_sequence| as +// defined by RFC 5280 section 7.1. |name_rdn_sequence| and +// |parent_rdn_sequence| should be the DER-encoded sequence values (not +// including the Sequence tag). +NET_EXPORT bool VerifyNameInSubtree(const der::Input& name_rdn_sequence, + const der::Input& parent_rdn_sequence); + +// Helper functions: + +// Checks if |name_rdn_sequence| contains an emailAddress attribute type. +// If the return value is true, |*contained_email_address| will be set to +// indicate whether an emailAddress attribute was present. +// Returns false if there was a parsing error. +[[nodiscard]] bool NameContainsEmailAddress(const der::Input& name_rdn_sequence, + bool* contained_email_address); + +} // namespace net + +#endif // NET_CERT_PKI_VERIFY_NAME_MATCH_H_ diff --git a/chromium/net/cert/pki/verify_name_match_fuzzer.cc b/chromium/net/cert/pki/verify_name_match_fuzzer.cc new file mode 100644 index 00000000000..02ae46f62bd --- /dev/null +++ b/chromium/net/cert/pki/verify_name_match_fuzzer.cc @@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_name_match.h" + +#include <stddef.h> +#include <stdint.h> + +#include <fuzzer/FuzzedDataProvider.h> + +#include <vector> + +#include "net/der/input.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fuzzed_data(data, size); + + // Intentionally using uint16_t here to avoid empty |second_part|. + size_t first_part_size = fuzzed_data.ConsumeIntegral<uint16_t>(); + std::vector<uint8_t> first_part = + fuzzed_data.ConsumeBytes<uint8_t>(first_part_size); + std::vector<uint8_t> second_part = + fuzzed_data.ConsumeRemainingBytes<uint8_t>(); + + net::der::Input in1(first_part.data(), first_part.size()); + net::der::Input in2(second_part.data(), second_part.size()); + bool match = net::VerifyNameMatch(in1, in2); + bool reverse_order_match = net::VerifyNameMatch(in2, in1); + // Result should be the same regardless of argument order. + CHECK_EQ(match, reverse_order_match); + return 0; +} diff --git a/chromium/net/cert/pki/verify_name_match_normalizename_fuzzer.cc b/chromium/net/cert/pki/verify_name_match_normalizename_fuzzer.cc new file mode 100644 index 00000000000..dc5c810c501 --- /dev/null +++ b/chromium/net/cert/pki/verify_name_match_normalizename_fuzzer.cc @@ -0,0 +1,26 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_name_match.h" + +#include "net/cert/pki/cert_errors.h" +#include "net/der/input.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + net::der::Input in(data, size); + std::string normalized_der; + net::CertErrors errors; + bool success = net::NormalizeName(in, &normalized_der, &errors); + if (success) { + // If the input was successfully normalized, re-normalizing it should + // produce the same output again. + std::string renormalized_der; + bool renormalize_success = net::NormalizeName( + net::der::Input(&normalized_der), &renormalized_der, &errors); + CHECK(renormalize_success); + CHECK_EQ(normalized_der, renormalized_der); + } + return 0; +} diff --git a/chromium/net/cert/pki/verify_name_match_unittest.cc b/chromium/net/cert/pki/verify_name_match_unittest.cc new file mode 100644 index 00000000000..59660c0c936 --- /dev/null +++ b/chromium/net/cert/pki/verify_name_match_unittest.cc @@ -0,0 +1,612 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_name_match.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "net/cert/pki/test_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +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 = "net/data/verify_name_match_unittest/names/" + prefix + + "-" + value_type + "-" + suffix + ".pem"; + + const PemBlockMapping mappings[] = { + {"NAME", result}, + }; + + return ReadTestDataFromPemFile(path, mappings); +} + +bool TypesAreComparable(const std::string& type_1, const std::string& type_2) { + if (type_1 == type_2) + return true; + if ((type_1 == "PRINTABLESTRING" || type_1 == "UTF8" || + type_1 == "BMPSTRING" || type_1 == "UNIVERSALSTRING") && + (type_2 == "PRINTABLESTRING" || type_2 == "UTF8" || + type_2 == "BMPSTRING" || type_2 == "UNIVERSALSTRING")) { + return true; + } + return false; +} + +// All string types. +static const char* kValueTypes[] = {"PRINTABLESTRING", "T61STRING", "UTF8", + "BMPSTRING", "UNIVERSALSTRING"}; +// String types that can encode the Unicode Basic Multilingual Plane. +static const char* kUnicodeBMPValueTypes[] = {"UTF8", "BMPSTRING", + "UNIVERSALSTRING"}; +// String types that can encode the Unicode Supplementary Planes. +static const char* kUnicodeSupplementaryValueTypes[] = {"UTF8", + "UNIVERSALSTRING"}; + +static const char* kMangleTypes[] = {"unmangled", "case_swap", + "extra_whitespace"}; + +} // namespace + +class VerifyNameMatchSimpleTest + : public ::testing::TestWithParam< + ::testing::tuple<const char*, const char*>> { + public: + std::string value_type() const { return ::testing::get<0>(GetParam()); } + std::string suffix() const { return ::testing::get<1>(GetParam()); } +}; + +// Compare each input against itself, verifies that all input data is parsed +// successfully. +TEST_P(VerifyNameMatchSimpleTest, ExactEquality) { + std::string der; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix(), &der)); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&der), + SequenceValueFromString(&der))); + + std::string der_extra_attr; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix() + "-extra_attr", + &der_extra_attr)); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&der_extra_attr), + SequenceValueFromString(&der_extra_attr))); + + std::string der_extra_rdn; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix() + "-extra_rdn", + &der_extra_rdn)); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&der_extra_rdn), + SequenceValueFromString(&der_extra_rdn))); +} + +// Ensure that a Name does not match another Name which is exactly the same but +// with an extra attribute in one Relative Distinguished Name. +TEST_P(VerifyNameMatchSimpleTest, ExtraAttrDoesNotMatch) { + std::string der; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix(), &der)); + std::string der_extra_attr; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix() + "-extra_attr", + &der_extra_attr)); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der), + SequenceValueFromString(&der_extra_attr))); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der_extra_attr), + SequenceValueFromString(&der))); +} + +// Ensure that a Name does not match another Name which has the same number of +// RDNs and attributes, but where one of the attributes is duplicated in one of +// the names but not in the other. +TEST_P(VerifyNameMatchSimpleTest, DupeAttrDoesNotMatch) { + std::string der_dupe_attr; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix() + "-dupe_attr", + &der_dupe_attr)); + std::string der_extra_attr; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix() + "-extra_attr", + &der_extra_attr)); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der_dupe_attr), + SequenceValueFromString(&der_extra_attr))); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der_extra_attr), + SequenceValueFromString(&der_dupe_attr))); + // However, the name with a dupe attribute should match itself. + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&der_dupe_attr), + SequenceValueFromString(&der_dupe_attr))); +} + +// Ensure that a Name does not match another Name which is exactly the same but +// with an extra Relative Distinguished Name. +TEST_P(VerifyNameMatchSimpleTest, ExtraRdnDoesNotMatch) { + std::string der; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix(), &der)); + std::string der_extra_rdn; + ASSERT_TRUE(LoadTestData("ascii", value_type(), suffix() + "-extra_rdn", + &der_extra_rdn)); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der), + SequenceValueFromString(&der_extra_rdn))); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der_extra_rdn), + SequenceValueFromString(&der))); +} + +// Runs VerifyNameMatchSimpleTest for all combinations of value_type and and +// suffix. +INSTANTIATE_TEST_SUITE_P(InstantiationName, + VerifyNameMatchSimpleTest, + ::testing::Combine(::testing::ValuesIn(kValueTypes), + ::testing::ValuesIn(kMangleTypes))); + +class VerifyNameMatchNormalizationTest + : public ::testing::TestWithParam<::testing::tuple<bool, const char*>> { + public: + bool expected_result() const { return ::testing::get<0>(GetParam()); } + std::string value_type() const { return ::testing::get<1>(GetParam()); } +}; + +// Verify matching is case insensitive (for the types which currently support +// normalization). +TEST_P(VerifyNameMatchNormalizationTest, CaseInsensitivity) { + std::string normal; + ASSERT_TRUE(LoadTestData("ascii", value_type(), "unmangled", &normal)); + std::string case_swap; + ASSERT_TRUE(LoadTestData("ascii", value_type(), "case_swap", &case_swap)); + EXPECT_EQ(expected_result(), + VerifyNameMatch(SequenceValueFromString(&normal), + SequenceValueFromString(&case_swap))); + EXPECT_EQ(expected_result(), + VerifyNameMatch(SequenceValueFromString(&case_swap), + SequenceValueFromString(&normal))); +} + +// Verify matching folds whitespace (for the types which currently support +// normalization). +TEST_P(VerifyNameMatchNormalizationTest, CollapseWhitespace) { + std::string normal; + ASSERT_TRUE(LoadTestData("ascii", value_type(), "unmangled", &normal)); + std::string whitespace; + ASSERT_TRUE( + LoadTestData("ascii", value_type(), "extra_whitespace", &whitespace)); + EXPECT_EQ(expected_result(), + VerifyNameMatch(SequenceValueFromString(&normal), + SequenceValueFromString(&whitespace))); + EXPECT_EQ(expected_result(), + VerifyNameMatch(SequenceValueFromString(&whitespace), + SequenceValueFromString(&normal))); +} + +// Runs VerifyNameMatchNormalizationTest for each (expected_result, value_type) +// tuple. +INSTANTIATE_TEST_SUITE_P( + InstantiationName, + VerifyNameMatchNormalizationTest, + ::testing::Values( + ::testing::make_tuple(true, + static_cast<const char*>("PRINTABLESTRING")), + ::testing::make_tuple(false, static_cast<const char*>("T61STRING")), + ::testing::make_tuple(true, static_cast<const char*>("UTF8")), + ::testing::make_tuple(true, static_cast<const char*>("BMPSTRING")), + ::testing::make_tuple(true, + static_cast<const char*>("UNIVERSALSTRING")))); + +class VerifyNameMatchDifferingTypesTest + : public ::testing::TestWithParam< + ::testing::tuple<const char*, const char*>> { + public: + std::string value_type_1() const { return ::testing::get<0>(GetParam()); } + std::string value_type_2() const { return ::testing::get<1>(GetParam()); } +}; + +TEST_P(VerifyNameMatchDifferingTypesTest, NormalizableTypesAreEqual) { + std::string der_1; + ASSERT_TRUE(LoadTestData("ascii", value_type_1(), "unmangled", &der_1)); + std::string der_2; + ASSERT_TRUE(LoadTestData("ascii", value_type_2(), "unmangled", &der_2)); + if (TypesAreComparable(value_type_1(), value_type_2())) { + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2))); + } else { + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2))); + } +} + +TEST_P(VerifyNameMatchDifferingTypesTest, NormalizableTypesInSubtrees) { + std::string der_1; + ASSERT_TRUE(LoadTestData("ascii", value_type_1(), "unmangled", &der_1)); + std::string der_1_extra_rdn; + ASSERT_TRUE(LoadTestData("ascii", value_type_1(), "unmangled-extra_rdn", + &der_1_extra_rdn)); + std::string der_1_extra_attr; + ASSERT_TRUE(LoadTestData("ascii", value_type_1(), "unmangled-extra_attr", + &der_1_extra_attr)); + std::string der_2; + ASSERT_TRUE(LoadTestData("ascii", value_type_2(), "unmangled", &der_2)); + std::string der_2_extra_rdn; + ASSERT_TRUE(LoadTestData("ascii", value_type_2(), "unmangled-extra_rdn", + &der_2_extra_rdn)); + std::string der_2_extra_attr; + ASSERT_TRUE(LoadTestData("ascii", value_type_2(), "unmangled-extra_attr", + &der_2_extra_attr)); + + if (TypesAreComparable(value_type_1(), value_type_2())) { + EXPECT_TRUE(VerifyNameInSubtree(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2))); + EXPECT_TRUE(VerifyNameInSubtree(SequenceValueFromString(&der_2), + SequenceValueFromString(&der_1))); + EXPECT_TRUE(VerifyNameInSubtree(SequenceValueFromString(&der_1_extra_rdn), + SequenceValueFromString(&der_2))); + EXPECT_TRUE(VerifyNameInSubtree(SequenceValueFromString(&der_2_extra_rdn), + SequenceValueFromString(&der_1))); + } else { + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_2), + SequenceValueFromString(&der_1))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_1_extra_rdn), + SequenceValueFromString(&der_2))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_2_extra_rdn), + SequenceValueFromString(&der_1))); + } + + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2_extra_rdn))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_2), + SequenceValueFromString(&der_1_extra_rdn))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_1_extra_attr), + SequenceValueFromString(&der_2))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_2_extra_attr), + SequenceValueFromString(&der_1))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2_extra_attr))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&der_2), + SequenceValueFromString(&der_1_extra_attr))); +} + +// Runs VerifyNameMatchDifferingTypesTest for all combinations of value types in +// value_type1 and value_type_2. +INSTANTIATE_TEST_SUITE_P(InstantiationName, + VerifyNameMatchDifferingTypesTest, + ::testing::Combine(::testing::ValuesIn(kValueTypes), + ::testing::ValuesIn(kValueTypes))); + +class VerifyNameMatchUnicodeConversionTest + : public ::testing::TestWithParam< + ::testing::tuple<const char*, + ::testing::tuple<const char*, const char*>>> { + public: + std::string prefix() const { return ::testing::get<0>(GetParam()); } + std::string value_type_1() const { + return ::testing::get<0>(::testing::get<1>(GetParam())); + } + std::string value_type_2() const { + return ::testing::get<1>(::testing::get<1>(GetParam())); + } +}; + +TEST_P(VerifyNameMatchUnicodeConversionTest, UnicodeConversionsAreEqual) { + std::string der_1; + ASSERT_TRUE(LoadTestData(prefix(), value_type_1(), "unmangled", &der_1)); + std::string der_2; + ASSERT_TRUE(LoadTestData(prefix(), value_type_2(), "unmangled", &der_2)); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&der_1), + SequenceValueFromString(&der_2))); +} + +// Runs VerifyNameMatchUnicodeConversionTest with prefix="unicode_bmp" for all +// combinations of Basic Multilingual Plane-capable value types in value_type1 +// and value_type_2. +INSTANTIATE_TEST_SUITE_P( + BMPConversion, + VerifyNameMatchUnicodeConversionTest, + ::testing::Combine( + ::testing::Values("unicode_bmp"), + ::testing::Combine(::testing::ValuesIn(kUnicodeBMPValueTypes), + ::testing::ValuesIn(kUnicodeBMPValueTypes)))); + +// Runs VerifyNameMatchUnicodeConversionTest with prefix="unicode_supplementary" +// for all combinations of Unicode Supplementary Plane-capable value types in +// value_type1 and value_type_2. +INSTANTIATE_TEST_SUITE_P( + SMPConversion, + VerifyNameMatchUnicodeConversionTest, + ::testing::Combine( + ::testing::Values("unicode_supplementary"), + ::testing::Combine( + ::testing::ValuesIn(kUnicodeSupplementaryValueTypes), + ::testing::ValuesIn(kUnicodeSupplementaryValueTypes)))); + +// Matching should fail if a PrintableString contains invalid characters. +TEST(VerifyNameMatchInvalidDataTest, FailOnInvalidPrintableStringChars) { + std::string der; + ASSERT_TRUE(LoadTestData("ascii", "PRINTABLESTRING", "unmangled", &der)); + // Find a known location inside a PrintableString in the DER-encoded data. + size_t replace_location = der.find("0123456789"); + ASSERT_NE(std::string::npos, replace_location); + for (int c = 0; c < 256; ++c) { + SCOPED_TRACE(base::NumberToString(c)); + if (base::IsAsciiAlpha(c) || base::IsAsciiDigit(c)) + continue; + switch (c) { + case ' ': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case '=': + case '?': + continue; + } + der.replace(replace_location, 1, 1, c); + // Verification should fail due to the invalid character. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&der), + SequenceValueFromString(&der))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE( + NormalizeName(SequenceValueFromString(&der), &normalized_der, &errors)); + } +} + +// Matching should fail if an IA5String contains invalid characters. +TEST(VerifyNameMatchInvalidDataTest, FailOnInvalidIA5StringChars) { + std::string der; + ASSERT_TRUE(LoadTestData("ascii", "mixed", "rdn_dupetype_sorting_1", &der)); + // Find a known location inside an IA5String in the DER-encoded data. + size_t replace_location = der.find("eXaMple"); + ASSERT_NE(std::string::npos, replace_location); + for (int c = 0; c < 256; ++c) { + SCOPED_TRACE(base::NumberToString(c)); + der.replace(replace_location, 1, 1, c); + bool expected_result = (c <= 127); + EXPECT_EQ(expected_result, VerifyNameMatch(SequenceValueFromString(&der), + SequenceValueFromString(&der))); + std::string normalized_der; + CertErrors errors; + EXPECT_EQ(expected_result, NormalizeName(SequenceValueFromString(&der), + &normalized_der, &errors)); + } +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnAttributeTypeAndValueExtraData) { + std::string invalid; + ASSERT_TRUE( + LoadTestData("invalid", "AttributeTypeAndValue", "extradata", &invalid)); + // Verification should fail due to extra element in AttributeTypeAndValue + // sequence. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnAttributeTypeAndValueShort) { + std::string invalid; + ASSERT_TRUE(LoadTestData("invalid", "AttributeTypeAndValue", "onlyOneElement", + &invalid)); + // Verification should fail due to AttributeTypeAndValue sequence having only + // one element. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnAttributeTypeAndValueEmpty) { + std::string invalid; + ASSERT_TRUE( + LoadTestData("invalid", "AttributeTypeAndValue", "empty", &invalid)); + // Verification should fail due to empty AttributeTypeAndValue sequence. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnBadAttributeType) { + std::string invalid; + ASSERT_TRUE(LoadTestData("invalid", "AttributeTypeAndValue", + "badAttributeType", &invalid)); + // Verification should fail due to Attribute Type not being an OID. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnAttributeTypeAndValueNotSequence) { + std::string invalid; + ASSERT_TRUE(LoadTestData("invalid", "AttributeTypeAndValue", "setNotSequence", + &invalid)); + // Verification should fail due to AttributeTypeAndValue being a Set instead + // of a Sequence. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnRdnNotSet) { + std::string invalid; + ASSERT_TRUE(LoadTestData("invalid", "RDN", "sequenceInsteadOfSet", &invalid)); + // Verification should fail due to RDN being a Sequence instead of a Set. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchInvalidDataTest, FailOnEmptyRdn) { + std::string invalid; + ASSERT_TRUE(LoadTestData("invalid", "RDN", "empty", &invalid)); + // Verification should fail due to RDN having zero AttributeTypeAndValues. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +// Matching should fail if a BMPString contains surrogates. +TEST(VerifyNameMatchInvalidDataTest, FailOnBmpStringSurrogates) { + std::string normal; + ASSERT_TRUE(LoadTestData("unicode_bmp", "BMPSTRING", "unmangled", &normal)); + // Find a known location inside a BMPSTRING in the DER-encoded data. + size_t replace_location = normal.find("\x67\x71\x4e\xac"); + ASSERT_NE(std::string::npos, replace_location); + // Replace with U+1D400 MATHEMATICAL BOLD CAPITAL A, which requires surrogates + // to represent. + std::string invalid = + normal.replace(replace_location, 4, std::string("\xd8\x35\xdc\x00", 4)); + // Verification should fail due to the invalid codepoints. + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); + std::string normalized_der; + CertErrors errors; + EXPECT_FALSE(NormalizeName(SequenceValueFromString(&invalid), &normalized_der, + &errors)); +} + +TEST(VerifyNameMatchTest, EmptyNameMatching) { + std::string empty; + ASSERT_TRUE(LoadTestData("valid", "Name", "empty", &empty)); + // Empty names are equal. + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&empty), + SequenceValueFromString(&empty))); + // An empty name normalized is unchanged. + std::string normalized_empty_der; + CertErrors errors; + EXPECT_TRUE(NormalizeName(SequenceValueFromString(&empty), + &normalized_empty_der, &errors)); + EXPECT_EQ(SequenceValueFromString(&empty), der::Input(&normalized_empty_der)); + + // An empty name is not equal to non-empty name. + std::string non_empty; + ASSERT_TRUE( + LoadTestData("ascii", "PRINTABLESTRING", "unmangled", &non_empty)); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&empty), + SequenceValueFromString(&non_empty))); + EXPECT_FALSE(VerifyNameMatch(SequenceValueFromString(&non_empty), + SequenceValueFromString(&empty))); +} + +// Matching should succeed when the RDNs are sorted differently but are still +// equal after normalizing. +TEST(VerifyNameMatchRDNSorting, Simple) { + std::string a; + ASSERT_TRUE(LoadTestData("ascii", "PRINTABLESTRING", "rdn_sorting_1", &a)); + std::string b; + ASSERT_TRUE(LoadTestData("ascii", "PRINTABLESTRING", "rdn_sorting_2", &b)); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&a), + SequenceValueFromString(&b))); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&b), + SequenceValueFromString(&a))); +} + +// Matching should succeed when the RDNs are sorted differently but are still +// equal after normalizing, even in malformed RDNs that contain multiple +// elements with the same type. +TEST(VerifyNameMatchRDNSorting, DuplicateTypes) { + std::string a; + ASSERT_TRUE(LoadTestData("ascii", "mixed", "rdn_dupetype_sorting_1", &a)); + std::string b; + ASSERT_TRUE(LoadTestData("ascii", "mixed", "rdn_dupetype_sorting_2", &b)); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&a), + SequenceValueFromString(&b))); + EXPECT_TRUE(VerifyNameMatch(SequenceValueFromString(&b), + SequenceValueFromString(&a))); +} + +TEST(VerifyNameInSubtreeInvalidDataTest, FailOnEmptyRdn) { + std::string valid; + ASSERT_TRUE(LoadTestData("ascii", "PRINTABLESTRING", "unmangled", &valid)); + std::string invalid; + ASSERT_TRUE(LoadTestData("invalid", "RDN", "empty", &invalid)); + // For both |name| and |parent|, a RelativeDistinguishedName must have at + // least one AttributeTypeAndValue. + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&valid), + SequenceValueFromString(&invalid))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&invalid), + SequenceValueFromString(&valid))); + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&invalid), + SequenceValueFromString(&invalid))); +} + +TEST(VerifyNameInSubtreeTest, EmptyNameMatching) { + std::string empty; + ASSERT_TRUE(LoadTestData("valid", "Name", "empty", &empty)); + std::string non_empty; + ASSERT_TRUE( + LoadTestData("ascii", "PRINTABLESTRING", "unmangled", &non_empty)); + // Empty name is in the subtree defined by empty name. + EXPECT_TRUE(VerifyNameInSubtree(SequenceValueFromString(&empty), + SequenceValueFromString(&empty))); + // Any non-empty name is in the subtree defined by empty name. + EXPECT_TRUE(VerifyNameInSubtree(SequenceValueFromString(&non_empty), + SequenceValueFromString(&empty))); + // Empty name is not in the subtree defined by non-empty name. + EXPECT_FALSE(VerifyNameInSubtree(SequenceValueFromString(&empty), + SequenceValueFromString(&non_empty))); +} + +// Verify that the normalized output matches the pre-generated expected value +// for a single larger input that exercises all of the string types, unicode +// (basic and supplemental planes), whitespace collapsing, case folding, as +// well as SET sorting. +TEST(NameNormalizationTest, TestEverything) { + std::string expected_normalized_der; + ASSERT_TRUE( + LoadTestData("unicode", "mixed", "normalized", &expected_normalized_der)); + + std::string raw_der; + ASSERT_TRUE(LoadTestData("unicode", "mixed", "unnormalized", &raw_der)); + std::string normalized_der; + CertErrors errors; + ASSERT_TRUE(NormalizeName(SequenceValueFromString(&raw_der), &normalized_der, + &errors)); + EXPECT_EQ(SequenceValueFromString(&expected_normalized_der), + der::Input(&normalized_der)); + // Re-normalizing an already normalized Name should not change it. + std::string renormalized_der; + ASSERT_TRUE( + NormalizeName(der::Input(&normalized_der), &renormalized_der, &errors)); + EXPECT_EQ(normalized_der, renormalized_der); +} + +// Unknown AttributeValue types normalize as-is, even non-primitive tags. +TEST(NameNormalizationTest, NormalizeCustom) { + std::string raw_der; + ASSERT_TRUE(LoadTestData("custom", "custom", "normalized", &raw_der)); + + std::string normalized_der; + CertErrors errors; + ASSERT_TRUE(NormalizeName(SequenceValueFromString(&raw_der), &normalized_der, + &errors)); + EXPECT_EQ(SequenceValueFromString(&raw_der), der::Input(&normalized_der)); +} + +} // namespace net diff --git a/chromium/net/cert/pki/verify_name_match_verifynameinsubtree_fuzzer.cc b/chromium/net/cert/pki/verify_name_match_verifynameinsubtree_fuzzer.cc new file mode 100644 index 00000000000..996a6353342 --- /dev/null +++ b/chromium/net/cert/pki/verify_name_match_verifynameinsubtree_fuzzer.cc @@ -0,0 +1,35 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_name_match.h" + +#include <stddef.h> +#include <stdint.h> + +#include <fuzzer/FuzzedDataProvider.h> + +#include <vector> + +#include "net/der/input.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fuzzed_data(data, size); + + // Intentionally using uint16_t here to avoid empty |second_part|. + size_t first_part_size = fuzzed_data.ConsumeIntegral<uint16_t>(); + std::vector<uint8_t> first_part = + fuzzed_data.ConsumeBytes<uint8_t>(first_part_size); + std::vector<uint8_t> second_part = + fuzzed_data.ConsumeRemainingBytes<uint8_t>(); + + net::der::Input in1(first_part.data(), first_part.size()); + net::der::Input in2(second_part.data(), second_part.size()); + bool match = net::VerifyNameInSubtree(in1, in2); + bool reverse_order_match = net::VerifyNameInSubtree(in2, in1); + // If both InSubtree matches are true, then in1 == in2 (modulo normalization). + if (match && reverse_order_match) + CHECK(net::VerifyNameMatch(in1, in2)); + return 0; +} diff --git a/chromium/net/cert/pki/verify_signed_data.cc b/chromium/net/cert/pki/verify_signed_data.cc new file mode 100644 index 00000000000..5dc399129a2 --- /dev/null +++ b/chromium/net/cert/pki/verify_signed_data.cc @@ -0,0 +1,217 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_signed_data.h" + +#include "base/numerics/safe_math.h" +#include "crypto/openssl_util.h" +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" + +namespace net { + +// Parses an RSA public key or EC public key from SPKI to an EVP_PKEY. Returns +// true on success. +// +// This function only recognizes the "pk-rsa" (rsaEncryption) flavor of RSA +// public key from RFC 5912. +// +// pk-rsa PUBLIC-KEY ::= { +// IDENTIFIER rsaEncryption +// KEY RSAPublicKey +// PARAMS TYPE NULL ARE absent +// -- Private key format not in this module -- +// CERT-KEY-USAGE {digitalSignature, nonRepudiation, +// keyEncipherment, dataEncipherment, keyCertSign, cRLSign} +// } +// +// COMPATIBILITY NOTE: RFC 5912 and RFC 3279 are in disagreement on the value +// of parameters for rsaEncryption. Whereas RFC 5912 says they must be absent, +// RFC 3279 says they must be NULL: +// +// The rsaEncryption OID is intended to be used in the algorithm field +// of a value of type AlgorithmIdentifier. The parameters field MUST +// have ASN.1 type NULL for this algorithm identifier. +// +// Following RFC 3279 in this case. +// +// In the case of parsing EC keys, RFC 5912 describes all the ECDSA +// signature algorithms as requiring a public key of type "pk-ec": +// +// pk-ec PUBLIC-KEY ::= { +// IDENTIFIER id-ecPublicKey +// KEY ECPoint +// PARAMS TYPE ECParameters ARE required +// -- Private key format not in this module -- +// CERT-KEY-USAGE { digitalSignature, nonRepudiation, keyAgreement, +// keyCertSign, cRLSign } +// } +// +// Moreover RFC 5912 stipulates what curves are allowed. The ECParameters +// MUST NOT use an implicitCurve or specificCurve for PKIX: +// +// ECParameters ::= CHOICE { +// namedCurve CURVE.&id({NamedCurve}) +// -- implicitCurve NULL +// -- implicitCurve MUST NOT be used in PKIX +// -- specifiedCurve SpecifiedCurve +// -- specifiedCurve MUST NOT be used in PKIX +// -- Details for specifiedCurve can be found in [X9.62] +// -- Any future additions to this CHOICE should be coordinated +// -- with ANSI X.9. +// } +// -- If you need to be able to decode ANSI X.9 parameter structures, +// -- uncomment the implicitCurve and specifiedCurve above, and also +// -- uncomment the following: +// --(WITH COMPONENTS {namedCurve PRESENT}) +// +// The namedCurves are extensible. The ones described by RFC 5912 are: +// +// NamedCurve CURVE ::= { +// { ID secp192r1 } | { ID sect163k1 } | { ID sect163r2 } | +// { ID secp224r1 } | { ID sect233k1 } | { ID sect233r1 } | +// { ID secp256r1 } | { ID sect283k1 } | { ID sect283r1 } | +// { ID secp384r1 } | { ID sect409k1 } | { ID sect409r1 } | +// { ID secp521r1 } | { ID sect571k1 } | { ID sect571r1 }, +// ... -- Extensible +// } +bool ParsePublicKey(const der::Input& public_key_spki, + bssl::UniquePtr<EVP_PKEY>* public_key) { + // Parse the SPKI to an EVP_PKEY. + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + CBS cbs; + CBS_init(&cbs, public_key_spki.UnsafeData(), public_key_spki.Length()); + public_key->reset(EVP_parse_public_key(&cbs)); + if (!*public_key || CBS_len(&cbs) != 0) { + public_key->reset(); + return false; + } + return true; +} + +bool VerifySignedData(SignatureAlgorithm algorithm, + const der::Input& signed_data, + const der::BitString& signature_value, + EVP_PKEY* public_key) { + int expected_pkey_id = 1; + const EVP_MD* digest = nullptr; + bool is_rsa_pss = false; + switch (algorithm) { + case SignatureAlgorithm::kRsaPkcs1Sha1: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha1(); + break; + case SignatureAlgorithm::kRsaPkcs1Sha256: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha256(); + break; + case SignatureAlgorithm::kRsaPkcs1Sha384: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha384(); + break; + case SignatureAlgorithm::kRsaPkcs1Sha512: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha512(); + break; + + case SignatureAlgorithm::kEcdsaSha1: + expected_pkey_id = EVP_PKEY_EC; + digest = EVP_sha1(); + break; + case SignatureAlgorithm::kEcdsaSha256: + expected_pkey_id = EVP_PKEY_EC; + digest = EVP_sha256(); + break; + case SignatureAlgorithm::kEcdsaSha384: + expected_pkey_id = EVP_PKEY_EC; + digest = EVP_sha384(); + break; + case SignatureAlgorithm::kEcdsaSha512: + expected_pkey_id = EVP_PKEY_EC; + digest = EVP_sha512(); + break; + + case SignatureAlgorithm::kRsaPssSha256: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha256(); + is_rsa_pss = true; + break; + case SignatureAlgorithm::kRsaPssSha384: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha384(); + is_rsa_pss = true; + break; + case SignatureAlgorithm::kRsaPssSha512: + expected_pkey_id = EVP_PKEY_RSA; + digest = EVP_sha512(); + is_rsa_pss = true; + break; + + case SignatureAlgorithm::kDsaSha1: + case SignatureAlgorithm::kDsaSha256: + case SignatureAlgorithm::kRsaPkcs1Md2: + case SignatureAlgorithm::kRsaPkcs1Md4: + case SignatureAlgorithm::kRsaPkcs1Md5: + // DSA, MD2, MD4, and MD5 are not supported. See + // https://crbug.com/1321688. + return false; + } + + if (expected_pkey_id != EVP_PKEY_id(public_key)) + return false; + + // For the supported algorithms the signature value must be a whole + // number of bytes. + if (signature_value.unused_bits() != 0) + return false; + const der::Input& signature_value_bytes = signature_value.bytes(); + + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + bssl::ScopedEVP_MD_CTX ctx; + EVP_PKEY_CTX* pctx = nullptr; // Owned by |ctx|. + + if (!EVP_DigestVerifyInit(ctx.get(), &pctx, digest, nullptr, public_key)) + return false; + + if (is_rsa_pss) { + // All supported RSASSA-PSS algorithms match signing and MGF-1 digest. They + // also use the digest length as the salt length, which is specified with -1 + // in OpenSSL's API. + if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) || + !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1)) { + return false; + } + } + + if (!EVP_DigestVerifyUpdate(ctx.get(), signed_data.UnsafeData(), + signed_data.Length())) { + return false; + } + + return 1 == EVP_DigestVerifyFinal(ctx.get(), + signature_value_bytes.UnsafeData(), + signature_value_bytes.Length()); +} + +bool VerifySignedData(SignatureAlgorithm algorithm, + const der::Input& signed_data, + const der::BitString& signature_value, + const der::Input& public_key_spki) { + bssl::UniquePtr<EVP_PKEY> public_key; + if (!ParsePublicKey(public_key_spki, &public_key)) + return false; + return VerifySignedData(algorithm, signed_data, signature_value, + public_key.get()); +} + +} // namespace net diff --git a/chromium/net/cert/pki/verify_signed_data.h b/chromium/net/cert/pki/verify_signed_data.h new file mode 100644 index 00000000000..b904992dc1c --- /dev/null +++ b/chromium/net/cert/pki/verify_signed_data.h @@ -0,0 +1,49 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_CERT_PKI_VERIFY_SIGNED_DATA_H_ +#define NET_CERT_PKI_VERIFY_SIGNED_DATA_H_ + +#include "crypto/openssl_util.h" +#include "net/base/net_export.h" +#include "net/cert/pki/signature_algorithm.h" +#include "third_party/boringssl/src/include/openssl/evp.h" + +namespace net { + +namespace der { +class BitString; +class Input; +} // namespace der + +// Verifies that |signature_value| is a valid signature of |signed_data| using +// the algorithm |algorithm| and the public key |public_key|. +// +// |algorithm| - The parsed AlgorithmIdentifier +// |signed_data| - The blob of data to verify +// |signature_value| - The BIT STRING for the signature's value +// |public_key| - The parsed (non-null) public key. +// +// Returns true if verification was successful. +[[nodiscard]] NET_EXPORT bool VerifySignedData( + SignatureAlgorithm algorithm, + const der::Input& signed_data, + const der::BitString& signature_value, + EVP_PKEY* public_key); + +// Same as above overload, only the public key is inputted as an SPKI and will +// be parsed internally. +[[nodiscard]] NET_EXPORT bool VerifySignedData( + SignatureAlgorithm algorithm, + const der::Input& signed_data, + const der::BitString& signature_value, + const der::Input& public_key_spki); + +[[nodiscard]] NET_EXPORT bool ParsePublicKey( + const der::Input& public_key_spki, + bssl::UniquePtr<EVP_PKEY>* public_key); + +} // namespace net + +#endif // NET_CERT_PKI_VERIFY_SIGNED_DATA_H_ diff --git a/chromium/net/cert/pki/verify_signed_data_unittest.cc b/chromium/net/cert/pki/verify_signed_data_unittest.cc new file mode 100644 index 00000000000..8a0a26e9cb0 --- /dev/null +++ b/chromium/net/cert/pki/verify_signed_data_unittest.cc @@ -0,0 +1,190 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/pki/verify_signed_data.h" + +#include <memory> +#include <set> + +#include "net/cert/pki/cert_errors.h" +#include "net/cert/pki/signature_algorithm.h" +#include "net/cert/pki/test_helpers.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +namespace { + +enum VerifyResult { + SUCCESS, + FAILURE, +}; + +// Reads test data from |file_name| and runs VerifySignedData() over its +// inputs. +// +// If expected_result was SUCCESS then the test will only succeed if +// VerifySignedData() returns true. +// +// If expected_result was FAILURE then the test will only succeed if +// VerifySignedData() returns false. +void RunTestCase(VerifyResult expected_result, const char* file_name) { + std::string path = + std::string("net/data/verify_signed_data_unittest/") + file_name; + + std::string public_key; + std::string algorithm; + std::string signed_data; + std::string signature_value; + + const PemBlockMapping mappings[] = { + {"PUBLIC KEY", &public_key}, + {"ALGORITHM", &algorithm}, + {"DATA", &signed_data}, + {"SIGNATURE", &signature_value}, + }; + + ASSERT_TRUE(ReadTestDataFromPemFile(path, mappings)); + + CertErrors algorithm_errors; + absl::optional<SignatureAlgorithm> signature_algorithm = + ParseSignatureAlgorithm(der::Input(&algorithm), &algorithm_errors); + ASSERT_TRUE(signature_algorithm) << algorithm_errors.ToDebugString(); + + der::Parser signature_value_parser((der::Input(&signature_value))); + absl::optional<der::BitString> signature_value_bit_string = + signature_value_parser.ReadBitString(); + ASSERT_TRUE(signature_value_bit_string.has_value()) + << "The signature value is not a valid BIT STRING"; + + bool expected_result_bool = expected_result == SUCCESS; + + bool result = VerifySignedData(*signature_algorithm, der::Input(&signed_data), + signature_value_bit_string.value(), + der::Input(&public_key)); + + EXPECT_EQ(expected_result_bool, result); +} + +// Read the descriptions in the test files themselves for details on what is +// being tested. + +TEST(VerifySignedDataTest, RsaPkcs1Sha1) { + RunTestCase(SUCCESS, "rsa-pkcs1-sha1.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha256) { + RunTestCase(SUCCESS, "rsa-pkcs1-sha256.pem"); +} + +TEST(VerifySignedDataTest, Rsa2048Pkcs1Sha512) { + RunTestCase(SUCCESS, "rsa2048-pkcs1-sha512.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha256KeyEncodedBer) { + RunTestCase(FAILURE, "rsa-pkcs1-sha256-key-encoded-ber.pem"); +} + +TEST(VerifySignedDataTest, EcdsaSecp384r1Sha256) { + RunTestCase(SUCCESS, "ecdsa-secp384r1-sha256.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512) { + RunTestCase(SUCCESS, "ecdsa-prime256v1-sha512.pem"); +} + +TEST(VerifySignedDataTest, RsaPssSha256) { + RunTestCase(SUCCESS, "rsa-pss-sha256.pem"); +} + +TEST(VerifySignedDataTest, RsaPssSha256WrongSalt) { + RunTestCase(FAILURE, "rsa-pss-sha256-wrong-salt.pem"); +} + +TEST(VerifySignedDataTest, EcdsaSecp384r1Sha256CorruptedData) { + RunTestCase(FAILURE, "ecdsa-secp384r1-sha256-corrupted-data.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha1WrongAlgorithm) { + RunTestCase(FAILURE, "rsa-pkcs1-sha1-wrong-algorithm.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512WrongSignatureFormat) { + RunTestCase(FAILURE, "ecdsa-prime256v1-sha512-wrong-signature-format.pem"); +} + +TEST(VerifySignedDataTest, EcdsaUsingRsaKey) { + RunTestCase(FAILURE, "ecdsa-using-rsa-key.pem"); +} + +TEST(VerifySignedDataTest, RsaUsingEcKey) { + RunTestCase(FAILURE, "rsa-using-ec-key.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha1BadKeyDerNull) { + RunTestCase(FAILURE, "rsa-pkcs1-sha1-bad-key-der-null.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha1BadKeyDerLength) { + RunTestCase(FAILURE, "rsa-pkcs1-sha1-bad-key-der-length.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha256UsingEcdsaAlgorithm) { + RunTestCase(FAILURE, "rsa-pkcs1-sha256-using-ecdsa-algorithm.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512UsingRsaAlgorithm) { + RunTestCase(FAILURE, "ecdsa-prime256v1-sha512-using-rsa-algorithm.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512UsingEcdhKey) { + RunTestCase(FAILURE, "ecdsa-prime256v1-sha512-using-ecdh-key.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512UsingEcmqvKey) { + RunTestCase(FAILURE, "ecdsa-prime256v1-sha512-using-ecmqv-key.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha1KeyParamsAbsent) { + RunTestCase(FAILURE, "rsa-pkcs1-sha1-key-params-absent.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha1UsingPssKeyNoParams) { + RunTestCase(FAILURE, "rsa-pkcs1-sha1-using-pss-key-no-params.pem"); +} + +TEST(VerifySignedDataTest, RsaPssSha256UsingPssKeyWithParams) { + // We do not support RSA-PSS SPKIs. + RunTestCase(FAILURE, "rsa-pss-sha256-using-pss-key-with-params.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512SpkiParamsNull) { + RunTestCase(FAILURE, "ecdsa-prime256v1-sha512-spki-params-null.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha256UsingIdEaRsa) { + RunTestCase(FAILURE, "rsa-pkcs1-sha256-using-id-ea-rsa.pem"); +} + +TEST(VerifySignedDataTest, RsaPkcs1Sha256SpkiNonNullParams) { + RunTestCase(FAILURE, "rsa-pkcs1-sha256-spki-non-null-params.pem"); +} + +TEST(VerifySignedDataTest, EcdsaPrime256v1Sha512UnusedBitsSignature) { + RunTestCase(FAILURE, "ecdsa-prime256v1-sha512-unused-bits-signature.pem"); +} + +TEST(VerifySignedDataTest, Ecdsa384) { + // Using the regular policy both secp384r1 and secp256r1 should be accepted. + RunTestCase(SUCCESS, "ecdsa-secp384r1-sha256.pem"); + RunTestCase(SUCCESS, "ecdsa-prime256v1-sha512.pem"); +} + +} // namespace + +} // namespace net |