Some additional network settings cleanup
[chromium-blink-merge.git] / chromeos / network / onc / onc_certificate_importer_impl.cc
blob7bb18333aee9a004e67d0be61c7c28c671c06fd1
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/logging.h"
13 #include "base/values.h"
14 #include "chromeos/network/network_event_log.h"
15 #include "chromeos/network/onc/onc_utils.h"
16 #include "components/onc/onc_constants.h"
17 #include "crypto/scoped_nss_types.h"
18 #include "net/base/crypto_module.h"
19 #include "net/base/net_errors.h"
20 #include "net/cert/nss_cert_database.h"
21 #include "net/cert/x509_certificate.h"
23 #define ONC_LOG_WARNING(message) \
24 NET_LOG_DEBUG("ONC Certificate Import Warning", message)
25 #define ONC_LOG_ERROR(message) \
26 NET_LOG_ERROR("ONC Certificate Import Error", message)
28 namespace chromeos {
29 namespace onc {
31 CertificateImporterImpl::CertificateImporterImpl(
32 net::NSSCertDatabase* target_nssdb)
33 : target_nssdb_(target_nssdb) {
34 CHECK(target_nssdb);
37 bool CertificateImporterImpl::ImportCertificates(
38 const base::ListValue& certificates,
39 ::onc::ONCSource source,
40 net::CertificateList* onc_trusted_certificates) {
41 VLOG(2) << "ONC file has " << certificates.GetSize() << " certificates";
43 // Web trust is only granted to certificates imported by the user.
44 bool allow_trust_imports = source == ::onc::ONC_SOURCE_USER_IMPORT;
45 if (!ParseAndStoreCertificates(allow_trust_imports,
46 certificates,
47 onc_trusted_certificates,
48 NULL)) {
49 LOG(ERROR) << "Cannot parse some of the certificates in the ONC from "
50 << onc::GetSourceAsString(source);
51 return false;
53 return true;
56 bool CertificateImporterImpl::ParseAndStoreCertificates(
57 bool allow_trust_imports,
58 const base::ListValue& certificates,
59 net::CertificateList* onc_trusted_certificates,
60 CertsByGUID* imported_server_and_ca_certs) {
61 bool success = true;
62 for (size_t i = 0; i < certificates.GetSize(); ++i) {
63 const base::DictionaryValue* certificate = NULL;
64 certificates.GetDictionary(i, &certificate);
65 DCHECK(certificate != NULL);
67 VLOG(2) << "Parsing certificate at index " << i << ": " << *certificate;
69 if (!ParseAndStoreCertificate(allow_trust_imports,
70 *certificate,
71 onc_trusted_certificates,
72 imported_server_and_ca_certs)) {
73 success = false;
74 ONC_LOG_ERROR(
75 base::StringPrintf("Cannot parse certificate at index %zu", i));
76 } else {
77 VLOG(2) << "Successfully imported certificate at index " << i;
80 return success;
83 // static
84 void CertificateImporterImpl::ListCertsWithNickname(
85 const std::string& label,
86 net::CertificateList* result,
87 net::NSSCertDatabase* target_nssdb) {
88 net::CertificateList all_certs;
89 // TODO(tbarzic): Use async |ListCerts|.
90 target_nssdb->ListCertsSync(&all_certs);
91 result->clear();
92 for (net::CertificateList::iterator iter = all_certs.begin();
93 iter != all_certs.end(); ++iter) {
94 if (iter->get()->os_cert_handle()->nickname) {
95 // Separate the nickname stored in the certificate at the colon, since
96 // NSS likes to store it as token:nickname.
97 const char* delimiter =
98 ::strchr(iter->get()->os_cert_handle()->nickname, ':');
99 if (delimiter) {
100 ++delimiter; // move past the colon.
101 if (strcmp(delimiter, label.c_str()) == 0) {
102 result->push_back(*iter);
103 continue;
107 // Now we find the private key for this certificate and see if it has a
108 // nickname that matches. If there is a private key, and it matches,
109 // then this is a client cert that we are looking for.
110 SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
111 iter->get()->os_cert_handle()->slot,
112 iter->get()->os_cert_handle(),
113 NULL); // wincx
114 if (private_key) {
115 char* private_key_nickname = PK11_GetPrivateKeyNickname(private_key);
116 if (private_key_nickname && std::string(label) == private_key_nickname)
117 result->push_back(*iter);
118 PORT_Free(private_key_nickname);
119 SECKEY_DestroyPrivateKey(private_key);
124 // static
125 bool CertificateImporterImpl::DeleteCertAndKeyByNickname(
126 const std::string& label,
127 net::NSSCertDatabase* target_nssdb) {
128 net::CertificateList cert_list;
129 ListCertsWithNickname(label, &cert_list, target_nssdb);
130 bool result = true;
131 for (net::CertificateList::iterator iter = cert_list.begin();
132 iter != cert_list.end(); ++iter) {
133 // If we fail, we try and delete the rest still.
134 // TODO(gspencer): this isn't very "transactional". If we fail on some, but
135 // not all, then it's possible to leave things in a weird state.
136 // Luckily there should only be one cert with a particular
137 // label, and the cert not being found is one of the few reasons the
138 // delete could fail, but still... The other choice is to return
139 // failure immediately, but that doesn't seem to do what is intended.
140 if (!target_nssdb->DeleteCertAndKey(iter->get()))
141 result = false;
143 return result;
146 bool CertificateImporterImpl::ParseAndStoreCertificate(
147 bool allow_trust_imports,
148 const base::DictionaryValue& certificate,
149 net::CertificateList* onc_trusted_certificates,
150 CertsByGUID* imported_server_and_ca_certs) {
151 // Get out the attributes of the given certificate.
152 std::string guid;
153 certificate.GetStringWithoutPathExpansion(::onc::certificate::kGUID, &guid);
154 DCHECK(!guid.empty());
156 bool remove = false;
157 if (certificate.GetBooleanWithoutPathExpansion(::onc::kRemove, &remove) &&
158 remove) {
159 if (!DeleteCertAndKeyByNickname(guid, target_nssdb_)) {
160 ONC_LOG_ERROR("Unable to delete certificate");
161 return false;
162 } else {
163 return true;
167 // Not removing, so let's get the data we need to add this certificate.
168 std::string cert_type;
169 certificate.GetStringWithoutPathExpansion(::onc::certificate::kType,
170 &cert_type);
171 if (cert_type == ::onc::certificate::kServer ||
172 cert_type == ::onc::certificate::kAuthority) {
173 return ParseServerOrCaCertificate(allow_trust_imports,
174 cert_type,
175 guid,
176 certificate,
177 onc_trusted_certificates,
178 imported_server_and_ca_certs);
179 } else if (cert_type == ::onc::certificate::kClient) {
180 return ParseClientCertificate(guid, certificate);
183 NOTREACHED();
184 return false;
187 bool CertificateImporterImpl::ParseServerOrCaCertificate(
188 bool allow_trust_imports,
189 const std::string& cert_type,
190 const std::string& guid,
191 const base::DictionaryValue& certificate,
192 net::CertificateList* onc_trusted_certificates,
193 CertsByGUID* imported_server_and_ca_certs) {
194 bool web_trust_flag = false;
195 const base::ListValue* trust_list = NULL;
196 if (certificate.GetListWithoutPathExpansion(::onc::certificate::kTrustBits,
197 &trust_list)) {
198 for (base::ListValue::const_iterator it = trust_list->begin();
199 it != trust_list->end(); ++it) {
200 std::string trust_type;
201 if (!(*it)->GetAsString(&trust_type))
202 NOTREACHED();
204 if (trust_type == ::onc::certificate::kWeb) {
205 // "Web" implies that the certificate is to be trusted for SSL
206 // identification.
207 web_trust_flag = true;
208 } else {
209 // Trust bits should only increase trust and never restrict. Thus,
210 // ignoring unknown bits should be safe.
211 ONC_LOG_WARNING("Certificate contains unknown trust type " +
212 trust_type);
217 bool import_with_ssl_trust = false;
218 if (web_trust_flag) {
219 if (!allow_trust_imports)
220 ONC_LOG_WARNING("Web trust not granted for certificate: " + guid);
221 else
222 import_with_ssl_trust = true;
225 std::string x509_data;
226 if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kX509,
227 &x509_data) ||
228 x509_data.empty()) {
229 ONC_LOG_ERROR(
230 "Certificate missing appropriate certificate data for type: " +
231 cert_type);
232 return false;
235 scoped_refptr<net::X509Certificate> x509_cert =
236 DecodePEMCertificate(x509_data);
237 if (!x509_cert.get()) {
238 ONC_LOG_ERROR("Unable to create certificate from PEM encoding, type: " +
239 cert_type);
240 return false;
243 net::NSSCertDatabase::TrustBits trust = (import_with_ssl_trust ?
244 net::NSSCertDatabase::TRUSTED_SSL :
245 net::NSSCertDatabase::TRUST_DEFAULT);
247 if (x509_cert->os_cert_handle()->isperm) {
248 net::CertType net_cert_type =
249 cert_type == ::onc::certificate::kServer ? net::SERVER_CERT
250 : net::CA_CERT;
251 VLOG(1) << "Certificate is already installed.";
252 net::NSSCertDatabase::TrustBits missing_trust_bits =
253 trust & ~target_nssdb_->GetCertTrust(x509_cert.get(), net_cert_type);
254 if (missing_trust_bits) {
255 std::string error_reason;
256 bool success = false;
257 if (target_nssdb_->IsReadOnly(x509_cert.get())) {
258 error_reason = " Certificate is stored read-only.";
259 } else {
260 success = target_nssdb_->SetCertTrust(x509_cert.get(),
261 net_cert_type,
262 trust);
264 if (!success) {
265 ONC_LOG_ERROR("Certificate of type " + cert_type +
266 " was already present, but trust couldn't be set." +
267 error_reason);
270 } else {
271 net::CertificateList cert_list;
272 cert_list.push_back(x509_cert);
273 net::NSSCertDatabase::ImportCertFailureList failures;
274 bool success = false;
275 if (cert_type == ::onc::certificate::kServer)
276 success = target_nssdb_->ImportServerCert(cert_list, trust, &failures);
277 else // Authority cert
278 success = target_nssdb_->ImportCACerts(cert_list, trust, &failures);
280 if (!failures.empty()) {
281 std::string error_string = net::ErrorToString(failures[0].net_error);
282 ONC_LOG_ERROR(
283 base::StringPrintf("Error ( %s ) importing %s certificate",
284 error_string.c_str(),
285 cert_type.c_str()));
286 return false;
289 if (!success) {
290 ONC_LOG_ERROR("Unknown error importing " + cert_type + " certificate.");
291 return false;
295 if (web_trust_flag && onc_trusted_certificates)
296 onc_trusted_certificates->push_back(x509_cert);
298 if (imported_server_and_ca_certs)
299 (*imported_server_and_ca_certs)[guid] = x509_cert;
301 return true;
304 bool CertificateImporterImpl::ParseClientCertificate(
305 const std::string& guid,
306 const base::DictionaryValue& certificate) {
307 std::string pkcs12_data;
308 if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kPKCS12,
309 &pkcs12_data) ||
310 pkcs12_data.empty()) {
311 ONC_LOG_ERROR("PKCS12 data is missing for client certificate.");
312 return false;
315 std::string decoded_pkcs12;
316 if (!base::Base64Decode(pkcs12_data, &decoded_pkcs12)) {
317 ONC_LOG_ERROR(
318 "Unable to base64 decode PKCS#12 data: \"" + pkcs12_data + "\".");
319 return false;
322 // Since this has a private key, always use the private module.
323 crypto::ScopedPK11Slot private_slot(target_nssdb_->GetPrivateSlot());
324 if (!private_slot)
325 return false;
326 scoped_refptr<net::CryptoModule> module(
327 net::CryptoModule::CreateFromHandle(private_slot.get()));
328 net::CertificateList imported_certs;
330 int import_result = target_nssdb_->ImportFromPKCS12(
331 module.get(), decoded_pkcs12, base::string16(), false, &imported_certs);
332 if (import_result != net::OK) {
333 std::string error_string = net::ErrorToString(import_result);
334 ONC_LOG_ERROR(
335 base::StringPrintf("Unable to import client certificate (error %s)",
336 error_string.c_str()));
337 return false;
340 if (imported_certs.size() == 0) {
341 ONC_LOG_WARNING("PKCS12 data contains no importable certificates.");
342 return true;
345 if (imported_certs.size() != 1) {
346 ONC_LOG_WARNING("ONC File: PKCS12 data contains more than one certificate. "
347 "Only the first one will be imported.");
350 scoped_refptr<net::X509Certificate> cert_result = imported_certs[0];
352 // Find the private key associated with this certificate, and set the
353 // nickname on it.
354 SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
355 cert_result->os_cert_handle()->slot,
356 cert_result->os_cert_handle(),
357 NULL); // wincx
358 if (private_key) {
359 PK11_SetPrivateKeyNickname(private_key, const_cast<char*>(guid.c_str()));
360 SECKEY_DestroyPrivateKey(private_key);
361 } else {
362 ONC_LOG_WARNING("Unable to find private key for certificate.");
364 return true;
367 } // namespace onc
368 } // namespace chromeos