blob: 7432c21bcfc5365e3af85ca5935d6d4438b29821 [file] [log] [blame]
Mike Frysinger8155d082012-04-06 15:23:18 -04001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Bruno Rocha7f9aea22011-09-12 14:31:24 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "update_engine/certificate_checker.h"
6
7#include <string>
8
9#include <base/string_number_conversions.h>
10#include <base/string_util.h>
Mike Frysinger8155d082012-04-06 15:23:18 -040011#include <base/stringprintf.h>
Bruno Rocha7f9aea22011-09-12 14:31:24 -070012#include <base/logging.h>
13#include <curl/curl.h>
14#include <metrics/metrics_library.h>
15#include <openssl/evp.h>
16#include <openssl/ssl.h>
17
18#include "update_engine/prefs_interface.h"
19#include "update_engine/utils.h"
20
21using std::string;
22
23namespace chromeos_update_engine {
24
25namespace {
26// This should be in the same order of CertificateChecker::ServerToCheck, with
27// the exception of kNone.
28static const char* kReportToSendKey[2] =
29 {kPrefsCertificateReportToSendUpdate,
30 kPrefsCertificateReportToSendDownload};
31} // namespace {}
32
33bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
34 int* out_depth,
35 unsigned int* out_digest_length,
36 unsigned char* out_digest) const {
37 TEST_AND_RETURN_FALSE(out_digest);
38 X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
39 TEST_AND_RETURN_FALSE(certificate);
40 int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
41 if (out_depth)
42 *out_depth = depth;
43
44 unsigned int len;
45 const EVP_MD* digest_function = EVP_sha256();
46 bool success = X509_digest(certificate, digest_function, out_digest, &len);
47
48 if (success && out_digest_length)
49 *out_digest_length = len;
50 return success;
51}
52
53// static
Jay Srinivasan6f6ea002012-12-14 11:26:28 -080054SystemState* CertificateChecker::system_state_ = NULL;
Bruno Rocha7f9aea22011-09-12 14:31:24 -070055
56// static
57OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = NULL;
58
59// static
60CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
61 SSL_CTX* ssl_ctx,
62 void* ptr) {
63 // From here we set the SSL_CTX to another callback, from the openssl library,
64 // which will be called after each server certificate is validated. However,
65 // since openssl does not allow us to pass our own data pointer to the
66 // callback, the certificate check will have to be done statically. Since we
67 // need to know which update server we are using in order to check the
68 // certificate, we hardcode Chrome OS's two known update servers here, and
69 // define a different static callback for each. Since this code should only
70 // run in official builds, this should not be a problem. However, if an update
71 // server different from the ones listed here is used, the check will not
72 // take place.
73 ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
74
75 // We check which server to check and set the appropriate static callback.
76 if (*server_to_check == kUpdate)
77 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
78 if (*server_to_check == kDownload)
79 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
80
81 return CURLE_OK;
82}
83
84// static
85int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
86 X509_STORE_CTX* x509_ctx) {
87 return CertificateChecker::CheckCertificateChange(
88 kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
89}
90
91// static
92int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
93 X509_STORE_CTX* x509_ctx) {
94 return CertificateChecker::CheckCertificateChange(
95 kDownload, preverify_ok, x509_ctx) ? 1 : 0;
96}
97
98// static
99bool CertificateChecker::CheckCertificateChange(
100 ServerToCheck server_to_check, int preverify_ok,
101 X509_STORE_CTX* x509_ctx) {
102 static const char kUMAActionCertChanged[] =
103 "Updater.ServerCertificateChanged";
104 static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800105 TEST_AND_RETURN_FALSE(system_state_ != NULL);
106 TEST_AND_RETURN_FALSE(system_state_->prefs() != NULL);
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700107 TEST_AND_RETURN_FALSE(server_to_check != kNone);
108
109 // If pre-verification failed, we are not interested in the current
110 // certificate. We store a report to UMA and just propagate the fail result.
111 if (!preverify_ok) {
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800112 LOG_IF(WARNING, !system_state_->prefs()->SetString(
113 kReportToSendKey[server_to_check], kUMAActionCertFailed))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700114 << "Failed to store UMA report on a failure to validate "
115 << "certificate from update server.";
116 return false;
117 }
118
119 int depth;
120 unsigned int digest_length;
121 unsigned char digest[EVP_MAX_MD_SIZE];
122
123 if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
124 &depth,
125 &digest_length,
126 digest)) {
127 LOG(WARNING) << "Failed to generate digest of X509 certificate "
128 << "from update server.";
129 return true;
130 }
131
132 // We convert the raw bytes of the digest to an hex string, for storage in
133 // prefs.
134 string digest_string = base::HexEncode(digest, digest_length);
135
136 string storage_key = StringPrintf("%s-%d-%d",
137 kPrefsUpdateServerCertificate,
138 server_to_check,
139 depth);
140 string stored_digest;
141 // If there's no stored certificate, we just store the current one and return.
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800142 if (!system_state_->prefs()->GetString(storage_key, &stored_digest)) {
143 LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
144 digest_string))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700145 << "Failed to store server certificate on storage key " << storage_key;
146 return true;
147 }
148
149 // Certificate changed, we store a report to UMA and store the most recent
150 // certificate.
151 if (stored_digest != digest_string) {
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800152 LOG_IF(WARNING, !system_state_->prefs()->SetString(
153 kReportToSendKey[server_to_check], kUMAActionCertChanged))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700154 << "Failed to store UMA report on a change on the "
155 << "certificate from update server.";
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800156 LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
157 digest_string))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700158 << "Failed to store server certificate on storage key " << storage_key;
159 }
160
161 // Since we don't perform actual SSL verification, we return success.
162 return true;
163}
164
165// static
166void CertificateChecker::FlushReport() {
167 // This check shouldn't be needed, but it is useful for testing.
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800168 TEST_AND_RETURN(system_state_);
169 TEST_AND_RETURN(system_state_->metrics_lib());
170 TEST_AND_RETURN(system_state_->prefs());
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700171
172 // We flush reports for both servers.
173 for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
174 string report_to_send;
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800175 if (system_state_->prefs()->GetString(kReportToSendKey[i], &report_to_send)
176 && !report_to_send.empty()) {
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700177 // There is a report to be sent. We send it and erase it.
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800178 LOG(INFO) << "Found report #" << i << ". Sending it";
179 LOG_IF(WARNING, !system_state_->metrics_lib()->SendUserActionToUMA(
180 report_to_send))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700181 << "Failed to send server certificate report to UMA: "
182 << report_to_send;
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800183 LOG_IF(WARNING, !system_state_->prefs()->Delete(kReportToSendKey[i]))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700184 << "Failed to erase server certificate report to be sent to UMA";
185 }
186 }
187}
188
189} // namespace chromeos_update_engine