Bug 1421501 - WebIDL and DOM for PrioEncoder r=edgar,hsivonen
authorRobert Helmer <rhelmer@mozilla.com>
Wed, 20 Jun 2018 17:21:17 -0700
changeset 481559 9b41ca132dbc6462cc5afbd52b06a9a9b5b00c12
parent 481558 1e6b919293b6075e306b013f76230ecef262d59d
child 481560 2622040931e64baff36decfec3cec21301f02270
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersedgar, hsivonen
bugs1421501
milestone63.0a1
Bug 1421501 - WebIDL and DOM for PrioEncoder r=edgar,hsivonen MozReview-Commit-ID: L8htRm3J1mZ
browser/app/profile/firefox.js
dom/bindings/moz.build
dom/chrome-webidl/PrioEncoder.webidl
dom/chrome-webidl/moz.build
dom/moz.build
dom/prio/PrioEncoder.cpp
dom/prio/PrioEncoder.h
dom/prio/moz.build
mfbt/TextUtils.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1739,8 +1739,13 @@ pref("app.shield.optoutstudies.enabled",
 #endif
 
 // Savant Shield study preferences
 pref("shield.savant.enabled", false);
 pref("shield.savant.loglevel", "warn");
 
 // Multi-lingual preferences
 pref("intl.multilingual.enabled", false);
+
+// Prio preferences
+// Curve25519 public keys for Prio servers
+pref("prio.publicKeyA", "35AC1C7576C7C6EDD7FED6BCFC337B34D48CB4EE45C86BEEFB40BD8875707733");
+pref("prio.publicKeyB", "26E6674E65425B823F1F1D5F96E3BB3EF9E406EC7FBA7DEF8B08A35DD135AF50");
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -90,16 +90,17 @@ LOCAL_INCLUDES += [
     '/layout/generic',
     '/layout/style',
     '/layout/xul/tree',
     '/media/mtransport',
     '/media/webrtc/',
     '/media/webrtc/signaling/src/common/time_profiling',
     '/media/webrtc/signaling/src/peerconnection',
     '/media/webrtc/trunk/',
+    '/third_party/msgpack/include',
 ]
 
 DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
 DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True
 
 UNIFIED_SOURCES += [
     'BindingUtils.cpp',
     'CallbackInterface.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/PrioEncoder.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[ChromeOnly, Exposed=(Window,System)]
+namespace PrioEncoder {
+  [Throws, NewObject]
+  Promise<PrioEncodedData> encode(ByteString batchID, PrioParams params);
+};
+
+dictionary PrioParams {
+  required boolean startupCrashDetected;
+  required boolean safeModeUsage;
+  required boolean browserIsUserDefault;
+};
+
+dictionary PrioEncodedData {
+  Uint8Array a;
+  Uint8Array b;
+};
\ No newline at end of file
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -38,16 +38,17 @@ WEBIDL_FILES = [
     'MatchPattern.webidl',
     'MessageManager.webidl',
     'MozDocumentObserver.webidl',
     'MozSharedMap.webidl',
     'MozStorageAsyncStatementParams.webidl',
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
+    'PrioEncoder.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
     'XULFrameElement.webidl',
     'XULScrollElement.webidl'
 ]
 
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -58,16 +58,17 @@ DIRS += [
     'jsurl',
     'asmjscache',
     'mathml',
     'media',
     'midi',
     'notification',
     'offline',
     'power',
+    'prio',
     'push',
     'quota',
     'security',
     'storage',
     'svg',
     'locales',
     'network',
     'permission',
new file mode 100644
--- /dev/null
+++ b/dom/prio/PrioEncoder.cpp
@@ -0,0 +1,180 @@
+/* -*- 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 "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Services.h"
+#include "mozilla/TextUtils.h"
+
+#include "PrioEncoder.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ StaticRefPtr<PrioEncoder> PrioEncoder::sSingleton;
+
+/* static */ PublicKey PrioEncoder::sPublicKeyA = nullptr;
+/* static */ PublicKey PrioEncoder::sPublicKeyB = nullptr;
+
+PrioEncoder::PrioEncoder() = default;
+PrioEncoder::~PrioEncoder()
+{
+  if (sPublicKeyA) {
+    PublicKey_clear(sPublicKeyA);
+    sPublicKeyA = nullptr;
+  }
+
+  if (sPublicKeyB) {
+    PublicKey_clear(sPublicKeyB);
+    sPublicKeyB = nullptr;
+  }
+
+  Prio_clear();
+}
+
+/* static */
+already_AddRefed<Promise>
+PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const PrioParams& aPrioParams, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+  if (!global) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  SECStatus prio_rv = SECSuccess;
+
+  if (!sSingleton) {
+    sSingleton = new PrioEncoder();
+
+    ClearOnShutdown(&sSingleton);
+
+    Prio_init();
+
+    nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyA;
+    nsresult rv = Preferences::GetCString("prio.publicKeyA", prioKeyA);
+    if (NS_FAILED(rv)) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyB;
+    rv = Preferences::GetCString("prio.publicKeyB", prioKeyB);
+    if (NS_FAILED(rv)) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    // Check that both public keys are of the right length
+    // and contain only hex digits 0-9a-fA-f
+    if (!PrioEncoder::IsValidHexPublicKey(prioKeyA)
+        || !PrioEncoder::IsValidHexPublicKey(prioKeyB))  {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    prio_rv = PublicKey_import_hex(&sPublicKeyA, reinterpret_cast<const unsigned char*>(prioKeyA.BeginReading()), CURVE25519_KEY_LEN_HEX);
+    if (prio_rv != SECSuccess) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    prio_rv = PublicKey_import_hex(&sPublicKeyB, reinterpret_cast<const unsigned char*>(prioKeyB.BeginReading()), CURVE25519_KEY_LEN_HEX);
+    if (prio_rv != SECSuccess) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+  }
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+
+  bool dataItems[] = {
+    aPrioParams.mStartupCrashDetected,
+    aPrioParams.mSafeModeUsage,
+    aPrioParams.mBrowserIsUserDefault
+  };
+
+  PrioConfig prioConfig = PrioConfig_new(mozilla::ArrayLength(dataItems), sPublicKeyA, sPublicKeyB, reinterpret_cast<const unsigned char*>(aBatchID.BeginReading()), aBatchID.Length());
+
+  if (!prioConfig) {
+    promise->MaybeReject(NS_ERROR_FAILURE);
+    return promise.forget();
+  }
+
+  auto configGuard = MakeScopeExit([&] {
+    PrioConfig_clear(prioConfig);
+  });
+
+  unsigned char* forServerA = nullptr;
+  unsigned int lenA = 0;
+  unsigned char* forServerB = nullptr;
+  unsigned int lenB = 0;
+
+  prio_rv = PrioClient_encode(prioConfig, dataItems, &forServerA, &lenA, &forServerB, &lenB);
+
+  // Package the data into the dictionary
+  PrioEncodedData data;
+
+  nsTArray<uint8_t> arrayForServerA;
+  nsTArray<uint8_t> arrayForServerB;
+
+  if (!arrayForServerA.AppendElements(reinterpret_cast<uint8_t*>(forServerA), lenA, fallible)) {
+    promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+    return promise.forget();
+  }
+
+  free(forServerA);
+
+  if (!arrayForServerB.AppendElements(reinterpret_cast<uint8_t*>(forServerB), lenB, fallible)) {
+    promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+    return promise.forget();
+  }
+
+  free(forServerB);
+
+  JS::Rooted<JS::Value> valueA(aGlobal.Context());
+  if (!ToJSValue(aGlobal.Context(), TypedArrayCreator<Uint8Array>(arrayForServerA), &valueA)) {
+    promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+    return promise.forget();
+  }
+  data.mA.Construct().Init(&valueA.toObject());
+
+  JS::Rooted<JS::Value> valueB(aGlobal.Context());
+  if (!ToJSValue(aGlobal.Context(), TypedArrayCreator<Uint8Array>(arrayForServerB), &valueB)) {
+    promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+    return promise.forget();
+  }
+  data.mB.Construct().Init(&valueB.toObject());
+
+  if (prio_rv != SECSuccess) {
+    promise->MaybeReject(NS_ERROR_FAILURE);
+    return promise.forget();
+  }
+
+  promise->MaybeResolve(data);
+
+  return promise.forget();
+}
+
+bool
+PrioEncoder::IsValidHexPublicKey(mozilla::Span<const char> aStr)
+{
+  if (aStr.Length() != CURVE25519_KEY_LEN_HEX) {
+    return false;
+  }
+
+  for (auto c : aStr) {
+    if (!IsAsciiHexDigit(c)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/prio/PrioEncoder.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PrioEncoder_h
+#define mozilla_dom_PrioEncoder_h
+
+#include "mozilla/dom/PrioEncoderBinding.h"
+
+#include "mprio.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class PrioEncoder
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(PrioEncoder)
+
+  static already_AddRefed<Promise>
+  Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const PrioParams& aPrioParams, ErrorResult& aRv);
+
+private:
+  PrioEncoder();
+  ~PrioEncoder();
+
+  static PublicKey
+  sPublicKeyA;
+
+  static PublicKey
+  sPublicKeyB;
+
+  static StaticRefPtr<PrioEncoder>
+  sSingleton;
+
+  static bool
+  IsValidHexPublicKey(mozilla::Span<const char>);
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_PrioEncoder_h
new file mode 100644
--- /dev/null
+++ b/dom/prio/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
+LOCAL_INCLUDES += [
+    '/third_party/msgpack/include'
+]
+
+EXPORTS.mozilla.dom += [
+    'PrioEncoder.h',
+]
+
+UNIFIED_SOURCES += [
+    'PrioEncoder.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
--- a/mfbt/TextUtils.h
+++ b/mfbt/TextUtils.h
@@ -116,16 +116,32 @@ constexpr bool
 IsAsciiDigit(Char aChar)
 {
   using UnsignedChar = typename detail::MakeUnsignedChar<Char>::Type;
   auto uc = static_cast<UnsignedChar>(aChar);
   return '0' <= uc && uc <= '9';
 }
 
 /**
+ * Returns true iff |aChar| matches [0-9a-fA-F].
+ *
+ * This function is basically isxdigit, but guaranteed to be only for ASCII.
+ */
+template<typename Char>
+constexpr bool
+IsAsciiHexDigit(Char aChar)
+{
+  using UnsignedChar = typename detail::MakeUnsignedChar<Char>::Type;
+  auto uc = static_cast<UnsignedChar>(aChar);
+  return ('0' <= uc && uc <= '9') ||
+         ('a' <= uc && uc <= 'f') ||
+         ('A' <= uc && uc <= 'F');
+}
+
+/**
  * Returns true iff |aChar| matches [a-zA-Z0-9].
  *
  * This function is basically what you thought isalnum was, except its behavior
  * doesn't depend on the user's current locale.
  */
 template<typename Char>
 constexpr bool
 IsAsciiAlphanumeric(Char aChar)