Revert of Roll src/third_party/WebKit e0eac24:489c548 (svn 193311:193320) (patchset...
[chromium-blink-merge.git] / chromeos / network / onc / onc_certificate_importer_impl.cc
blob7f57161a79451de87dc6ca20028cc1201d458d74
1 // Copyright 2013 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.
5 #include "chromeos/network/onc/onc_certificate_importer_impl.h"
7 #include <cert.h>
8 #include <keyhi.h>
9 #include <pk11pub.h>
11 #include "base/base64.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/callback.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/sequenced_task_runner.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/values.h"
21 #include "chromeos/network/network_event_log.h"
22 #include "chromeos/network/onc/onc_utils.h"
23 #include "crypto/scoped_nss_types.h"
24 #include "net/base/crypto_module.h"
25 #include "net/base/net_errors.h"
26 #include "net/cert/nss_cert_database.h"
27 #include "net/cert/x509_certificate.h"
29 namespace chromeos {
30 namespace onc {
32 namespace {
34 void CallBackOnOriginLoop(
35 const scoped_refptr<base::SingleThreadTaskRunner>& origin_loop,
36 const CertificateImporter::DoneCallback& callback,
37 bool success,
38 const net::CertificateList& onc_trusted_certificates) {
39 origin_loop->PostTask(
40 FROM_HERE, base::Bind(callback, success, onc_trusted_certificates));
43 } // namespace
45 CertificateImporterImpl::CertificateImporterImpl(
46 const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
47 net::NSSCertDatabase* target_nssdb)
48 : io_task_runner_(io_task_runner),
49 target_nssdb_(target_nssdb),
50 weak_factory_(this) {
51 CHECK(target_nssdb);
54 CertificateImporterImpl::~CertificateImporterImpl() {
57 void CertificateImporterImpl::ImportCertificates(
58 const base::ListValue& certificates,
59 ::onc::ONCSource source,
60 const DoneCallback& done_callback) {
61 VLOG(2) << "ONC file has " << certificates.GetSize() << " certificates";
62 // |done_callback| must only be called as long as |this| still exists.
63 // Thereforce, call back to |this|. This check of |this| must happen last and
64 // on the origin thread.
65 DoneCallback callback_to_this =
66 base::Bind(&CertificateImporterImpl::RunDoneCallback,
67 weak_factory_.GetWeakPtr(),
68 done_callback);
70 // |done_callback| must be called on the origin thread.
71 DoneCallback callback_on_origin_loop =
72 base::Bind(&CallBackOnOriginLoop,
73 base::ThreadTaskRunnerHandle::Get(),
74 callback_to_this);
76 // This is the actual function that imports the certificates.
77 base::Closure import_certs_callback =
78 base::Bind(&ParseAndStoreCertificates,
79 source,
80 callback_on_origin_loop,
81 base::Owned(certificates.DeepCopy()),
82 target_nssdb_);
84 // The NSSCertDatabase must be accessed on |io_task_runner_|
85 io_task_runner_->PostTask(FROM_HERE, import_certs_callback);
88 // static
89 void CertificateImporterImpl::ParseAndStoreCertificates(
90 ::onc::ONCSource source,
91 const DoneCallback& done_callback,
92 base::ListValue* certificates,
93 net::NSSCertDatabase* nssdb) {
94 // Web trust is only granted to certificates imported by the user.
95 bool allow_trust_imports = source == ::onc::ONC_SOURCE_USER_IMPORT;
96 net::CertificateList onc_trusted_certificates;
97 bool success = true;
98 for (size_t i = 0; i < certificates->GetSize(); ++i) {
99 const base::DictionaryValue* certificate = NULL;
100 certificates->GetDictionary(i, &certificate);
101 DCHECK(certificate != NULL);
103 VLOG(2) << "Parsing certificate at index " << i << ": " << *certificate;
105 if (!ParseAndStoreCertificate(allow_trust_imports,
106 *certificate,
107 nssdb,
108 &onc_trusted_certificates)) {
109 success = false;
110 LOG(ERROR) << "Cannot parse certificate at index " << i;
111 } else {
112 VLOG(2) << "Successfully imported certificate at index " << i;
116 done_callback.Run(success, onc_trusted_certificates);
119 // static
120 void CertificateImporterImpl::ListCertsWithNickname(
121 const std::string& label,
122 net::CertificateList* result,
123 net::NSSCertDatabase* target_nssdb) {
124 net::CertificateList all_certs;
125 // TODO(tbarzic): Use async |ListCerts|.
126 target_nssdb->ListCertsSync(&all_certs);
127 result->clear();
128 for (net::CertificateList::iterator iter = all_certs.begin();
129 iter != all_certs.end(); ++iter) {
130 if (iter->get()->os_cert_handle()->nickname) {
131 // Separate the nickname stored in the certificate at the colon, since
132 // NSS likes to store it as token:nickname.
133 const char* delimiter =
134 ::strchr(iter->get()->os_cert_handle()->nickname, ':');
135 if (delimiter) {
136 ++delimiter; // move past the colon.
137 if (strcmp(delimiter, label.c_str()) == 0) {
138 result->push_back(*iter);
139 continue;
143 // Now we find the private key for this certificate and see if it has a
144 // nickname that matches. If there is a private key, and it matches,
145 // then this is a client cert that we are looking for.
146 SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
147 iter->get()->os_cert_handle()->slot,
148 iter->get()->os_cert_handle(),
149 NULL); // wincx
150 if (private_key) {
151 char* private_key_nickname = PK11_GetPrivateKeyNickname(private_key);
152 if (private_key_nickname && std::string(label) == private_key_nickname)
153 result->push_back(*iter);
154 PORT_Free(private_key_nickname);
155 SECKEY_DestroyPrivateKey(private_key);
160 // static
161 bool CertificateImporterImpl::DeleteCertAndKeyByNickname(
162 const std::string& label,
163 net::NSSCertDatabase* target_nssdb) {
164 net::CertificateList cert_list;
165 ListCertsWithNickname(label, &cert_list, target_nssdb);
166 bool result = true;
167 for (net::CertificateList::iterator iter = cert_list.begin();
168 iter != cert_list.end(); ++iter) {
169 // If we fail, we try and delete the rest still.
170 // TODO(gspencer): this isn't very "transactional". If we fail on some, but
171 // not all, then it's possible to leave things in a weird state.
172 // Luckily there should only be one cert with a particular
173 // label, and the cert not being found is one of the few reasons the
174 // delete could fail, but still... The other choice is to return
175 // failure immediately, but that doesn't seem to do what is intended.
176 if (!target_nssdb->DeleteCertAndKey(iter->get()))
177 result = false;
179 return result;
182 void CertificateImporterImpl::RunDoneCallback(
183 const CertificateImporter::DoneCallback& callback,
184 bool success,
185 const net::CertificateList& onc_trusted_certificates) {
186 if (!success)
187 NET_LOG_ERROR("ONC Certificate Import Error", "");
188 callback.Run(success, onc_trusted_certificates);
191 bool CertificateImporterImpl::ParseAndStoreCertificate(
192 bool allow_trust_imports,
193 const base::DictionaryValue& certificate,
194 net::NSSCertDatabase* nssdb,
195 net::CertificateList* onc_trusted_certificates) {
196 // Get out the attributes of the given certificate.
197 std::string guid;
198 certificate.GetStringWithoutPathExpansion(::onc::certificate::kGUID, &guid);
199 DCHECK(!guid.empty());
201 bool remove = false;
202 if (certificate.GetBooleanWithoutPathExpansion(::onc::kRemove, &remove) &&
203 remove) {
204 if (!DeleteCertAndKeyByNickname(guid, nssdb)) {
205 LOG(ERROR) << "Unable to delete certificate";
206 return false;
207 } else {
208 return true;
212 // Not removing, so let's get the data we need to add this certificate.
213 std::string cert_type;
214 certificate.GetStringWithoutPathExpansion(::onc::certificate::kType,
215 &cert_type);
216 if (cert_type == ::onc::certificate::kServer ||
217 cert_type == ::onc::certificate::kAuthority) {
218 return ParseServerOrCaCertificate(allow_trust_imports,
219 cert_type,
220 guid,
221 certificate,
222 nssdb,
223 onc_trusted_certificates);
224 } else if (cert_type == ::onc::certificate::kClient) {
225 return ParseClientCertificate(guid, certificate, nssdb);
228 NOTREACHED();
229 return false;
232 bool CertificateImporterImpl::ParseServerOrCaCertificate(
233 bool allow_trust_imports,
234 const std::string& cert_type,
235 const std::string& guid,
236 const base::DictionaryValue& certificate,
237 net::NSSCertDatabase* nssdb,
238 net::CertificateList* onc_trusted_certificates) {
239 bool web_trust_flag = false;
240 const base::ListValue* trust_list = NULL;
241 if (certificate.GetListWithoutPathExpansion(::onc::certificate::kTrustBits,
242 &trust_list)) {
243 for (base::ListValue::const_iterator it = trust_list->begin();
244 it != trust_list->end(); ++it) {
245 std::string trust_type;
246 if (!(*it)->GetAsString(&trust_type))
247 NOTREACHED();
249 if (trust_type == ::onc::certificate::kWeb) {
250 // "Web" implies that the certificate is to be trusted for SSL
251 // identification.
252 web_trust_flag = true;
253 } else {
254 // Trust bits should only increase trust and never restrict. Thus,
255 // ignoring unknown bits should be safe.
256 LOG(WARNING) << "Certificate contains unknown trust type "
257 << trust_type;
262 bool import_with_ssl_trust = false;
263 if (web_trust_flag) {
264 if (!allow_trust_imports)
265 LOG(WARNING) << "Web trust not granted for certificate: " << guid;
266 else
267 import_with_ssl_trust = true;
270 std::string x509_data;
271 if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kX509,
272 &x509_data) ||
273 x509_data.empty()) {
274 LOG(ERROR) << "Certificate missing appropriate certificate data for type: "
275 << cert_type;
276 return false;
279 scoped_refptr<net::X509Certificate> x509_cert =
280 DecodePEMCertificate(x509_data);
281 if (!x509_cert.get()) {
282 LOG(ERROR) << "Unable to create certificate from PEM encoding, type: "
283 << cert_type;
284 return false;
287 net::NSSCertDatabase::TrustBits trust = (import_with_ssl_trust ?
288 net::NSSCertDatabase::TRUSTED_SSL :
289 net::NSSCertDatabase::TRUST_DEFAULT);
291 if (x509_cert->os_cert_handle()->isperm) {
292 net::CertType net_cert_type =
293 cert_type == ::onc::certificate::kServer ? net::SERVER_CERT
294 : net::CA_CERT;
295 VLOG(1) << "Certificate is already installed.";
296 net::NSSCertDatabase::TrustBits missing_trust_bits =
297 trust & ~nssdb->GetCertTrust(x509_cert.get(), net_cert_type);
298 if (missing_trust_bits) {
299 std::string error_reason;
300 bool success = false;
301 if (nssdb->IsReadOnly(x509_cert.get())) {
302 error_reason = " Certificate is stored read-only.";
303 } else {
304 success = nssdb->SetCertTrust(x509_cert.get(), net_cert_type, trust);
306 if (!success) {
307 LOG(ERROR) << "Certificate of type " << cert_type
308 << " was already present, but trust couldn't be set."
309 << error_reason;
312 } else {
313 net::CertificateList cert_list;
314 cert_list.push_back(x509_cert);
315 net::NSSCertDatabase::ImportCertFailureList failures;
316 bool success = false;
317 if (cert_type == ::onc::certificate::kServer)
318 success = nssdb->ImportServerCert(cert_list, trust, &failures);
319 else // Authority cert
320 success = nssdb->ImportCACerts(cert_list, trust, &failures);
322 if (!failures.empty()) {
323 std::string error_string = net::ErrorToString(failures[0].net_error);
324 LOG(ERROR) << "Error ( " << error_string
325 << " ) importing certificate of type " << cert_type;
326 return false;
329 if (!success) {
330 LOG(ERROR) << "Unknown error importing " << cert_type << " certificate.";
331 return false;
335 if (web_trust_flag && onc_trusted_certificates)
336 onc_trusted_certificates->push_back(x509_cert);
338 return true;
341 bool CertificateImporterImpl::ParseClientCertificate(
342 const std::string& guid,
343 const base::DictionaryValue& certificate,
344 net::NSSCertDatabase* nssdb) {
345 std::string pkcs12_data;
346 if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kPKCS12,
347 &pkcs12_data) ||
348 pkcs12_data.empty()) {
349 LOG(ERROR) << "PKCS12 data is missing for client certificate.";
350 return false;
353 std::string decoded_pkcs12;
354 if (!base::Base64Decode(pkcs12_data, &decoded_pkcs12)) {
355 LOG(ERROR) << "Unable to base64 decode PKCS#12 data: \"" << pkcs12_data
356 << "\".";
357 return false;
360 // Since this has a private key, always use the private module.
361 crypto::ScopedPK11Slot private_slot(nssdb->GetPrivateSlot());
362 if (!private_slot)
363 return false;
364 scoped_refptr<net::CryptoModule> module(
365 net::CryptoModule::CreateFromHandle(private_slot.get()));
366 net::CertificateList imported_certs;
368 int import_result = nssdb->ImportFromPKCS12(
369 module.get(), decoded_pkcs12, base::string16(), false, &imported_certs);
370 if (import_result != net::OK) {
371 std::string error_string = net::ErrorToString(import_result);
372 LOG(ERROR) << "Unable to import client certificate, error: "
373 << error_string;
374 return false;
377 if (imported_certs.size() == 0) {
378 LOG(WARNING) << "PKCS12 data contains no importable certificates.";
379 return true;
382 if (imported_certs.size() != 1) {
383 LOG(WARNING) << "ONC File: PKCS12 data contains more than one certificate. "
384 "Only the first one will be imported.";
387 scoped_refptr<net::X509Certificate> cert_result = imported_certs[0];
389 // Find the private key associated with this certificate, and set the
390 // nickname on it.
391 SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
392 cert_result->os_cert_handle()->slot,
393 cert_result->os_cert_handle(),
394 NULL); // wincx
395 if (private_key) {
396 PK11_SetPrivateKeyNickname(private_key, const_cast<char*>(guid.c_str()));
397 SECKEY_DestroyPrivateKey(private_key);
398 } else {
399 LOG(WARNING) << "Unable to find private key for certificate.";
401 return true;
404 } // namespace onc
405 } // namespace chromeos