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 "mozilla/dom/MLS.h"
8 #include "mozilla/dom/MLSGroupView.h"
9 #include "mozilla/dom/TypedArray.h"
10 #include "mozilla/dom/Promise.h"
13 #include "nsIGlobalObject.h"
14 #include "mozilla/ipc/PBackgroundChild.h"
15 #include "mozilla/ipc/BackgroundChild.h"
16 #include "mozilla/dom/MLSTransactionChild.h"
17 #include "mozilla/dom/MLSTransactionMessage.h"
18 #include "mozilla/dom/PMLSTransaction.h"
19 #include "mozilla/ipc/Endpoint.h"
20 #include "mozilla/BasePrincipal.h"
21 #include "MLSGroupView.h"
23 #include "mozilla/Logging.h"
24 #include "mozilla/Span.h"
26 #include "MLSLogging.h"
27 #include "MLSTypeUtils.h"
29 namespace mozilla::dom
{
31 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MLS
, mGlobalObject
)
33 NS_IMPL_CYCLE_COLLECTING_ADDREF(MLS
)
34 NS_IMPL_CYCLE_COLLECTING_RELEASE(MLS
)
36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MLS
)
37 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
38 NS_INTERFACE_MAP_ENTRY(nsISupports
)
42 mozilla::LazyLogModule
gMlsLog("MLS");
44 /* static */ already_AddRefed
<MLS
> MLS::Constructor(GlobalObject
& aGlobalObject
,
46 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::Constructor()"));
48 nsCOMPtr
<nsIGlobalObject
> global(
49 do_QueryInterface(aGlobalObject
.GetAsSupports()));
50 if (NS_WARN_IF(!global
)) {
51 aRv
.Throw(NS_ERROR_FAILURE
);
55 // Get the principal and perform some validation on it.
56 // We do not allow MLS in Private Browsing Mode for now.
57 nsIPrincipal
* principal
= global
->PrincipalOrNull();
58 if (!principal
|| !principal
->GetIsContentPrincipal() ||
59 principal
->GetIsInPrivateBrowsing()) {
60 aRv
.ThrowSecurityError("Cannot create MLS store for origin");
64 // Create the endpoints for the MLS actor
65 mozilla::ipc::Endpoint
<PMLSTransactionParent
> parentEndpoint
;
66 mozilla::ipc::Endpoint
<PMLSTransactionChild
> childEndpoint
;
68 PMLSTransaction::CreateEndpoints(&parentEndpoint
, &childEndpoint
));
70 mozilla::ipc::PBackgroundChild
* backgroundChild
=
71 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
72 if (!backgroundChild
) {
73 aRv
.Throw(NS_ERROR_UNEXPECTED
);
77 // Bind the child actor, and send the parent endpoint.
78 RefPtr
<MLSTransactionChild
> actor
= new MLSTransactionChild();
79 MOZ_ALWAYS_TRUE(childEndpoint
.Bind(actor
));
81 MOZ_ALWAYS_TRUE(backgroundChild
->SendCreateMLSTransaction(
82 std::move(parentEndpoint
), WrapNotNull(principal
)));
84 return MakeAndAddRef
<MLS
>(global
, actor
);
87 MLS::MLS(nsIGlobalObject
* aGlobalObject
, MLSTransactionChild
* aActor
)
88 : mGlobalObject(aGlobalObject
), mTransactionChild(aActor
) {
89 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::MLS()"));
93 if (mTransactionChild
) {
94 mTransactionChild
->Close();
98 JSObject
* MLS::WrapObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
99 return MLS_Binding::Wrap(aCx
, this, aGivenProto
);
106 already_AddRefed
<Promise
> MLS::DeleteState(ErrorResult
& aRv
) {
107 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::DeleteState()"));
109 // Create a new Promise object for the result
110 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
111 if (NS_WARN_IF(aRv
.Failed())) {
115 mTransactionChild
->SendRequestStateDelete(
116 [promise
](bool result
) {
118 promise
->MaybeResolveWithUndefined();
120 promise
->MaybeReject(NS_ERROR_FAILURE
);
123 [promise
](::mozilla::ipc::ResponseRejectReason
) {
124 promise
->MaybeRejectWithUnknownError("deleteState failed");
127 return promise
.forget();
130 already_AddRefed
<Promise
> MLS::GenerateIdentity(ErrorResult
& aRv
) {
131 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::GenerateIdentity()"));
133 // Create a new Promise object for the result
134 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
135 if (NS_WARN_IF(aRv
.Failed())) {
139 mTransactionChild
->SendRequestGenerateIdentityKeypair()->Then(
140 GetCurrentSerialEventTarget(), __func__
,
141 [promise
, self
= RefPtr
{this}](Maybe
<RawBytes
>&& result
) {
142 // Check if the value is Nothing
143 if (result
.isNothing()) {
144 promise
->MaybeRejectWithUnknownError(
145 "generateIdentityKeypair failed");
149 // Get the context from the GlobalObject
151 if (NS_WARN_IF(!jsapi
.Init(self
->mGlobalObject
))) {
152 promise
->MaybeRejectWithUnknownError(
153 "generateIdentityKeypair failed");
156 JSContext
* cx
= jsapi
.cx();
158 // Construct the Uint8Array object
160 JS::Rooted
<JSObject
*> content(
161 cx
, Uint8Array::Create(cx
, result
->data(), error
));
162 error
.WouldReportJSException();
163 if (error
.Failed()) {
164 promise
->MaybeReject(std::move(error
));
168 // Construct MLSBytes with the client identifer as content
169 RootedDictionary
<MLSBytes
> rvalue(cx
);
170 rvalue
.mType
= MLSObjectType::Client_identifier
;
171 rvalue
.mContent
.Init(content
);
173 // Resolve the promise with the MLSBytes object
174 promise
->MaybeResolve(rvalue
);
176 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
177 promise
->MaybeRejectWithUnknownError("generateIdentity failed");
180 return promise
.forget();
183 already_AddRefed
<Promise
> MLS::GenerateCredential(
184 const MLSBytesOrUint8ArrayOrUTF8String
& aJsCredContent
, ErrorResult
& aRv
) {
185 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
,
186 ("MLS::GenerateCredentialBasic()"));
188 // Handle the credential content parameter
189 nsTArray
<uint8_t> credContent
= ExtractMLSBytesOrUint8ArrayOrUTF8String(
190 MLSObjectType::Credential_basic
, aJsCredContent
, aRv
);
191 if (NS_WARN_IF(aRv
.Failed())) {
195 // Check if the credContent is empty
196 if (NS_WARN_IF(credContent
.IsEmpty())) {
197 aRv
.ThrowTypeError("The credential content must not be empty");
201 // Create a new Promise object for the result
202 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
203 if (NS_WARN_IF(aRv
.Failed())) {
207 mTransactionChild
->SendRequestGenerateCredentialBasic(credContent
)
209 GetCurrentSerialEventTarget(), __func__
,
210 [promise
, self
= RefPtr
{this}](Maybe
<RawBytes
>&& result
) {
211 // Check if the value is Nothing
212 if (result
.isNothing()) {
213 promise
->MaybeRejectWithUnknownError(
214 "generateCredentialBasic failed");
218 // Get the context from the GlobalObject
220 if (NS_WARN_IF(!jsapi
.Init(self
->mGlobalObject
))) {
221 promise
->MaybeRejectWithUnknownError(
222 "generateCredentialBasic failed");
225 JSContext
* cx
= jsapi
.cx();
227 // Construct the Uint8Array object
229 JS::Rooted
<JSObject
*> content(
230 cx
, Uint8Array::Create(cx
, result
->data(), error
));
231 error
.WouldReportJSException();
232 if (error
.Failed()) {
233 promise
->MaybeReject(std::move(error
));
237 // Construct MLSBytes with the client identifer as content
238 RootedDictionary
<MLSBytes
> rvalue(cx
);
239 rvalue
.mType
= MLSObjectType::Credential_basic
;
240 rvalue
.mContent
.Init(content
);
242 // Resolve the promise
243 promise
->MaybeResolve(rvalue
);
245 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
246 promise
->MaybeRejectWithUnknownError(
247 "generateCredentialBasic failed");
250 return promise
.forget();
253 already_AddRefed
<Promise
> MLS::GenerateKeyPackage(
254 const MLSBytesOrUint8Array
& aJsClientIdentifier
,
255 const MLSBytesOrUint8Array
& aJsCredential
, ErrorResult
& aRv
) {
256 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::GenerateKeyPackage()"));
258 // Handle the client identifier parameter
259 nsTArray
<uint8_t> clientIdentifier
= ExtractMLSBytesOrUint8Array(
260 MLSObjectType::Client_identifier
, aJsClientIdentifier
, aRv
);
261 if (NS_WARN_IF(aRv
.Failed())) {
265 // Check if the client identifier is empty
266 if (NS_WARN_IF(clientIdentifier
.IsEmpty())) {
267 aRv
.ThrowTypeError("The client identifier must not be empty");
271 // Handle the credential parameter
272 nsTArray
<uint8_t> credential
= ExtractMLSBytesOrUint8Array(
273 MLSObjectType::Credential_basic
, aJsCredential
, aRv
);
274 if (NS_WARN_IF(aRv
.Failed())) {
278 // Check if the credential is empty
279 if (NS_WARN_IF(credential
.IsEmpty())) {
280 aRv
.ThrowTypeError("The credential must not be empty");
284 // Create a new Promise object for the result
285 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
286 if (NS_WARN_IF(aRv
.Failed())) {
290 // Use the static method or instance to send the IPC message
291 mTransactionChild
->SendRequestGenerateKeyPackage(clientIdentifier
, credential
)
293 GetCurrentSerialEventTarget(), __func__
,
294 [promise
, self
= RefPtr
{this}](Maybe
<RawBytes
>&& keyPackage
) {
295 // Check if the value is Nothing
296 if (keyPackage
.isNothing()) {
297 promise
->MaybeReject(NS_ERROR_FAILURE
);
301 // Get the context from the GlobalObject
303 if (NS_WARN_IF(!jsapi
.Init(self
->mGlobalObject
))) {
304 promise
->MaybeReject(NS_ERROR_FAILURE
);
307 JSContext
* cx
= jsapi
.cx();
309 // Construct the Uint8Array object
311 JS::Rooted
<JSObject
*> content(
312 cx
, Uint8Array::Create(cx
, keyPackage
->data(), error
));
313 error
.WouldReportJSException();
314 if (error
.Failed()) {
315 promise
->MaybeReject(std::move(error
));
319 // Construct MLSBytes with the client identifer as content
320 RootedDictionary
<MLSBytes
> rvalue(cx
);
321 rvalue
.mType
= MLSObjectType::Key_package
;
322 rvalue
.mContent
.Init(content
);
324 // Resolve the promise
325 promise
->MaybeResolve(rvalue
);
327 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
328 promise
->MaybeRejectWithUnknownError("generateKeyPackage failed");
331 return promise
.forget();
334 already_AddRefed
<Promise
> MLS::GroupCreate(
335 const MLSBytesOrUint8Array
& aJsClientIdentifier
,
336 const MLSBytesOrUint8Array
& aJsCredential
, ErrorResult
& aRv
) {
337 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::GroupCreate()"));
339 // Handle the client identifier parameter
340 nsTArray
<uint8_t> clientIdentifier
= ExtractMLSBytesOrUint8Array(
341 MLSObjectType::Client_identifier
, aJsClientIdentifier
, aRv
);
342 if (NS_WARN_IF(aRv
.Failed())) {
346 // Check if the client identifier is empty
347 if (NS_WARN_IF(clientIdentifier
.IsEmpty())) {
348 aRv
.ThrowTypeError("The client identifier must not be empty");
352 // Handle the credential parameter
353 nsTArray
<uint8_t> credential
= ExtractMLSBytesOrUint8Array(
354 MLSObjectType::Credential_basic
, aJsCredential
, aRv
);
355 if (NS_WARN_IF(aRv
.Failed())) {
359 // Check if the credential is empty
360 if (NS_WARN_IF(credential
.IsEmpty())) {
361 aRv
.ThrowTypeError("The credential must not be empty");
365 // Log the hex of clientIdentifier
366 if (MOZ_LOG_TEST(gMlsLog
, LogLevel::Debug
)) {
367 nsAutoCString clientIdHex
;
368 for (uint8_t byte
: clientIdentifier
) {
369 clientIdHex
.AppendPrintf("%02X", byte
);
371 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
,
372 ("clientIdentifier in hex: %s\n", clientIdHex
.get()));
375 // Initialize jsGroupIdentifier to one byte of value 0xFF.
376 // We do not want to allow choosing the GID at this point.
377 // This value not being of the correct length will be discarded
378 // internally and a fresh GID will be generated.
380 // In the future, the caller will allow choosing the GID.
381 AutoTArray
<uint8_t, 1> groupIdentifier
{0xFF};
383 // Create a new Promise object for the result
384 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
385 if (NS_WARN_IF(aRv
.Failed())) {
389 // Use the static method or instance to send the IPC message
391 ->SendRequestGroupCreate(clientIdentifier
, credential
, groupIdentifier
)
393 GetCurrentSerialEventTarget(), __func__
,
394 [promise
, self
= RefPtr
{this},
395 clientIdentifier(std::move(clientIdentifier
))](
396 Maybe
<mozilla::security::mls::GkGroupIdEpoch
>&&
397 groupIdEpoch
) mutable {
398 // Check if the value is Nothing
399 if (groupIdEpoch
.isNothing()) {
400 promise
->MaybeReject(NS_ERROR_FAILURE
);
404 RefPtr
<MLSGroupView
> group
=
405 new MLSGroupView(self
, std::move(groupIdEpoch
->group_id
),
406 std::move(clientIdentifier
));
408 promise
->MaybeResolve(group
);
410 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
411 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Error
,
412 ("IPC message rejected with reason: %d",
413 static_cast<int>(aReason
)));
414 promise
->MaybeRejectWithUnknownError("groupCreate failed");
417 return promise
.forget();
420 already_AddRefed
<mozilla::dom::Promise
> MLS::GroupGet(
421 const MLSBytesOrUint8Array
& aJsGroupIdentifier
,
422 const MLSBytesOrUint8Array
& aJsClientIdentifier
, ErrorResult
& aRv
) {
423 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::GroupGet()"));
425 // Handle the group identifier parameter
426 nsTArray
<uint8_t> groupIdentifier
= ExtractMLSBytesOrUint8Array(
427 MLSObjectType::Group_identifier
, aJsGroupIdentifier
, aRv
);
428 if (NS_WARN_IF(aRv
.Failed())) {
432 // Check if the group identifier is empty
433 if (NS_WARN_IF(groupIdentifier
.IsEmpty())) {
434 aRv
.ThrowTypeError("The group identifier must not be empty");
438 // Handle the client identifier parameter
439 nsTArray
<uint8_t> clientIdentifier
= ExtractMLSBytesOrUint8Array(
440 MLSObjectType::Client_identifier
, aJsClientIdentifier
, aRv
);
441 if (NS_WARN_IF(aRv
.Failed())) {
445 // Check if the client identifier is empty
446 if (NS_WARN_IF(clientIdentifier
.IsEmpty())) {
447 aRv
.ThrowTypeError("The client identifier must not be empty");
451 // Create a new Promise object for the result
452 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
453 if (NS_WARN_IF(aRv
.Failed())) {
457 // Initialize label, context and len
458 // We pass this through IPC to be able to reuse the same code for different
459 // labels in the future
460 AutoTArray
<uint8_t, 7> label
{'l', 'i', 'v', 'e', 'n', 'e', 's', 's'};
461 AutoTArray
<uint8_t, 1> context
{0x00};
464 // Use the static method or instance to send the IPC message
466 ->SendRequestExportSecret(groupIdentifier
, clientIdentifier
, label
,
469 GetCurrentSerialEventTarget(), __func__
,
470 [promise
, self
= RefPtr
{this},
471 groupIdentifier(std::move(groupIdentifier
)),
472 clientIdentifier(std::move(clientIdentifier
))](
473 Maybe
<mozilla::security::mls::GkExporterOutput
>&&
474 exporterOutput
) mutable {
475 // Check if the exporterOutput contains a value
476 if (exporterOutput
.isNothing()) {
477 promise
->MaybeReject(NS_ERROR_FAILURE
);
481 RefPtr
<MLSGroupView
> group
=
482 new MLSGroupView(self
, std::move(exporterOutput
->group_id
),
483 std::move(clientIdentifier
));
484 promise
->MaybeResolve(group
);
486 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
487 promise
->MaybeRejectWithUnknownError("exportSecret failed");
490 return promise
.forget();
493 already_AddRefed
<Promise
> MLS::GroupJoin(
494 const MLSBytesOrUint8Array
& aJsClientIdentifier
,
495 const MLSBytesOrUint8Array
& aJsWelcome
, ErrorResult
& aRv
) {
496 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::GroupJoin()"));
498 // Handle the client identifier parameter
499 nsTArray
<uint8_t> clientIdentifier
= ExtractMLSBytesOrUint8Array(
500 MLSObjectType::Client_identifier
, aJsClientIdentifier
, aRv
);
501 if (NS_WARN_IF(aRv
.Failed())) {
505 // Check if the client identifier is empty
506 if (NS_WARN_IF(clientIdentifier
.IsEmpty())) {
507 aRv
.ThrowTypeError("The client identifier must not be empty");
511 // Handle the welcome parameter
512 nsTArray
<uint8_t> welcome
=
513 ExtractMLSBytesOrUint8Array(MLSObjectType::Welcome
, aJsWelcome
, aRv
);
514 if (NS_WARN_IF(aRv
.Failed())) {
518 // Check if the welcome is empty
519 if (NS_WARN_IF(welcome
.IsEmpty())) {
520 aRv
.ThrowTypeError("The welcome must not be empty");
524 // Create a new Promise object for the result
525 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
526 if (NS_WARN_IF(aRv
.Failed())) {
530 mTransactionChild
->SendRequestGroupJoin(clientIdentifier
, welcome
)
532 GetCurrentSerialEventTarget(), __func__
,
533 [promise
, self
= RefPtr
{this},
534 clientIdentifier(std::move(clientIdentifier
))](
535 Maybe
<mozilla::security::mls::GkGroupIdEpoch
>&&
536 groupIdEpoch
) mutable {
537 // Check if the value is Nothing
538 if (groupIdEpoch
.isNothing()) {
539 promise
->MaybeReject(NS_ERROR_FAILURE
);
543 // Returns groupId and epoch
544 RefPtr
<MLSGroupView
> group
=
545 new MLSGroupView(self
, std::move(groupIdEpoch
->group_id
),
546 std::move(clientIdentifier
));
548 // Resolve the promise
549 promise
->MaybeResolve(group
);
551 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
552 promise
->MaybeRejectWithUnknownError("groupJoin failed");
555 return promise
.forget();
558 already_AddRefed
<Promise
> MLS::GetGroupIdFromMessage(
559 const MLSBytesOrUint8Array
& aJsMessage
, ErrorResult
& aRv
) {
560 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
, ("MLS::GetGroupIdFromMessage()"));
562 // Handle the message parameter
563 nsTArray
<uint8_t> message
=
564 ExtractMLSBytesOrUint8ArrayWithUnknownType(aJsMessage
, aRv
);
565 if (NS_WARN_IF(aRv
.Failed())) {
569 // Check if the message is empty
570 if (NS_WARN_IF(message
.IsEmpty())) {
571 aRv
.ThrowTypeError("The message must not be empty");
575 // Create a new Promise object for the result
576 RefPtr
<Promise
> promise
= Promise::Create(mGlobalObject
, aRv
);
577 if (NS_WARN_IF(aRv
.Failed())) {
581 mTransactionChild
->SendRequestGetGroupIdentifier(message
)->Then(
582 GetCurrentSerialEventTarget(), __func__
,
583 [promise
, self
= RefPtr
{this},
584 message(std::move(message
))](Maybe
<RawBytes
>&& result
) {
585 // Check if the value is Nothing
586 if (result
.isNothing()) {
587 promise
->MaybeReject(NS_ERROR_FAILURE
);
591 // Get the context from the GlobalObject
593 if (NS_WARN_IF(!jsapi
.Init(self
->mGlobalObject
))) {
594 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Error
,
595 ("Failed to initialize JSAPI"));
596 promise
->MaybeReject(NS_ERROR_FAILURE
);
599 JSContext
* cx
= jsapi
.cx();
601 // Construct the Uint8Array objects based on the tag
603 JS::Rooted
<JSObject
*> jsGroupId(
604 cx
, Uint8Array::Create(cx
, result
->data(), error
));
605 error
.WouldReportJSException();
606 if (error
.Failed()) {
607 promise
->MaybeReject(std::move(error
));
611 // Construct the MLSBytes object for the groupId
612 RootedDictionary
<MLSBytes
> rvalue(cx
);
613 rvalue
.mType
= MLSObjectType::Group_identifier
;
614 rvalue
.mContent
.Init(jsGroupId
);
616 // Log if in debug mode
617 if (MOZ_LOG_TEST(gMlsLog
, LogLevel::Debug
)) {
618 MOZ_LOG(gMlsLog
, mozilla::LogLevel::Debug
,
619 ("Successfully constructed MLSBytes"));
622 // Resolve the promise
623 promise
->MaybeResolve(rvalue
);
625 [promise
](::mozilla::ipc::ResponseRejectReason aReason
) {
627 gMlsLog
, mozilla::LogLevel::Error
,
628 ("IPC call rejected with reason: %d", static_cast<int>(aReason
)));
629 promise
->MaybeRejectWithUnknownError("getGroupIdFromMessage failed");
632 return promise
.forget();
635 } // namespace mozilla::dom