Files
tubestation/dom/mls/MLSTransactionParent.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

575 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "MLSTransactionParent.h"
#include "MLSTransactionMessage.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/security/mls/mls_gk_ffi_generated.h"
#include "MLSLogging.h"
#include "mozilla/Base64.h"
#include "nsIFile.h"
#include "nsIPrincipal.h"
#include "nsString.h"
#include "nsCOMPtr.h"
using mozilla::dom::quota::QuotaManager;
namespace mozilla::dom {
/* static */ nsresult MLSTransactionParent::CreateDirectoryIfNotExists(
nsIFile* aDir) {
nsresult rv = aDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
// Evaluate if the file is a directory
bool isDirectory = false;
rv = aDir->IsDirectory(&isDirectory);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Check if the file is actually a directory
if (!isDirectory) {
return NS_ERROR_FILE_NOT_DIRECTORY;
}
return NS_OK;
}
return rv;
}
/* static */ nsresult MLSTransactionParent::ConstructDatabasePrefixPath(
nsCOMPtr<nsIFile>& aFile) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath()"));
// Get the base path from the quota manager
QuotaManager* quotaManager = QuotaManager::Get();
if (NS_WARN_IF(!quotaManager)) {
return NS_ERROR_FAILURE;
}
// Create an nsIFile object from the path
nsresult rv =
NS_NewLocalFile(quotaManager->GetBasePath(), getter_AddRefs(aFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Append the hardcoded "mls" directory name to the path
rv = aFile->AppendNative("mls"_ns);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
/* static */ nsresult MLSTransactionParent::ConstructDatabaseFullPath(
nsCOMPtr<nsIFile>& aFile, nsIPrincipal* aPrincipal,
nsCString& aDatabasePath) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabaseFullPath()"));
// Get StorageOriginKey
nsAutoCString originKey;
nsresult rv = aPrincipal->GetStorageOriginKey(originKey);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Get OriginSuffix
nsAutoCString originAttrSuffix;
rv = aPrincipal->GetOriginSuffix(originAttrSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Set the base path and origin
nsAutoCString origin = originKey + originAttrSuffix;
// Encode the origin with its suffix
nsAutoCString encodedOrigin;
rv = mozilla::Base64Encode(origin, encodedOrigin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath() - origin: %s",
origin.get()));
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath() - encodedOrigin: "
"%s",
encodedOrigin.get()));
// Append the origin to the path
rv = aFile->AppendNative(encodedOrigin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Get the updated path back into the nsCString
nsAutoString databasePathUTF16;
rv = aFile->GetPath(databasePathUTF16);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aDatabasePath = NS_ConvertUTF16toUTF8(databasePathUTF16);
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ConstructDatabasePath() - databasePath: %s",
aDatabasePath.get()));
return NS_OK;
}
void MLSTransactionParent::ActorDestroy(ActorDestroyReason) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::ActorDestroy()"));
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestStateDelete(
RequestStateDeleteResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestStateDelete()"));
// Call to the MLS rust code
nsresult rv = security::mls::mls_state_delete(&mDatabasePath);
aResolver(NS_SUCCEEDED(rv));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupStateDelete(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupStateDeleteResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupStateDelete()"));
// Call to the MLS rust code
security::mls::GkGroupIdEpoch groupIdEpoch;
nsresult rv = security::mls::mls_state_delete_group(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), &groupIdEpoch);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(groupIdEpoch)));
return IPC_OK();
}
mozilla::ipc::IPCResult
MLSTransactionParent::RecvRequestGenerateIdentityKeypair(
RequestGenerateIdentityKeypairResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGenerateIdentityKeypair()"));
// Call to the MLS rust code
nsTArray<uint8_t> signatureIdentifier;
nsresult rv = security::mls::mls_generate_signature_keypair(
&mDatabasePath, &signatureIdentifier);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(signatureIdentifier)}));
return IPC_OK();
}
mozilla::ipc::IPCResult
MLSTransactionParent::RecvRequestGenerateCredentialBasic(
const nsTArray<uint8_t>& aCredContent,
RequestGenerateCredentialBasicResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGenerateCredentialBasic()"));
// Call to the MLS rust code
nsTArray<uint8_t> credential;
nsresult rv = security::mls::mls_generate_credential_basic(
aCredContent.Elements(), aCredContent.Length(), &credential);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(credential)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGenerateKeyPackage(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aCredential,
RequestGenerateKeyPackageResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGenerateKeyPackage()"));
// Call to the MLS rust code
nsTArray<uint8_t> keyPackage;
nsresult rv = security::mls::mls_generate_keypackage(
&mDatabasePath, aIdentifier.Elements(), aIdentifier.Length(),
aCredential.Elements(), aCredential.Length(), &keyPackage);
// Return Nothing if failed
if (NS_FAILED(rv)) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(keyPackage)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupCreate(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aCredential,
const nsTArray<uint8_t>& aInOptGroupIdentifier,
RequestGroupCreateResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupCreate()"));
// Call to the MLS rust code
security::mls::GkGroupIdEpoch groupIdEpoch;
nsresult rv = security::mls::mls_group_create(
&mDatabasePath, aIdentifier.Elements(), aIdentifier.Length(),
aCredential.Elements(), aCredential.Length(),
aInOptGroupIdentifier.Elements(), aInOptGroupIdentifier.Length(),
&groupIdEpoch);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(groupIdEpoch)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupJoin(
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aWelcome,
RequestGroupJoinResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupJoin()"));
// Call to the MLS rust code
security::mls::GkGroupIdEpoch groupIdEpoch;
nsresult rv = security::mls::mls_group_join(
&mDatabasePath, aIdentifier.Elements(), aIdentifier.Length(),
aWelcome.Elements(), aWelcome.Length(), &groupIdEpoch);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(groupIdEpoch)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupAdd(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aKeyPackage,
RequestGroupAddResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupAdd()"));
// Call to the MLS rust code
security::mls::GkMlsCommitOutput commitOutput;
nsresult rv = security::mls::mls_group_add(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aKeyPackage.Elements(),
aKeyPackage.Length(), &commitOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(commitOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupProposeAdd(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aKeyPackage,
RequestGroupProposeAddResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupProposeAdd()"));
// Call to the MLS rust code
nsTArray<uint8_t> proposal;
nsresult rv = security::mls::mls_group_propose_add(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aKeyPackage.Elements(),
aKeyPackage.Length(), &proposal);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(proposal)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupRemove(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aRemIdentifier,
RequestGroupRemoveResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupRemove()"));
// Call to the MLS rust code
security::mls::GkMlsCommitOutput commitOutput;
nsresult rv = security::mls::mls_group_remove(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aRemIdentifier.Elements(),
aRemIdentifier.Length(), &commitOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(commitOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupProposeRemove(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
const nsTArray<uint8_t>& aRemIdentifier,
RequestGroupProposeRemoveResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupProposeRemove()"));
nsTArray<uint8_t> proposal;
nsresult rv = security::mls::mls_group_propose_remove(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aRemIdentifier.Elements(),
aRemIdentifier.Length(), &proposal);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(proposal)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupClose(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupCloseResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupClose()"));
// Call to the MLS rust code
security::mls::GkMlsCommitOutput commitOutput;
nsresult rv = security::mls::mls_group_close(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), &commitOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(commitOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGroupDetails(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier,
RequestGroupDetailsResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGroupDetails()"));
// Call to the MLS rust code
security::mls::GkGroupMembers members;
nsresult rv = security::mls::mls_group_members(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), &members);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(members)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestReceive(
const nsTArray<uint8_t>& aClientIdentifier,
const nsTArray<uint8_t>& aMessage, RequestReceiveResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestReceive()"));
// Call to the MLS rust code
GkReceived received;
nsTArray<uint8_t> group_id_bytes;
nsresult rv = security::mls::mls_receive(
&mDatabasePath, aClientIdentifier.Elements(), aClientIdentifier.Length(),
aMessage.Elements(), aMessage.Length(), &group_id_bytes, &received);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(GkReceived());
return IPC_OK();
}
// Return the result if success
aResolver(received);
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestApplyPendingCommit(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aClientIdentifier,
RequestApplyPendingCommitResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestApplyPendingCommit()"));
// Call to the MLS rust code
GkReceived received;
nsresult rv = security::mls::mls_receive_ack(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aClientIdentifier.Elements(), aClientIdentifier.Length(), &received);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(GkReceived());
return IPC_OK();
}
// Return the result if success
aResolver(received);
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestSend(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aMessage,
RequestSendResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestSend()"));
// Call to the MLS rust code
nsTArray<uint8_t> outputMessage;
nsresult rv = security::mls::mls_send(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aMessage.Elements(),
aMessage.Length(), &outputMessage);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(outputMessage)}));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestExportSecret(
const nsTArray<uint8_t>& aGroupIdentifier,
const nsTArray<uint8_t>& aIdentifier, const nsTArray<uint8_t>& aLabel,
const nsTArray<uint8_t>& aContext, uint64_t aLen,
RequestExportSecretResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestExportSecret()"));
// Call to the MLS rust code
security::mls::GkExporterOutput exporterOutput;
nsresult rv = security::mls::mls_derive_exporter(
&mDatabasePath, aGroupIdentifier.Elements(), aGroupIdentifier.Length(),
aIdentifier.Elements(), aIdentifier.Length(), aLabel.Elements(),
aLabel.Length(), aContext.Elements(), aContext.Length(), aLen,
&exporterOutput);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(std::move(exporterOutput)));
return IPC_OK();
}
mozilla::ipc::IPCResult MLSTransactionParent::RecvRequestGetGroupIdentifier(
const nsTArray<uint8_t>& aMessage,
RequestGetGroupIdentifierResolver&& aResolver) {
MOZ_LOG(gMlsLog, mozilla::LogLevel::Debug,
("MLSTransactionParent::RecvRequestGetGroupIdentifier()"));
nsTArray<uint8_t> groupId;
nsresult rv = security::mls::mls_get_group_id(aMessage.Elements(),
aMessage.Length(), &groupId);
// Return Nothing if failed
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(Nothing());
return IPC_OK();
}
// Return the result if success
aResolver(Some(RawBytes{std::move(groupId)}));
return IPC_OK();
}
} // namespace mozilla::dom