Bug 1421501 - WebIDL and DOM for PrioEncoder r=edgar,hsivonen
authorRobert Helmer <rhelmer@mozilla.com>
Wed, 20 Jun 2018 17:21:17 -0700
changeset 433174 9b41ca132dbc6462cc5afbd52b06a9a9b5b00c12
parent 433173 1e6b919293b6075e306b013f76230ecef262d59d
child 433175 2622040931e64baff36decfec3cec21301f02270
push id34500
push usertoros@mozilla.com
push dateFri, 24 Aug 2018 09:42:33 +0000
treeherdermozilla-central@043aff7fda61 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersedgar, hsivonen
bugs1421501
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)