blob: 4c1d2ed634fbdc61f274cab7eb7caa781933af4f [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "update_engine/certificate_checker.h"
#include <string>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/logging.h>
#include <curl/curl.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include "metrics/metrics_library.h"
#include "update_engine/constants.h"
#include "update_engine/prefs_interface.h"
#include "update_engine/utils.h"
using std::string;
namespace chromeos_update_engine {
namespace {
// This should be in the same order of CertificateChecker::ServerToCheck, with
// the exception of kNone.
static const char* kReportToSendKey[2] =
{kPrefsCertificateReportToSendUpdate,
kPrefsCertificateReportToSendDownload};
} // namespace
bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
int* out_depth,
unsigned int* out_digest_length,
uint8_t* out_digest) const {
TEST_AND_RETURN_FALSE(out_digest);
X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
TEST_AND_RETURN_FALSE(certificate);
int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
if (out_depth)
*out_depth = depth;
unsigned int len;
const EVP_MD* digest_function = EVP_sha256();
bool success = X509_digest(certificate, digest_function, out_digest, &len);
if (success && out_digest_length)
*out_digest_length = len;
return success;
}
// static
SystemState* CertificateChecker::system_state_ = nullptr;
// static
OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = nullptr;
// static
CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
SSL_CTX* ssl_ctx,
void* ptr) {
// From here we set the SSL_CTX to another callback, from the openssl library,
// which will be called after each server certificate is validated. However,
// since openssl does not allow us to pass our own data pointer to the
// callback, the certificate check will have to be done statically. Since we
// need to know which update server we are using in order to check the
// certificate, we hardcode Chrome OS's two known update servers here, and
// define a different static callback for each. Since this code should only
// run in official builds, this should not be a problem. However, if an update
// server different from the ones listed here is used, the check will not
// take place.
ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
// We check which server to check and set the appropriate static callback.
if (*server_to_check == kUpdate)
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
if (*server_to_check == kDownload)
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
return CURLE_OK;
}
// static
int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
X509_STORE_CTX* x509_ctx) {
return CertificateChecker::CheckCertificateChange(
kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
}
// static
int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
X509_STORE_CTX* x509_ctx) {
return CertificateChecker::CheckCertificateChange(
kDownload, preverify_ok, x509_ctx) ? 1 : 0;
}
// static
bool CertificateChecker::CheckCertificateChange(
ServerToCheck server_to_check, int preverify_ok,
X509_STORE_CTX* x509_ctx) {
static const char kUMAActionCertChanged[] =
"Updater.ServerCertificateChanged";
static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
TEST_AND_RETURN_FALSE(system_state_ != nullptr);
TEST_AND_RETURN_FALSE(system_state_->prefs() != nullptr);
TEST_AND_RETURN_FALSE(server_to_check != kNone);
// If pre-verification failed, we are not interested in the current
// certificate. We store a report to UMA and just propagate the fail result.
if (!preverify_ok) {
LOG_IF(WARNING, !system_state_->prefs()->SetString(
kReportToSendKey[server_to_check], kUMAActionCertFailed))
<< "Failed to store UMA report on a failure to validate "
<< "certificate from update server.";
return false;
}
int depth;
unsigned int digest_length;
uint8_t digest[EVP_MAX_MD_SIZE];
if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
&depth,
&digest_length,
digest)) {
LOG(WARNING) << "Failed to generate digest of X509 certificate "
<< "from update server.";
return true;
}
// We convert the raw bytes of the digest to an hex string, for storage in
// prefs.
string digest_string = base::HexEncode(digest, digest_length);
string storage_key = base::StringPrintf("%s-%d-%d",
kPrefsUpdateServerCertificate,
server_to_check,
depth);
string stored_digest;
// If there's no stored certificate, we just store the current one and return.
if (!system_state_->prefs()->GetString(storage_key, &stored_digest)) {
LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
digest_string))
<< "Failed to store server certificate on storage key " << storage_key;
return true;
}
// Certificate changed, we store a report to UMA and store the most recent
// certificate.
if (stored_digest != digest_string) {
LOG_IF(WARNING, !system_state_->prefs()->SetString(
kReportToSendKey[server_to_check], kUMAActionCertChanged))
<< "Failed to store UMA report on a change on the "
<< "certificate from update server.";
LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
digest_string))
<< "Failed to store server certificate on storage key " << storage_key;
}
// Since we don't perform actual SSL verification, we return success.
return true;
}
// static
void CertificateChecker::FlushReport() {
// This check shouldn't be needed, but it is useful for testing.
TEST_AND_RETURN(system_state_);
TEST_AND_RETURN(system_state_->metrics_lib());
TEST_AND_RETURN(system_state_->prefs());
// We flush reports for both servers.
for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
string report_to_send;
if (system_state_->prefs()->GetString(kReportToSendKey[i], &report_to_send)
&& !report_to_send.empty()) {
// There is a report to be sent. We send it and erase it.
LOG(INFO) << "Found report #" << i << ". Sending it";
LOG_IF(WARNING, !system_state_->metrics_lib()->SendUserActionToUMA(
report_to_send))
<< "Failed to send server certificate report to UMA: "
<< report_to_send;
LOG_IF(WARNING, !system_state_->prefs()->Delete(kReportToSendKey[i]))
<< "Failed to erase server certificate report to be sent to UMA";
}
}
}
} // namespace chromeos_update_engine