Files
tubestation/dom/mls/MLSGroupView.cpp
Benjamin Beurdouche 4e1e4fe2c9 Bug 1900537 - Part 3: Web API for MLS. r=webidl,ipc-reviewers,smaug,nika
This patch contains an experimental Web API for the Messaging Layer Security (RFC 9420) protocol.
The API allows to securely generate cryptographic material and build large dynamic groups with state-of-the-art security.

The state (both public and secret) is stored in the profile and isolated by origin within dedicated databases.
No secret can be exfiltrated through the API, and privacy risks are minimized due to the selected isolation.

Differential Revision: https://phabricator.services.mozilla.com/D210568
2024-12-18 14:04:20 +00:00

1162 lines
43 KiB
C++

/* -*- 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 "MLSGroupView.h"
#include "mozilla/dom/MLSBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/Promise.h"
#include "nsTArray.h"
#include "mozilla/dom/MLSTransactionChild.h"
#include "mozilla/dom/MLSTransactionMessage.h"
#include "ipc/IPCMessageUtilsSpecializations.h"
#include "mozilla/BasePrincipal.h"
#include "nsTArray.h"
#include "mozilla/Logging.h"
#include "mozilla/Span.h"
#include "nsDebug.h"
#include "MLSTypeUtils.h"
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(MLSGroupView, (mMLS),
(mJsGroupId, mJsClientId))
NS_IMPL_CYCLE_COLLECTING_ADDREF(MLSGroupView)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MLSGroupView)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MLSGroupView)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
// Setup logging
extern mozilla::LazyLogModule gMlsLog;
MLSGroupView::MLSGroupView(MLS* aMLS, nsTArray<uint8_t>&& aGroupId,
nsTArray<uint8_t>&& aClientId)
: mMLS(aMLS),
mGroupId(std::move(aGroupId)),
mClientId(std::move(aClientId)) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::MLSGroupView()"));
// Indicate that the object holds JS objects
mozilla::HoldJSObjects(this);
}
JSObject* MLSGroupView::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return MLSGroupView_Binding::Wrap(aCx, this, aGivenProto);
}
//
// API
//
void MLSGroupView::GetGroupId(JSContext* aCx,
JS::MutableHandle<JSObject*> aGroupId,
ErrorResult& aRv) {
if (!mJsGroupId) {
mJsGroupId = Uint8Array::Create(aCx, this, mGroupId, aRv);
if (aRv.Failed()) {
return;
}
}
aGroupId.set(mJsGroupId);
}
void MLSGroupView::GetClientId(JSContext* aCx,
JS::MutableHandle<JSObject*> aClientId,
ErrorResult& aRv) {
if (!mJsClientId) {
mJsClientId = Uint8Array::Create(aCx, this, mClientId, aRv);
if (aRv.Failed()) {
return;
}
}
aClientId.set(mJsClientId);
}
already_AddRefed<Promise> MLSGroupView::DeleteState(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::DeleteState()"));
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild->SendRequestGroupStateDelete(mGroupId, mClientId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](
Maybe<mozilla::security::mls::GkGroupIdEpoch>&& groupIdEpoch) {
// Check if the value is Nothing or Some with an empty group_epoch
if (groupIdEpoch.isNothing()) {
promise->MaybeRejectWithUnknownError(
"Failed to delete group state");
return;
}
// Check if the epoch is 0xFFFF..FF
bool isMaxEpoch =
std::all_of(groupIdEpoch->group_epoch.begin(),
groupIdEpoch->group_epoch.end(),
[](uint8_t byte) { return byte == 0xFF; });
// If the epoch is 0xFFFF..FF, then the group has been deleted
if (isMaxEpoch) {
promise->MaybeResolveWithUndefined();
} else {
promise->MaybeRejectWithUnknownError(
"Group has not been deleted");
}
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError(
"Failed to delete group state");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::Add(
const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Add()"));
// Handle the key package parameter
nsTArray<uint8_t> keyPackage = ExtractMLSBytesOrUint8Array(
MLSObjectType::Key_package, aJsKeyPackage, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Check if the key package is empty
if (NS_WARN_IF(keyPackage.IsEmpty())) {
aRv.ThrowTypeError("The key package must not be empty");
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild->SendRequestGroupAdd(mGroupId, mClientId, keyPackage)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkMlsCommitOutput>&& commitOutput) {
// Check if the value is Nothing
if (commitOutput.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, commitOutput->identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, commitOutput->commit, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(cx, commitOutput->welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(cx, commitOutput->group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsRatchetTree(
cx, Uint8Array::Create(cx, commitOutput->ratchet_tree, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSCommitOutput with the parsed data
RootedDictionary<MLSCommitOutput> rvalue(cx);
rvalue.mType = MLSObjectType::Commit_output;
rvalue.mGroupId.Init(jsGroupId);
rvalue.mCommit.Init(jsCommit);
if (!commitOutput->welcome.IsEmpty()) {
rvalue.mWelcome.Construct();
rvalue.mWelcome.Value().Init(jsWelcome);
}
if (!commitOutput->group_info.IsEmpty()) {
rvalue.mGroupInfo.Construct();
rvalue.mGroupInfo.Value().Init(jsGroupInfo);
}
if (!commitOutput->ratchet_tree.IsEmpty()) {
rvalue.mRatchetTree.Construct();
rvalue.mRatchetTree.Value().Init(jsRatchetTree);
}
if (!commitOutput->identity.IsEmpty()) {
rvalue.mClientId.Construct();
rvalue.mClientId.Value().Init(jsClientId);
}
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError("Failed to add to group");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ProposeAdd(
const MLSBytesOrUint8Array& aJsKeyPackage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ProposeAdd()"));
// Handle the key package parameter
nsTArray<uint8_t> keyPackage = ExtractMLSBytesOrUint8Array(
MLSObjectType::Key_package, aJsKeyPackage, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Check if the key package is empty
if (NS_WARN_IF(keyPackage.IsEmpty())) {
aRv.ThrowTypeError("The key package must not be empty");
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild
->SendRequestGroupProposeAdd(mGroupId, mClientId, keyPackage)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise,
self = RefPtr<MLSGroupView>(this)](Maybe<RawBytes>&& proposal) {
// Check if the value is Nothing
if (proposal.isNothing()) {
promise->MaybeRejectWithUnknownError(
"Failed to propose add to group");
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array object
ErrorResult error;
JS::Rooted<JSObject*> content(
cx, Uint8Array::Create(cx, proposal->data(), error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSBytes with the proposal as content
RootedDictionary<MLSBytes> rvalue(cx);
rvalue.mType = MLSObjectType::Proposal;
rvalue.mContent.Init(content);
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError(
"Failed to propose add to group");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::Remove(
const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Remove()"));
// Handle the remove client identifier parameter
nsTArray<uint8_t> remClientIdentifier = ExtractMLSBytesOrUint8Array(
MLSObjectType::Client_identifier, aJsRemClientIdentifier, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Check if the remove client identifier is empty
if (NS_WARN_IF(remClientIdentifier.IsEmpty())) {
aRv.ThrowTypeError("The remove client identifier must not be empty");
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Use the static method or instance to send the IPC message
mMLS->mTransactionChild
->SendRequestGroupRemove(mGroupId, mClientId, remClientIdentifier)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkMlsCommitOutput>&& commitOutput) {
// Check if the value is Nothing
if (commitOutput.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, commitOutput->identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, commitOutput->commit, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(cx, commitOutput->welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(cx, commitOutput->group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsRatchetTree(
cx, Uint8Array::Create(cx, commitOutput->ratchet_tree, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSCommitOutput with the parsed data
RootedDictionary<MLSCommitOutput> rvalue(cx);
rvalue.mType = MLSObjectType::Commit_output;
rvalue.mGroupId.Init(jsGroupId);
rvalue.mCommit.Init(jsCommit);
if (!commitOutput->welcome.IsEmpty()) {
rvalue.mWelcome.Construct();
rvalue.mWelcome.Value().Init(jsWelcome);
}
if (!commitOutput->group_info.IsEmpty()) {
rvalue.mGroupInfo.Construct();
rvalue.mGroupInfo.Value().Init(jsGroupInfo);
}
if (!commitOutput->ratchet_tree.IsEmpty()) {
rvalue.mRatchetTree.Construct();
rvalue.mRatchetTree.Value().Init(jsRatchetTree);
}
if (!commitOutput->identity.IsEmpty()) {
rvalue.mClientId.Construct();
rvalue.mClientId.Value().Init(jsClientId);
}
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError("Failed to remove from group");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ProposeRemove(
const MLSBytesOrUint8Array& aJsRemClientIdentifier, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ProposeRemove()"));
// Handle the remove client identifier parameter
nsTArray<uint8_t> remClientIdentifier = ExtractMLSBytesOrUint8Array(
MLSObjectType::Client_identifier, aJsRemClientIdentifier, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Check if the removed client identifier is empty
if (NS_WARN_IF(remClientIdentifier.IsEmpty())) {
aRv.ThrowTypeError("The removed client identifier must not be empty");
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild
->SendRequestGroupProposeRemove(mGroupId, mClientId, remClientIdentifier)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise,
self = RefPtr<MLSGroupView>(this)](Maybe<RawBytes>&& proposal) {
// Check if the value is Nothing
if (proposal.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array object
ErrorResult error;
JS::Rooted<JSObject*> content(
cx, Uint8Array::Create(cx, proposal->data(), error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSBytes with the proposal as content
RootedDictionary<MLSBytes> rvalue(cx);
rvalue.mType = MLSObjectType::Proposal;
rvalue.mContent.Init(content);
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError(
"Failed to propose remove from group");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::Close(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Close()"));
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild->SendRequestGroupClose(mGroupId, mClientId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkMlsCommitOutput>&& commitOutput) {
// Check if the value is Nothing
if (commitOutput.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, commitOutput->identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, commitOutput->commit, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(cx, commitOutput->welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(cx, commitOutput->group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsRatchetTree(
cx, Uint8Array::Create(cx, commitOutput->ratchet_tree, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSCommitOutput with the parsed data
RootedDictionary<MLSCommitOutput> rvalue(cx);
rvalue.mType = MLSObjectType::Commit_output;
rvalue.mGroupId.Init(jsGroupId);
rvalue.mCommit.Init(jsCommit);
if (!commitOutput->welcome.IsEmpty()) {
rvalue.mWelcome.Construct();
rvalue.mWelcome.Value().Init(jsWelcome);
}
if (!commitOutput->group_info.IsEmpty()) {
rvalue.mGroupInfo.Construct();
rvalue.mGroupInfo.Value().Init(jsGroupInfo);
}
if (!commitOutput->ratchet_tree.IsEmpty()) {
rvalue.mRatchetTree.Construct();
rvalue.mRatchetTree.Value().Init(jsRatchetTree);
}
if (!commitOutput->identity.IsEmpty()) {
rvalue.mClientId.Construct();
rvalue.mClientId.Value().Init(jsClientId);
}
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError("Failed to close group");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::Details(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Details()"));
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild->SendRequestGroupDetails(mGroupId, mClientId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](
Maybe<GkGroupMembers>&& groupMembers) {
// Check if the value is Nothing
if (groupMembers.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, groupMembers->group_id, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupEpoch(
cx, Uint8Array::Create(cx, groupMembers->group_epoch, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSGroupDetails
RootedDictionary<MLSGroupDetails> rvalue(cx);
rvalue.mType = MLSObjectType::Group_info;
rvalue.mGroupId.Init(jsGroupId);
rvalue.mGroupEpoch.Init(jsGroupEpoch);
rvalue.mMembers.Clear();
Sequence<MLSGroupMember> membersSequence;
for (size_t i = 0; i < groupMembers->group_members.Length(); ++i) {
const GkClientIdentifiers& member =
groupMembers->group_members[i];
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(cx, member.identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCredential(
cx, Uint8Array::Create(cx, member.credential, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
MLSGroupMember jsMember;
jsMember.mClientId.Init(jsClientId);
jsMember.mCredential.Init(jsCredential);
if (!membersSequence.AppendElement(std::move(jsMember),
fallible)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
rvalue.mMembers = std::move(membersSequence);
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError("Failed to get group details");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::Send(
const MLSBytesOrUint8ArrayOrUTF8String& aJsMessage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Send()"));
// Handle the message parameter
nsTArray<uint8_t> message = ExtractMLSBytesOrUint8ArrayOrUTF8String(
MLSObjectType::Application_message_plaintext, aJsMessage, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild->SendRequestSend(mGroupId, mClientId, message)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise,
self = RefPtr<MLSGroupView>(this)](Maybe<RawBytes>&& result) {
// Check if the value is Nothing
if (result.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array object
ErrorResult error;
JS::Rooted<JSObject*> content(
cx, Uint8Array::Create(cx, result->data(), error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSBytes with the group identifier as content
RootedDictionary<MLSBytes> rvalue(cx);
rvalue.mType = MLSObjectType::Application_message_ciphertext;
rvalue.mContent.Init(content);
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError("Failed to send message");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::Receive(
const MLSBytesOrUint8Array& aJsMessage, ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::Receive()"));
// Handle the message parameter
nsTArray<uint8_t> message =
ExtractMLSBytesOrUint8ArrayWithUnknownType(aJsMessage, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Check if the message is empty
if (NS_WARN_IF(message.IsEmpty())) {
aRv.ThrowTypeError("The receivedmessage must not be empty");
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Receive the message
mMLS->mTransactionChild->SendRequestReceive(mClientId, message)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](GkReceived&& received) {
// Check if the Maybe contains a value
if (received.tag == GkReceived::Tag::None) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects based on the tag
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Initialize the Received dictionary
RootedDictionary<MLSReceived> rvalue(cx);
rvalue.mGroupId.Init(jsGroupId);
// Populate the Received object based on the tag
switch (received.tag) {
case GkReceived::Tag::GroupIdEpoch: {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("Processing GroupIdEpoch"));
JS::Rooted<JSObject*> jsGroupEpoch(
cx, Uint8Array::Create(
cx, received.group_id_epoch._0.group_epoch, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Populate the Received object
rvalue.mType = MLSObjectType::Commit_processed;
rvalue.mGroupEpoch.Construct();
rvalue.mGroupEpoch.Value().Init(jsGroupEpoch);
break;
}
case GkReceived::Tag::ApplicationMessage: {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("Processing ApplicationMessage"));
JS::Rooted<JSObject*> jsApplicationMessage(
cx, Uint8Array::Create(cx, received.application_message._0,
error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Populate the Received object
rvalue.mType = MLSObjectType::Application_message_plaintext;
rvalue.mContent.Construct();
rvalue.mContent.Value().Init(jsApplicationMessage);
break;
}
case GkReceived::Tag::CommitOutput: {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("Processing CommitOutput"));
JS::Rooted<JSObject*> jsClientId(
cx, Uint8Array::Create(
cx, received.commit_output._0.identity, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsCommit(
cx, Uint8Array::Create(cx, received.commit_output._0.commit,
error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsWelcome(
cx, Uint8Array::Create(
cx, received.commit_output._0.welcome, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupInfo(
cx, Uint8Array::Create(
cx, received.commit_output._0.group_info, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsRatchetTree(
cx, Uint8Array::Create(
cx, received.commit_output._0.ratchet_tree, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSCommitOutput with the parsed data
rvalue.mType = MLSObjectType::Commit_output;
rvalue.mGroupId.Init(jsGroupId);
rvalue.mCommitOutput.Construct();
rvalue.mCommitOutput.Value().mType =
MLSObjectType::Commit_output;
rvalue.mCommitOutput.Value().mCommit.Init(jsCommit);
rvalue.mCommitOutput.Value().mGroupId.Init(jsGroupId);
if (!received.commit_output._0.welcome.IsEmpty()) {
rvalue.mCommitOutput.Value().mWelcome.Construct();
rvalue.mCommitOutput.Value().mWelcome.Value().Init(jsWelcome);
}
if (!received.commit_output._0.group_info.IsEmpty()) {
rvalue.mCommitOutput.Value().mGroupInfo.Construct();
rvalue.mCommitOutput.Value().mGroupInfo.Value().Init(
jsGroupInfo);
}
if (!received.commit_output._0.ratchet_tree.IsEmpty()) {
rvalue.mCommitOutput.Value().mRatchetTree.Construct();
rvalue.mCommitOutput.Value().mRatchetTree.Value().Init(
jsRatchetTree);
}
if (!received.commit_output._0.identity.IsEmpty()) {
rvalue.mCommitOutput.Value().mClientId.Construct();
rvalue.mCommitOutput.Value().mClientId.Value().Init(
jsClientId);
}
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("Finished processing CommitOutput"));
break;
}
default:
MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
("Unhandled tag in received data"));
promise->MaybeRejectWithUnknownError(
"Unhandled tag in received data");
return;
}
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("Successfully constructed MLSReceived"));
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
("IPC call rejected with reason: %d",
static_cast<int>(aReason)));
promise->MaybeRejectWithUnknownError("Failed to receive message");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ApplyPendingCommit(ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSGroupView::ApplyPendingCommit()"));
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Receive the message
mMLS->mTransactionChild->SendRequestApplyPendingCommit(mGroupId, mClientId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](GkReceived&& received) {
// Check if the Maybe contains a value
if (received.tag == GkReceived::Tag::None) {
promise->MaybeRejectWithUnknownError(
"Failed to apply pending commit");
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
("Failed to initialize JSAPI"));
promise->MaybeRejectWithUnknownError(
"Failed to initialize JSAPI");
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects based on the tag
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, self->mGroupId, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Initialize the Received dictionary
RootedDictionary<MLSReceived> rvalue(cx);
rvalue.mGroupId.Init(jsGroupId);
// Populate the Received object based on the tag
switch (received.tag) {
case GkReceived::Tag::GroupIdEpoch: {
JS::Rooted<JSObject*> jsGroupEpoch(
cx, Uint8Array::Create(
cx, received.group_id_epoch._0.group_epoch, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Populate the Received object
rvalue.mType = MLSObjectType::Commit_processed;
rvalue.mGroupEpoch.Construct();
rvalue.mGroupEpoch.Value().Init(jsGroupEpoch);
break;
}
default:
MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
("Unhandled tag in received data"));
promise->MaybeRejectWithUnknownError(
"Unhandled tag in received data");
return;
}
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("Successfully constructed MLSReceived"));
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Error,
("IPC call rejected with reason: %d",
static_cast<int>(aReason)));
promise->MaybeRejectWithUnknownError(
"Failed to apply pending commit");
});
return promise.forget();
}
already_AddRefed<Promise> MLSGroupView::ExportSecret(
const MLSBytesOrUint8ArrayOrUTF8String& aJsLabel,
const MLSBytesOrUint8Array& aJsContext, const uint64_t aLen,
ErrorResult& aRv) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug, ("MLSGroupView::ExportSecret()"));
// Handle the label parameter
nsTArray<uint8_t> label = ExtractMLSBytesOrUint8ArrayOrUTF8String(
MLSObjectType::Exporter_label, aJsLabel, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// We allow the context to be empty
if (NS_WARN_IF(label.IsEmpty())) {
aRv.ThrowTypeError("The label must not be empty");
return nullptr;
}
// Handle the context parameter
// Note: we allow the context to be empty
nsTArray<uint8_t> context = ExtractMLSBytesOrUint8Array(
MLSObjectType::Exporter_context, aJsContext, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Create a new Promise object for the result
RefPtr<Promise> promise = Promise::Create(mMLS->GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mMLS->mTransactionChild
->SendRequestExportSecret(mGroupId, mClientId, label, context, aLen)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, self = RefPtr<MLSGroupView>(this)](
Maybe<mozilla::security::mls::GkExporterOutput>&&
exporterOutput) {
// Check if the Maybe contains a value
if (exporterOutput.isNothing()) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
// Get the context from the GlobalObject
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(self->mMLS->GetParentObject()))) {
promise->MaybeRejectWithUnknownError(
"Failed to initialize JSAPI");
return;
}
JSContext* cx = jsapi.cx();
// Construct the Uint8Array objects
ErrorResult error;
JS::Rooted<JSObject*> jsGroupId(
cx, Uint8Array::Create(cx, exporterOutput->group_id, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsGroupEpoch(
cx, Uint8Array::Create(cx, exporterOutput->group_epoch, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsLabel(
cx, Uint8Array::Create(cx, exporterOutput->label, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsContext(
cx, Uint8Array::Create(cx, exporterOutput->context, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
JS::Rooted<JSObject*> jsExporter(
cx, Uint8Array::Create(cx, exporterOutput->exporter, error));
error.WouldReportJSException();
if (error.Failed()) {
promise->MaybeReject(std::move(error));
return;
}
// Construct MLSBytes with the group identifier as content
RootedDictionary<MLSExporterOutput> rvalue(cx);
rvalue.mType = MLSObjectType::Exporter_output;
rvalue.mGroupId.Init(jsGroupId);
rvalue.mGroupEpoch.Init(jsGroupEpoch);
rvalue.mLabel.Init(jsLabel);
rvalue.mContext.Init(jsContext);
rvalue.mSecret.Init(jsExporter);
// Resolve the promise
promise->MaybeResolve(rvalue);
},
[promise](::mozilla::ipc::ResponseRejectReason aReason) {
promise->MaybeRejectWithUnknownError("Failed to export secret");
});
return promise.forget();
}
}; // namespace mozilla::dom