Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 39 additions & 98 deletions net/cert/internal/trust_store_in_memory_starboard.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,61 +16,40 @@

#include <dirent.h>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/time/time.h"
#include "net/cert/pki/cert_errors.h"
#include "net/cert/pem.h"
#include "net/cert/pki/cert_errors.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "starboard/common/string.h"
#include "starboard/configuration_constants.h"
#include "starboard/directory.h"
#include "starboard/file.h"
#include "starboard/string.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
#include "third_party/boringssl/src/include/openssl/x509.h"

namespace net {

namespace {
// PEM encoded DER cert is usually around or less than 2000 bytes long.
const short kCertBufferSize = 8 * 1024;
// Each certificate file name is 8 bit hash + ".0" suffix.
const short kCertFileNameLength = 10;
const char kCertificateHeader[] = "CERTIFICATE";
const char kSSLDirName[] = "ssl";
const char kCertsDirName[] = "certs";

// Essentially an X509_NAME_hash without using X509_NAME. We did not use the
// boringSSL function directly because net does not store certs with X509
// struct anymore and converting binary certs to X509_NAME is slightly
// expensive.
unsigned long CertNameHash(const void* data, size_t length) {
unsigned long ret = 0;
unsigned char md[SHA_DIGEST_LENGTH];
if (!EVP_Digest(data, length, md, NULL, EVP_sha1(), NULL))
return 0;

ret = (((unsigned long)md[0]) | ((unsigned long)md[1] << 8L) |
((unsigned long)md[2] << 16L) | ((unsigned long)md[3] << 24L)) &
0xffffffffL;
return ret;
}

base::FilePath GetCertificateDirPath() {
base::FilePath cert_path;
base::PathService::Get(base::DIR_EXE, &cert_path);
cert_path = cert_path.Append(kSSLDirName).Append(kCertsDirName);
return std::move(cert_path);
}

std::unordered_set<std::string> GetCertNamesOnDisk() {
DIR* sb_certs_directory =
opendir(GetCertificateDirPath().value().c_str());
std::vector<std::shared_ptr<const ParsedCertificate>> GetAllCertsOnDisk() {
base::FilePath cert_dir_path = GetCertificateDirPath();
DIR* sb_certs_directory = opendir(cert_dir_path.value().c_str());
if (!sb_certs_directory) {
// Unit tests, for example, do not use production certificates.
#if defined(STARBOARD_BUILD_TYPE_QA) || defined(STARBOARD_BUILD_TYPE_GOLD)
Expand All @@ -79,9 +58,10 @@ std::unordered_set<std::string> GetCertNamesOnDisk() {
DLOG(WARNING) << "ssl/certs directory is not valid, no root certificates"
" will be loaded";
#endif
return std::unordered_set<std::string>();
return {};
}
std::unordered_set<std::string> trusted_certs_on_disk;

std::vector<std::shared_ptr<const ParsedCertificate>> certs;
std::vector<char> dir_entry(kSbFileMaxName);

struct dirent dirent_buffer;
Expand All @@ -99,90 +79,51 @@ std::unordered_set<std::string> GetCertNamesOnDisk() {
if (strlen(dir_entry.data()) != kCertFileNameLength) {
continue;
}
trusted_certs_on_disk.emplace(dir_entry.data());

base::FilePath cert_path = cert_dir_path.Append(dir_entry.data());
std::string cert_buffer;
CHECK(base::ReadFileToString(cert_path, &cert_buffer))
<< "ssl/certs/" << cert_path.value() << " failed to open.";
PEMTokenizer pem_tokenizer(cert_buffer, {kCertificateHeader});
CHECK(pem_tokenizer.GetNext()) << "Failed to parse PEM from cert file: "
<< cert_path.value();
std::string decoded(pem_tokenizer.data());
CHECK(!pem_tokenizer.GetNext()) << "Multiple certificates found in "
<< cert_path.value();
auto crypto_buffer = x509_util::CreateCryptoBuffer(decoded);
CHECK(crypto_buffer) << "Failed to create crypto buffer for "
<< cert_path.value();
CertErrors errors;
auto parsed = ParsedCertificate::Create(
std::move(crypto_buffer), x509_util::DefaultParseCertificateOptions(),
&errors);
CHECK(parsed) << "Failed to parse certificate " << cert_path.value()
<< ": " << errors.ToDebugString();
certs.push_back(parsed);
}
closedir(sb_certs_directory);
return std::move(trusted_certs_on_disk);
return certs;
}
} // namespace

std::shared_ptr<const ParsedCertificate> TrustStoreInMemoryStarboard::TryLoadCert(
const base::StringPiece& cert_name) const {
auto hash = CertNameHash(cert_name.data(), cert_name.length());
char cert_file_name[256];
snprintf(cert_file_name, 256, "%08lx.%d", hash, 0);

if (trusted_cert_names_on_disk_.find(cert_file_name) ==
trusted_cert_names_on_disk_.end()) {
// The requested certificate is not found.
return nullptr;
TrustStoreInMemoryStarboard::TrustStoreInMemoryStarboard() {
for (const auto& cert : GetAllCertsOnDisk()) {
underlying_trust_store_.AddTrustAnchor(cert);
}

char cert_buffer[kCertBufferSize];
base::FilePath cert_path = GetCertificateDirPath().Append(cert_file_name);
base::File cert_file(cert_path, base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);
// The file was in certs directory when we iterated the directory at startup,
// opening it should not fail.
if (!cert_file.IsValid()) {
NOTREACHED() << "ssl/certs/" << cert_path << " failed to open.";
return nullptr;
}
int cert_size = cert_file.ReadAtCurrentPos(cert_buffer, kCertBufferSize);
PEMTokenizer pem_tokenizer(base::StringPiece(cert_buffer, cert_size),
{kCertificateHeader});
pem_tokenizer.GetNext();
std::string decoded(pem_tokenizer.data());
DCHECK(!pem_tokenizer.GetNext());
auto crypto_buffer = x509_util::CreateCryptoBuffer(decoded);
CertErrors errors;
auto parsed = ParsedCertificate::Create(
std::move(crypto_buffer),
x509_util::DefaultParseCertificateOptions(), &errors);
CHECK(parsed) << errors.ToDebugString();
return parsed;
}

TrustStoreInMemoryStarboard::TrustStoreInMemoryStarboard()
: trusted_cert_names_on_disk_(std::move(GetCertNamesOnDisk())) {}

TrustStoreInMemoryStarboard::~TrustStoreInMemoryStarboard() = default;

void TrustStoreInMemoryStarboard::SyncGetIssuersOf(
const ParsedCertificate* cert,
ParsedCertificateList* issuers) {
DCHECK(issuers);
DCHECK(issuers->empty());
base::AutoLock scoped_lock(load_mutex_);
// Look up the request certificate first in the trust store in memory.
underlying_trust_store_.SyncGetIssuersOf(cert, issuers);
if (issuers->empty()) {
// If the requested certificate is not found, compute certificate hash name
// and see if the certificate is stored on disk.
auto parsed_cert = TryLoadCert(cert->normalized_issuer().AsStringView());
if (parsed_cert.get()) {
issuers->push_back(parsed_cert);
underlying_trust_store_.AddTrustAnchor(parsed_cert);
}
}
}

CertificateTrust TrustStoreInMemoryStarboard::GetTrust(const ParsedCertificate* cert,
base::SupportsUserData* debug_data) {
base::AutoLock scoped_lock(load_mutex_);
// Loop up the request certificate first in the trust store in memory.
CertificateTrust trust = underlying_trust_store_.GetTrust(cert, debug_data);
if (trust.HasUnspecifiedTrust()) {
// If the requested certificate is not found, compute certificate hash name
// and see if the certificate is stored on disk.
auto parsed_cert = TryLoadCert(cert->normalized_subject().AsStringView());
if (parsed_cert.get()) {
trust = CertificateTrust::ForTrustAnchor();
const_cast<TrustStoreInMemoryStarboard*>(this)
->underlying_trust_store_.AddTrustAnchor(parsed_cert);
}
}
return trust;
CertificateTrust TrustStoreInMemoryStarboard::GetTrust(
const ParsedCertificate* cert,
base::SupportsUserData* debug_data) {
return underlying_trust_store_.GetTrust(cert, debug_data);
}

} // namespace net

22 changes: 2 additions & 20 deletions net/cert/internal/trust_store_in_memory_starboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@
#ifndef NET_CERT_INTERNAL_TRUST_STORE_IN_MEMORY_STARBOARD_H_
#define NET_CERT_INTERNAL_TRUST_STORE_IN_MEMORY_STARBOARD_H_

#include <unordered_set>

#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
#include "net/cert/pki/trust_store_in_memory.h"

namespace net {

// Wrapper around TrustStoreInMemory to lazily load trusted root certificates.
// Wrapper around TrustStoreInMemory to eagerly load trusted root certificates.
class NET_EXPORT TrustStoreInMemoryStarboard : public TrustStore {
public:
TrustStoreInMemoryStarboard();
Expand All @@ -33,12 +30,11 @@ class NET_EXPORT TrustStoreInMemoryStarboard : public TrustStore {
void SyncGetIssuersOf(const ParsedCertificate* cert,
ParsedCertificateList* issuers) override;
CertificateTrust GetTrust(const ParsedCertificate* cert,
base::SupportsUserData* debug_data) override;
base::SupportsUserData* debug_data) override;

// Returns true if the trust store contains the given ParsedCertificate
// (matches by DER).
bool Contains(const ParsedCertificate* cert) const {
base::AutoLock scoped_lock(load_mutex_);
return underlying_trust_store_.Contains(cert);
}

Expand All @@ -48,22 +44,8 @@ class NET_EXPORT TrustStoreInMemoryStarboard : public TrustStore {
delete;

TrustStoreInMemory underlying_trust_store_;

// Given a certificate's canonical name, try to load this cert from trusted
// certs on disk if it is found.
std::shared_ptr<const ParsedCertificate> TryLoadCert(
const base::StringPiece& cert_name) const;

// The memory trust store can be accessed by multiple threads, in Chromium,
// the synchronization issue is solved by initializing trust store at startup
// and passing constant reference to consumers. Cobalt loads certs lazily and
// therefore guards the underlying_trust_store_ with mutex.
mutable base::Lock load_mutex_;

const std::unordered_set<std::string> trusted_cert_names_on_disk_;
};

} // namespace net

#endif // NET_CERT_INTERNAL_TRUST_STORE_IN_MEMORY_STARBOARD_H_

Loading