blob: 1f19baff85ac09977228b257c441f95a97f8158c [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/string_number_conversions.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <base/logging.h>
#include <curl/curl.h>
#include <metrics/metrics_library.h>
#include <openssl/evp.h>
#include <openssl/ssl.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] =
} // namespace {}
bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
int* out_depth,
unsigned int* out_digest_length,
unsigned char* out_digest) const {
X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
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
MetricsLibraryInterface* CertificateChecker::metrics_lib_ = NULL;
// static
PrefsInterface* CertificateChecker::prefs_ = NULL;
// static
OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = NULL;
// 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[] =
static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
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, !prefs_->SetString(kReportToSendKey[server_to_check],
<< "Failed to store UMA report on a failure to validate "
<< "certificate from update server.";
return false;
int depth;
unsigned int digest_length;
unsigned char digest[EVP_MAX_MD_SIZE];
if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
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 = StringPrintf("%s-%d-%d",
string stored_digest;
// If there's no stored certificate, we just store the current one and return.
if (!prefs_->GetString(storage_key, &stored_digest)) {
LOG_IF(WARNING, !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, !prefs_->SetString(kReportToSendKey[server_to_check],
<< "Failed to store UMA report on a change on the "
<< "certificate from update server.";
LOG_IF(WARNING, !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(metrics_lib_ && prefs_);
// We flush reports for both servers.
for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
string report_to_send;
if (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_IF(WARNING, !metrics_lib_->SendUserActionToUMA(report_to_send))
<< "Failed to send server certificate report to UMA: "
<< report_to_send;
// Since prefs doesn't provide deletion, we just set it as an empty
// string.
LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[i], ""))
<< "Failed to erase server certificate report to be sent to UMA";
} // namespace chromeos_update_engine