blob: 75bd6be79ae0e833d457cda808229ba52f0df95a [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
Jay Srinivasand29695d2013-04-08 15:08:05 -070018#include "update_engine/constants.h"
Bruno Rocha7f9aea22011-09-12 14:31:24 -070019#include "update_engine/prefs_interface.h"
20#include "update_engine/utils.h"
21
22using std::string;
23
24namespace chromeos_update_engine {
25
26namespace {
27// This should be in the same order of CertificateChecker::ServerToCheck, with
28// the exception of kNone.
29static const char* kReportToSendKey[2] =
30 {kPrefsCertificateReportToSendUpdate,
31 kPrefsCertificateReportToSendDownload};
32} // namespace {}
33
34bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
35 int* out_depth,
36 unsigned int* out_digest_length,
37 unsigned char* out_digest) const {
38 TEST_AND_RETURN_FALSE(out_digest);
39 X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
40 TEST_AND_RETURN_FALSE(certificate);
41 int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
42 if (out_depth)
43 *out_depth = depth;
44
45 unsigned int len;
46 const EVP_MD* digest_function = EVP_sha256();
47 bool success = X509_digest(certificate, digest_function, out_digest, &len);
48
49 if (success && out_digest_length)
50 *out_digest_length = len;
51 return success;
52}
53
54// static
Jay Srinivasan6f6ea002012-12-14 11:26:28 -080055SystemState* CertificateChecker::system_state_ = NULL;
Bruno Rocha7f9aea22011-09-12 14:31:24 -070056
57// static
58OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = NULL;
59
60// static
61CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
62 SSL_CTX* ssl_ctx,
63 void* ptr) {
64 // From here we set the SSL_CTX to another callback, from the openssl library,
65 // which will be called after each server certificate is validated. However,
66 // since openssl does not allow us to pass our own data pointer to the
67 // callback, the certificate check will have to be done statically. Since we
68 // need to know which update server we are using in order to check the
69 // certificate, we hardcode Chrome OS's two known update servers here, and
70 // define a different static callback for each. Since this code should only
71 // run in official builds, this should not be a problem. However, if an update
72 // server different from the ones listed here is used, the check will not
73 // take place.
74 ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
75
76 // We check which server to check and set the appropriate static callback.
77 if (*server_to_check == kUpdate)
78 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
79 if (*server_to_check == kDownload)
80 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
81
82 return CURLE_OK;
83}
84
85// static
86int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
87 X509_STORE_CTX* x509_ctx) {
88 return CertificateChecker::CheckCertificateChange(
89 kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
90}
91
92// static
93int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
94 X509_STORE_CTX* x509_ctx) {
95 return CertificateChecker::CheckCertificateChange(
96 kDownload, preverify_ok, x509_ctx) ? 1 : 0;
97}
98
99// static
100bool CertificateChecker::CheckCertificateChange(
101 ServerToCheck server_to_check, int preverify_ok,
102 X509_STORE_CTX* x509_ctx) {
103 static const char kUMAActionCertChanged[] =
104 "Updater.ServerCertificateChanged";
105 static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800106 TEST_AND_RETURN_FALSE(system_state_ != NULL);
107 TEST_AND_RETURN_FALSE(system_state_->prefs() != NULL);
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700108 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) {
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800113 LOG_IF(WARNING, !system_state_->prefs()->SetString(
114 kReportToSendKey[server_to_check], kUMAActionCertFailed))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700115 << "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.
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800143 if (!system_state_->prefs()->GetString(storage_key, &stored_digest)) {
144 LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
145 digest_string))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700146 << "Failed to store server certificate on storage key " << storage_key;
147 return true;
148 }
149
150 // Certificate changed, we store a report to UMA and store the most recent
151 // certificate.
152 if (stored_digest != digest_string) {
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800153 LOG_IF(WARNING, !system_state_->prefs()->SetString(
154 kReportToSendKey[server_to_check], kUMAActionCertChanged))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700155 << "Failed to store UMA report on a change on the "
156 << "certificate from update server.";
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800157 LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
158 digest_string))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700159 << "Failed to store server certificate on storage key " << storage_key;
160 }
161
162 // Since we don't perform actual SSL verification, we return success.
163 return true;
164}
165
166// static
167void CertificateChecker::FlushReport() {
168 // This check shouldn't be needed, but it is useful for testing.
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800169 TEST_AND_RETURN(system_state_);
170 TEST_AND_RETURN(system_state_->metrics_lib());
171 TEST_AND_RETURN(system_state_->prefs());
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700172
173 // We flush reports for both servers.
174 for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
175 string report_to_send;
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800176 if (system_state_->prefs()->GetString(kReportToSendKey[i], &report_to_send)
177 && !report_to_send.empty()) {
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700178 // There is a report to be sent. We send it and erase it.
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800179 LOG(INFO) << "Found report #" << i << ". Sending it";
180 LOG_IF(WARNING, !system_state_->metrics_lib()->SendUserActionToUMA(
181 report_to_send))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700182 << "Failed to send server certificate report to UMA: "
183 << report_to_send;
Jay Srinivasan6f6ea002012-12-14 11:26:28 -0800184 LOG_IF(WARNING, !system_state_->prefs()->Delete(kReportToSendKey[i]))
Bruno Rocha7f9aea22011-09-12 14:31:24 -0700185 << "Failed to erase server certificate report to be sent to UMA";
186 }
187 }
188}
189
190} // namespace chromeos_update_engine