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"
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"
34 void CallBackOnOriginLoop(
35 const scoped_refptr
<base::SingleThreadTaskRunner
>& origin_loop
,
36 const CertificateImporter::DoneCallback
& callback
,
38 const net::CertificateList
& onc_trusted_certificates
) {
39 origin_loop
->PostTask(
40 FROM_HERE
, base::Bind(callback
, success
, onc_trusted_certificates
));
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
),
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(),
70 // |done_callback| must be called on the origin thread.
71 DoneCallback callback_on_origin_loop
=
72 base::Bind(&CallBackOnOriginLoop
,
73 base::ThreadTaskRunnerHandle::Get(),
76 // This is the actual function that imports the certificates.
77 base::Closure import_certs_callback
=
78 base::Bind(&ParseAndStoreCertificates
,
80 callback_on_origin_loop
,
81 base::Owned(certificates
.DeepCopy()),
84 // The NSSCertDatabase must be accessed on |io_task_runner_|
85 io_task_runner_
->PostTask(FROM_HERE
, import_certs_callback
);
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
;
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
,
108 &onc_trusted_certificates
)) {
110 LOG(ERROR
) << "Cannot parse certificate at index " << i
;
112 VLOG(2) << "Successfully imported certificate at index " << i
;
116 done_callback
.Run(success
, onc_trusted_certificates
);
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
);
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
, ':');
136 ++delimiter
; // move past the colon.
137 if (strcmp(delimiter
, label
.c_str()) == 0) {
138 result
->push_back(*iter
);
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(),
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
);
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
);
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()))
182 void CertificateImporterImpl::RunDoneCallback(
183 const CertificateImporter::DoneCallback
& callback
,
185 const net::CertificateList
& onc_trusted_certificates
) {
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.
198 certificate
.GetStringWithoutPathExpansion(::onc::certificate::kGUID
, &guid
);
199 DCHECK(!guid
.empty());
202 if (certificate
.GetBooleanWithoutPathExpansion(::onc::kRemove
, &remove
) &&
204 if (!DeleteCertAndKeyByNickname(guid
, nssdb
)) {
205 LOG(ERROR
) << "Unable to delete certificate";
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
,
216 if (cert_type
== ::onc::certificate::kServer
||
217 cert_type
== ::onc::certificate::kAuthority
) {
218 return ParseServerOrCaCertificate(allow_trust_imports
,
223 onc_trusted_certificates
);
224 } else if (cert_type
== ::onc::certificate::kClient
) {
225 return ParseClientCertificate(guid
, certificate
, nssdb
);
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
,
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
))
249 if (trust_type
== ::onc::certificate::kWeb
) {
250 // "Web" implies that the certificate is to be trusted for SSL
252 web_trust_flag
= true;
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 "
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
;
267 import_with_ssl_trust
= true;
270 std::string x509_data
;
271 if (!certificate
.GetStringWithoutPathExpansion(::onc::certificate::kX509
,
274 LOG(ERROR
) << "Certificate missing appropriate certificate data for type: "
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: "
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
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.";
304 success
= nssdb
->SetCertTrust(x509_cert
.get(), net_cert_type
, trust
);
307 LOG(ERROR
) << "Certificate of type " << cert_type
308 << " was already present, but trust couldn't be set."
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
;
330 LOG(ERROR
) << "Unknown error importing " << cert_type
<< " certificate.";
335 if (web_trust_flag
&& onc_trusted_certificates
)
336 onc_trusted_certificates
->push_back(x509_cert
);
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
,
348 pkcs12_data
.empty()) {
349 LOG(ERROR
) << "PKCS12 data is missing for client certificate.";
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
360 // Since this has a private key, always use the private module.
361 crypto::ScopedPK11Slot
private_slot(nssdb
->GetPrivateSlot());
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: "
377 if (imported_certs
.size() == 0) {
378 LOG(WARNING
) << "PKCS12 data contains no importable certificates.";
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
391 SECKEYPrivateKey
* private_key
= PK11_FindPrivateKeyFromCert(
392 cert_result
->os_cert_handle()->slot
,
393 cert_result
->os_cert_handle(),
396 PK11_SetPrivateKeyNickname(private_key
, const_cast<char*>(guid
.c_str()));
397 SECKEY_DestroyPrivateKey(private_key
);
399 LOG(WARNING
) << "Unable to find private key for certificate.";
405 } // namespace chromeos