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