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