Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / dom / origin-trials / OriginTrials.cpp
blob9f9e6b44fee05367ff2322b70e2655ac61364dc0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "OriginTrials.h"
8 #include "mozilla/Base64.h"
9 #include "mozilla/Span.h"
10 #include "nsString.h"
11 #include "nsIPrincipal.h"
12 #include "nsIURI.h"
13 #include "nsNetUtil.h"
14 #include "nsContentUtils.h"
15 #include "xpcpublic.h"
16 #include "jsapi.h"
17 #include "js/Wrapper.h"
18 #include "nsGlobalWindowInner.h"
19 #include "mozilla/ClearOnShutdown.h"
20 #include "mozilla/dom/Document.h"
21 #include "mozilla/dom/WorkerPrivate.h"
22 #include "mozilla/dom/WorkletThread.h"
23 #include "mozilla/dom/WebCryptoCommon.h"
24 #include "mozilla/StaticPrefs_dom.h"
25 #include "ScopedNSSTypes.h"
26 #include <mutex>
28 namespace mozilla {
30 LazyLogModule sOriginTrialsLog("OriginTrials");
31 #define LOG(...) MOZ_LOG(sOriginTrialsLog, LogLevel::Debug, (__VA_ARGS__))
33 // prod.pub is the EcdsaP256 public key from the production key managed in
34 // Google Cloud. See:
36 // https://github.com/mozilla/origin-trial-token/blob/main/tools/README.md#get-the-public-key
38 // for how to get the public key.
40 // See also:
42 // https://github.com/mozilla/origin-trial-token/blob/main/tools/README.md#sign-a-token-using-gcloud
44 // for how to sign using this key.
46 // test.pub is the EcdsaP256 public key from this key pair:
48 // * https://github.com/mozilla/origin-trial-token/blob/64f03749e2e8c58f811f67044cecc7d6955fd51a/tools/test-keys/test-ecdsa.pkcs8
49 // * https://github.com/mozilla/origin-trial-token/blob/64f03749e2e8c58f811f67044cecc7d6955fd51a/tools/test-keys/test-ecdsa.pub
51 #include "keys.inc"
53 constexpr auto kEcAlgorithm =
54 NS_LITERAL_STRING_FROM_CSTRING(WEBCRYPTO_NAMED_CURVE_P256);
56 using RawKeyRef = Span<const unsigned char, sizeof(kProdKey)>;
58 struct StaticCachedPublicKey {
59 constexpr StaticCachedPublicKey() = default;
61 SECKEYPublicKey* Get(const RawKeyRef aRawKey);
63 private:
64 std::once_flag mFlag;
65 UniqueSECKEYPublicKey mKey;
68 SECKEYPublicKey* StaticCachedPublicKey::Get(const RawKeyRef aRawKey) {
69 std::call_once(mFlag, [&] {
70 const SECItem item{siBuffer, const_cast<unsigned char*>(aRawKey.data()),
71 unsigned(aRawKey.Length())};
72 MOZ_RELEASE_ASSERT(item.data[0] == EC_POINT_FORM_UNCOMPRESSED);
73 mKey = dom::CreateECPublicKey(&item, kEcAlgorithm);
74 if (mKey) {
75 // It's fine to capture [this] by pointer because we are always static.
76 if (NS_IsMainThread()) {
77 RunOnShutdown([this] { mKey = nullptr; });
78 } else {
79 NS_DispatchToMainThread(NS_NewRunnableFunction(
80 "ClearStaticCachedPublicKey",
81 [this] { RunOnShutdown([this] { mKey = nullptr; }); }));
84 });
85 return mKey.get();
88 bool VerifySignature(const uint8_t* aSignature, uintptr_t aSignatureLen,
89 const uint8_t* aData, uintptr_t aDataLen,
90 void* aUserData) {
91 MOZ_RELEASE_ASSERT(aSignatureLen == 64);
92 static StaticCachedPublicKey sTestKey;
93 static StaticCachedPublicKey sProdKey;
95 LOG("VerifySignature()\n");
97 SECKEYPublicKey* pubKey = StaticPrefs::dom_origin_trials_test_key_enabled()
98 ? sTestKey.Get(Span(kTestKey))
99 : sProdKey.Get(Span(kProdKey));
100 if (NS_WARN_IF(!pubKey)) {
101 LOG(" Failed to create public key?");
102 return false;
105 if (NS_WARN_IF(aDataLen > UINT_MAX)) {
106 LOG(" Way too large data.");
107 return false;
110 const SECItem signature{siBuffer, const_cast<unsigned char*>(aSignature),
111 unsigned(aSignatureLen)};
112 const SECItem data{siBuffer, const_cast<unsigned char*>(aData),
113 unsigned(aDataLen)};
115 // SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE
116 const SECStatus result = PK11_VerifyWithMechanism(
117 pubKey, CKM_ECDSA_SHA256, nullptr, &signature, &data, nullptr);
118 if (NS_WARN_IF(result != SECSuccess)) {
119 LOG(" Failed to verify data.");
120 return false;
122 return true;
125 bool MatchesOrigin(const uint8_t* aOrigin, size_t aOriginLen, bool aIsSubdomain,
126 bool aIsThirdParty, bool aIsUsageSubset, void* aUserData) {
127 const nsDependentCSubstring origin(reinterpret_cast<const char*>(aOrigin),
128 aOriginLen);
130 LOG("MatchesOrigin(%d, %d, %d, %s)\n", aIsThirdParty, aIsSubdomain,
131 aIsUsageSubset, nsCString(origin).get());
133 if (aIsThirdParty || aIsUsageSubset) {
134 // TODO(emilio): Support third-party tokens and so on.
135 return false;
138 auto* principal = static_cast<nsIPrincipal*>(aUserData);
139 nsCOMPtr<nsIURI> originURI;
140 if (NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(originURI), origin)))) {
141 return false;
144 const bool originMatches = [&] {
145 if (principal->IsSameOrigin(originURI)) {
146 return true;
148 if (aIsSubdomain) {
149 for (nsCOMPtr<nsIPrincipal> prin = principal->GetNextSubDomainPrincipal();
150 prin; prin = prin->GetNextSubDomainPrincipal()) {
151 if (prin->IsSameOrigin(originURI)) {
152 return true;
156 return false;
157 }();
159 if (NS_WARN_IF(!originMatches)) {
160 LOG("Origin doesn't match\n");
161 return false;
164 return true;
167 void OriginTrials::UpdateFromToken(const nsAString& aBase64EncodedToken,
168 nsIPrincipal* aPrincipal) {
169 if (!StaticPrefs::dom_origin_trials_enabled()) {
170 return;
173 LOG("OriginTrials::UpdateFromToken()\n");
175 nsAutoCString decodedToken;
176 nsresult rv = mozilla::Base64Decode(aBase64EncodedToken, decodedToken);
177 if (NS_WARN_IF(NS_FAILED(rv))) {
178 return;
181 const Span<const uint8_t> decodedTokenSpan(decodedToken);
182 const origin_trials_ffi::OriginTrialValidationParams params{
183 VerifySignature,
184 MatchesOrigin,
185 /* user_data = */ aPrincipal,
187 auto result = origin_trials_ffi::origin_trials_parse_and_validate_token(
188 decodedTokenSpan.data(), decodedTokenSpan.size(), &params);
189 if (NS_WARN_IF(!result.IsOk())) {
190 LOG(" result = %d\n", int(result.tag));
191 return; // TODO(emilio): Maybe report to console or what not?
193 OriginTrial trial = result.AsOk().trial;
194 LOG(" result = Ok(%d)\n", int(trial));
195 mEnabledTrials += trial;
198 OriginTrials OriginTrials::FromWindow(const nsGlobalWindowInner* aWindow) {
199 if (!aWindow) {
200 return {};
202 const dom::Document* doc = aWindow->GetExtantDoc();
203 if (!doc) {
204 return {};
206 return doc->Trials();
209 static int32_t PrefState(OriginTrial aTrial) {
210 switch (aTrial) {
211 case OriginTrial::TestTrial:
212 return StaticPrefs::dom_origin_trials_test_trial_state();
213 case OriginTrial::CoepCredentialless:
214 return StaticPrefs::dom_origin_trials_coep_credentialless_state();
215 case OriginTrial::MAX:
216 MOZ_ASSERT_UNREACHABLE("Unknown trial!");
217 break;
219 return 0;
222 bool OriginTrials::IsEnabled(OriginTrial aTrial) const {
223 switch (PrefState(aTrial)) {
224 case 1:
225 return true;
226 case 2:
227 return false;
228 default:
229 break;
232 return mEnabledTrials.contains(aTrial);
235 bool OriginTrials::IsEnabled(JSContext* aCx, JSObject* aObject,
236 OriginTrial aTrial) {
237 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
238 return true;
240 LOG("OriginTrials::IsEnabled(%d)\n", int(aTrial));
241 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
242 MOZ_ASSERT(global);
243 return global && global->Trials().IsEnabled(aTrial);
246 #undef LOG
248 } // namespace mozilla