summaryrefslogtreecommitdiffstats
path: root/dom/wifi/WifiCertService.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/wifi/WifiCertService.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-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.cpp536
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;