// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ios/web/session/session_certificate.h"
#include <string_view>
#include "ios/web/public/session/proto/session.pb.h"
#include "ios/web/session/hash_util.h"
#include "net/cert/x509_util.h"
// Break if CertStatus values changed, as they are persisted on disk and thus
// must be consistent.
static_assert(net::CERT_STATUS_ALL_ERRORS == 0xFF00FFFF,
"The value of CERT_STATUS_ALL_ERRORS changed!");
static_assert(net::CERT_STATUS_COMMON_NAME_INVALID == 1 << 0,
"The value of CERT_STATUS_COMMON_NAME_INVALID changed!");
static_assert(net::CERT_STATUS_DATE_INVALID == 1 << 1,
"The value of CERT_STATUS_DATE_INVALID changed!");
static_assert(net::CERT_STATUS_AUTHORITY_INVALID == 1 << 2,
"The value of CERT_STATUS_AUTHORITY_INVALID changed!");
static_assert(net::CERT_STATUS_NO_REVOCATION_MECHANISM == 1 << 4,
"The value of CERT_STATUS_NO_REVOCATION_MECHANISM changed!");
static_assert(net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION == 1 << 5,
"The value of CERT_STATUS_UNABLE_TO_CHECK_REVOCATION changed!");
static_assert(net::CERT_STATUS_REVOKED == 1 << 6,
"The value of CERT_STATUS_REVOKED changed!");
static_assert(net::CERT_STATUS_INVALID == 1 << 7,
"The value of CERT_STATUS_INVALID changed!");
static_assert(net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM == 1 << 8,
"The value of CERT_STATUS_WEAK_SIGNATURE_ALGORITHM changed!");
static_assert(net::CERT_STATUS_NON_UNIQUE_NAME == 1 << 10,
"The value of CERT_STATUS_NON_UNIQUE_NAME changed!");
static_assert(net::CERT_STATUS_WEAK_KEY == 1 << 11,
"The value of CERT_STATUS_WEAK_KEY changed!");
static_assert(net::CERT_STATUS_IS_EV == 1 << 16,
"The value of CERT_STATUS_IS_EV changed!");
static_assert(net::CERT_STATUS_REV_CHECKING_ENABLED == 1 << 17,
"The value of CERT_STATUS_REV_CHECKING_ENABLED changed!");
namespace web {
namespace {
// Extracts the leaf certificate in the chain from `certificate`.
scoped_refptr<net::X509Certificate> ExtractLeafCertificate(
const scoped_refptr<net::X509Certificate>& certificate) {
// Nothing to do if `certificate` is already a leaf certificate.
if (certificate->intermediate_buffers().empty()) {
return certificate;
}
scoped_refptr<net::X509Certificate> leaf_certificate =
net::X509Certificate::CreateFromBuffer(
bssl::UpRef(certificate->cert_buffer()), {});
CHECK(leaf_certificate);
CHECK(leaf_certificate->intermediate_buffers().empty());
return leaf_certificate;
}
} // namespace
// Store user decisions with the leaf cert, ignoring any intermediates.
// This is because WKWebView returns the verified certificate chain in
// `-webView:didReceiveAuthenticationChallenge:completionHandler:` but
// `-webView:didFailProvisionalNavigation:withError:` only receive the
// server-supplied chain.
SessionCertificate::SessionCertificate(
const scoped_refptr<net::X509Certificate>& certificate,
const std::string& host,
net::CertStatus status)
: certificate_(ExtractLeafCertificate(certificate)),
host_(host),
status_(status) {}
SessionCertificate::SessionCertificate(const proto::CertificateStorage& storage)
: host_(storage.host()), status_(storage.status()) {
certificate_ = net::X509Certificate::CreateFromBytes(
base::as_byte_span(storage.certificate()));
}
SessionCertificate::SessionCertificate(SessionCertificate&&) = default;
SessionCertificate::SessionCertificate(const SessionCertificate&) = default;
SessionCertificate& SessionCertificate::operator=(SessionCertificate&&) =
default;
SessionCertificate& SessionCertificate::operator=(const SessionCertificate&) =
default;
SessionCertificate::~SessionCertificate() = default;
void SessionCertificate::SerializeToProto(
proto::CertificateStorage& storage) const {
const std::string_view cert_string =
net::x509_util::CryptoBufferAsStringPiece(certificate_->cert_buffer());
storage.set_certificate(cert_string.data(), cert_string.size());
storage.set_host(host_);
storage.set_status(status_);
}
bool operator==(const SessionCertificate& lhs, const SessionCertificate& rhs) {
if (lhs.status() != rhs.status()) {
return false;
}
if (lhs.host() != rhs.host()) {
return false;
}
return net::x509_util::CryptoBufferEqual(lhs.certificate()->cert_buffer(),
rhs.certificate()->cert_buffer());
}
bool operator!=(const SessionCertificate& lhs, const SessionCertificate& rhs) {
return !(lhs == rhs);
}
size_t SessionCertificateHasher::operator()(
const SessionCertificate& value) const {
return session::ComputeHash(value.certificate(), value.host(),
value.status());
}
} // namespace web