/* -*- 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/ScopeExit.h" #include "mozilla/TextUtils.h" #include "mozilla/dom/ToJSValue.h" #include "mprio.h" #include "PrioEncoder.h" namespace mozilla::dom { /* static */ StaticRefPtr PrioEncoder::sSingleton; /* static */ PublicKey PrioEncoder::sPublicKeyA = nullptr; /* static */ PublicKey PrioEncoder::sPublicKeyB = nullptr; // Production keys from bug 1552315 comment#3 /* static */ const char* kDefaultKeyA = "E780C1A9C50E3FC5A9B39469FCC92D62D2527BAE6AF76BBDEF128883FA400846"; /* static */ const char* kDefaultKeyB = "F992B575840AEC202289FBF99D6C04FB2A37B1DA1CDEB1DF8036E1340D46C561"; PrioEncoder::PrioEncoder() = default; PrioEncoder::~PrioEncoder() { if (sPublicKeyA) { PublicKey_clear(sPublicKeyA); sPublicKeyA = nullptr; } if (sPublicKeyB) { PublicKey_clear(sPublicKeyB); sPublicKeyB = nullptr; } Prio_clear(); } /* static */ void PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const PrioParams& aPrioParams, RootedDictionary& aData, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } nsCString aResult; nsCString bResult; nsresult rv = PrioEncoder::EncodeNative(aBatchID, aPrioParams.mBooleans, aResult, bResult); if (NS_FAILED(rv)) { aRv.Throw(rv); return; } nsTArray arrayForServerA; nsTArray arrayForServerB; if (!arrayForServerA.AppendElements( reinterpret_cast(aResult.BeginReading()), aResult.Length(), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } if (!arrayForServerB.AppendElements( reinterpret_cast(bResult.BeginReading()), bResult.Length(), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } JS::Rooted valueA(aGlobal.Context()); if (!ToJSValue(aGlobal.Context(), TypedArrayCreator(arrayForServerA), &valueA)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } aData.mA.Construct().Init(&valueA.toObject()); JS::Rooted valueB(aGlobal.Context()); if (!ToJSValue(aGlobal.Context(), TypedArrayCreator(arrayForServerB), &valueB)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } aData.mB.Construct().Init(&valueB.toObject()); } /* static */ nsresult PrioEncoder::EncodeNative(const nsCString& aBatchID, const nsTArray& aData, nsCString& aResult, nsCString& bResult) { SECStatus prio_rv = SECSuccess; nsresult rv = PrioEncoder::LazyInitSingleton(); if (NS_FAILED(rv)) { return rv; } if (aData.Length() > gNumBooleans) { return NS_ERROR_INVALID_ARG; } PrioConfig prioConfig = PrioConfig_new( aData.Length(), sPublicKeyA, sPublicKeyB, reinterpret_cast(aBatchID.BeginReading()), aBatchID.Length()); if (!prioConfig) { return NS_ERROR_FAILURE; } 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, aData.Elements(), &forServerA, &lenA, &forServerB, &lenB); aResult.Adopt(reinterpret_cast(forServerA), lenA); bResult.Adopt(reinterpret_cast(forServerB), lenB); if (prio_rv != SECSuccess) { return NS_ERROR_FAILURE; } return NS_OK; } bool PrioEncoder::IsValidHexPublicKey(mozilla::Span aStr) { if (aStr.Length() != CURVE25519_KEY_LEN_HEX) { return false; } for (auto c : aStr) { if (!IsAsciiHexDigit(c)) { return false; } } return true; } /* static */ nsresult PrioEncoder::SetKeys(const char* aKeyA, const char* aKeyB) { nsAutoCStringN prioKeyA; if (aKeyA == nullptr) { prioKeyA = kDefaultKeyA; } else { prioKeyA = aKeyA; } nsAutoCStringN prioKeyB; if (aKeyB == nullptr) { prioKeyB = kDefaultKeyB; } else { prioKeyB = aKeyB; } // 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)) { return NS_ERROR_UNEXPECTED; } SECStatus prio_rv = SECSuccess; prio_rv = Prio_init(); if (prio_rv != SECSuccess) { return NS_ERROR_UNEXPECTED; } prio_rv = PublicKey_import_hex( &sPublicKeyA, reinterpret_cast(prioKeyA.BeginReading()), CURVE25519_KEY_LEN_HEX); if (prio_rv != SECSuccess) { return NS_ERROR_UNEXPECTED; } prio_rv = PublicKey_import_hex( &sPublicKeyB, reinterpret_cast(prioKeyB.BeginReading()), CURVE25519_KEY_LEN_HEX); if (prio_rv != SECSuccess) { return NS_ERROR_UNEXPECTED; } return NS_OK; } /* static */ nsresult PrioEncoder::LazyInitSingleton() { if (!sSingleton) { // Init to the default keys. nsresult rv = PrioEncoder::SetKeys(); if (!NS_SUCCEEDED(rv)) { return rv; } sSingleton = new PrioEncoder(); ClearOnShutdown(&sSingleton); } return NS_OK; } } // namespace mozilla::dom