Remove libcurl support from update_engine_sideload.
While sideloading an update from recovery we don't need nor want to
download payload from HTTP(S) URLs, only local file:// are supported.
This patch moves libcurl_http_fetcher and certificate_checker files out
of libpayload_consumer dropping the libcurl and libssl dependencies from
it and the update_engine_sideload.
Bug: 27178350
TEST=build UE for Brillo and Android. Unittests still pass and
update_engine_sideload doesn't link to libcurl.
Change-Id: Iffefdb094654f7277dc825c041fe55aac9ee8756
diff --git a/certificate_checker.cc b/certificate_checker.cc
new file mode 100644
index 0000000..6e886e7
--- /dev/null
+++ b/certificate_checker.cc
@@ -0,0 +1,204 @@
+//
+// 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/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 "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 {
+
+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
+CertificateChecker* CertificateChecker::cert_checker_singleton_ = nullptr;
+
+CertificateChecker::CertificateChecker(PrefsInterface* prefs,
+ OpenSSLWrapper* openssl_wrapper)
+ : prefs_(prefs), openssl_wrapper_(openssl_wrapper) {
+}
+
+CertificateChecker::~CertificateChecker() {
+ if (cert_checker_singleton_ == this)
+ cert_checker_singleton_ = nullptr;
+}
+
+void CertificateChecker::Init() {
+ CHECK(cert_checker_singleton_ == nullptr);
+ cert_checker_singleton_ = this;
+}
+
+// static
+CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
+ SSL_CTX* ssl_ctx,
+ void* ptr) {
+ ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
+
+ if (!cert_checker_singleton_) {
+ DLOG(WARNING) << "No CertificateChecker singleton initialized.";
+ return CURLE_FAILED_INIT;
+ }
+
+ // 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.
+ int (*verify_callback)(int, X509_STORE_CTX*);
+ switch (*server_to_check) {
+ case ServerToCheck::kDownload:
+ verify_callback = &CertificateChecker::VerifySSLCallbackDownload;
+ break;
+ case ServerToCheck::kUpdate:
+ verify_callback = &CertificateChecker::VerifySSLCallbackUpdate;
+ break;
+ case ServerToCheck::kNone:
+ verify_callback = nullptr;
+ break;
+ }
+
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
+ return CURLE_OK;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ return VerifySSLCallback(preverify_ok, x509_ctx, ServerToCheck::kDownload);
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackUpdate(int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ return VerifySSLCallback(preverify_ok, x509_ctx, ServerToCheck::kUpdate);
+}
+
+// static
+int CertificateChecker::VerifySSLCallback(int preverify_ok,
+ X509_STORE_CTX* x509_ctx,
+ ServerToCheck server_to_check) {
+ CHECK(cert_checker_singleton_ != nullptr);
+ return cert_checker_singleton_->CheckCertificateChange(
+ preverify_ok, x509_ctx, server_to_check) ? 1 : 0;
+}
+
+bool CertificateChecker::CheckCertificateChange(int preverify_ok,
+ X509_STORE_CTX* x509_ctx,
+ ServerToCheck server_to_check) {
+ TEST_AND_RETURN_FALSE(prefs_ != nullptr);
+
+ // 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) {
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kFailed);
+ 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.";
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+ 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,
+ static_cast<int>(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)) {
+ if (!prefs_->SetString(storage_key, digest_string)) {
+ LOG(WARNING) << "Failed to store server certificate on storage key "
+ << storage_key;
+ }
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+ return true;
+ }
+
+ // Certificate changed, we store a report to UMA and store the most recent
+ // certificate.
+ if (stored_digest != digest_string) {
+ if (!prefs_->SetString(storage_key, digest_string)) {
+ LOG(WARNING) << "Failed to store server certificate on storage key "
+ << storage_key;
+ }
+ LOG(INFO) << "Certificate changed from " << stored_digest << " to "
+ << digest_string << ".";
+ NotifyCertificateChecked(server_to_check,
+ CertificateCheckResult::kValidChanged);
+ return true;
+ }
+
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+ // Since we don't perform actual SSL verification, we return success.
+ return true;
+}
+
+void CertificateChecker::NotifyCertificateChecked(
+ ServerToCheck server_to_check,
+ CertificateCheckResult result) {
+ if (observer_)
+ observer_->CertificateChecked(server_to_check, result);
+}
+
+} // namespace chromeos_update_engine