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/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 
     27 namespace chromeos {
     28 namespace onc {
     29 
     30 CertificateImporterImpl::CertificateImporterImpl() {
     31 }
     32 
     33 bool 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 
     50 bool 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
     78 void 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
    116 bool 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 
    136 bool 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 
    175 bool 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 
    291 bool 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