blob: 1f19baff85ac09977228b257c441f95a97f8158c [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
54MetricsLibraryInterface* CertificateChecker::metrics_lib_ = NULL;
55
56// static
57PrefsInterface* CertificateChecker::prefs_ = NULL;
58
59// static
60OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = NULL;
61
62// static
63CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
64 SSL_CTX* ssl_ctx,
65 void* ptr) {
66 // From here we set the SSL_CTX to another callback, from the openssl library,
67 // which will be called after each server certificate is validated. However,
68 // since openssl does not allow us to pass our own data pointer to the
69 // callback, the certificate check will have to be done statically. Since we
70 // need to know which update server we are using in order to check the
71 // certificate, we hardcode Chrome OS's two known update servers here, and
72 // define a different static callback for each. Since this code should only
73 // run in official builds, this should not be a problem. However, if an update
74 // server different from the ones listed here is used, the check will not
75 // take place.
76 ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
77
78 // We check which server to check and set the appropriate static callback.
79 if (*server_to_check == kUpdate)
80 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
81 if (*server_to_check == kDownload)
82 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
83
84 return CURLE_OK;
85}
86
87// static
88int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
89 X509_STORE_CTX* x509_ctx) {
90 return CertificateChecker::CheckCertificateChange(
91 kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
92}
93
94// static
95int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
96 X509_STORE_CTX* x509_ctx) {
97 return CertificateChecker::CheckCertificateChange(
98 kDownload, preverify_ok, x509_ctx) ? 1 : 0;
99}
100
101// static
102bool CertificateChecker::CheckCertificateChange(
103 ServerToCheck server_to_check, int preverify_ok,
104 X509_STORE_CTX* x509_ctx) {
105 static const char kUMAActionCertChanged[] =
106 "Updater.ServerCertificateChanged";
107 static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
108 TEST_AND_RETURN_FALSE(server_to_check != kNone);
109
110 // If pre-verification failed, we are not interested in the current
111 // certificate. We store a report to UMA and just propagate the fail result.
112 if (!preverify_ok) {
113 LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[server_to_check],
114 kUMAActionCertFailed))
115 << "Failed to store UMA report on a failure to validate "
116 << "certificate from update server.";
117 return false;
118 }
119
120 int depth;
121 unsigned int digest_length;
122 unsigned char digest[EVP_MAX_MD_SIZE];
123
124 if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
125 &depth,
126 &digest_length,
127 digest)) {
128 LOG(WARNING) << "Failed to generate digest of X509 certificate "
129 << "from update server.";
130 return true;
131 }
132
133 // We convert the raw bytes of the digest to an hex string, for storage in
134 // prefs.
135 string digest_string = base::HexEncode(digest, digest_length);
136
137 string storage_key = StringPrintf("%s-%d-%d",
138 kPrefsUpdateServerCertificate,
139 server_to_check,
140 depth);
141 string stored_digest;
142 // If there's no stored certificate, we just store the current one and return.
143 if (!prefs_->GetString(storage_key, &stored_digest)) {
144 LOG_IF(WARNING, !prefs_->SetString(storage_key, digest_string))
145 << "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) {
152 LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[server_to_check],
153 kUMAActionCertChanged))
154 << "Failed to store UMA report on a change on the "
155 << "certificate from update server.";
156 LOG_IF(WARNING, !prefs_->SetString(storage_key, digest_string))
157 << "Failed to store server certificate on storage key " << storage_key;
158 }
159
160 // Since we don't perform actual SSL verification, we return success.
161 return true;
162}
163
164// static
165void CertificateChecker::FlushReport() {
166 // This check shouldn't be needed, but it is useful for testing.
167 TEST_AND_RETURN(metrics_lib_ && prefs_);
168
169 // We flush reports for both servers.
170 for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
171 string report_to_send;
172 if (prefs_->GetString(kReportToSendKey[i], &report_to_send) &&
173 !report_to_send.empty()) {
174 // There is a report to be sent. We send it and erase it.
175 LOG_IF(WARNING, !metrics_lib_->SendUserActionToUMA(report_to_send))
176 << "Failed to send server certificate report to UMA: "
177 << report_to_send;
178 // Since prefs doesn't provide deletion, we just set it as an empty
179 // string.
180 LOG_IF(WARNING, !prefs_->SetString(kReportToSendKey[i], ""))
181 << "Failed to erase server certificate report to be sent to UMA";
182 }
183 }
184}
185
186} // namespace chromeos_update_engine