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"
11 #include "nsIPrincipal.h"
13 #include "nsNetUtil.h"
14 #include "nsContentUtils.h"
15 #include "xpcpublic.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"
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
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.
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
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
);
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
);
75 // It's fine to capture [this] by pointer because we are always static.
76 if (NS_IsMainThread()) {
77 RunOnShutdown([this] { mKey
= nullptr; });
79 NS_DispatchToMainThread(NS_NewRunnableFunction(
80 "ClearStaticCachedPublicKey",
81 [this] { RunOnShutdown([this] { mKey
= nullptr; }); }));
88 bool VerifySignature(const uint8_t* aSignature
, uintptr_t aSignatureLen
,
89 const uint8_t* aData
, uintptr_t aDataLen
,
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?");
105 if (NS_WARN_IF(aDataLen
> UINT_MAX
)) {
106 LOG(" Way too large data.");
110 const SECItem signature
{siBuffer
, const_cast<unsigned char*>(aSignature
),
111 unsigned(aSignatureLen
)};
112 const SECItem data
{siBuffer
, const_cast<unsigned char*>(aData
),
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.");
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
),
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.
138 auto* principal
= static_cast<nsIPrincipal
*>(aUserData
);
139 nsCOMPtr
<nsIURI
> originURI
;
140 if (NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(originURI
), origin
)))) {
144 const bool originMatches
= [&] {
145 if (principal
->IsSameOrigin(originURI
)) {
149 for (nsCOMPtr
<nsIPrincipal
> prin
= principal
->GetNextSubDomainPrincipal();
150 prin
; prin
= prin
->GetNextSubDomainPrincipal()) {
151 if (prin
->IsSameOrigin(originURI
)) {
159 if (NS_WARN_IF(!originMatches
)) {
160 LOG("Origin doesn't match\n");
167 void OriginTrials::UpdateFromToken(const nsAString
& aBase64EncodedToken
,
168 nsIPrincipal
* aPrincipal
) {
169 if (!StaticPrefs::dom_origin_trials_enabled()) {
173 LOG("OriginTrials::UpdateFromToken()\n");
175 nsAutoCString decodedToken
;
176 nsresult rv
= mozilla::Base64Decode(aBase64EncodedToken
, decodedToken
);
177 if (NS_WARN_IF(NS_FAILED(rv
))) {
181 const Span
<const uint8_t> decodedTokenSpan(decodedToken
);
182 const origin_trials_ffi::OriginTrialValidationParams params
{
185 /* user_data = */ aPrincipal
,
187 auto result
= origin_trials_ffi::origin_trials_parse_and_validate_token(
188 decodedTokenSpan
.data(), decodedTokenSpan
.size(), ¶ms
);
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
) {
202 const dom::Document
* doc
= aWindow
->GetExtantDoc();
206 return doc
->Trials();
209 static int32_t PrefState(OriginTrial 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!");
222 bool OriginTrials::IsEnabled(OriginTrial aTrial
) const {
223 switch (PrefState(aTrial
)) {
232 return mEnabledTrials
.contains(aTrial
);
235 bool OriginTrials::IsEnabled(JSContext
* aCx
, JSObject
* aObject
,
236 OriginTrial aTrial
) {
237 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx
)) {
240 LOG("OriginTrials::IsEnabled(%d)\n", int(aTrial
));
241 nsIGlobalObject
* global
= xpc::CurrentNativeGlobal(aCx
);
243 return global
&& global
->Trials().IsEnabled(aTrial
);
248 } // namespace mozilla