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/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_