// 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 "chrome/browser/chromeos/options/cert_library.h"

#include <algorithm>

#include "base/command_line.h"
#include "base/i18n/string_compare.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list_threadsafe.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"  // g_browser_process
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/x509_certificate_model.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/login/login_state.h"
#include "chromeos/network/onc/onc_utils.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_util.h"
#include "net/cert/cert_database.h"
#include "net/cert/nss_cert_database.h"
#include "third_party/icu/source/i18n/unicode/coll.h"  // icu::Collator
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_collator.h"

namespace chromeos {

namespace {

// Root CA certificates that are built into Chrome use this token name.
const char kRootCertificateTokenName[] = "Builtin Object Token";

base::string16 GetDisplayString(net::X509Certificate* cert,
                                bool hardware_backed) {
  std::string org;
  if (!cert->subject().organization_names.empty())
    org = cert->subject().organization_names[0];
  if (org.empty())
    org = cert->subject().GetDisplayName();
  base::string16 issued_by = base::UTF8ToUTF16(
      x509_certificate_model::GetIssuerCommonName(cert->os_cert_handle(),
                                                  org));  // alternative text
  base::string16 issued_to = base::UTF8ToUTF16(
      x509_certificate_model::GetCertNameOrNickname(cert->os_cert_handle()));

  if (hardware_backed) {
    return l10n_util::GetStringFUTF16(
        IDS_CERT_MANAGER_HARDWARE_BACKED_KEY_FORMAT_LONG,
        issued_by,
        issued_to,
        l10n_util::GetStringUTF16(IDS_CERT_MANAGER_HARDWARE_BACKED));
  } else {
    return l10n_util::GetStringFUTF16(
        IDS_CERT_MANAGER_KEY_FORMAT_LONG,
        issued_by,
        issued_to);
  }
}

std::string CertToPEM(const net::X509Certificate& cert) {
  std::string pem_encoded_cert;
  if (!net::X509Certificate::GetPEMEncoded(cert.os_cert_handle(),
                                           &pem_encoded_cert)) {
    LOG(ERROR) << "Couldn't PEM-encode certificate";
    return std::string();
  }
  return pem_encoded_cert;
}

}  // namespace

class CertNameComparator {
 public:
  explicit CertNameComparator(icu::Collator* collator)
      : collator_(collator) {
  }

  bool operator()(const scoped_refptr<net::X509Certificate>& lhs,
                  const scoped_refptr<net::X509Certificate>& rhs) const {
    base::string16 lhs_name = GetDisplayString(lhs.get(), false);
    base::string16 rhs_name = GetDisplayString(rhs.get(), false);
    if (collator_ == NULL)
      return lhs_name < rhs_name;
    return base::i18n::CompareString16WithCollator(
        collator_, lhs_name, rhs_name) == UCOL_LESS;
  }

 private:
  icu::Collator* collator_;
};

static CertLibrary* g_cert_library = NULL;

// static
void CertLibrary::Initialize() {
  CHECK(!g_cert_library);
  g_cert_library = new CertLibrary();
}

// static
void CertLibrary::Shutdown() {
  CHECK(g_cert_library);
  delete g_cert_library;
  g_cert_library = NULL;
}

// static
CertLibrary* CertLibrary::Get() {
  CHECK(g_cert_library) << "CertLibrary::Get() called before Initialize()";
  return g_cert_library;
}

// static
bool CertLibrary::IsInitialized() {
  return g_cert_library;
}

CertLibrary::CertLibrary() {
  CertLoader::Get()->AddObserver(this);
}

CertLibrary::~CertLibrary() {
  CertLoader::Get()->RemoveObserver(this);
}

void CertLibrary::AddObserver(CertLibrary::Observer* observer) {
  observer_list_.AddObserver(observer);
}

void CertLibrary::RemoveObserver(CertLibrary::Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

bool CertLibrary::CertificatesLoading() const {
  return CertLoader::Get()->CertificatesLoading();
}

bool CertLibrary::CertificatesLoaded() const {
  return CertLoader::Get()->certificates_loaded();
}

bool CertLibrary::IsHardwareBacked() const {
  return CertLoader::Get()->IsHardwareBacked();
}

int CertLibrary::NumCertificates(CertType type) const {
  const net::CertificateList& cert_list = GetCertificateListForType(type);
  return static_cast<int>(cert_list.size());
}

base::string16 CertLibrary::GetCertDisplayStringAt(CertType type,
                                                   int index) const {
  net::X509Certificate* cert = GetCertificateAt(type, index);
  bool hardware_backed = IsCertHardwareBackedAt(type, index);
  return GetDisplayString(cert, hardware_backed);
}

std::string CertLibrary::GetServerCACertPEMAt(int index) const {
  return CertToPEM(*GetCertificateAt(CERT_TYPE_SERVER_CA, index));
}

std::string CertLibrary::GetUserCertPkcs11IdAt(int index, int* slot_id) const {
  net::X509Certificate* cert = GetCertificateAt(CERT_TYPE_USER, index);
  return CertLoader::GetPkcs11IdAndSlotForCert(*cert, slot_id);
}

bool CertLibrary::IsCertHardwareBackedAt(CertType type, int index) const {
  net::X509Certificate* cert = GetCertificateAt(type, index);
  return CertLoader::Get()->IsCertificateHardwareBacked(cert);
}

int CertLibrary::GetServerCACertIndexByPEM(
    const std::string& pem_encoded) const {
  int num_certs = NumCertificates(CERT_TYPE_SERVER_CA);
  for (int index = 0; index < num_certs; ++index) {
    net::X509Certificate* cert = GetCertificateAt(CERT_TYPE_SERVER_CA, index);
    if (CertToPEM(*cert) != pem_encoded)
      continue;
    return index;
  }
  return -1;
}

int CertLibrary::GetUserCertIndexByPkcs11Id(
    const std::string& pkcs11_id) const {
  int num_certs = NumCertificates(CERT_TYPE_USER);
  for (int index = 0; index < num_certs; ++index) {
    net::X509Certificate* cert = GetCertificateAt(CERT_TYPE_USER, index);
    int slot_id = -1;
    std::string id = CertLoader::GetPkcs11IdAndSlotForCert(*cert, &slot_id);
    if (id == pkcs11_id)
      return index;
  }
  return -1;  // Not found.
}

void CertLibrary::OnCertificatesLoaded(const net::CertificateList& cert_list,
                                       bool initial_load) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  VLOG(1) << "CertLibrary::OnCertificatesLoaded: " << cert_list.size();
  certs_.clear();
  user_certs_.clear();
  server_certs_.clear();
  server_ca_certs_.clear();

  // Add certificates to the appropriate list.
  for (net::CertificateList::const_iterator iter = cert_list.begin();
       iter != cert_list.end(); ++iter) {
    certs_.push_back(iter->get());
    net::X509Certificate::OSCertHandle cert_handle =
        iter->get()->os_cert_handle();
    net::CertType type = x509_certificate_model::GetType(cert_handle);
    switch (type) {
      case net::USER_CERT:
        user_certs_.push_back(iter->get());
        break;
      case net::SERVER_CERT:
        server_certs_.push_back(iter->get());
        break;
      case net::CA_CERT: {
        // Exclude root CA certificates that are built into Chrome.
        std::string token_name =
            x509_certificate_model::GetTokenName(cert_handle);
        if (token_name != kRootCertificateTokenName)
          server_ca_certs_.push_back(iter->get());
        break;
      }
      default:
        break;
    }
  }

  // Perform locale-sensitive sorting by certificate name.
  UErrorCode error = U_ZERO_ERROR;
  scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(
      icu::Locale(g_browser_process->GetApplicationLocale().c_str()), error));
  if (U_FAILURE(error))
    collator.reset();
  CertNameComparator cert_name_comparator(collator.get());
  std::sort(certs_.begin(), certs_.end(), cert_name_comparator);
  std::sort(user_certs_.begin(), user_certs_.end(), cert_name_comparator);
  std::sort(server_certs_.begin(), server_certs_.end(), cert_name_comparator);
  std::sort(server_ca_certs_.begin(), server_ca_certs_.end(),
            cert_name_comparator);

  VLOG(1) << "certs_: " << certs_.size();
  VLOG(1) << "user_certs_: " << user_certs_.size();
  VLOG(1) << "server_certs_: " << server_certs_.size();
  VLOG(1) << "server_ca_certs_: " << server_ca_certs_.size();

  FOR_EACH_OBSERVER(CertLibrary::Observer, observer_list_,
                    OnCertificatesLoaded(initial_load));
}

net::X509Certificate* CertLibrary::GetCertificateAt(CertType type,
                                                    int index) const {
  const net::CertificateList& cert_list = GetCertificateListForType(type);
  DCHECK_GE(index, 0);
  DCHECK_LT(index, static_cast<int>(cert_list.size()));
  return cert_list[index].get();
}

const net::CertificateList& CertLibrary::GetCertificateListForType(
    CertType type) const {
  if (type == CERT_TYPE_USER)
    return user_certs_;
  if (type == CERT_TYPE_SERVER)
    return server_certs_;
  if (type == CERT_TYPE_SERVER_CA)
    return server_ca_certs_;
  DCHECK(type == CERT_TYPE_DEFAULT);
  return certs_;
}

}  // namespace chromeos