diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/wifi/WifiCertService.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/wifi/WifiCertService.cpp')
-rw-r--r-- | dom/wifi/WifiCertService.cpp | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/dom/wifi/WifiCertService.cpp b/dom/wifi/WifiCertService.cpp new file mode 100644 index 000000000..85e0d2f5b --- /dev/null +++ b/dom/wifi/WifiCertService.cpp @@ -0,0 +1,536 @@ +/* -*- 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(ShutdownCalledFrom::Object); +} + +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; |