Home | History | Annotate | Download | only in onc
      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 
     30 namespace chromeos {
     31 namespace onc {
     32 
     33 namespace {
     34 
     35 void 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 
     46 CertificateImporterImpl::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 
     55 CertificateImporterImpl::~CertificateImporterImpl() {
     56 }
     57 
     58 void 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
     90 void 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
    121 void 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
    162 bool 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 
    183 void 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 
    192 bool 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 
    233 bool 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 
    342 bool 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