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