[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 1 | // Copyright 2014 The Chromium 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 "remoting/host/token_validator_base.h" |
| 6 | |
avi | c5960f3 | 2015-12-22 22:49:48 | [diff] [blame] | 7 | #include <stddef.h> |
| 8 | |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 9 | #include "base/base64.h" |
| 10 | #include "base/bind.h" |
| 11 | #include "base/callback.h" |
| 12 | #include "base/json/json_reader.h" |
| 13 | #include "base/logging.h" |
| 14 | #include "base/memory/weak_ptr.h" |
| 15 | #include "base/single_thread_task_runner.h" |
| 16 | #include "base/strings/string_util.h" |
| 17 | #include "base/values.h" |
avi | c5960f3 | 2015-12-22 22:49:48 | [diff] [blame] | 18 | #include "build/build_config.h" |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 19 | #include "net/base/escape.h" |
| 20 | #include "net/base/io_buffer.h" |
| 21 | #include "net/base/request_priority.h" |
| 22 | #include "net/base/upload_bytes_element_reader.h" |
| 23 | #include "net/base/upload_data_stream.h" |
| 24 | #include "net/ssl/client_cert_store.h" |
davidben | 71f35ff | 2015-04-17 20:54:48 | [diff] [blame] | 25 | #if defined(USE_NSS_CERTS) |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 26 | #include "net/ssl/client_cert_store_nss.h" |
| 27 | #elif defined(OS_WIN) |
| 28 | #include "net/ssl/client_cert_store_win.h" |
| 29 | #elif defined(OS_MACOSX) |
| 30 | #include "net/ssl/client_cert_store_mac.h" |
| 31 | #endif |
| 32 | #include "net/ssl/ssl_cert_request_info.h" |
svaldez | 7872fd0 | 2015-11-19 21:10:54 | [diff] [blame] | 33 | #include "net/ssl/ssl_private_key.h" |
lambroslambrou | f43816ad | 2015-12-16 03:50:14 | [diff] [blame] | 34 | #include "net/url_request/redirect_info.h" |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 35 | #include "net/url_request/url_request.h" |
| 36 | #include "net/url_request/url_request_context.h" |
| 37 | #include "net/url_request/url_request_status.h" |
joedow | d455f537 | 2017-01-26 00:26:40 | [diff] [blame] | 38 | #include "remoting/base/logging.h" |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 39 | #include "url/gurl.h" |
| 40 | |
| 41 | namespace { |
| 42 | |
Joe Downing | 78bfcca | 2018-11-07 23:43:05 | [diff] [blame] | 43 | constexpr int kBufferSize = 4096; |
| 44 | constexpr char kCertIssuerWildCard[] = "*"; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 45 | |
Joe Downing | 78bfcca | 2018-11-07 23:43:05 | [diff] [blame] | 46 | // Returns a value from the issuer field for certificate selection, in order of |
| 47 | // preference. If the O or OU entries are populated with multiple values, we |
| 48 | // choose the first one. This function should not be used for validation, only |
| 49 | // for logging or determining which certificate to select for validation. |
| 50 | std::string GetPreferredIssuerFieldValue(const net::X509Certificate* cert) { |
| 51 | if (!cert->issuer().common_name.empty()) |
| 52 | return cert->issuer().common_name; |
| 53 | if (!cert->issuer().organization_names.empty() && |
| 54 | !cert->issuer().organization_names[0].empty()) |
| 55 | return cert->issuer().organization_names[0]; |
| 56 | if (!cert->issuer().organization_unit_names.empty() && |
| 57 | !cert->issuer().organization_unit_names[0].empty()) |
| 58 | return cert->issuer().organization_unit_names[0]; |
| 59 | |
| 60 | return std::string(); |
| 61 | } |
| 62 | |
| 63 | // The certificate is valid if both are true: |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 64 | // * The certificate issuer matches exactly |issuer| or the |issuer| is a |
Joe Downing | 78bfcca | 2018-11-07 23:43:05 | [diff] [blame] | 65 | // wildcard. |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 66 | // * |now| is within [valid_start, valid_expiry]. |
| 67 | bool IsCertificateValid(const std::string& issuer, |
| 68 | const base::Time& now, |
mattm | 436ccfe | 2017-06-19 20:24:08 | [diff] [blame] | 69 | const net::X509Certificate* cert) { |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 70 | return (issuer == kCertIssuerWildCard || |
Joe Downing | 78bfcca | 2018-11-07 23:43:05 | [diff] [blame] | 71 | issuer == GetPreferredIssuerFieldValue(cert)) && |
| 72 | cert->valid_start() <= now && cert->valid_expiry() > now; |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 73 | } |
| 74 | |
| 75 | // Returns true if the certificate |c1| is worse than |c2|. |
| 76 | // |
| 77 | // Criteria: |
| 78 | // 1. An invalid certificate is always worse than a valid certificate. |
| 79 | // 2. Invalid certificates are equally bad, in which case false will be |
| 80 | // returned. |
| 81 | // 3. A certificate with earlier |valid_start| time is worse. |
| 82 | // 4. When |valid_start| are the same, the certificate with earlier |
| 83 | // |valid_expiry| is worse. |
| 84 | bool WorseThan(const std::string& issuer, |
| 85 | const base::Time& now, |
Jeremy Roman | 3775f2c6 | 2017-07-17 20:59:58 | [diff] [blame] | 86 | const net::X509Certificate* c1, |
| 87 | const net::X509Certificate* c2) { |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 88 | if (!IsCertificateValid(issuer, now, c2)) |
| 89 | return false; |
| 90 | |
| 91 | if (!IsCertificateValid(issuer, now, c1)) |
| 92 | return true; |
| 93 | |
| 94 | if (c1->valid_start() != c2->valid_start()) |
| 95 | return c1->valid_start() < c2->valid_start(); |
| 96 | |
| 97 | return c1->valid_expiry() < c2->valid_expiry(); |
| 98 | } |
| 99 | |
David Benjamin | ac83aab | 2019-05-29 22:14:34 | [diff] [blame] | 100 | #if defined(OS_WIN) |
| 101 | HCERTSTORE OpenLocalMachineCertStore() { |
| 102 | return ::CertOpenStore( |
| 103 | CERT_STORE_PROV_SYSTEM, 0, NULL, |
| 104 | CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG, L"MY"); |
| 105 | } |
| 106 | #endif |
| 107 | |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 108 | } // namespace |
| 109 | |
| 110 | namespace remoting { |
| 111 | |
| 112 | TokenValidatorBase::TokenValidatorBase( |
| 113 | const ThirdPartyAuthConfig& third_party_auth_config, |
| 114 | const std::string& token_scope, |
| 115 | scoped_refptr<net::URLRequestContextGetter> request_context_getter) |
| 116 | : third_party_auth_config_(third_party_auth_config), |
| 117 | token_scope_(token_scope), |
| 118 | request_context_getter_(request_context_getter), |
Jeremy Roman | 7c5cfabd | 2019-08-12 15:45:27 | [diff] [blame] | 119 | buffer_(base::MakeRefCounted<net::IOBuffer>(kBufferSize)) { |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 120 | DCHECK(third_party_auth_config_.token_url.is_valid()); |
| 121 | DCHECK(third_party_auth_config_.token_validation_url.is_valid()); |
| 122 | } |
| 123 | |
Chris Watkins | 6fe52aa | 2017-11-28 03:24:05 | [diff] [blame] | 124 | TokenValidatorBase::~TokenValidatorBase() = default; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 125 | |
| 126 | // TokenValidator interface. |
| 127 | void TokenValidatorBase::ValidateThirdPartyToken( |
| 128 | const std::string& token, |
| 129 | const base::Callback<void( |
| 130 | const std::string& shared_secret)>& on_token_validated) { |
| 131 | DCHECK(!request_); |
| 132 | DCHECK(!on_token_validated.is_null()); |
| 133 | |
| 134 | on_token_validated_ = on_token_validated; |
lambroslambrou | f43816ad | 2015-12-16 03:50:14 | [diff] [blame] | 135 | token_ = token; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 136 | StartValidateRequest(token); |
| 137 | } |
| 138 | |
| 139 | const GURL& TokenValidatorBase::token_url() const { |
| 140 | return third_party_auth_config_.token_url; |
| 141 | } |
| 142 | |
| 143 | const std::string& TokenValidatorBase::token_scope() const { |
| 144 | return token_scope_; |
| 145 | } |
| 146 | |
| 147 | // URLFetcherDelegate interface. |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 148 | void TokenValidatorBase::OnResponseStarted(net::URLRequest* source, |
| 149 | int net_result) { |
| 150 | DCHECK_NE(net_result, net::ERR_IO_PENDING); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 151 | DCHECK_EQ(request_.get(), source); |
| 152 | |
mmenke | 94f1bd9 | 2016-12-07 21:13:05 | [diff] [blame] | 153 | if (net_result != net::OK) { |
| 154 | // Process all network errors in the same manner as read errors. |
| 155 | OnReadCompleted(request_.get(), net_result); |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 156 | return; |
mmenke | 94f1bd9 | 2016-12-07 21:13:05 | [diff] [blame] | 157 | } |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 158 | |
| 159 | int bytes_read = request_->Read(buffer_.get(), kBufferSize); |
mmenke | 94f1bd9 | 2016-12-07 21:13:05 | [diff] [blame] | 160 | if (bytes_read != net::ERR_IO_PENDING) |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 161 | OnReadCompleted(request_.get(), bytes_read); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 162 | } |
| 163 | |
| 164 | void TokenValidatorBase::OnReadCompleted(net::URLRequest* source, |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 165 | int net_result) { |
| 166 | DCHECK_NE(net_result, net::ERR_IO_PENDING); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 167 | DCHECK_EQ(request_.get(), source); |
| 168 | |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 169 | while (net_result > 0) { |
| 170 | data_.append(buffer_->data(), net_result); |
| 171 | net_result = request_->Read(buffer_.get(), kBufferSize); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 172 | } |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 173 | |
| 174 | if (net_result == net::ERR_IO_PENDING) |
| 175 | return; |
| 176 | |
| 177 | retrying_request_ = false; |
| 178 | std::string shared_token = ProcessResponse(net_result); |
| 179 | request_.reset(); |
| 180 | on_token_validated_.Run(shared_token); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 181 | } |
| 182 | |
lambroslambrou | f43816ad | 2015-12-16 03:50:14 | [diff] [blame] | 183 | void TokenValidatorBase::OnReceivedRedirect( |
| 184 | net::URLRequest* request, |
| 185 | const net::RedirectInfo& redirect_info, |
| 186 | bool* defer_redirect) { |
| 187 | if (!retrying_request_ && redirect_info.new_method == "GET" && |
| 188 | redirect_info.new_url == third_party_auth_config_.token_validation_url) { |
| 189 | // A sequence of redirects caused the original POST request to become a GET |
| 190 | // request for this URL. Cancel the request, and re-submit the POST request. |
| 191 | // The chain of redirects are expected to set some cookies that will |
| 192 | // ensure the new POST request succeeds. |
| 193 | retrying_request_ = true; |
| 194 | DCHECK(data_.empty()); |
| 195 | StartValidateRequest(token_); |
| 196 | } |
| 197 | } |
| 198 | |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 199 | void TokenValidatorBase::OnCertificateRequested( |
| 200 | net::URLRequest* source, |
| 201 | net::SSLCertRequestInfo* cert_request_info) { |
| 202 | DCHECK_EQ(request_.get(), source); |
| 203 | |
| 204 | net::ClientCertStore* client_cert_store; |
davidben | 71f35ff | 2015-04-17 20:54:48 | [diff] [blame] | 205 | #if defined(USE_NSS_CERTS) |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 206 | client_cert_store = new net::ClientCertStoreNSS( |
| 207 | net::ClientCertStoreNSS::PasswordDelegateFactory()); |
| 208 | #elif defined(OS_WIN) |
weitaosu | b1d542d | 2015-11-07 02:11:58 | [diff] [blame] | 209 | // The network process is running as "Local Service" whose "Current User" |
| 210 | // cert store doesn't contain any certificates. Use the "Local Machine" |
| 211 | // store instead. |
| 212 | // The ACL on the private key of the machine certificate in the "Local |
| 213 | // Machine" cert store needs to allow access by "Local Service". |
David Benjamin | ac83aab | 2019-05-29 22:14:34 | [diff] [blame] | 214 | client_cert_store = new net::ClientCertStoreWin( |
| 215 | base::BindRepeating(&OpenLocalMachineCertStore)); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 216 | #elif defined(OS_MACOSX) |
| 217 | client_cert_store = new net::ClientCertStoreMac(); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 218 | #else |
svaldez | 2135be5 | 2016-04-20 16:34:53 | [diff] [blame] | 219 | // OpenSSL does not use the ClientCertStore infrastructure. |
| 220 | client_cert_store = nullptr; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 221 | #endif |
mattm | 7ed243f | 2017-04-28 05:28:58 | [diff] [blame] | 222 | // The callback is uncancellable, and GetClientCert requires |
| 223 | // client_cert_store to stay alive until the callback is called. So we must |
| 224 | // give it a WeakPtr for |this|, and ownership of the other parameters. |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 225 | client_cert_store->GetClientCerts( |
mattm | 7ed243f | 2017-04-28 05:28:58 | [diff] [blame] | 226 | *cert_request_info, |
David Benjamin | 0cda204 | 2019-04-08 23:00:58 | [diff] [blame] | 227 | base::BindOnce(&TokenValidatorBase::OnCertificatesSelected, |
| 228 | weak_factory_.GetWeakPtr(), |
| 229 | base::Owned(client_cert_store))); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 230 | } |
| 231 | |
| 232 | void TokenValidatorBase::OnCertificatesSelected( |
mattm | 7ed243f | 2017-04-28 05:28:58 | [diff] [blame] | 233 | net::ClientCertStore* unused, |
mattm | 436ccfe | 2017-06-19 20:24:08 | [diff] [blame] | 234 | net::ClientCertIdentityList selected_certs) { |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 235 | const std::string& issuer = |
| 236 | third_party_auth_config_.token_validation_cert_issuer; |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 237 | |
| 238 | base::Time now = base::Time::Now(); |
| 239 | |
Jeremy Roman | 3775f2c6 | 2017-07-17 20:59:58 | [diff] [blame] | 240 | auto best_match_position = std::max_element( |
| 241 | selected_certs.begin(), selected_certs.end(), |
| 242 | [&issuer, now](const std::unique_ptr<net::ClientCertIdentity>& i1, |
| 243 | const std::unique_ptr<net::ClientCertIdentity>& i2) { |
| 244 | return WorseThan(issuer, now, i1->certificate(), i2->certificate()); |
| 245 | }); |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 246 | |
mattm | 7ed243f | 2017-04-28 05:28:58 | [diff] [blame] | 247 | if (best_match_position == selected_certs.end() || |
mattm | 436ccfe | 2017-06-19 20:24:08 | [diff] [blame] | 248 | !IsCertificateValid(issuer, now, (*best_match_position)->certificate())) { |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 249 | ContinueWithCertificate(nullptr, nullptr); |
| 250 | } else { |
mattm | 436ccfe | 2017-06-19 20:24:08 | [diff] [blame] | 251 | scoped_refptr<net::X509Certificate> cert = |
| 252 | (*best_match_position)->certificate(); |
| 253 | net::ClientCertIdentity::SelfOwningAcquirePrivateKey( |
| 254 | std::move(*best_match_position), |
David Benjamin | 0cda204 | 2019-04-08 23:00:58 | [diff] [blame] | 255 | base::BindOnce(&TokenValidatorBase::ContinueWithCertificate, |
| 256 | weak_factory_.GetWeakPtr(), std::move(cert))); |
yuweih | e4807bad | 2016-09-30 00:56:54 | [diff] [blame] | 257 | } |
| 258 | } |
| 259 | |
| 260 | void TokenValidatorBase::ContinueWithCertificate( |
mattm | 436ccfe | 2017-06-19 20:24:08 | [diff] [blame] | 261 | scoped_refptr<net::X509Certificate> client_cert, |
| 262 | scoped_refptr<net::SSLPrivateKey> client_private_key) { |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 263 | if (request_) { |
joedow | d455f537 | 2017-01-26 00:26:40 | [diff] [blame] | 264 | if (client_cert) { |
Joe Downing | 78bfcca | 2018-11-07 23:43:05 | [diff] [blame] | 265 | HOST_LOG << "Using client certificate issued by: '" |
| 266 | << GetPreferredIssuerFieldValue(client_cert.get()) |
| 267 | << "' with start date: '" << client_cert->valid_start() |
| 268 | << "' and expiry date: '" << client_cert->valid_expiry() << "'"; |
joedow | d455f537 | 2017-01-26 00:26:40 | [diff] [blame] | 269 | } |
| 270 | |
mattm | 436ccfe | 2017-06-19 20:24:08 | [diff] [blame] | 271 | request_->ContinueWithCertificate(std::move(client_cert), |
| 272 | std::move(client_private_key)); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 273 | } |
| 274 | } |
| 275 | |
| 276 | bool TokenValidatorBase::IsValidScope(const std::string& token_scope) { |
| 277 | // TODO(rmsousa): Deal with reordering/subsets/supersets/aliases/etc. |
| 278 | return token_scope == token_scope_; |
| 279 | } |
| 280 | |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 281 | std::string TokenValidatorBase::ProcessResponse(int net_result) { |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 282 | // Verify that we got a successful response. |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 283 | if (net_result != net::OK) { |
| 284 | LOG(ERROR) << "Error validating token, err=" << net_result; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 285 | return std::string(); |
| 286 | } |
| 287 | |
| 288 | int response = request_->GetResponseCode(); |
| 289 | if (response != 200) { |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 290 | LOG(ERROR) << "Error " << response << " validating token: '" << data_ |
| 291 | << "'"; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 292 | return std::string(); |
| 293 | } |
| 294 | |
| 295 | // Decode the JSON data from the response. |
Lei Zhang | 48a8433 | 2019-02-16 02:35:31 | [diff] [blame] | 296 | std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(data_); |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 297 | base::DictionaryValue* dict; |
sergeyu | 2a138b4 | 2014-10-01 02:00:40 | [diff] [blame] | 298 | if (!value || !value->GetAsDictionary(&dict)) { |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 299 | LOG(ERROR) << "Invalid token validation response: '" << data_ << "'"; |
| 300 | return std::string(); |
| 301 | } |
| 302 | |
| 303 | std::string token_scope; |
| 304 | dict->GetStringWithoutPathExpansion("scope", &token_scope); |
| 305 | if (!IsValidScope(token_scope)) { |
maksim.sisov | c023fa2 | 2016-09-22 04:16:32 | [diff] [blame] | 306 | LOG(ERROR) << "Invalid scope: '" << token_scope << "', expected: '" |
| 307 | << token_scope_ << "'."; |
[email protected] | d95ee26 | 2014-02-26 06:30:31 | [diff] [blame] | 308 | return std::string(); |
| 309 | } |
| 310 | |
| 311 | std::string shared_secret; |
| 312 | // Everything is valid, so return the shared secret to the caller. |
| 313 | dict->GetStringWithoutPathExpansion("access_token", &shared_secret); |
| 314 | return shared_secret; |
| 315 | } |
| 316 | |
| 317 | } // namespace remoting |