dom/wifi/WifiCertService.cpp
author Masayuki Nakano <masayuki@d-toybox.com>
Sat, 11 Jun 2016 11:06:37 +0900
changeset 341662 b64f33cd6b5431a6bd97c3afea39f943ca2b9022
parent 338424 86cda9d3eaa2c6ca8c88801f44dcfaff22591ed8
child 352078 9aac837c6e4955057c53f0608431b099d45dfe41
permissions -rw-r--r--
Bug 1278014 part.2 Define mozilla::SelectionType as an enum class and use it instead of RawSelectionType as far as possible r=smaug This patch defines mozilla::SelectionType as an enum class. This is safer than nsISelectionController::SELECTION_* since setting illegal value to its variable is checked at build time. So, as far as possible, this should be used everywhere (but of course, this isn't available in scriptable interfaces). And also this implements some useful methods for managing SelectionType and RawSelectionType which are implemented in layout/nsSelection.cpp because nsISelectionController is implemented by both PresShell and nsTextEditorState. Therefore, implementing one of them may make hard to find them. On the other hand, nsSelection.cpp is a better file name to look for them. Note that this patch creates mozilla::Selection::RawType() for binding. Native code should keep using Selection::Type() but the binding code needs to use RawType() due to impossible to convert from SelectionType to RawSelectionType without explicit cast. MozReview-Commit-ID: 81vX7A0hHQN

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WifiCertService.h"

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ModuleUtils.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/ToJSValue.h"
#include "cert.h"
#include "certdb.h"
#include "CryptoTask.h"
#include "nsIDOMBlob.h"
#include "nsIWifiService.h"
#include "nsNetUtil.h"
#include "nsIInputStream.h"
#include "nsServiceManagerUtils.h"
#include "nsXULAppAPI.h"
#include "ScopedNSSTypes.h"

#define NS_WIFICERTSERVICE_CID \
  { 0x83585afd, 0x0e11, 0x43aa, {0x83, 0x46, 0xf3, 0x4d, 0x97, 0x5e, 0x46, 0x77} }

using namespace mozilla;
using namespace mozilla::dom;

namespace mozilla {

// The singleton Wifi Cert service, to be used on the main thread.
StaticRefPtr<WifiCertService> gWifiCertService;

class ImportCertTask final: public CryptoTask
{
public:
  ImportCertTask(int32_t aId, Blob* aCertBlob,
                 const nsAString& aCertPassword,
                 const nsAString& aCertNickname)
    : mBlob(aCertBlob)
    , mPassword(aCertPassword)
  {
    MOZ_ASSERT(NS_IsMainThread());

    mResult.mId = aId;
    mResult.mStatus = 0;
    mResult.mUsageFlag = 0;
    mResult.mNickname = aCertNickname;
  }

private:
  virtual void ReleaseNSSResources() {}

  virtual nsresult CalculateResult() override
  {
    MOZ_ASSERT(!NS_IsMainThread());

    // read data from blob.
    nsCString blobBuf;
    nsresult rv = ReadBlob(blobBuf);
    if (NS_FAILED(rv)) {
      return rv;
    }

    char* buf;
    uint32_t size = blobBuf.GetMutableData(&buf);
    if (size == 0) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    // Try import as DER format first.
    rv = ImportDERBlob(buf, size);
    if (NS_SUCCEEDED(rv)) {
      return rv;
    }

    // Try import as PKCS#12 format.
    return ImportPKCS12Blob(buf, size, mPassword);
  }

  virtual void CallCallback(nsresult rv)
  {
    if (NS_FAILED(rv)) {
      mResult.mStatus = -1;
    }
    gWifiCertService->DispatchResult(mResult);
  }

  nsresult ImportDERBlob(char* buf, uint32_t size)
  {
    // Create certificate object.
    ScopedCERTCertificate cert(CERT_DecodeCertFromPackage(buf, size));
    if (!cert) {
      return MapSECStatus(SECFailure);
    }

    // Import certificate.
    return ImportCert(cert);
  }

  static SECItem*
  HandleNicknameCollision(SECItem* aOldNickname, PRBool* aCancel, void* aWincx)
  {
    const char* dummyName = "Imported User Cert";
    const size_t dummyNameLen = strlen(dummyName);
    SECItem* newNick = ::SECITEM_AllocItem(nullptr, nullptr, dummyNameLen + 1);
    if (!newNick) {
      return nullptr;
    }

    newNick->type = siAsciiString;
    // Dummy name, will be renamed later.
    memcpy(newNick->data, dummyName, dummyNameLen + 1);
    newNick->len = dummyNameLen;

    return newNick;
  }

  static SECStatus
  HandleNicknameUpdate(const CERTCertificate *aCert,
                       const SECItem *default_nickname,
                       SECItem **new_nickname,
                       void *arg)
  {
    WifiCertServiceResultOptions *result = (WifiCertServiceResultOptions *)arg;

    nsCString userNickname;
    CopyUTF16toUTF8(result->mNickname, userNickname);

    nsCString fullNickname;
    if (aCert->isRoot && (aCert->nsCertType & NS_CERT_TYPE_SSL_CA)) {
      // Accept self-signed SSL CA as server certificate.
      fullNickname.AssignLiteral("WIFI_SERVERCERT_");
      fullNickname += userNickname;
      result->mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_SERVER;
    } else if (aCert->nsCertType & NS_CERT_TYPE_SSL_CLIENT) {
      // User Certificate
      fullNickname.AssignLiteral("WIFI_USERCERT_");
      fullNickname += userNickname;
      result->mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_USER;
    }
    char* nickname;
    uint32_t length = fullNickname.GetMutableData(&nickname);

    SECItem* newNick = ::SECITEM_AllocItem(nullptr, nullptr, length + 1);
    if (!newNick) {
      return SECFailure;
    }

    newNick->type = siAsciiString;
    memcpy(newNick->data, nickname, length + 1);
    newNick->len = length;

    *new_nickname = newNick;
    return SECSuccess;
  }

  nsresult ImportPKCS12Blob(char* buf, uint32_t size, const nsAString& aPassword)
  {
    nsString password(aPassword);

    // password is null-terminated wide-char string.
    // passwordItem is required to be big-endian form of password, stored in char
    // array, including the null-termination.
    uint32_t length = password.Length() + 1;
    ScopedSECItem passwordItem(
      ::SECITEM_AllocItem(nullptr, nullptr, length * sizeof(nsString::char_type)));

    if (!passwordItem) {
      return NS_ERROR_FAILURE;
    }

    mozilla::NativeEndian::copyAndSwapToBigEndian(passwordItem->data,
                                                  password.BeginReading(),
                                                  length);
    // Create a decoder.
    ScopedSEC_PKCS12DecoderContext p12dcx(SEC_PKCS12DecoderStart(
                                            passwordItem, nullptr, nullptr,
                                            nullptr, nullptr, nullptr, nullptr,
                                            nullptr));

    if (!p12dcx) {
      return NS_ERROR_FAILURE;
    }

    // Assign data to decorder.
    SECStatus srv = SEC_PKCS12DecoderUpdate(p12dcx,
                                            reinterpret_cast<unsigned char*>(buf),
                                            size);
    if (srv != SECSuccess) {
      return MapSECStatus(srv);
    }

    // Verify certificates.
    srv = SEC_PKCS12DecoderVerify(p12dcx);
    if (srv != SECSuccess) {
      return MapSECStatus(srv);
    }

    // Set certificate nickname and usage flag.
    srv = SEC_PKCS12DecoderRenameCertNicknames(p12dcx, HandleNicknameUpdate,
                                               &mResult);

    // Validate certificates.
    srv = SEC_PKCS12DecoderValidateBags(p12dcx, HandleNicknameCollision);
    if (srv != SECSuccess) {
      return MapSECStatus(srv);
    }

    // Initialize slot.
    ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
    if (!slot) {
      return NS_ERROR_FAILURE;
    }
    if (PK11_NeedLogin(slot) && PK11_NeedUserInit(slot)) {
      srv = PK11_InitPin(slot, "", "");
      if (srv != SECSuccess) {
        return MapSECStatus(srv);
      }
    }

    // Import cert and key.
    srv = SEC_PKCS12DecoderImportBags(p12dcx);
    if (srv != SECSuccess) {
      return MapSECStatus(srv);
    }

    // User certificate must be imported from PKCS#12.
    return (mResult.mUsageFlag & nsIWifiCertService::WIFI_CERT_USAGE_FLAG_USER)
            ? NS_OK : NS_ERROR_FAILURE;
  }

  nsresult ReadBlob(/*out*/ nsCString& aBuf)
  {
    NS_ENSURE_ARG_POINTER(mBlob);

    static const uint64_t MAX_FILE_SIZE = 16384;

    ErrorResult rv;
    uint64_t size = mBlob->GetSize(rv);
    if (NS_WARN_IF(rv.Failed())) {
      return rv.StealNSResult();
    }

    if (size > MAX_FILE_SIZE) {
      return NS_ERROR_FILE_TOO_BIG;
    }

    nsCOMPtr<nsIInputStream> inputStream;
    mBlob->GetInternalStream(getter_AddRefs(inputStream), rv);
    if (NS_WARN_IF(rv.Failed())) {
      return rv.StealNSResult();
    }

    rv = NS_ReadInputStreamToString(inputStream, aBuf, (uint32_t)size);
    if (NS_WARN_IF(rv.Failed())) {
      return rv.StealNSResult();
    }

    return NS_OK;
  }

  nsresult ImportCert(CERTCertificate* aCert)
  {
    nsCString userNickname, fullNickname;

    CopyUTF16toUTF8(mResult.mNickname, userNickname);
    // Determine certificate nickname by adding prefix according to its type.
    if (aCert->isRoot && (aCert->nsCertType & NS_CERT_TYPE_SSL_CA)) {
      // Accept self-signed SSL CA as server certificate.
      fullNickname.AssignLiteral("WIFI_SERVERCERT_");
      fullNickname += userNickname;
      mResult.mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_SERVER;
    } else if (aCert->nsCertType & NS_CERT_TYPE_SSL_CLIENT) {
      // User Certificate
      fullNickname.AssignLiteral("WIFI_USERCERT_");
      fullNickname += userNickname;
      mResult.mUsageFlag |= nsIWifiCertService::WIFI_CERT_USAGE_FLAG_USER;
    } else {
      return NS_ERROR_ABORT;
    }

    char* nickname;
    uint32_t length;
    length = fullNickname.GetMutableData(&nickname);
    if (length == 0) {
      return NS_ERROR_UNEXPECTED;
    }

    // Import certificate, duplicated nickname will cause error.
    SECStatus srv = CERT_AddTempCertToPerm(aCert, nickname, nullptr);
    if (srv != SECSuccess) {
      return MapSECStatus(srv);
    }

    return NS_OK;
  }

  RefPtr<Blob> mBlob;
  nsString mPassword;
  WifiCertServiceResultOptions mResult;
};

class DeleteCertTask final: public CryptoTask
{
public:
  DeleteCertTask(int32_t aId, const nsAString& aCertNickname)
  {
    MOZ_ASSERT(NS_IsMainThread());

    mResult.mId = aId;
    mResult.mStatus = 0;
    mResult.mUsageFlag = 0;
    mResult.mNickname = aCertNickname;
  }

private:
  virtual void ReleaseNSSResources() {}

  virtual nsresult CalculateResult() override
  {
    MOZ_ASSERT(!NS_IsMainThread());

    nsCString userNickname;
    CopyUTF16toUTF8(mResult.mNickname, userNickname);

    // Delete server certificate.
    nsCString serverCertName("WIFI_SERVERCERT_", 16);
    serverCertName += userNickname;
    nsresult rv = deleteCert(serverCertName);
    if (NS_FAILED(rv)) {
      return rv;
    }

    // Delete user certificate and private key.
    nsCString userCertName("WIFI_USERCERT_", 14);
    userCertName += userNickname;
    rv = deleteCert(userCertName);
    if (NS_FAILED(rv)) {
      return rv;
    }

    return NS_OK;
  }

  nsresult deleteCert(const nsCString &aCertNickname)
  {
    ScopedCERTCertificate cert(
      CERT_FindCertByNickname(CERT_GetDefaultCertDB(), aCertNickname.get())
    );
    // Because we delete certificates in blind, so it's acceptable to delete
    // a non-exist certificate.
    if (!cert) {
      return NS_OK;
    }

    ScopedPK11SlotInfo slot(
      PK11_KeyForCertExists(cert, nullptr, nullptr)
    );

    SECStatus srv;
    if (slot) {
      // Delete private key along with certificate.
      srv = PK11_DeleteTokenCertAndKey(cert, nullptr);
    } else {
      srv = SEC_DeletePermCertificate(cert);
    }

    if (srv != SECSuccess) {
      return MapSECStatus(srv);
    }

    return NS_OK;
  }

  virtual void CallCallback(nsresult rv)
  {
    if (NS_FAILED(rv)) {
      mResult.mStatus = -1;
    }
    gWifiCertService->DispatchResult(mResult);
  }

  WifiCertServiceResultOptions mResult;
};

NS_IMPL_ISUPPORTS(WifiCertService, nsIWifiCertService)

NS_IMETHODIMP
WifiCertService::Start(nsIWifiEventListener* aListener)
{
  MOZ_ASSERT(aListener);

  mListener = aListener;

  return NS_OK;
}

NS_IMETHODIMP
WifiCertService::Shutdown()
{
  MOZ_ASSERT(NS_IsMainThread());

  mListener = nullptr;

  return NS_OK;
}

void
WifiCertService::DispatchResult(const WifiCertServiceResultOptions& aOptions)
{
  MOZ_ASSERT(NS_IsMainThread());

  mozilla::AutoSafeJSContext cx;
  JS::RootedValue val(cx);
  nsCString dummyInterface;

  if (!ToJSValue(cx, aOptions, &val)) {
    return;
  }

  // Certll the listener with a JS value.
  mListener->OnCommand(val, dummyInterface);
}

WifiCertService::WifiCertService()
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!gWifiCertService);
}

WifiCertService::~WifiCertService()
{
  MOZ_ASSERT(!gWifiCertService);

  nsNSSShutDownPreventionLock locker;
  if (isAlreadyShutDown()) {
    return;
  }
  shutdown(calledFromObject);
}

already_AddRefed<WifiCertService>
WifiCertService::FactoryCreate()
{
  if (!XRE_IsParentProcess()) {
    return nullptr;
  }

  MOZ_ASSERT(NS_IsMainThread());

  if (!gWifiCertService) {
    gWifiCertService = new WifiCertService();
    ClearOnShutdown(&gWifiCertService);
  }

  RefPtr<WifiCertService> service = gWifiCertService.get();
  return service.forget();
}

NS_IMETHODIMP
WifiCertService::ImportCert(int32_t aId, nsIDOMBlob* aCertBlob,
                            const nsAString& aCertPassword,
                            const nsAString& aCertNickname)
{
  RefPtr<Blob> blob = static_cast<Blob*>(aCertBlob);
  RefPtr<CryptoTask> task = new ImportCertTask(aId, blob, aCertPassword,
                                               aCertNickname);
  return task->Dispatch("WifiImportCert");
}

NS_IMETHODIMP
WifiCertService::DeleteCert(int32_t aId, const nsAString& aCertNickname)
{
  RefPtr<CryptoTask> task = new DeleteCertTask(aId, aCertNickname);
  return task->Dispatch("WifiDeleteCert");
}

NS_IMETHODIMP
WifiCertService::HasPrivateKey(const nsAString& aCertNickname, bool *aHasKey)
{
  *aHasKey = false;

  nsNSSShutDownPreventionLock locker;
  if (isAlreadyShutDown()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCString certNickname;
  CopyUTF16toUTF8(aCertNickname, certNickname);

  ScopedCERTCertificate cert(
    CERT_FindCertByNickname(CERT_GetDefaultCertDB(), certNickname.get())
  );
  if (!cert) {
    return NS_OK;
  }

  ScopedPK11SlotInfo slot(
    PK11_KeyForCertExists(cert, nullptr, nullptr)
  );
  if (slot) {
    *aHasKey = true;
  }

  return NS_OK;
}

NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WifiCertService,
                                         WifiCertService::FactoryCreate)

NS_DEFINE_NAMED_CID(NS_WIFICERTSERVICE_CID);

static const mozilla::Module::CIDEntry kWifiCertServiceCIDs[] = {
  { &kNS_WIFICERTSERVICE_CID, false, nullptr, WifiCertServiceConstructor },
  { nullptr }
};

static const mozilla::Module::ContractIDEntry kWifiCertServiceContracts[] = {
  { "@mozilla.org/wifi/certservice;1", &kNS_WIFICERTSERVICE_CID },
  { nullptr }
};

static const mozilla::Module kWifiCertServiceModule = {
  mozilla::Module::kVersion,
  kWifiCertServiceCIDs,
  kWifiCertServiceContracts,
  nullptr
};

} // namespace mozilla

NSMODULE_DEFN(WifiCertServiceModule) = &kWifiCertServiceModule;