blob: 7b2d43bd41a9eda307d139b1451e436664984d58 [file] [log] [blame]
// Copyright 2012 The Goma 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 "openssl_engine.h"
#include <openssl/asn1.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <map>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <vector>
#include "absl/base/call_once.h"
#include "absl/base/macros.h"
#include "absl/memory/memory.h"
#include "absl/strings/match.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "autolock_timer.h"
#include "callback.h"
#include "compiler_specific.h"
#include "file_dir.h"
#include "file_helper.h"
#include "glog/logging.h"
#include "glog/stl_logging.h"
MSVC_PUSH_DISABLE_WARNING_FOR_PROTO()
#include "google/protobuf/io/zero_copy_stream.h"
MSVC_POP_WARNING()
#include "http.h"
#include "http_util.h"
#include "mypath.h"
#include "openssl_engine_helper.h"
#include "path.h"
#include "platform_thread.h"
#include "scoped_fd.h"
#include "socket_pool.h"
#ifndef OPENSSL_IS_BORINGSSL
#error "This code is written for BoringSSL"
#endif
namespace devtools_goma {
namespace {
// Prevent use of SSL on error for this period.
constexpr absl::Duration kErrorTimeout = absl::Seconds(60);
// Wait for this period if no more sockets are in the pool.
constexpr absl::Duration kWaitForThingsGetsBetter = absl::Seconds(1);
absl::once_flag g_openssl_init_once;
class ScopedBIOFree {
public:
inline void operator()(BIO *x) const { if (x) CHECK(BIO_free(x)); }
};
class ScopedX509Free {
public:
inline void operator()(X509 *x) const { if (x) X509_free(x); }
};
class ScopedX509StoreCtxFree {
public:
inline void operator()(X509_STORE_CTX *x) const {
if (x) X509_STORE_CTX_free(x);
}
};
class ScopedX509StoreFree {
public:
inline void operator()(X509_STORE *x) const { if (x) X509_STORE_free(x); }
};
template<typename T>
string GetHumanReadableInfo(T* data, int (*func)(BIO*, T*)) {
std::unique_ptr<BIO, ScopedBIOFree> bio(BIO_new(BIO_s_mem()));
func(bio.get(), data);
char* x509_for_print;
const int x509_for_print_len = BIO_get_mem_data(bio.get(), &x509_for_print);
string ret(x509_for_print, x509_for_print_len);
return ret;
}
string GetHumanReadableCert(X509* x509) {
return GetHumanReadableInfo<X509>(x509, X509_print);
}
string GetHumanReadableCRL(X509_CRL* x509_crl) {
return GetHumanReadableInfo<X509_CRL>(x509_crl, X509_CRL_print);
}
string GetHumanReadableCerts(STACK_OF(X509)* x509s) {
string ret;
for (size_t i = 0; i < sk_X509_num(x509s); i++) {
ret.append(GetHumanReadableCert(sk_X509_value(x509s, i)));
}
return ret;
}
string GetHumanReadableSessionInfo(const SSL_SESSION* s) {
std::ostringstream ss;
ss << "SSL Session info:";
ss << " protocol=" << SSL_SESSION_get_version(s);
unsigned int len;
const uint8_t* c = SSL_SESSION_get_id(s, &len);
std::ostringstream sess_id;
for (size_t i = 0; i < len; ++i) {
sess_id << std::setfill('0') << std::setw(2)
<< std::hex << static_cast<int>(c[i]);
}
ss << " session_id=" << sess_id.str();
ss << " time=" << SSL_SESSION_get_time(s);
ss << " timeout=" << SSL_SESSION_get_timeout(s);
return ss.str();
}
string GetHumanReadableSSLInfo(const SSL* ssl) {
const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
std::ostringstream ss;
ss << "SSL info:";
ss << " cipher:"
<< " name=" << SSL_CIPHER_get_name(cipher)
<< " bits=" << SSL_CIPHER_get_bits(cipher, nullptr)
<< " version=" << SSL_CIPHER_get_version(cipher);
uint16_t curve_id = SSL_get_curve_id(ssl);
if (curve_id != 0) {
ss << " curve=" << SSL_get_curve_name(curve_id);
}
return ss.str();
}
// A class that controls lifetime of the SSL session.
class OpenSSLSessionCache {
public:
static void Init() {
InitOpenSSLSessionCache();
}
// Set configs for the SSL session to the SSL context.
static void Setup(SSL_CTX* ctx) {
if (!cache_)
InitOpenSSLSessionCache();
DCHECK(cache_);
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT);
SSL_CTX_sess_set_remove_cb(ctx, RemoveSessionCallBack);
}
// Set a session to a SSL structure instance if we have a cache.
static bool SetCachedSession(SSL_CTX* ctx, SSL* ssl) {
DCHECK(cache_);
return cache_->SetCachedSessionInternal(ctx, ssl);
}
static void RecordSession(SSL* ssl) {
DCHECK(cache_);
DCHECK(ssl);
SSL_SESSION* sess = SSL_get1_session(ssl);
SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
LOG(INFO) << "Storing SSL session."
<< " ssl_ctx=" << ctx
<< " session_info=" << GetHumanReadableSessionInfo(sess)
<< " secure_renegotiation_support="
<< SSL_get_secure_renegotiation_support(ssl);
if (!cache_->RecordSessionInternal(ctx, sess)) {
LOG(INFO) << "Tried to store already stored session.";
// Since SSL_get1_session increases a reference count of |sess|,
// we need to decrease the reference count here.
// Note that we do not decrease the reference count if
// RecordSessionInternal returned true because we need to keep
// the session valid while we have it in our session cache store.
SSL_SESSION_free(sess);
}
}
private:
OpenSSLSessionCache() {}
~OpenSSLSessionCache() {
// Destructor deletes all cached sessions.
for (auto& it : session_map_) {
SSL_SESSION_free(it.second);
}
session_map_.clear();
}
static void InitOpenSSLSessionCache() {
cache_ = new OpenSSLSessionCache();
atexit(FinalizeOpenSSLSessionCache);
}
static void FinalizeOpenSSLSessionCache() {
if (cache_)
delete cache_;
cache_ = nullptr;
}
static void RemoveSessionCallBack(SSL_CTX* ctx, SSL_SESSION* sess) {
DCHECK(cache_);
LOG(INFO) << "Released stored SSL session."
<< " session_info=" << GetHumanReadableSessionInfo(sess);
cache_->RemoveSessionInternal(ctx);
}
// To avoid race condition, you SHOULD call SSL_set_session while
// |mu_| is held. Or, you may cause use-after-free.
//
// The SSL_SESSION instance life time is controlled by reference counting.
// SSL_set_session increase the reference count, and SSL_SESSION_free
// or SSL_free SSL instance that has the session decrease the reference
// count. When session is revoked, SSL_SESSION instance is free'd via
// RemoveSession. At the same time, RemoveSession removes the instance
// from internal session_map_.
// If you do SSL_set_session outside of |mu_| lock, you may use the
// SSL_SESSION instance already free'd.
// Note that increasing reference count and decreasing reference count
// are done under a lock held by BoringSSL, we do not need to lock for them.
// That is why we use ReadWriteLock.
// TODO: use mutex lock if it is much faster than shared lock.
bool SetCachedSessionInternal(SSL_CTX* ctx, SSL* ssl) {
AUTO_SHARED_LOCK(lock, &mu_);
SSL_SESSION* sess = GetInternalUnlocked(ctx);
if (sess == nullptr)
return false;
VLOG(3) << "Reused session."
<< " ssl_ctx=" << ctx
<< " session_info=" << GetHumanReadableSessionInfo(sess);
SSL_set_session(ssl, sess);
return true;
}
// Returns true if the session is added.
bool RecordSessionInternal(SSL_CTX* ctx, SSL_SESSION* session) {
AUTO_EXCLUSIVE_LOCK(lock, &mu_);
if (GetInternalUnlocked(ctx) != nullptr)
return false;
CHECK(session_map_.insert(std::make_pair(ctx, session)).second);
return true;
}
// Returns true if the session is removed.
bool RemoveSessionInternal(SSL_CTX* ctx) {
AUTO_EXCLUSIVE_LOCK(lock, &mu_);
auto found = session_map_.find(ctx);
if (found == session_map_.end()) {
return false;
}
// Decrement reference count to revoke the session when nobody use it.
// See: https://www.openssl.org/docs/ssl/SSL_SESSION_free.html
SSL_SESSION_free(found->second);
session_map_.erase(found);
return true;
}
SSL_SESSION* GetInternalUnlocked(SSL_CTX* ctx) {
auto found = session_map_.find(ctx);
if (found != session_map_.end()) {
return found->second;
}
return nullptr;
}
mutable ReadWriteLock mu_;
// Won't take ownership of SSL_CTX*.
// Ownership of SSL_SESSION* is kept by the OpenSSL library, but
// we decrement a reference count to notify it an obsolete session.
std::unordered_map<SSL_CTX*, SSL_SESSION*> session_map_;
static OpenSSLSessionCache* cache_;
DISALLOW_COPY_AND_ASSIGN(OpenSSLSessionCache);
};
/* static */
OpenSSLSessionCache* OpenSSLSessionCache::cache_ = nullptr;
// A class that controls socket_pool used in OpenSSL engine.
class OpenSSLSocketPoolCache {
public:
static void Init() {
if (!cache_) {
cache_.reset(new OpenSSLSocketPoolCache);
atexit(FinalizeOpenSSLSocketPoolCache);
}
}
static SocketPool* GetSocketPool(const string& host, int port) {
DCHECK(cache_);
return cache_->GetSocketPoolInternal(host, port);
}
private:
OpenSSLSocketPoolCache() {}
~OpenSSLSocketPoolCache() = default;
friend std::unique_ptr<OpenSSLSocketPoolCache>::deleter_type;
static void FinalizeOpenSSLSocketPoolCache() { cache_.reset(); }
SocketPool* GetSocketPoolInternal(const string& host, int port) {
std::ostringstream ss;
ss << host << ":" << port;
const string key = ss.str();
AUTOLOCK(lock, &socket_pool_mu_);
auto p = socket_pools_.emplace(key, nullptr);
if (p.second) {
p.first->second = absl::make_unique<SocketPool>(host, port);
}
return p.first->second.get();
}
Lock socket_pool_mu_;
std::unordered_map<string, std::unique_ptr<SocketPool>> socket_pools_;
static std::unique_ptr<OpenSSLSocketPoolCache> cache_;
DISALLOW_COPY_AND_ASSIGN(OpenSSLSocketPoolCache);
};
/* static */
std::unique_ptr<OpenSSLSocketPoolCache> OpenSSLSocketPoolCache::cache_ =
nullptr;
class OpenSSLCertificateStore {
public:
static void Init() {
if (!store_) {
store_ = new OpenSSLCertificateStore;
store_->InitInternal();
atexit(FinalizeOpenSSLCertificateStore);
}
}
static bool AddCertificateFromFile(const string& filename) {
DCHECK(store_);
if (store_->IsKnownCertfileInternal(filename)) {
LOG(INFO) << "Known cerficiate:" << filename;
return false;
}
string user_cert;
if (!ReadFileToString(filename.c_str(), &user_cert)) {
LOG(ERROR) << "Failed to read:" << filename;
return false;
}
return store_->AddCertificateFromStringInternal(filename, user_cert);
}
static bool AddCertificateFromString(
const string& source, const string& cert) {
DCHECK(store_);
return store_->AddCertificateFromStringInternal(source, cert);
}
static void SetCertsToCTX(SSL_CTX* ctx) {
DCHECK(store_);
store_->SetCertsToCTXInternal(ctx);
}
static bool IsReady() {
DCHECK(store_);
return store_->IsReadyInternal();
}
static string GetTrustedCertificates() {
DCHECK(store_);
return store_->GetTrustedCertificatesInternal();
}
private:
OpenSSLCertificateStore() {}
~OpenSSLCertificateStore() {}
static void FinalizeOpenSSLCertificateStore() {
delete store_;
store_ = nullptr;
}
void InitInternal() {
string root_certs;
CHECK(GetTrustedRootCerts(&root_certs))
<< "Failed to read trusted root certificates from the system.";
AddCertificateFromStringInternal("system", root_certs);
LOG(INFO) << "Loaded root certificates.";
}
bool IsReadyInternal() const {
AUTO_SHARED_LOCK(lock, &mu_);
return certs_.size() != 0;
}
// Note: you must not return the value via const reference.
// trusted_certificates_ is a member of the class, which is protected
// by the mutex (mu_). It could be updated after return of the function
// by another thread.
string GetTrustedCertificatesInternal() const {
AUTO_SHARED_LOCK(lock, &mu_);
return trusted_certificates_;
}
void SetCertsToCTXInternal(SSL_CTX* ctx) const {
AUTO_SHARED_LOCK(lock, &mu_);
for (const auto& it : certs_) {
LOG(INFO) << "setting certs from: " << it.first
<< " size=" << it.second->size();
for (const auto& x509 : *it.second) {
X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), x509.get());
}
}
}
bool IsKnownCertfileInternal(const string& filename) const {
AUTO_SHARED_LOCK(lock, &mu_);
return certs_.find(filename) != certs_.end();
}
bool AddCertificateFromStringInternal(const string& source,
const string& cert) {
// Create BIO instance to be used by PEM_read_bio_X509_AUX.
std::unique_ptr<BIO, ScopedBIOFree> bio(
BIO_new_mem_buf(cert.data(), cert.size()));
AUTO_EXCLUSIVE_LOCK(lock, &mu_);
auto it = certs_.insert(std::make_pair(source, nullptr));
if (!it.second) {
LOG(WARNING) << "cert store already has certificate for "
<< source;
return false;
}
it.first->second =
absl::make_unique<std::vector<std::unique_ptr<X509, ScopedX509Free>>>();
for (;;) {
std::unique_ptr<X509, ScopedX509Free> x509(
PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, nullptr));
if (x509.get() == nullptr)
break;
const string readable_cert = GetHumanReadableCert(x509.get());
if (source == "system") { // system certificate should be trivial.
VLOG(2) << "Certificate loaded from " << source << ": "
<< readable_cert;
} else {
LOG(INFO) << "Certificate loaded from " << source << ": "
<< readable_cert;
}
trusted_certificates_.append(readable_cert);
it.first->second->emplace_back(std::move(x509));
}
if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE)
ERR_clear_error();
else
LOG(ERROR) << "Unexpected error occured during reading SSL certificate."
<< " source:" << source;
// TODO: log error with source info when no certificate found.
LOG_IF(ERROR, it.first->second->size() == 0)
<< "No certificate found in " << source;
return it.first->second->size() > 0;
}
mutable ReadWriteLock mu_;
std::map<string,
std::unique_ptr<
std::vector<std::unique_ptr<X509, ScopedX509Free>>>> certs_;
string trusted_certificates_;
static OpenSSLCertificateStore* store_;
DISALLOW_COPY_AND_ASSIGN(OpenSSLCertificateStore);
};
/* static */
OpenSSLCertificateStore* OpenSSLCertificateStore::store_ = nullptr;
class OpenSSLCRLCache {
public:
static void Init() {
if (!cache_) {
cache_ = new OpenSSLCRLCache;
atexit(FinalizeOpenSSLCRLCache);
}
}
// Caller owns returned X509_CRL*.
// It is caller's responsibility to free it with X509_CRL_free.
static ScopedX509CRL LookupCRL(const string& url) {
DCHECK(cache_);
return cache_->LookupCRLInternal(url);
}
// Returns true if url exists in internal database and successfully removed.
// Otherwise, e.g. not registered, returns false.
static bool DeleteCRL(const string& url) {
DCHECK(cache_);
return cache_->DeleteCRLInternal(url);
}
// Won't take ownership of |crl|. This function duplicates it internally.
static void SetCRL(const string& url, X509_CRL* crl) {
DCHECK(cache_);
return cache_->SetCRLInternal(url, crl);
}
private:
OpenSSLCRLCache() {}
~OpenSSLCRLCache() {
crls_.clear();
}
static void FinalizeOpenSSLCRLCache() {
delete cache_;
cache_ = nullptr;
}
// Note: caller should free X509_CRL.
ScopedX509CRL LookupCRLInternal(const string& url) {
AUTO_SHARED_LOCK(lock, &mu_);
const auto& it = crls_.find(url);
if (it == crls_.end())
return nullptr;
return ScopedX509CRL(X509_CRL_dup(it->second.get()));
}
bool DeleteCRLInternal(const string& url) {
AUTO_EXCLUSIVE_LOCK(lock, &mu_);
const auto& it = crls_.find(url);
if (it == crls_.end())
return false;
crls_.erase(it);
return true;
}
void SetCRLInternal(const string& url, X509_CRL* crl) {
AUTO_EXCLUSIVE_LOCK(lock, &mu_);
if (crls_.count(url) > 0) {
DeleteCRLInternal(url);
}
CHECK(crls_.insert(
std::make_pair(url, ScopedX509CRL(X509_CRL_dup(crl)))).second)
<< "We already have the same URL in CRL store."
<< " url=" << url;
}
mutable ReadWriteLock mu_;
std::map<string, ScopedX509CRL> crls_;
static OpenSSLCRLCache* cache_;
DISALLOW_COPY_AND_ASSIGN(OpenSSLCRLCache);
};
/* static */
OpenSSLCRLCache* OpenSSLCRLCache::cache_ = nullptr;
// Goma client also uses BoringSSL.
// Let's follow chromium's net/socket/ssl_client_socket_impl.cc.
// It uses BoringSSL default but avoid to select CBC ciphers.
const char* kCipherList = "ALL:!SHA256:!SHA384:!aPSK:!ECDSA+SHA1";
constexpr absl::Duration kCrlIoTimeout = absl::Seconds(1);
constexpr size_t kMaxDownloadCrlRetry = 5; // times.
void InitOpenSSL() {
CRYPTO_library_init();
OpenSSLSessionCache::Init();
OpenSSLSocketPoolCache::Init();
OpenSSLCertificateStore::Init();
OpenSSLCRLCache::Init();
LOG(INFO) << "OpenSSL is initialized.";
}
int NormalizeChar(int input) {
if (!isalnum(input)) {
return '_';
}
return input;
}
// Converts non-alphanum in a filename to '_'.
string NormalizeToUseFilename(const string& input) {
string out(input);
std::transform(out.begin(), out.end(), out.begin(), NormalizeChar);
return out;
}
ScopedX509CRL ParseCrl(const string& crl_str) {
// See: http://www.openssl.org/docs/apps/crl.html
if (crl_str.find("-----BEGIN X509 CRL-----") != string::npos) { // PEM
std::unique_ptr<BIO, ScopedBIOFree> bio(
BIO_new_mem_buf(crl_str.data(), crl_str.size()));
return ScopedX509CRL(
PEM_read_bio_X509_CRL(bio.get(), nullptr, nullptr, nullptr));
}
// DER
const unsigned char* p =
reinterpret_cast<const unsigned char*>(crl_str.data());
return ScopedX509CRL(d2i_X509_CRL(nullptr, &p, crl_str.size()));
}
string GetSubjectCommonName(X509* x509) {
static const size_t kMaxHostname = 1024;
X509_NAME* subject = X509_get_subject_name(x509);
char buf[kMaxHostname];
if (X509_NAME_get_text_by_NID(subject, NID_commonName, buf, sizeof(buf))
!= -1) {
return buf;
}
return "";
}
std::vector<string> GetAltDNSNames(X509* x509) {
int index = X509_get_ext_by_NID(x509, NID_subject_alt_name, -1);
if (index < 0) {
LOG(INFO) << "cert has no subject alt name";
return std::vector<string>();
}
X509_EXTENSION* subject_alt_name_extension = X509_get_ext(x509, index);
if (!subject_alt_name_extension) {
LOG(INFO) << "cert has no subject alt name extension";
return std::vector<string>();
}
GENERAL_NAMES* subject_alt_names = reinterpret_cast<GENERAL_NAMES*>(
X509V3_EXT_d2i(subject_alt_name_extension));
if (!subject_alt_names) {
LOG(INFO) << "unable to get subject alt name extension";
return std::vector<string>();
}
VLOG(1) << "subject alt names=" << sk_GENERAL_NAME_num(subject_alt_names);
std::vector<string> names;
for (size_t i = 0; i < sk_GENERAL_NAME_num(subject_alt_names); ++i) {
GENERAL_NAME* subject_alt_name =
sk_GENERAL_NAME_value(subject_alt_names, i);
switch (subject_alt_name->type) {
case GEN_DNS:
{
unsigned char* dns_name =
ASN1_STRING_data(subject_alt_name->d.dNSName);
if (!dns_name)
continue;
int len = ASN1_STRING_length(subject_alt_name->d.dNSName);
string name = string(reinterpret_cast<char*>(dns_name), len);
VLOG(1) << "subject alt name[" << i << "]=" << name;
names.push_back(name);
}
break;
case GEN_IPADD:
VLOG(1) << "ignore ip address";
break;
default:
LOG(INFO) << "unsupported alt name type:" << subject_alt_name->type;
break;
}
}
sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
return names;
}
bool MatchAltIPAddress(X509* x509, int af, void* ap) {
int index = X509_get_ext_by_NID(x509, NID_subject_alt_name, -1);
if (index < 0) {
LOG(INFO) << "cert has no subject alt name";
return false;
}
X509_EXTENSION* subject_alt_name_extension = X509_get_ext(x509, index);
if (!subject_alt_name_extension) {
LOG(INFO) << "cert has no subject alt name extension";
return false;
}
GENERAL_NAMES* subject_alt_names = reinterpret_cast<GENERAL_NAMES*>(
X509V3_EXT_d2i(subject_alt_name_extension));
if (!subject_alt_names) {
LOG(INFO) << "unable to get subject alt name extension";
return false;
}
VLOG(1) << "subject alt names=" << sk_GENERAL_NAME_num(subject_alt_names);
bool matched = false;
for (size_t i = 0; i < sk_GENERAL_NAME_num(subject_alt_names); ++i) {
GENERAL_NAME* subject_alt_name =
sk_GENERAL_NAME_value(subject_alt_names, i);
switch (subject_alt_name->type) {
case GEN_DNS:
VLOG(1) << "ignore dns name";
break;
case GEN_IPADD:
{
// ASN1_OCTET_STRING *iPAddress;
unsigned char* ipaddr =
ASN1_STRING_data(subject_alt_name->d.iPAddress);
if (!ipaddr)
continue;
int len = ASN1_STRING_length(subject_alt_name->d.iPAddress);
switch (len) {
case 4:
if (af == AF_INET) {
if (memcmp(ipaddr, ap, len) == 0) {
matched = true;
}
}
break;
case 16:
if (af == AF_INET6) {
if (memcmp(ipaddr, ap, len) == 0) {
matched = true;
}
}
break;
default:
LOG(WARNING) << "invalid IP address: length=" << len;
}
}
break;
default:
LOG(INFO) << "unsupported alt name type:" << subject_alt_name->type;
break;
}
if (matched) {
break;
}
}
sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
return matched;
}
// URL should be http (not https).
void DownloadCrl(
ScopedSocket* sock,
const HttpRequest& req,
HttpResponse* resp) {
resp->Reset();
// Send request.
if (!sock->valid()) {
LOG(ERROR) << "connection failure:" << *sock;
return;
}
std::unique_ptr<google::protobuf::io::ZeroCopyInputStream> request =
req.NewStream();
const void* data = nullptr;
int size = 0;
while (request->Next(&data, &size)) {
absl::string_view buf(static_cast<const char*>(data), size);
if (sock->WriteString(buf, kCrlIoTimeout) != OK) {
LOG(ERROR) << "write failure:"
<< " fd=" << *sock;
return;
}
}
for (;;) {
char* buf;
int buf_size;
resp->NextBuffer(&buf, &buf_size);
ssize_t len = sock->ReadWithTimeout(buf, buf_size, kCrlIoTimeout);
if (len < 0) {
LOG(ERROR) << "read failure:"
<< " fd=" << *sock
<< " len=" << len
<< " resp has_header=" << resp->HasHeader()
<< " resp status_code=" << resp->status_code()
<< " resp total_recv_len=" << resp->total_recv_len();
return;
}
if (resp->Recv(len)) {
resp->Parse();
return;
}
}
// UNREACHABLE.
}
string GetCrlUrl(X509* x509) {
int loc = X509_get_ext_by_NID(x509, NID_crl_distribution_points, -1);
if (loc < 0)
return "";
X509_EXTENSION* ext = X509_get_ext(x509, loc);
ASN1_OCTET_STRING* asn1_os = X509_EXTENSION_get_data(ext);
const unsigned char* data = ASN1_STRING_data(asn1_os);
const long data_len = ASN1_STRING_length(asn1_os);
STACK_OF(DIST_POINT)* dps = d2i_CRL_DIST_POINTS(nullptr, &data, data_len);
if (dps == nullptr) {
LOG(ERROR) << "could not find distpoints in CRL.";
return "";
}
string url;
for (size_t i = 0; i < sk_DIST_POINT_num(dps) && url.empty(); i++) {
DIST_POINT* dp = sk_DIST_POINT_value(dps, i);
if (dp->distpoint && dp->distpoint->type == 0) {
STACK_OF(GENERAL_NAME)* general_names = dp->distpoint->name.fullname;
for (size_t j = 0; j < sk_GENERAL_NAME_num(general_names) && url.empty();
j++) {
GENERAL_NAME* general_name = sk_GENERAL_NAME_value(general_names, j);
if (general_name->type == GEN_URI) {
url.assign(reinterpret_cast<const char*>(general_name->d.ia5->data));
if (url.find("http://") != 0) {
LOG(INFO) << "Unsupported distribution point URI:" << url;
url.clear();
continue;
}
} else {
LOG(INFO) << "Unsupported distribution point type:"
<< general_name->type;
}
}
}
}
sk_DIST_POINT_pop_free(dps, DIST_POINT_free);
return url;
}
bool VerifyCrl(X509_CRL* crl, X509_STORE_CTX* store_ctx) {
bool ok = true;
STACK_OF(X509)* x509s = X509_STORE_get1_certs(store_ctx,
X509_CRL_get_issuer(crl));
for (size_t j = 0; j < sk_X509_num(x509s); j++) {
EVP_PKEY *pkey;
pkey = X509_get_pubkey(sk_X509_value(x509s, j));
if (!X509_CRL_verify(crl, pkey)) {
ok = false;
break;
}
}
sk_X509_pop_free(x509s, X509_free);
return ok;
}
bool IsCrlExpired(const string& label, X509_CRL* crl,
absl::optional<absl::Duration> crl_max_valid_duration) {
// Is the CRL expired?
if (!X509_CRL_get_nextUpdate(crl) ||
X509_cmp_current_time(X509_CRL_get_nextUpdate(crl)) <= 0) {
LOG(INFO) << "CRL is expired: label=" << label
<< " info=" << GetHumanReadableCRL(crl);
return true;
}
// Does the CRL hit max valid duration set by the user?
if (crl_max_valid_duration.has_value()) {
ASN1_TIME* crl_last_update = X509_CRL_get_lastUpdate(crl);
time_t t = time(nullptr) -
absl::ToInt64Milliseconds(*crl_max_valid_duration);
if (X509_cmp_time(crl_last_update, &t) < 0) {
LOG(INFO) << "CRL is too old to use. We need to refresh: "
<< " label=" << label
<< " crl_max_valid_duration_=" << *crl_max_valid_duration
<< " info=" << GetHumanReadableCRL(crl);
return true;
}
}
return false;
}
} // anonymous namespace
//
// OpenSSLContext
//
void OpenSSLContext::Init(
const string& hostname,
absl::optional<absl::Duration> crl_max_valid_duration,
OneshotClosure* invalidate_closure) {
AUTOLOCK(lock, &mu_);
// To keep room to support higher version, let's allow to understand all
// TLS protocols here, and limit min supported version below.
// Note: if TLSv1_method is used, it won't understand TLS 1.1 or TLS 1.2.
// See: http://www.openssl.org/docs/ssl/SSL_CTX_new.html
ctx_ = SSL_CTX_new(TLS_method());
CHECK(ctx_);
// Disable legacy protocols.
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
// Allow BoringSSL to accept TLS 1.3.
SSL_CTX_set_max_proto_version(ctx_, TLS1_3_VERSION);
OpenSSLSessionCache::Setup(ctx_);
SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
CHECK(SSL_CTX_set_cipher_list(ctx_, kCipherList));
// TODO: write more config to ctx_.
OpenSSLCertificateStore::SetCertsToCTX(ctx_);
certs_info_ = OpenSSLCertificateStore::GetTrustedCertificates();
hostname_ = hostname;
crl_max_valid_duration_ = crl_max_valid_duration;
notify_invalidate_closure_ = invalidate_closure;
}
OpenSSLContext::OpenSSLContext() : is_crl_ready_(false), ref_cnt_(0) {
}
OpenSSLContext::~OpenSSLContext() {
CHECK_EQ(ref_cnt_, 0UL);
// The remove callback is called by SSL_CTX_free.
// See: http://www.openssl.org/docs/ssl/SSL_CTX_sess_set_get_cb.html
SSL_CTX_free(ctx_);
// In case it's not called.
if (notify_invalidate_closure_ != nullptr) {
delete notify_invalidate_closure_;
}
}
ScopedX509CRL OpenSSLContext::GetX509CrlsFromUrl(
const string& url, string* crl_str) {
LOG(INFO) << "ctx:" << this << ": DownloadCrl:" << url;
HttpClient::Options options;
if (!proxy_host_.empty()) {
options.proxy_host_name = proxy_host_;
options.proxy_port = proxy_port_;
}
options.InitFromURL(url);
HttpRequest req;
req.Init("GET", "", options);
req.AddHeader("Connection", "close");
HttpResponse resp;
resp.SetRequestPath(url);
resp.SetTraceId("downloadCrl");
SocketPool* socket_pool(OpenSSLSocketPoolCache::GetSocketPool(
options.SocketHost(), options.SocketPort()));
if (socket_pool == nullptr) {
LOG(ERROR) << "ctx:" << this << ": Socket Pool is nullptr:"
<< " host=" << options.SocketHost()
<< " port=" << options.SocketPort();
return nullptr;
}
for (size_t retry = 0;
retry < std::max(kMaxDownloadCrlRetry, socket_pool->NumAddresses());
++retry) {
ScopedSocket sock(socket_pool->NewSocket());
if (!sock.valid()) {
// We might have used up all candidate addresses in the pool.
// It might be better to wait a while.
LOG(WARNING) << "ctx:" << this
<< ": It seems to fail to connect to all available "
<< "addresses. Going to wait for a while."
<< " kWaitForThingsGetsBetter=" << kWaitForThingsGetsBetter;
absl::SleepFor(kWaitForThingsGetsBetter);
continue;
}
DownloadCrl(&sock, req, &resp);
if (resp.status_code() != 200) {
LOG(WARNING) << "ctx:" << this << ": download CRL retrying:"
<< " retry=" << retry
<< " url=" << url
<< " http=" << resp.status_code();
socket_pool->CloseSocket(std::move(sock), true);
continue;
}
crl_str->assign(resp.parsed_body());
ScopedX509CRL x509_crl(ParseCrl(*crl_str));
if (x509_crl == nullptr) {
LOG(WARNING) << "ctx:" << this << ": failed to parse CRL data:"
<< " url=" << url
<< " contents length=" << crl_str->length()
<< " resp header=" << resp.Header();
socket_pool->CloseSocket(std::move(sock), true);
continue;
}
// we requested "Connection: close", so close the socket, but no error.
socket_pool->CloseSocket(std::move(sock), false);
return x509_crl;
}
LOG(ERROR) << "ctx:" << this << ": failed to download CRL from " << url;
return nullptr;
}
bool OpenSSLContext::SetupCrlsUnlocked(STACK_OF(X509)* x509s) {
CHECK(!is_crl_ready_);
crls_.clear();
std::unique_ptr<X509_STORE, ScopedX509StoreFree> store(X509_STORE_new());
std::unique_ptr<X509_STORE_CTX, ScopedX509StoreCtxFree>
store_ctx(X509_STORE_CTX_new());
X509_STORE_CTX_init(store_ctx.get(), store.get(), nullptr, x509s);
const int num_x509s = sk_X509_num(x509s);
for (int i = 0; i < num_x509s; i++) {
X509* x509 = sk_X509_value(x509s, i);
string url = GetCrlUrl(x509);
if (url.empty())
continue;
ScopedX509CRL crl;
string crl_str;
// CRL is loaded in following steps:
// 1. try memory cache.
// 2. try disk cache.
// 3. download from URL.
// Read from memory cache.
bool is_mem_cache_used = false;
crl = OpenSSLCRLCache::LookupCRL(url);
if (crl) {
if (IsCrlExpired("memory", crl.get(), crl_max_valid_duration_)) {
OpenSSLCRLCache::DeleteCRL(url);
crl.reset();
}
// Is the CRL valid?
if (crl.get() && !VerifyCrl(crl.get(), store_ctx.get())) {
LOG(WARNING) << "ctx:" << this
<< ": Failed to verify memory cached CRL."
<< " url=" << url;
OpenSSLCRLCache::DeleteCRL(url);
crl.reset();
}
is_mem_cache_used = (crl.get() != nullptr);
}
// Read from disk cache.
const string& cache_file =
file::JoinPath(GetCacheDirectory(),
"CRL-" + NormalizeToUseFilename(url));
bool is_disk_cache_used = false;
if (!is_mem_cache_used && ReadFileToString(cache_file.c_str(), &crl_str)) {
crl = ParseCrl(crl_str);
if (crl &&
IsCrlExpired(cache_file, crl.get(), crl_max_valid_duration_)) {
remove(cache_file.c_str());
crl.reset();
}
// Is the CRL valid?
if (crl.get() && !VerifyCrl(crl.get(), store_ctx.get())) {
LOG(WARNING) << "ctx:" << this
<< ": Failed to verify disk cached CRL: " << cache_file;
remove(cache_file.c_str());
crl.reset();
}
is_disk_cache_used = (crl.get() != nullptr);
}
// Download from URL.
if (!is_mem_cache_used && !is_disk_cache_used) {
crl = GetX509CrlsFromUrl(url, &crl_str);
if (crl &&
IsCrlExpired(url, crl.get(), crl_max_valid_duration_)) {
crl.reset();
}
// Is the CRL valid?
if (crl.get() && !VerifyCrl(crl.get(), store_ctx.get())) {
LOG(WARNING) << "ctx:" << this << ": Failed to verify CRL: " << url;
crl.reset();
}
}
// Without CRL, TLS is not safe.
if (!crl.get()) {
std::ostringstream ss;
ss << "CRL is not available";
last_error_ = ss.str();
last_error_time_ = absl::Now();
ss << ":" << GetHumanReadableCert(x509);
// This error may occurs if the network is broken, unstable,
// or untrustable.
// We believe that not running compiler_proxy is better than hiding
// the strange situation. However, at the same time, sudden death is
// usually difficult for users to understand what is bad.
// Decision: die at start-time, won't die after that, but it seems
// too late to die here.
LOG(ERROR) << "ctx:" << this << ": " << ss.str();
return false;
}
X509_STORE_add_crl(SSL_CTX_get_cert_store(ctx_), crl.get());
certs_info_.append(GetHumanReadableCRL(crl.get()));
if (!is_mem_cache_used && !is_disk_cache_used) {
LOG(INFO) << "ctx:" << this
<< ": CRL loaded from: " << url;
const string& cache_dir = string(file::Dirname(cache_file));
if (!EnsureDirectory(cache_dir, 0700)) {
LOG(WARNING) << "Failed to create cache dir: " << cache_dir;
}
if (WriteStringToFile(crl_str, cache_file)) {
LOG(INFO) << "CRL is cached to: " << cache_file;
} else {
LOG(WARNING) << "Failed to write CRL cache to: " << cache_file;
}
}
if (is_disk_cache_used) {
LOG(INFO) << "ctx:" << this
<< ": Read CRL from cache:"
<< " url=" << url
<< " cache_file=" << cache_file;
}
if (is_mem_cache_used) {
LOG(INFO) << "ctx:" << this
<< ": loaded CRL in memory: " << url;
} else {
OpenSSLCRLCache::SetCRL(url, crl.get());
// If loaded from memory, we can assume we have already shown CRL info
// to log, and we do not show it again.
LOG(INFO) << "ctx:" << this << ": " << GetHumanReadableCRL(crl.get());
}
crls_.emplace_back(std::move(crl));
}
LOG_IF(WARNING, crls_.empty())
<< "ctx:" << this
<< ": A certificate should usually have its CRL."
<< " If we cannot not load any CRLs, something should be broken."
<< " certificates=" << GetHumanReadableCerts(x509s);
if (!crls_.empty()) {
VLOG(1) << "ctx:" << this
<< ": CRL is loaded. We will check it during verification.";
X509_VERIFY_PARAM *verify_param = X509_VERIFY_PARAM_new();
X509_VERIFY_PARAM_set_flags(verify_param, X509_V_FLAG_CRL_CHECK);
SSL_CTX_set1_param(ctx_, verify_param);
X509_VERIFY_PARAM_free(verify_param);
LOG(INFO) << "ctx:" << this
<< ": We may reject if the domain is not listed in loaded CRLs.";
}
is_crl_ready_ = true;
return true;
}
bool OpenSSLContext::IsRevoked(STACK_OF(X509)* x509s) {
AUTOLOCK(lock, &mu_);
if (!last_error_.empty() && last_error_time_ &&
absl::Now() < *last_error_time_ + kErrorTimeout) {
LOG(ERROR) << "ctx:" << this
<< ": Preventing using SSL because of:" << last_error_
<< " last_error_time_=" << *last_error_time_;
return true;
}
if (!is_crl_ready_ && !SetupCrlsUnlocked(x509s)) {
LOG(ERROR) << "ctx:" << this
<< ": Failed to load CRLs:"
<< GetHumanReadableCerts(x509s);
return true;
}
// Check CRLs.
for (size_t i = 0; i < sk_X509_num(x509s); i++) {
X509* x509 = sk_X509_value(x509s, i);
for (size_t j = 0; j < crls_.size(); j++) {
X509_REVOKED* rev;
if (X509_CRL_get0_by_cert(crls_[j].get(), &rev, x509)) {
LOG(ERROR) << "ctx:" << this
<< ": Certificate is already revoked:"
<< GetHumanReadableCert(x509);
return true;
}
}
}
return false;
}
/* static */
bool OpenSSLContext::IsHostnameMatched(
absl::string_view hostname, absl::string_view pattern) {
absl::string_view::size_type pos = pattern.find("*");
if (pos == absl::string_view::npos && pattern == hostname) {
return true;
}
absl::string_view prefix = pattern.substr(0, pos);
absl::string_view suffix = pattern.substr(pos + 1); // skip "*".
VLOG(1) << "prefix=" << prefix;
VLOG(1) << "suffix=" << suffix;
if (!prefix.empty() && !absl::StartsWith(hostname, prefix)) {
return false;
}
if (!suffix.empty() && !absl::EndsWith(hostname, suffix)) {
return false;
}
absl::string_view wildcard_part = hostname.substr(
prefix.length(),
hostname.length() - prefix.length() - suffix.length());
if (wildcard_part.find(".") != absl::string_view::npos) {
return false;
}
return true;
}
bool OpenSSLContext::IsValidServerIdentity(X509* cert) {
AUTOLOCK(lock, &mu_);
struct in_addr in4;
if (inet_pton(AF_INET, hostname_.c_str(), &in4) == 1) {
// hostname is IPv4 addr.
if (MatchAltIPAddress(cert, AF_INET, &in4)) {
LOG(INFO) << "ctx:" << this << ": Hostname matches with IPv4 address:"
<< " hostname=" << hostname_;
return true;
}
LOG(INFO) << "ctx:" << this
<< ": Hostname(IPv4) didn't match with certificate:"
<< " hostname=" << hostname_;
return false;
}
struct in6_addr in6;
if (inet_pton(AF_INET6, hostname_.c_str(), &in6) == 1) {
// hostname is IPv6 addr.
if (MatchAltIPAddress(cert, AF_INET6, &in6)) {
LOG(INFO) << "ctx:" << this
<< ": Hostname matches with IPv6 address:"
<< " hostname=" << hostname_;
return true;
}
LOG(INFO) << "ctx:" << this
<< ": Hostname(IPv6) didn't match with certificate:"
<< " hostname=" << hostname_;
return false;
}
const std::vector<string>& sans = GetAltDNSNames(cert);
if (sans.empty()) {
// Subject common name is used only when dNSName is not available.
//
// See: http://tools.ietf.org/html/rfc2818#section-3.1
// > If a subjectAltName extension of type dNSName is present, that MUST
// > be used as the identity. Otherwise, the (most specific) Common Name
// > field in the Subject field of the certificate MUST be used.
const string& cn = GetSubjectCommonName(cert);
if (OpenSSLContext::IsHostnameMatched(hostname_, cn)) {
LOG(INFO) << "ctx:" << this
<< ": Hostname matches with common name:"
<< " hostname=" << hostname_
<< " cn=" << cn;
return true;
}
LOG(INFO) << "ctx:" << this
<< ": Hostname didn't match with common name:"
<< " hostname=" << hostname_
<< " cn=" << cn;
return false;
}
for (const auto& san : sans) {
if (OpenSSLContext::IsHostnameMatched(hostname_, san)) {
LOG(INFO) << "ctx:" << this
<< ": Hostname matches with subject alternative names:"
<< " hostname=" << hostname_
<< " san=" << san;
return true;
}
}
LOG(ERROR) << "ctx:" << this
<< ": Hostname did not match with certificate:"
<< " hostname=" << hostname_;
return false;
}
void OpenSSLContext::SetProxy(const string& proxy_host, const int proxy_port) {
proxy_host_.assign(proxy_host);
proxy_port_ = proxy_port;
}
void OpenSSLContext::Invalidate() {
OneshotClosure* c = nullptr;
{
AUTOLOCK(lock, &mu_);
if (notify_invalidate_closure_) {
c = notify_invalidate_closure_;
notify_invalidate_closure_ = nullptr;
}
}
if (c) {
c->Run();
}
}
SSL* OpenSSLContext::NewSSL(bool* session_reused) {
CHECK(session_reused);
SSL* ssl = SSL_new(ctx_);
CHECK(ssl) << "Failed on SSL_new.";
// TLS Server Name Indication (SNI).
DCHECK(!hostname_.empty());
CHECK(SSL_set_tlsext_host_name(ssl, hostname_.c_str()))
<< "TLS Server Name Indication (SNI) failed:" << hostname_;
*session_reused = true;
if (!OpenSSLSessionCache::SetCachedSession(ctx_, ssl)) {
LOG(INFO) << "ctx:" << this
<< ": No session is cached. We need to start from handshake."
<< " key=" << ctx_
<< " hostname=" << hostname_;
*session_reused = false;
}
++ref_cnt_;
return ssl;
}
void OpenSSLContext::DeleteSSL(SSL* ssl) {
DCHECK(ssl);
DCHECK_GT(ref_cnt_, 0UL);
--ref_cnt_;
SSL_free(ssl);
}
void OpenSSLContext::RecordSession(SSL* ssl) {
OpenSSLSessionCache::RecordSession(ssl);
}
//
// TLS Engine
//
OpenSSLEngine::OpenSSLEngine()
: ssl_(nullptr), network_bio_(nullptr),
want_read_(false), want_write_(false),
recycled_(false), need_self_verify_(false),
need_to_store_session_(false), state_(BEFORE_INIT) {}
OpenSSLEngine::~OpenSSLEngine() {
if (ssl_ != nullptr) {
// TODO: actually send shutdown to server not BIO.
SSL_shutdown(ssl_);
ctx_->DeleteSSL(ssl_);
}
if (network_bio_ != nullptr) {
BIO_free_all(network_bio_);
}
}
void OpenSSLEngine::Init(OpenSSLContext* ctx) {
DCHECK(ctx);
DCHECK(!ssl_);
DCHECK_EQ(state_, BEFORE_INIT);
// If IsCrlReady() comes after creating SSL*, ssl_ may not check CRLs
// even if it should do. Since loaded CRLs are cached in OpenSSLContext,
// penalty to check it should be little.
need_self_verify_ = !ctx->IsCrlReady();
bool session_reused = false;
ssl_ = ctx->NewSSL(&session_reused);
DCHECK(ssl_);
if (!session_reused) {
LOG(INFO) << "ctx:" << ctx
<< ": Need to register session by myself."
<< " hostname=" << ctx->hostname();
need_to_store_session_ = true;
}
DCHECK(ssl_);
// Since internal_bio is free'd by SSL_free, we do not need to keep this
// separately.
BIO* internal_bio;
CHECK(BIO_new_bio_pair(&internal_bio, kNetworkBufSize, &network_bio_,
kNetworkBufSize))
<< "BIO_new_bio_pair failed.";
SSL_set_bio(ssl_, internal_bio, internal_bio);
ctx_ = ctx;
Connect(); // Do not check anything since nothing has started here.
state_ = IN_CONNECT;
}
bool OpenSSLEngine::IsIOPending() const {
return (state_ == IN_CONNECT) || want_read_ || want_write_;
}
bool OpenSSLEngine::IsReady() const {
return state_ == READY;
}
int OpenSSLEngine::GetDataToSendTransport(string* data) {
DCHECK_NE(state_, BEFORE_INIT);
size_t max_read = BIO_ctrl(network_bio_, BIO_CTRL_PENDING, 0, nullptr);
if (max_read > 0) {
data->resize(max_read);
char* buf = &((*data)[0]);
int read_bytes = BIO_read(network_bio_, buf, max_read);
DCHECK_GT(read_bytes, 0);
CHECK_EQ(static_cast<int>(max_read), read_bytes);
}
want_write_ = false;
if (state_ == IN_CONNECT) {
int status = Connect();
if (status < 0 && status != TLSEngine::TLS_WANT_READ &&
status != TLSEngine::TLS_WANT_WRITE)
return TLSEngine::TLS_VERIFY_ERROR;
}
return max_read;
}
size_t OpenSSLEngine::GetBufSizeFromTransport() {
return BIO_ctrl_get_write_guarantee(network_bio_);
}
int OpenSSLEngine::SetDataFromTransport(const absl::string_view& data) {
DCHECK_NE(state_, BEFORE_INIT);
size_t max_write = BIO_ctrl_get_write_guarantee(network_bio_);
CHECK_LE(data.size(), max_write);
int ret = BIO_write(network_bio_, data.data(), data.size());
CHECK_EQ(ret, static_cast<int>(data.size()));
want_read_ = false;
if (state_ == IN_CONNECT) {
int status = Connect();
if (status < 0 && status != TLSEngine::TLS_WANT_READ &&
status != TLSEngine::TLS_WANT_WRITE)
return TLSEngine::TLS_VERIFY_ERROR;
}
return ret;
}
int OpenSSLEngine::Read(void* data, int size) {
DCHECK_EQ(state_, READY);
int ret = SSL_read(ssl_, data, size);
return UpdateStatus(ret);
}
int OpenSSLEngine::Write(const void* data, int size) {
DCHECK_EQ(state_, READY);
int ret = SSL_write(ssl_, data, size);
return UpdateStatus(ret);
}
int OpenSSLEngine::UpdateStatus(int return_value) {
want_read_ = false;
want_write_ = false;
if (return_value > 0)
return return_value;
int ssl_err = SSL_get_error(ssl_, return_value);
switch (ssl_err) {
case SSL_ERROR_WANT_READ:
want_read_ = true;
return TLSEngine::TLS_WANT_READ;
case SSL_ERROR_WANT_WRITE:
want_write_ = true;
return TLSEngine::TLS_WANT_WRITE;
case SSL_ERROR_SSL:
if (SSL_get_verify_result(ssl_) != X509_V_OK) {
// Renew CRLs in the next connection but fails for this time.
LOG(WARNING) << "ctx:" << ctx_
<< ": Resetting CRLs because of verify error."
<< " details=" << X509_verify_cert_error_string(
SSL_get_verify_result(ssl_));
ctx_->Invalidate();
}
ABSL_FALLTHROUGH_INTENDED;
default:
LOG(ERROR) << "ctx:" << ctx_ << ": OpenSSL error"
<< " ret=" << return_value
<< " ssl_err=" << ssl_err
<< " err_msg=" << GetLastErrorMessage();
return TLSEngine::TLS_ERROR;
}
}
int OpenSSLEngine::Connect() {
int ret = SSL_connect(ssl_);
if (ret > 0) {
VLOG(3) << "ctx:" << ctx_
<< ": session reused=" << SSL_session_reused(ssl_);
state_ = READY;
if (need_self_verify_) {
LOG(INFO) << GetHumanReadableSSLInfo(ssl_);
STACK_OF(X509)* x509s = SSL_get_peer_cert_chain(ssl_);
if (!x509s) {
LOG(ERROR) << "ctx:" << ctx_
<< ": No x509 stored in SSL structure.";
return TLSEngine::TLS_VERIFY_ERROR;
}
LOG(INFO) << "ctx:" << ctx_ << ": "
<< GetHumanReadableCerts(x509s)
<< " session_info="
<< GetHumanReadableSessionInfo(SSL_get_session(ssl_));
// Get server certificate to verify.
// For ease of the code, I will not get certificate from the certificate
// chain got above.
std::unique_ptr<X509, ScopedX509Free> cert(
SSL_get_peer_certificate(ssl_));
if (cert.get() == nullptr) {
LOG(ERROR) << "ctx:" << ctx_
<< ": Cannot obtain the server's certificate";
return TLSEngine::TLS_VERIFY_ERROR;
}
LOG(INFO) << "ctx:" << ctx_ << ": Checking server's identity.";
// OpenSSL library does not check a name written in certificate
// matches what we are connecting now.
// We MUST do it by ourselves. Or, we allow spoofing.
if (!ctx_->IsValidServerIdentity(cert.get())) {
return TLSEngine::TLS_VERIFY_ERROR;
}
// Since CRL did not set when SSL started, CRL verification should be
// done by myself. Note that this is usually treated by OpenSSL library.
LOG(INFO) << "ctx:" << ctx_
<< ": need to verify revoked certificate by myself.";
if (ctx_->IsRevoked(x509s)) {
return TLSEngine::TLS_VERIFY_ERROR;
}
}
if (need_to_store_session_) {
ctx_->RecordSession(ssl_);
}
}
return UpdateStatus(ret);
}
string OpenSSLEngine::GetErrorString() const {
auto err = ERR_peek_last_error();
if (err == 0) {
return "ok";
}
char error_message[1024];
ERR_error_string_n(err, error_message, sizeof error_message);
return error_message;
}
string OpenSSLEngine::GetLastErrorMessage() const {
std::ostringstream oss;
oss << GetErrorString();
const string& ctx_err = ctx_->GetLastErrorMessage();
if (!ctx_err.empty()) {
oss << " ctx_error=" << ctx_err;
}
if (ERR_GET_REASON(ERR_peek_last_error()) ==
SSL_R_CERTIFICATE_VERIFY_FAILED) {
oss << " verify_error="
<< X509_verify_cert_error_string(SSL_get_verify_result(ssl_));
}
return oss.str();
}
OpenSSLEngineCache::OpenSSLEngineCache() : ctx_(nullptr) {
absl::call_once(g_openssl_init_once, InitOpenSSL);
}
OpenSSLEngineCache::~OpenSSLEngineCache() {
// If OpenSSLEngineCache is deleted correctly, we can expect:
// 1. all outgoing sockets are closed.
// 2. all counterpart OpenSSLEngine instances are free'd.
// 3. contexts_to_delete_ should be empty.
// 4. reference count of ctx_ should be zero.
CHECK(contexts_to_delete_.empty());
CHECK(!ctx_.get() || ctx_->ref_cnt() == 0UL);
}
std::unique_ptr<OpenSSLEngine> OpenSSLEngineCache::GetOpenSSLEngineUnlocked() {
if (ctx_.get() == nullptr) {
CHECK(OpenSSLCertificateStore::IsReady())
<< "OpenSSLCertificateStore does not have any certificates.";
ctx_ = absl::make_unique<OpenSSLContext>();
ctx_->Init(hostname_, crl_max_valid_duration_,
NewCallback(this, &OpenSSLEngineCache::InvalidateContext));
if (!proxy_host_.empty())
ctx_->SetProxy(proxy_host_, proxy_port_);
}
std::unique_ptr<OpenSSLEngine> engine(new OpenSSLEngine());
engine->Init(ctx_.get());
return engine;
}
void OpenSSLEngineCache::AddCertificateFromFile(
const string& ssl_cert_filename) {
OpenSSLCertificateStore::AddCertificateFromFile(ssl_cert_filename);
}
void OpenSSLEngineCache::AddCertificateFromString(
const string& ssl_cert) {
OpenSSLCertificateStore::AddCertificateFromString("user", ssl_cert);
}
TLSEngine* OpenSSLEngineCache::NewTLSEngine(int sock) {
AUTOLOCK(lock, &mu_);
auto found = ssl_map_.find(sock);
if (found != ssl_map_.end()) {
found->second->SetRecycled();
return found->second.get();
}
std::unique_ptr<OpenSSLEngine> engine = GetOpenSSLEngineUnlocked();
OpenSSLEngine* engine_ptr = engine.get();
CHECK(ssl_map_.emplace(sock, std::move(engine)).second)
<< "ssl_map_ should not have the same key:" << sock;
VLOG(1) << "SSL engine allocated. sock=" << sock;
return engine_ptr;
}
void OpenSSLEngineCache::WillCloseSocket(int sock) {
AUTOLOCK(lock, &mu_);
VLOG(1) << "SSL engine release. sock=" << sock;
auto found = ssl_map_.find(sock);
if (found != ssl_map_.end()) {
ssl_map_.erase(found);
}
if (!contexts_to_delete_.empty()) {
std::vector<std::unique_ptr<OpenSSLContext>> new_contexts_to_delete;
for (auto& ctx : contexts_to_delete_) {
if (ctx->ref_cnt() == 0) {
CHECK(ctx.get() != ctx_.get());
continue;
}
new_contexts_to_delete.emplace_back(std::move(ctx));
}
contexts_to_delete_ = std::move(new_contexts_to_delete);
}
}
void OpenSSLEngineCache::InvalidateContext() {
AUTOLOCK(lock, &mu_);
// OpenSSLContext instance should be held until ref_cnt become zero
// i.e. no OpenSSLEngine instance use it.
LOG_IF(ERROR, ctx_->hostname() != hostname_)
<< "OpenSSLContext hostname is different from OpenSSLEngineFactory one. "
<< " It might be changed after ctx_ is created?"
<< " ctx_hostname=" << ctx_->hostname()
<< " factory_hostname=" << hostname_;
contexts_to_delete_.emplace_back(std::move(ctx_));
}
} // namespace devtools_goma