-
Notifications
You must be signed in to change notification settings - Fork 1
/
certificate_checker.cc
203 lines (173 loc) · 7.42 KB
/
certificate_checker.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//
// 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