blob: 579038e4ffcc164fddcb48e66181b0a06345c694 [file] [log] [blame]
Bruno Rocha7f9aea22011-09-12 14:31:24 -07001// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2// 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>
11#include <base/logging.h>
12#include <curl/curl.h>
13#include <metrics/metrics_library.h>
14#include <openssl/evp.h>
15#include <openssl/ssl.h>
16
17#include "update_engine/prefs_interface.h"
18#include "update_engine/utils.h"
19
20using std::string;
21
22namespace chromeos_update_engine {
23
24namespace {
25// This should be in the same order of CertificateChecker::ServerToCheck, with
26// the exception of kNone.
27static const char* kReportToSendKey[2] =
28 {kPrefsCertificateReportToSendUpdate,
29 kPrefsCertificateReportToSendDownload};
30} // namespace {}
31
32bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
33 int* out_depth,
34 unsigned int* out_digest_length,
35 unsigned char* out_digest) const {
36 TEST_AND_RETURN_FALSE(out_digest);
37 X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
38 TEST_AND_RETURN_FALSE(certificate);
39 int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
40 if (out_depth)
41 *out_depth = depth;
42
43 unsigned int len;
44 const EVP_MD* digest_function = EVP_sha256();
45 bool success = X509_digest(certificate, digest_function, out_digest, &len);
46
47 if (success && out_digest_length)
48 *out_digest_length = len;
49 return success;
50}
51
52// static
53MetricsLibraryInterface* CertificateChecker::metrics_lib_ = NULL;
54
55// static
56PrefsInterface* CertificateChecker::prefs_ = NULL;
57
58// static
59OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = NULL;
60
61// static
62CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
63 SSL_CTX* ssl_ctx,
64 void* ptr) {
65 // From here we set the SSL_CTX to another callback, from the openssl library,
66 // which will be called after each server certificate is validated. However,
67 // since openssl does not allow us to pass our own data pointer to the
68 // callback, the certificate check will have to be done statically. Since we
69 // need to know which update server we are using in order to check the
70 // certificate, we hardcode Chrome OS's two known update servers here, and
71 // define a different static callback for each. Since this code should only
72 // run in official builds, this should not be a problem. However, if an update
73 // server different from the ones listed here is used, the check will not
74 // take place.
75 ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
76
77 // We check which server to check and set the appropriate static callback.
78 if (*server_to_check == kUpdate)
79 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
80 if (*server_to_check == kDownload)
81 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
82
83 return CURLE_OK;
84}
85
86// static
87int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
88 X509_STORE_CTX* x509_ctx) {
89 return CertificateChecker::CheckCertificateChange(
90 kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
91}
92
93// static
94int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
95 X509_STORE_CTX* x509_ctx) {
96 return CertificateChecker::CheckCertificateChange(
97 kDownload, preverify_ok, x509_ctx) ? 1 : 0;
98}
99
100// static
101bool CertificateChecker::CheckCertificateChange(
102 ServerToCheck server_to_check, int preverify_ok,
103 X509_STORE_CTX* x509_ctx) {
104 static const char kUMAActionCertChanged[] =
105 "Updater.ServerCertificateChanged";
106 static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
107 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) {
112 LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[server_to_check],
113 kUMAActionCertFailed))
114 << "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.
142 if (!prefs_->GetString(storage_key, &stored_digest)) {
143 LOG_IF(WARNING, !prefs_->SetString(storage_key, digest_string))
144 << "Failed to store server certificate on storage key " << storage_key;
145 return true;
146 }
147
148 // Certificate changed, we store a report to UMA and store the most recent
149 // certificate.
150 if (stored_digest != digest_string) {
151 LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[server_to_check],
152 kUMAActionCertChanged))
153 << "Failed to store UMA report on a change on the "
154 << "certificate from update server.";
155 LOG_IF(WARNING, !prefs_->SetString(storage_key, digest_string))
156 << "Failed to store server certificate on storage key " << storage_key;
157 }
158
159 // Since we don't perform actual SSL verification, we return success.
160 return true;
161}
162
163// static
164void CertificateChecker::FlushReport() {
165 // This check shouldn't be needed, but it is useful for testing.
166 TEST_AND_RETURN(metrics_lib_ && prefs_);
167
168 // We flush reports for both servers.
169 for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
170 string report_to_send;
171 if (prefs_->GetString(kReportToSendKey[i], &report_to_send) &&
172 !report_to_send.empty()) {
173 // There is a report to be sent. We send it and erase it.
174 LOG_IF(WARNING, !metrics_lib_->SendUserActionToUMA(report_to_send))
175 << "Failed to send server certificate report to UMA: "
176 << report_to_send;
177 // Since prefs doesn't provide deletion, we just set it as an empty
178 // string.
179 LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[i], ""))
180 << "Failed to erase server certificate report to be sent to UMA";
181 }
182 }
183}
184
185} // namespace chromeos_update_engine