| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/network/onc/onc_certificate_importer_impl.h" |
| |
| #include <cert.h> |
| #include <keyhi.h> |
| #include <pk11pub.h> |
| |
| #include "base/base64.h" |
| #include "base/logging.h" |
| #include "base/values.h" |
| #include "chromeos/network/network_event_log.h" |
| #include "chromeos/network/onc/onc_utils.h" |
| #include "components/onc/onc_constants.h" |
| #include "crypto/scoped_nss_types.h" |
| #include "net/base/crypto_module.h" |
| #include "net/base/net_errors.h" |
| #include "net/cert/nss_cert_database.h" |
| #include "net/cert/x509_certificate.h" |
| |
| #define ONC_LOG_WARNING(message) \ |
| NET_LOG_DEBUG("ONC Certificate Import Warning", message) |
| #define ONC_LOG_ERROR(message) \ |
| NET_LOG_ERROR("ONC Certificate Import Error", message) |
| |
| namespace chromeos { |
| namespace onc { |
| |
| CertificateImporterImpl::CertificateImporterImpl( |
| net::NSSCertDatabase* target_nssdb) |
| : target_nssdb_(target_nssdb) { |
| CHECK(target_nssdb); |
| } |
| |
| bool CertificateImporterImpl::ImportCertificates( |
| const base::ListValue& certificates, |
| ::onc::ONCSource source, |
| net::CertificateList* onc_trusted_certificates) { |
| VLOG(2) << "ONC file has " << certificates.GetSize() << " certificates"; |
| |
| // Web trust is only granted to certificates imported by the user. |
| bool allow_trust_imports = source == ::onc::ONC_SOURCE_USER_IMPORT; |
| if (!ParseAndStoreCertificates(allow_trust_imports, |
| certificates, |
| onc_trusted_certificates, |
| NULL)) { |
| LOG(ERROR) << "Cannot parse some of the certificates in the ONC from " |
| << onc::GetSourceAsString(source); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CertificateImporterImpl::ParseAndStoreCertificates( |
| bool allow_trust_imports, |
| const base::ListValue& certificates, |
| net::CertificateList* onc_trusted_certificates, |
| CertsByGUID* imported_server_and_ca_certs) { |
| bool success = true; |
| for (size_t i = 0; i < certificates.GetSize(); ++i) { |
| const base::DictionaryValue* certificate = NULL; |
| certificates.GetDictionary(i, &certificate); |
| DCHECK(certificate != NULL); |
| |
| VLOG(2) << "Parsing certificate at index " << i << ": " << *certificate; |
| |
| if (!ParseAndStoreCertificate(allow_trust_imports, |
| *certificate, |
| onc_trusted_certificates, |
| imported_server_and_ca_certs)) { |
| success = false; |
| ONC_LOG_ERROR( |
| base::StringPrintf("Cannot parse certificate at index %zu", i)); |
| } else { |
| VLOG(2) << "Successfully imported certificate at index " << i; |
| } |
| } |
| return success; |
| } |
| |
| // static |
| void CertificateImporterImpl::ListCertsWithNickname( |
| const std::string& label, |
| net::CertificateList* result, |
| net::NSSCertDatabase* target_nssdb) { |
| net::CertificateList all_certs; |
| // TODO(tbarzic): Use async |ListCerts|. |
| target_nssdb->ListCertsSync(&all_certs); |
| result->clear(); |
| for (net::CertificateList::iterator iter = all_certs.begin(); |
| iter != all_certs.end(); ++iter) { |
| if (iter->get()->os_cert_handle()->nickname) { |
| // Separate the nickname stored in the certificate at the colon, since |
| // NSS likes to store it as token:nickname. |
| const char* delimiter = |
| ::strchr(iter->get()->os_cert_handle()->nickname, ':'); |
| if (delimiter) { |
| ++delimiter; // move past the colon. |
| if (strcmp(delimiter, label.c_str()) == 0) { |
| result->push_back(*iter); |
| continue; |
| } |
| } |
| } |
| // Now we find the private key for this certificate and see if it has a |
| // nickname that matches. If there is a private key, and it matches, |
| // then this is a client cert that we are looking for. |
| SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert( |
| iter->get()->os_cert_handle()->slot, |
| iter->get()->os_cert_handle(), |
| NULL); // wincx |
| if (private_key) { |
| char* private_key_nickname = PK11_GetPrivateKeyNickname(private_key); |
| if (private_key_nickname && std::string(label) == private_key_nickname) |
| result->push_back(*iter); |
| PORT_Free(private_key_nickname); |
| SECKEY_DestroyPrivateKey(private_key); |
| } |
| } |
| } |
| |
| // static |
| bool CertificateImporterImpl::DeleteCertAndKeyByNickname( |
| const std::string& label, |
| net::NSSCertDatabase* target_nssdb) { |
| net::CertificateList cert_list; |
| ListCertsWithNickname(label, &cert_list, target_nssdb); |
| bool result = true; |
| for (net::CertificateList::iterator iter = cert_list.begin(); |
| iter != cert_list.end(); ++iter) { |
| // If we fail, we try and delete the rest still. |
| // TODO(gspencer): this isn't very "transactional". If we fail on some, but |
| // not all, then it's possible to leave things in a weird state. |
| // Luckily there should only be one cert with a particular |
| // label, and the cert not being found is one of the few reasons the |
| // delete could fail, but still... The other choice is to return |
| // failure immediately, but that doesn't seem to do what is intended. |
| if (!target_nssdb->DeleteCertAndKey(iter->get())) |
| result = false; |
| } |
| return result; |
| } |
| |
| bool CertificateImporterImpl::ParseAndStoreCertificate( |
| bool allow_trust_imports, |
| const base::DictionaryValue& certificate, |
| net::CertificateList* onc_trusted_certificates, |
| CertsByGUID* imported_server_and_ca_certs) { |
| // Get out the attributes of the given certificate. |
| std::string guid; |
| certificate.GetStringWithoutPathExpansion(::onc::certificate::kGUID, &guid); |
| DCHECK(!guid.empty()); |
| |
| bool remove = false; |
| if (certificate.GetBooleanWithoutPathExpansion(::onc::kRemove, &remove) && |
| remove) { |
| if (!DeleteCertAndKeyByNickname(guid, target_nssdb_)) { |
| ONC_LOG_ERROR("Unable to delete certificate"); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| // Not removing, so let's get the data we need to add this certificate. |
| std::string cert_type; |
| certificate.GetStringWithoutPathExpansion(::onc::certificate::kType, |
| &cert_type); |
| if (cert_type == ::onc::certificate::kServer || |
| cert_type == ::onc::certificate::kAuthority) { |
| return ParseServerOrCaCertificate(allow_trust_imports, |
| cert_type, |
| guid, |
| certificate, |
| onc_trusted_certificates, |
| imported_server_and_ca_certs); |
| } else if (cert_type == ::onc::certificate::kClient) { |
| return ParseClientCertificate(guid, certificate); |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool CertificateImporterImpl::ParseServerOrCaCertificate( |
| bool allow_trust_imports, |
| const std::string& cert_type, |
| const std::string& guid, |
| const base::DictionaryValue& certificate, |
| net::CertificateList* onc_trusted_certificates, |
| CertsByGUID* imported_server_and_ca_certs) { |
| bool web_trust_flag = false; |
| const base::ListValue* trust_list = NULL; |
| if (certificate.GetListWithoutPathExpansion(::onc::certificate::kTrustBits, |
| &trust_list)) { |
| for (base::ListValue::const_iterator it = trust_list->begin(); |
| it != trust_list->end(); ++it) { |
| std::string trust_type; |
| if (!(*it)->GetAsString(&trust_type)) |
| NOTREACHED(); |
| |
| if (trust_type == ::onc::certificate::kWeb) { |
| // "Web" implies that the certificate is to be trusted for SSL |
| // identification. |
| web_trust_flag = true; |
| } else { |
| // Trust bits should only increase trust and never restrict. Thus, |
| // ignoring unknown bits should be safe. |
| ONC_LOG_WARNING("Certificate contains unknown trust type " + |
| trust_type); |
| } |
| } |
| } |
| |
| bool import_with_ssl_trust = false; |
| if (web_trust_flag) { |
| if (!allow_trust_imports) |
| ONC_LOG_WARNING("Web trust not granted for certificate: " + guid); |
| else |
| import_with_ssl_trust = true; |
| } |
| |
| std::string x509_data; |
| if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kX509, |
| &x509_data) || |
| x509_data.empty()) { |
| ONC_LOG_ERROR( |
| "Certificate missing appropriate certificate data for type: " + |
| cert_type); |
| return false; |
| } |
| |
| scoped_refptr<net::X509Certificate> x509_cert = |
| DecodePEMCertificate(x509_data); |
| if (!x509_cert.get()) { |
| ONC_LOG_ERROR("Unable to create certificate from PEM encoding, type: " + |
| cert_type); |
| return false; |
| } |
| |
| net::NSSCertDatabase::TrustBits trust = (import_with_ssl_trust ? |
| net::NSSCertDatabase::TRUSTED_SSL : |
| net::NSSCertDatabase::TRUST_DEFAULT); |
| |
| if (x509_cert->os_cert_handle()->isperm) { |
| net::CertType net_cert_type = |
| cert_type == ::onc::certificate::kServer ? net::SERVER_CERT |
| : net::CA_CERT; |
| VLOG(1) << "Certificate is already installed."; |
| net::NSSCertDatabase::TrustBits missing_trust_bits = |
| trust & ~target_nssdb_->GetCertTrust(x509_cert.get(), net_cert_type); |
| if (missing_trust_bits) { |
| std::string error_reason; |
| bool success = false; |
| if (target_nssdb_->IsReadOnly(x509_cert.get())) { |
| error_reason = " Certificate is stored read-only."; |
| } else { |
| success = target_nssdb_->SetCertTrust(x509_cert.get(), |
| net_cert_type, |
| trust); |
| } |
| if (!success) { |
| ONC_LOG_ERROR("Certificate of type " + cert_type + |
| " was already present, but trust couldn't be set." + |
| error_reason); |
| } |
| } |
| } else { |
| net::CertificateList cert_list; |
| cert_list.push_back(x509_cert); |
| net::NSSCertDatabase::ImportCertFailureList failures; |
| bool success = false; |
| if (cert_type == ::onc::certificate::kServer) |
| success = target_nssdb_->ImportServerCert(cert_list, trust, &failures); |
| else // Authority cert |
| success = target_nssdb_->ImportCACerts(cert_list, trust, &failures); |
| |
| if (!failures.empty()) { |
| std::string error_string = net::ErrorToString(failures[0].net_error); |
| ONC_LOG_ERROR( |
| base::StringPrintf("Error ( %s ) importing %s certificate", |
| error_string.c_str(), |
| cert_type.c_str())); |
| return false; |
| } |
| |
| if (!success) { |
| ONC_LOG_ERROR("Unknown error importing " + cert_type + " certificate."); |
| return false; |
| } |
| } |
| |
| if (web_trust_flag && onc_trusted_certificates) |
| onc_trusted_certificates->push_back(x509_cert); |
| |
| if (imported_server_and_ca_certs) |
| (*imported_server_and_ca_certs)[guid] = x509_cert; |
| |
| return true; |
| } |
| |
| bool CertificateImporterImpl::ParseClientCertificate( |
| const std::string& guid, |
| const base::DictionaryValue& certificate) { |
| std::string pkcs12_data; |
| if (!certificate.GetStringWithoutPathExpansion(::onc::certificate::kPKCS12, |
| &pkcs12_data) || |
| pkcs12_data.empty()) { |
| ONC_LOG_ERROR("PKCS12 data is missing for client certificate."); |
| return false; |
| } |
| |
| std::string decoded_pkcs12; |
| if (!base::Base64Decode(pkcs12_data, &decoded_pkcs12)) { |
| ONC_LOG_ERROR( |
| "Unable to base64 decode PKCS#12 data: \"" + pkcs12_data + "\"."); |
| return false; |
| } |
| |
| // Since this has a private key, always use the private module. |
| crypto::ScopedPK11Slot private_slot(target_nssdb_->GetPrivateSlot()); |
| if (!private_slot) |
| return false; |
| scoped_refptr<net::CryptoModule> module( |
| net::CryptoModule::CreateFromHandle(private_slot.get())); |
| net::CertificateList imported_certs; |
| |
| int import_result = target_nssdb_->ImportFromPKCS12( |
| module.get(), decoded_pkcs12, base::string16(), false, &imported_certs); |
| if (import_result != net::OK) { |
| std::string error_string = net::ErrorToString(import_result); |
| ONC_LOG_ERROR( |
| base::StringPrintf("Unable to import client certificate (error %s)", |
| error_string.c_str())); |
| return false; |
| } |
| |
| if (imported_certs.size() == 0) { |
| ONC_LOG_WARNING("PKCS12 data contains no importable certificates."); |
| return true; |
| } |
| |
| if (imported_certs.size() != 1) { |
| ONC_LOG_WARNING("ONC File: PKCS12 data contains more than one certificate. " |
| "Only the first one will be imported."); |
| } |
| |
| scoped_refptr<net::X509Certificate> cert_result = imported_certs[0]; |
| |
| // Find the private key associated with this certificate, and set the |
| // nickname on it. |
| SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert( |
| cert_result->os_cert_handle()->slot, |
| cert_result->os_cert_handle(), |
| NULL); // wincx |
| if (private_key) { |
| PK11_SetPrivateKeyNickname(private_key, const_cast<char*>(guid.c_str())); |
| SECKEY_DestroyPrivateKey(private_key); |
| } else { |
| ONC_LOG_WARNING("Unable to find private key for certificate."); |
| } |
| return true; |
| } |
| |
| } // namespace onc |
| } // namespace chromeos |