onc_certificate_importer_impl.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 "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"
22
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)
27
28namespace chromeos {
29namespace onc {
30
31CertificateImporterImpl::CertificateImporterImpl(
32    net::NSSCertDatabase* target_nssdb)
33    : target_nssdb_(target_nssdb) {
34  CHECK(target_nssdb);
35}
36
37bool 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";
42
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;
52  }
53  return true;
54}
55
56bool 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);
66
67    VLOG(2) << "Parsing certificate at index " << i << ": " << *certificate;
68
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;
78    }
79  }
80  return success;
81}
82
83// static
84void 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;
104        }
105      }
106    }
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);
120    }
121  }
122}
123
124// static
125bool 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;
142  }
143  return result;
144}
145
146bool 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());
155
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;
164    }
165  }
166
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);
181  }
182
183  NOTREACHED();
184  return false;
185}
186
187bool 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();
203
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);
213      }
214    }
215  }
216
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;
223  }
224
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;
233  }
234
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;
241  }
242
243  net::NSSCertDatabase::TrustBits trust = (import_with_ssl_trust ?
244                                           net::NSSCertDatabase::TRUSTED_SSL :
245                                           net::NSSCertDatabase::TRUST_DEFAULT);
246
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);
263      }
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);
268      }
269    }
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);
279
280    if (!failures.empty()) {
281      ONC_LOG_ERROR(
282          base::StringPrintf("Error ( %s ) importing %s certificate",
283                             net::ErrorToString(failures[0].net_error),
284                             cert_type.c_str()));
285      return false;
286    }
287
288    if (!success) {
289      ONC_LOG_ERROR("Unknown error importing " + cert_type + " certificate.");
290      return false;
291    }
292  }
293
294  if (web_trust_flag && onc_trusted_certificates)
295    onc_trusted_certificates->push_back(x509_cert);
296
297  if (imported_server_and_ca_certs)
298    (*imported_server_and_ca_certs)[guid] = x509_cert;
299
300  return true;
301}
302
303bool CertificateImporterImpl::ParseClientCertificate(
304    const std::string& guid,
305    const base::DictionaryValue& certificate) {
306  std::string pkcs12_data;
307  if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kPKCS12,
308                                                 &pkcs12_data) ||
309      pkcs12_data.empty()) {
310    ONC_LOG_ERROR("PKCS12 data is missing for client certificate.");
311    return false;
312  }
313
314  std::string decoded_pkcs12;
315  if (!base::Base64Decode(pkcs12_data, &decoded_pkcs12)) {
316    ONC_LOG_ERROR(
317        "Unable to base64 decode PKCS#12 data: \"" + pkcs12_data + "\".");
318    return false;
319  }
320
321  // Since this has a private key, always use the private module.
322  crypto::ScopedPK11Slot private_slot(target_nssdb_->GetPrivateSlot());
323  if (!private_slot)
324    return false;
325  scoped_refptr<net::CryptoModule> module(
326      net::CryptoModule::CreateFromHandle(private_slot.get()));
327  net::CertificateList imported_certs;
328
329  int import_result = target_nssdb_->ImportFromPKCS12(
330      module.get(), decoded_pkcs12, base::string16(), false, &imported_certs);
331  if (import_result != net::OK) {
332    ONC_LOG_ERROR(
333        base::StringPrintf("Unable to import client certificate (error %s)",
334                           net::ErrorToString(import_result)));
335    return false;
336  }
337
338  if (imported_certs.size() == 0) {
339    ONC_LOG_WARNING("PKCS12 data contains no importable certificates.");
340    return true;
341  }
342
343  if (imported_certs.size() != 1) {
344    ONC_LOG_WARNING("ONC File: PKCS12 data contains more than one certificate. "
345                    "Only the first one will be imported.");
346  }
347
348  scoped_refptr<net::X509Certificate> cert_result = imported_certs[0];
349
350  // Find the private key associated with this certificate, and set the
351  // nickname on it.
352  SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
353      cert_result->os_cert_handle()->slot,
354      cert_result->os_cert_handle(),
355      NULL);  // wincx
356  if (private_key) {
357    PK11_SetPrivateKeyNickname(private_key, const_cast<char*>(guid.c_str()));
358    SECKEY_DestroyPrivateKey(private_key);
359  } else {
360    ONC_LOG_WARNING("Unable to find private key for certificate.");
361  }
362  return true;
363}
364
365}  // namespace onc
366}  // namespace chromeos
367