Add UMA metrics for detecting and reporting update certificate changes.

LibcurlHttpFetcher checks if the update server certificate has changed
since last update, and stores an eventual report in prefs. UpdateCheckScheduler
submits to UMA reports from the previous update.

BUG=chromium-os:19842
TEST=Included unittest for the new class and tested locally on an Alex device,
since we need to verify against the actual server certificates.

Change-Id: I5bee5d648982cd7618db09b67d5bff377eaa1fc1
Reviewed-on: http://gerrit.chromium.org/gerrit/7565
Reviewed-by: Bruno Pontes Soares Rocha <bpontes@chromium.org>
Tested-by: Bruno Pontes Soares Rocha <bpontes@chromium.org>
Reviewed-by: Andrew de los Reyes <adlr@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
diff --git a/SConstruct b/SConstruct
index 5fada88..5037fa9 100644
--- a/SConstruct
+++ b/SConstruct
@@ -241,6 +241,7 @@
                    buffered_file_writer.cc
                    bzip.cc
                    bzip_extent_writer.cc
+                   certificate_checker.cc
                    chrome_browser_proxy_resolver.cc
                    chrome_proxy_resolver.cc
                    cycle_breaker.cc
@@ -289,6 +290,7 @@
                             action_processor_unittest.cc
                             buffered_file_writer_unittest.cc
                             bzip_extent_writer_unittest.cc
+                            certificate_checker_unittest.cc
                             chrome_browser_proxy_resolver_unittest.cc
                             chrome_proxy_resolver_unittest.cc
                             cycle_breaker_unittest.cc
diff --git a/certificate_checker.cc b/certificate_checker.cc
new file mode 100644
index 0000000..579038e
--- /dev/null
+++ b/certificate_checker.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 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/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] =
+    {kPrefsCertificateReportToSendUpdate,
+     kPrefsCertificateReportToSendDownload};
+}  // namespace {}
+
+bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+                                          int* out_depth,
+                                          unsigned int* out_digest_length,
+                                          unsigned char* 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
+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[] =
+      "Updater.ServerCertificateChanged";
+  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],
+                                       kUMAActionCertFailed))
+        << "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,
+                                              &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 = 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 (!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],
+                                       kUMAActionCertChanged))
+        << "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
diff --git a/certificate_checker.h b/certificate_checker.h
new file mode 100644
index 0000000..f04179b
--- /dev/null
+++ b/certificate_checker.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
+
+#include <string>
+
+#include <base/basictypes.h>
+#include <curl/curl.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <openssl/ssl.h>
+
+class MetricsLibraryInterface;
+namespace chromeos_update_engine { class PrefsInterface; }
+
+namespace chromeos_update_engine {
+
+// Wrapper for openssl operations with the certificates.
+class OpenSSLWrapper {
+ public:
+  OpenSSLWrapper() {}
+  virtual ~OpenSSLWrapper() {}
+
+  // Takes an openssl X509_STORE_CTX, extracts the corresponding certificate
+  // from it and calculates its fingerprint (SHA256 digest). Returns true on
+  // success and false otherwise.
+  //
+  // |x509_ctx| is the pointer to the openssl object that holds the certificate.
+  // |out_depth| is the depth of the current certificate, in the certificate
+  // chain.
+  // |out_digest_length| is the length of the generated digest.
+  // |out_digest| is the byte array where the digest itself will be written.
+  // It should be big enough to hold a SHA1 digest (e.g. EVP_MAX_MD_SIZE).
+  virtual bool GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+                                    int* out_depth,
+                                    unsigned int* out_digest_length,
+                                    unsigned char* out_digest) const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OpenSSLWrapper);
+};
+
+// Responsible for checking whether update server certificates change, and
+// reporting to UMA when this happens. Since all state information is persisted,
+// and openssl forces us to use a static callback with no data pointer, this
+// class is entirely static.
+class CertificateChecker {
+ public:
+  // These values are used to generate the keys of files persisted via prefs.
+  // This means that changing these will cause loss of information on metrics
+  // reporting, during the transition.
+  enum ServerToCheck {
+    kUpdate = 0,
+    kDownload = 1,
+    kNone = 2  // This needs to be the last element. Changing its value is ok.
+  };
+
+  CertificateChecker() {}
+  virtual ~CertificateChecker() {}
+
+  // This callback is called by libcurl just before the initialization of an
+  // SSL connection after having processed all other SSL related options. Used
+  // to check if server certificates change. |ptr| is expected to be a
+  // pointer to a ServerToCheck.
+  static CURLcode ProcessSSLContext(CURL* curl_handle, SSL_CTX* ssl_ctx,
+                                    void* ptr);
+
+  // Flushes to UMA any certificate-related report that was persisted.
+  static void FlushReport();
+
+  // Setters.
+  static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+    metrics_lib_ = metrics_lib;
+  }
+
+  static void set_prefs(PrefsInterface* prefs) {
+    prefs_ = prefs;
+  }
+
+  static void set_openssl_wrapper(OpenSSLWrapper* openssl_wrapper) {
+    openssl_wrapper_ = openssl_wrapper;
+  }
+
+ private:
+  FRIEND_TEST(CertificateCheckerTest, NewCertificate);
+  FRIEND_TEST(CertificateCheckerTest, SameCertificate);
+  FRIEND_TEST(CertificateCheckerTest, ChangedCertificate);
+  FRIEND_TEST(CertificateCheckerTest, FailedCertificate);
+  FRIEND_TEST(CertificateCheckerTest, FlushReport);
+  FRIEND_TEST(CertificateCheckerTest, FlushNothingToReport);
+
+  // These callbacks are called by openssl after initial SSL verification. They
+  // are used to perform any additional security verification on the connection,
+  // but we use them here to get hold of the server certificate, in order to
+  // determine if it has changed since the last connection. Since openssl forces
+  // us to do this statically, we define two different callbacks for the two
+  // different official update servers, and only assign the correspondent one.
+  // The assigned callback is then called once per each certificate on the
+  // server and returns 1 for success and 0 for failure.
+  static int VerifySSLCallbackUpdateCheck(int preverify_ok,
+                                          X509_STORE_CTX* x509_ctx);
+  static int VerifySSLCallbackDownload(int preverify_ok,
+                                       X509_STORE_CTX* x509_ctx);
+
+  // Checks if server certificate for |server_to_check|, stored in |x509_ctx|,
+  // has changed since last connection to that same server. This is called by
+  // one of the two callbacks defined above. If certificate fails to check or
+  // changes, a report is generated and persisted, to be later sent by
+  // FlushReport. Returns true on success and false otherwise.
+  static bool CheckCertificateChange(ServerToCheck server_to_check,
+                                     int preverify_ok,
+                                     X509_STORE_CTX* x509_ctx);
+
+  // Metrics library used to report to UMA.
+  static MetricsLibraryInterface* metrics_lib_;
+
+  // Prefs used to store certificates and UMA reports.
+  static PrefsInterface* prefs_;
+
+  // The wrapper for openssl operations.
+  static OpenSSLWrapper* openssl_wrapper_;
+
+  DISALLOW_COPY_AND_ASSIGN(CertificateChecker);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
diff --git a/certificate_checker_mock.h b/certificate_checker_mock.h
new file mode 100644
index 0000000..55668a1
--- /dev/null
+++ b/certificate_checker_mock.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROMEOS_UPDATE_ENGINE_CERTIFICATE_CHECKER_MOCK_H_
+#define CHROMEOS_UPDATE_ENGINE_CERTIFICATE_CHECKER_MOCK_H_
+
+#include <gmock/gmock.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/certificate_checker.h"
+
+namespace chromeos_update_engine {
+
+class OpenSSLWrapperMock : public OpenSSLWrapper {
+ public:
+  MOCK_CONST_METHOD4(GetCertificateDigest,
+                     bool(X509_STORE_CTX* x509_ctx,
+                          int* out_depth,
+                          unsigned int* out_digest_length,
+                          unsigned char* out_digest));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_UPDATE_ENGINE_CERTIFICATE_CHECKER_MOCK_H_
diff --git a/certificate_checker_unittest.cc b/certificate_checker_unittest.cc
new file mode 100644
index 0000000..ccff258
--- /dev/null
+++ b/certificate_checker_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright (c) 2011 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 <string>
+
+#include <base/string_util.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <metrics/metrics_library_mock.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/certificate_checker_mock.h"
+#include "update_engine/prefs_mock.h"
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::SetArrayArgument;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class CertificateCheckerTest : public testing::Test {
+ public:
+  CertificateCheckerTest() {}
+
+ protected:
+  virtual void SetUp() {
+    depth_ = 0;
+    length_ = 4;
+    digest_[0] = 0x17;
+    digest_[1] = 0x7D;
+    digest_[2] = 0x07;
+    digest_[3] = 0x5F;
+    digest_hex_ = "177D075F";
+    diff_digest_hex_ = "1234ABCD";
+    cert_key_prefix_ = kPrefsUpdateServerCertificate;
+    server_to_check_ = CertificateChecker::kUpdate;
+    cert_key_ = StringPrintf("%s-%d-%d",
+                             cert_key_prefix_.c_str(),
+                             server_to_check_,
+                             depth_);
+    kCertChanged = "Updater.ServerCertificateChanged";
+    kCertFailed = "Updater.ServerCertificateFailed";
+    CertificateChecker::set_metrics_lib(&metrics_lib_);
+    CertificateChecker::set_prefs(&prefs_);
+    CertificateChecker::set_openssl_wrapper(&openssl_wrapper_);
+  }
+
+  virtual void TearDown() {}
+
+  MetricsLibraryMock metrics_lib_;
+  PrefsMock prefs_;
+  OpenSSLWrapperMock openssl_wrapper_;
+  // Parameters of our mock certificate digest.
+  int depth_;
+  unsigned int length_;
+  unsigned char digest_[4];
+  string digest_hex_;
+  string diff_digest_hex_;
+  string cert_key_prefix_;
+  CertificateChecker::ServerToCheck server_to_check_;
+  string cert_key_;
+  string kCertChanged;
+  string kCertFailed;
+};
+
+// check certificate change, new
+TEST_F(CertificateCheckerTest, NewCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(NULL, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(cert_key_, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(prefs_, SetString(cert_key_, digest_hex_))
+      .WillOnce(Return(true));
+  ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 1, NULL));
+}
+
+// check certificate change, unchanged
+TEST_F(CertificateCheckerTest, SameCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(NULL, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(cert_key_, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(digest_hex_),
+          Return(true)));
+  EXPECT_CALL(prefs_, SetString(_, _)).Times(0);
+  ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 1, NULL));
+}
+
+// check certificate change, changed
+TEST_F(CertificateCheckerTest, ChangedCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(NULL, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(cert_key_, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(diff_digest_hex_),
+          Return(true)));
+  EXPECT_CALL(prefs_, SetString(kPrefsCertificateReportToSendUpdate,
+                                kCertChanged))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs_, SetString(cert_key_, digest_hex_))
+      .WillOnce(Return(true));
+  ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 1, NULL));
+}
+
+// check certificate change, failed
+TEST_F(CertificateCheckerTest, FailedCertificate) {
+  EXPECT_CALL(prefs_, SetString(kPrefsCertificateReportToSendUpdate,
+                                kCertFailed))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs_, GetString(_,_)).Times(0);
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(_,_,_,_)).Times(0);
+  ASSERT_FALSE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 0, NULL));
+}
+
+// flush send report
+TEST_F(CertificateCheckerTest, FlushReport) {
+  EXPECT_CALL(prefs_, GetString(kPrefsCertificateReportToSendUpdate, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(kCertChanged),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(kPrefsCertificateReportToSendDownload, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(metrics_lib_, SendUserActionToUMA(kCertChanged))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs_, SetString(kPrefsCertificateReportToSendUpdate, ""))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs_, SetString(kPrefsCertificateReportToSendDownload, _))
+      .Times(0);
+  CertificateChecker::FlushReport();
+}
+
+// flush nothing to report
+TEST_F(CertificateCheckerTest, FlushNothingToReport) {
+  string empty = "";
+  EXPECT_CALL(prefs_, GetString(kPrefsCertificateReportToSendUpdate, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(empty),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(kPrefsCertificateReportToSendDownload, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(metrics_lib_, SendUserActionToUMA(_)).Times(0);
+  EXPECT_CALL(prefs_, SetString(_,_)).Times(0);
+  CertificateChecker::FlushReport();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index eb9d7d8..7f5f859 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -9,6 +9,7 @@
 
 #include <base/logging.h>
 
+#include "update_engine/certificate_checker.h"
 #include "update_engine/chrome_proxy_resolver.h"
 #include "update_engine/dbus_interface.h"
 #include "update_engine/flimflam_proxy.h"
@@ -150,6 +151,14 @@
     CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CIPHER_LIST,
                               "HIGH:!ADH"),
              CURLE_OK);
+    if (check_certificate_ != CertificateChecker::kNone) {
+      CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_DATA,
+                                &check_certificate_),
+               CURLE_OK);
+      CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_FUNCTION,
+                                CertificateChecker::ProcessSSLContext),
+               CURLE_OK);
+    }
   }
 
   CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
index 177664b..b436de6 100644
--- a/libcurl_http_fetcher.h
+++ b/libcurl_http_fetcher.h
@@ -7,10 +7,13 @@
 
 #include <map>
 #include <string>
+
+#include <base/basictypes.h>
+#include <base/logging.h>
 #include <curl/curl.h>
 #include <glib.h>
-#include "base/basictypes.h"
-#include "base/logging.h"
+
+#include "update_engine/certificate_checker.h"
 #include "update_engine/http_fetcher.h"
 
 // This is a concrete implementation of HttpFetcher that uses libcurl to do the
@@ -42,7 +45,8 @@
         forced_official_build_(false),
         in_write_callback_(false),
         sent_byte_(false),
-        terminate_requested_(false) {}
+        terminate_requested_(false),
+        check_certificate_(CertificateChecker::kNone) {}
 
   // Cleans up all internal state. Does not notify delegate
   ~LibcurlHttpFetcher();
@@ -90,6 +94,11 @@
     forced_official_build_ = is_official;
   }
 
+  void set_check_certificate(
+      CertificateChecker::ServerToCheck check_certificate) {
+    check_certificate_ = check_certificate;
+  }
+
  private:
   // Callback for when proxy resolution has completed. This begins the
   // transfer.
@@ -223,6 +232,11 @@
   // if we get a terminate request, queue it until we can handle it.
   bool terminate_requested_;
 
+  // Represents which server certificate to be checked against this
+  // connection's certificate. If no certificate check needs to be performed,
+  // this should be kNone.
+  CertificateChecker::ServerToCheck check_certificate_;
+
   DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
 };
 
diff --git a/main.cc b/main.cc
index 52e9c9e..b96add3 100644
--- a/main.cc
+++ b/main.cc
@@ -16,6 +16,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#include "update_engine/certificate_checker.h"
 #include "update_engine/dbus_constants.h"
 #include "update_engine/dbus_interface.h"
 #include "update_engine/dbus_service.h"
@@ -172,6 +173,13 @@
   MetricsLibrary metrics_lib;
   metrics_lib.Init();
 
+  // Sets static members for the certificate checker.
+  chromeos_update_engine::CertificateChecker::set_metrics_lib(&metrics_lib);
+  chromeos_update_engine::CertificateChecker::set_prefs(&prefs);
+  chromeos_update_engine::OpenSSLWrapper openssl_wrapper;
+  chromeos_update_engine::CertificateChecker::set_openssl_wrapper(
+      &openssl_wrapper);
+
   // Create the update attempter:
   chromeos_update_engine::ConcreteDbusGlib dbus;
   chromeos_update_engine::UpdateAttempter update_attempter(&prefs,
diff --git a/prefs.cc b/prefs.cc
index 94d0baa..067f768 100644
--- a/prefs.cc
+++ b/prefs.cc
@@ -15,6 +15,10 @@
 
 namespace chromeos_update_engine {
 
+const char kPrefsCertificateReportToSendDownload[] =
+    "certificate-report-to-send-download";
+const char kPrefsCertificateReportToSendUpdate[] =
+    "certificate-report-to-send-update";
 const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
 const char kPrefsLastActivePingDay[] = "last-active-ping-day";
 const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
@@ -22,6 +26,7 @@
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
 const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
+const char kPrefsUpdateServerCertificate[] = "update-server-cert";
 const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
 const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
 const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
diff --git a/prefs_interface.h b/prefs_interface.h
index ddebb3b..624005e 100644
--- a/prefs_interface.h
+++ b/prefs_interface.h
@@ -9,6 +9,8 @@
 
 namespace chromeos_update_engine {
 
+extern const char kPrefsCertificateReportToSendDownload[];
+extern const char kPrefsCertificateReportToSendUpdate[];
 extern const char kPrefsDeltaUpdateFailures[];
 extern const char kPrefsLastActivePingDay[];
 extern const char kPrefsLastRollCallPingDay[];
@@ -16,6 +18,7 @@
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
 extern const char kPrefsUpdateCheckResponseHash[];
+extern const char kPrefsUpdateServerCertificate[];
 extern const char kPrefsUpdateStateNextDataOffset[];
 extern const char kPrefsUpdateStateNextOperation[];
 extern const char kPrefsUpdateStateSHA256Context[];
diff --git a/update_attempter.cc b/update_attempter.cc
index 57ddc66..8953803 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -20,6 +20,7 @@
 #include <policy/libpolicy.h>
 #include <policy/device_policy.h>
 
+#include "update_engine/certificate_checker.h"
 #include "update_engine/dbus_service.h"
 #include "update_engine/download_action.h"
 #include "update_engine/filesystem_copier_action.h"
@@ -195,6 +196,7 @@
   // Try harder to connect to the network, esp when not interactive.
   // See comment in libcurl_http_fetcher.cc.
   update_check_fetcher->set_no_network_max_retries(interactive ? 1 : 3);
+  update_check_fetcher->set_check_certificate(CertificateChecker::kUpdate);
   shared_ptr<OmahaRequestAction> update_check_action(
       new OmahaRequestAction(prefs_,
                              omaha_request_params_,
@@ -214,9 +216,13 @@
                                  OmahaEvent::kTypeUpdateDownloadStarted),
                              new LibcurlHttpFetcher(GetProxyResolver()),
                              false));
+  LibcurlHttpFetcher* download_fetcher =
+      new LibcurlHttpFetcher(GetProxyResolver());
+  download_fetcher->set_check_certificate(CertificateChecker::kDownload);
   shared_ptr<DownloadAction> download_action(
-      new DownloadAction(prefs_, new MultiRangeHTTPFetcher(
-          new LibcurlHttpFetcher(GetProxyResolver()))));
+      new DownloadAction(prefs_,
+                         new MultiRangeHTTPFetcher(
+                             download_fetcher)));  // passes ownership
   shared_ptr<OmahaRequestAction> download_finished_action(
       new OmahaRequestAction(prefs_,
                              omaha_request_params_,
diff --git a/update_check_scheduler.cc b/update_check_scheduler.cc
index 2d08aea..0218393 100644
--- a/update_check_scheduler.cc
+++ b/update_check_scheduler.cc
@@ -4,6 +4,7 @@
 
 #include "update_engine/update_check_scheduler.h"
 
+#include "update_engine/certificate_checker.h"
 #include "update_engine/utils.h"
 
 namespace chromeos_update_engine {
@@ -82,6 +83,8 @@
   CHECK(me->scheduled_);
   me->scheduled_ = false;
   if (me->IsOOBEComplete()) {
+    // Before updating, we flush any previously generated UMA reports.
+    CertificateChecker::FlushReport();
     me->update_attempter_->Update("", "", false, false);
   } else {
     // Skips all automatic update checks if the OOBE process is not complete and