blob: 564beed1a473586d1e11ff1036995083f88618e5 [file] [log] [blame]
// 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/internal/cert_issuer_source_aia.h"
#include "base/strings/string_piece.h"
#include "net/cert/cert_net_fetcher.h"
#include "net/cert/internal/cert_errors.h"
#include "net/cert/pem_tokenizer.h"
#include "net/cert/x509_util.h"
#include "url/gurl.h"
namespace net {
namespace {
// TODO(mattm): These are arbitrary choices. Re-evaluate.
const int kTimeoutMilliseconds = 10000;
const int kMaxResponseBytes = 65536;
const int kMaxFetchesPerCert = 5;
bool ParseCertFromDer(const uint8_t* data,
size_t length,
ParsedCertificateList* results) {
CertErrors errors;
if (!ParsedCertificate::CreateAndAddToVector(
x509_util::CreateCryptoBuffer(data, length),
x509_util::DefaultParseCertificateOptions(), results, &errors)) {
// TODO(crbug.com/634443): propagate error info.
LOG(ERROR) << "Error parsing cert retrieved from AIA (as DER):\n"
<< errors.ToDebugString();
return false;
}
return true;
}
bool ParseCertFromPem(const uint8_t* data,
size_t length,
ParsedCertificateList* results) {
base::StringPiece data_strpiece(reinterpret_cast<const char*>(data), length);
PEMTokenizer pem_tokenizer(data_strpiece, {"CERTIFICATE"});
if (!pem_tokenizer.GetNext())
return false;
return ParseCertFromDer(
reinterpret_cast<const uint8_t*>(pem_tokenizer.data().data()),
pem_tokenizer.data().size(), results);
}
class AiaRequest : public CertIssuerSource::Request {
public:
AiaRequest() = default;
~AiaRequest() override;
// CertIssuerSource::Request implementation.
void GetNext(ParsedCertificateList* issuers) override;
void AddCertFetcherRequest(
std::unique_ptr<CertNetFetcher::Request> cert_fetcher_request);
bool AddCompletedFetchToResults(Error error,
std::vector<uint8_t> fetched_bytes,
ParsedCertificateList* results);
private:
std::vector<std::unique_ptr<CertNetFetcher::Request>> cert_fetcher_requests_;
size_t current_request_ = 0;
DISALLOW_COPY_AND_ASSIGN(AiaRequest);
};
AiaRequest::~AiaRequest() = default;
void AiaRequest::GetNext(ParsedCertificateList* out_certs) {
// TODO(eroman): Rather than blocking in FIFO order, select the one that
// completes first.
while (current_request_ < cert_fetcher_requests_.size()) {
Error error;
std::vector<uint8_t> bytes;
auto req = std::move(cert_fetcher_requests_[current_request_++]);
req->WaitForResult(&error, &bytes);
if (AddCompletedFetchToResults(error, std::move(bytes), out_certs))
return;
}
}
void AiaRequest::AddCertFetcherRequest(
std::unique_ptr<CertNetFetcher::Request> cert_fetcher_request) {
DCHECK(cert_fetcher_request);
cert_fetcher_requests_.push_back(std::move(cert_fetcher_request));
}
bool AiaRequest::AddCompletedFetchToResults(Error error,
std::vector<uint8_t> fetched_bytes,
ParsedCertificateList* results) {
if (error != OK) {
// TODO(mattm): propagate error info.
LOG(ERROR) << "AiaRequest::OnFetchCompleted got error " << error;
return false;
}
// RFC 5280 section 4.2.2.1:
//
// Conforming applications that support HTTP or FTP for accessing
// certificates MUST be able to accept individual DER encoded
// certificates and SHOULD be able to accept "certs-only" CMS messages.
//
// TODO(mattm): Is supporting CMS message format important?
// TODO(https://crbug.com/870359): Some AIA responses are served as PEM, which
// is not part of RFC 5280's profile.
return ParseCertFromDer(fetched_bytes.data(), fetched_bytes.size(),
results) ||
ParseCertFromPem(fetched_bytes.data(), fetched_bytes.size(), results);
}
} // namespace
CertIssuerSourceAia::CertIssuerSourceAia(
scoped_refptr<CertNetFetcher> cert_fetcher)
: cert_fetcher_(std::move(cert_fetcher)) {}
CertIssuerSourceAia::~CertIssuerSourceAia() = default;
void CertIssuerSourceAia::SyncGetIssuersOf(const ParsedCertificate* cert,
ParsedCertificateList* issuers) {
// CertIssuerSourceAia never returns synchronous results.
}
void CertIssuerSourceAia::AsyncGetIssuersOf(const ParsedCertificate* cert,
std::unique_ptr<Request>* out_req) {
out_req->reset();
if (!cert->has_authority_info_access())
return;
// RFC 5280 section 4.2.2.1:
//
// An authorityInfoAccess extension may include multiple instances of
// the id-ad-caIssuers accessMethod. The different instances may
// specify different methods for accessing the same information or may
// point to different information.
std::vector<GURL> urls;
for (const auto& uri : cert->ca_issuers_uris()) {
GURL url(uri);
if (url.is_valid()) {
// TODO(mattm): do the kMaxFetchesPerCert check only on the number of
// supported URL schemes, not all the URLs.
if (urls.size() < kMaxFetchesPerCert) {
urls.push_back(url);
} else {
// TODO(mattm): propagate error info.
LOG(ERROR) << "kMaxFetchesPerCert exceeded, skipping";
}
} else {
// TODO(mattm): propagate error info.
LOG(ERROR) << "invalid AIA URL: " << uri;
}
}
if (urls.empty())
return;
std::unique_ptr<AiaRequest> aia_request(new AiaRequest());
for (const auto& url : urls) {
// TODO(mattm): add synchronous failure mode to FetchCaIssuers interface so
// that this doesn't need to wait for async callback just to tell that an
// URL has an unsupported scheme?
aia_request->AddCertFetcherRequest(cert_fetcher_->FetchCaIssuers(
url, kTimeoutMilliseconds, kMaxResponseBytes));
}
*out_req = std::move(aia_request);
}
} // namespace net