Split payload application code into a subdirectory.
This patch splits from the main libupdate_engine code the part that
is strictly used to download and apply a payload into a new static
library, moving the code to subdirectories. The new library is divided
in two subdirectories: common/ and payload_consumer/, and should not
depend on other update_engine files outside those two subdirectories.
The main difference between those two is that the common/ tools are more
generic and not tied to the payload consumer process, but otherwise they
are both compiled together.
There are still dependencies from the new libpayload_consumer library
into the main directory files and DBus generated files. Those will be
addressed in follow up CLs.
Bug: 25197634
Test: FEATURES=test emerge-link update_engine; `mm` on Brillo.
Change-Id: Id8d0204ea573627e6e26ca9ea17b9592ca95bc23
diff --git a/common/certificate_checker.cc b/common/certificate_checker.cc
new file mode 100644
index 0000000..87f9848
--- /dev/null
+++ b/common/certificate_checker.cc
@@ -0,0 +1,202 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/certificate_checker.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <curl/curl.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+#include "metrics/metrics_library.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/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