diff --git a/native/cpp/CommonCpp/CryptoTools/CryptoModule.h b/native/cpp/CommonCpp/CryptoTools/CryptoModule.h index a5ecbe2a6..46501da3d 100644 --- a/native/cpp/CommonCpp/CryptoTools/CryptoModule.h +++ b/native/cpp/CommonCpp/CryptoTools/CryptoModule.h @@ -1,88 +1,88 @@ #pragma once #include #include #include #include "folly/Optional.h" #include "olm/olm.h" #include "Persist.h" #include "Session.h" #include "Tools.h" namespace comm { namespace crypto { class CryptoModule { OlmBuffer accountBuffer; std::unordered_map> sessions = {}; Keys keys; OlmAccount *getOlmAccount(); void createAccount(); void exposePublicIdentityKeys(); void generateOneTimeKeys(size_t oneTimeKeysAmount); // returns number of published keys size_t publishOneTimeKeys(); public: const std::string id; CryptoModule(std::string id); CryptoModule(std::string id, std::string secretKey, Persist persist); // CryptoModule's accountBuffer cannot be safely copied // See explanation in https://phab.comm.dev/D9562 CryptoModule(const CryptoModule &) = delete; static Keys keysFromStrings( const std::string &identityKeys, const std::string &oneTimeKeys); std::string getIdentityKeys(); std::string getOneTimeKeys(size_t oneTimeKeysAmount = 50); // Prekey rotation methods for X3DH std::uint8_t getNumPrekeys(); std::string getPrekey(); std::string getPrekeySignature(); folly::Optional getUnpublishedPrekey(); std::string generateAndGetPrekey(); void markPrekeyAsPublished(); void forgetOldPrekey(); void initializeInboundForReceivingSession( const std::string &targetDeviceId, const OlmBuffer &encryptedMessage, const OlmBuffer &idKeys, - const bool overwrite = false); + const bool overwrite = true); void initializeOutboundForSendingSession( const std::string &targetDeviceId, const OlmBuffer &idKeys, const OlmBuffer &preKeys, const OlmBuffer &preKeySignature, const OlmBuffer &oneTimeKeys, size_t keyIndex = 0); bool hasSessionFor(const std::string &targetDeviceId); std::shared_ptr getSessionByDeviceId(const std::string &deviceId); Persist storeAsB64(const std::string &secretKey); void restoreFromB64(const std::string &secretKey, Persist persist); EncryptedData encrypt(const std::string &targetDeviceId, const std::string &content); std::string decrypt(const std::string &targetDeviceId, EncryptedData &encryptedData); std::string signMessage(const std::string &message); static void verifySignature( const std::string &publicKey, const std::string &message, const std::string &signature); }; } // namespace crypto } // namespace comm diff --git a/native/utils/crypto-utils.js b/native/utils/crypto-utils.js index 1d0727ac7..1b74d4531 100644 --- a/native/utils/crypto-utils.js +++ b/native/utils/crypto-utils.js @@ -1,83 +1,173 @@ // @flow +import { type ClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js'; import type { IdentityKeysBlob, OLMIdentityKeys, } from 'lib/types/crypto-types.js'; -import type { InboundKeyInfoResponse } from 'lib/types/identity-service-types.js'; +import type { + OutboundKeyInfoResponse, + InboundKeyInfoResponse, +} from 'lib/types/identity-service-types'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; -import type { OutboundSessionCreation } from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; +import { + type OutboundSessionCreation, + peerToPeerMessageTypes, +} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; import { commCoreModule, commRustModule } from '../native-modules.js'; function nativeNotificationsSessionCreator( notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, ): Promise { const { prekey, prekeySignature, oneTimeKey } = notificationsInitializationInfo; return commCoreModule.initializeNotificationsSession( JSON.stringify(notificationsIdentityKeys), prekey, prekeySignature, oneTimeKey, ); } async function getContentSigningKey(): Promise { await commCoreModule.initializeCryptoAccount(); const { primaryIdentityPublicKeys: { ed25519 }, } = await commCoreModule.getUserPublicKey(); return ed25519; } async function nativeInboundContentSessionCreator( message: OutboundSessionCreation, ): Promise { const { senderInfo, encryptedContent } = message; const authMetadata = await commCoreModule.getCommServicesAuthMetadata(); const { userID, deviceID, accessToken } = authMetadata; if (!userID || !deviceID || !accessToken) { throw new Error('CommServicesAuthMetadata is missing'); } const keysResponse = await commRustModule.getInboundKeysForUser( userID, deviceID, accessToken, senderInfo.userID, ); const inboundKeys: InboundKeyInfoResponse[] = JSON.parse(keysResponse); const deviceKeys: ?InboundKeyInfoResponse = inboundKeys.find(keys => { const keysPayload: IdentityKeysBlob = JSON.parse(keys.payload); return ( keysPayload.primaryIdentityPublicKeys.ed25519 === senderInfo.deviceID ); }); if (!deviceKeys) { throw new Error( 'No keys for the device that requested creating a session, ' + `deviceID: ${senderInfo.deviceID}`, ); } const keysPayload: IdentityKeysBlob = JSON.parse(deviceKeys.payload); const identityKeys = JSON.stringify({ curve25519: keysPayload.primaryIdentityPublicKeys.curve25519, ed25519: keysPayload.primaryIdentityPublicKeys.ed25519, }); return commCoreModule.initializeContentInboundSession( identityKeys, encryptedContent, keysPayload.primaryIdentityPublicKeys.ed25519, ); } +function nativeOutboundContentSessionCreator( + contentIdentityKeys: OLMIdentityKeys, + contentInitializationInfo: OlmSessionInitializationInfo, + deviceID: string, +): Promise { + const { prekey, prekeySignature, oneTimeKey } = contentInitializationInfo; + const identityKeys = JSON.stringify({ + curve25519: contentIdentityKeys.curve25519, + ed25519: contentIdentityKeys.ed25519, + }); + + return commCoreModule.initializeContentOutboundSession( + identityKeys, + prekey, + prekeySignature, + oneTimeKey, + deviceID, + ); +} + +async function createOlmSessionsWithOwnDevices( + sendMessage: (message: ClientMessageToDevice) => Promise, +): Promise { + const authMetadata = await commCoreModule.getCommServicesAuthMetadata(); + const { userID, deviceID, accessToken } = authMetadata; + if (!userID || !deviceID || !accessToken) { + throw new Error('CommServicesAuthMetadata is missing'); + } + + const keysResponse = await commRustModule.getOutboundKeysForUser( + userID, + deviceID, + accessToken, + userID, + ); + + const outboundKeys: OutboundKeyInfoResponse[] = JSON.parse(keysResponse); + + for (const deviceKeys: OutboundKeyInfoResponse of outboundKeys) { + const keysPayload: IdentityKeysBlob = JSON.parse(deviceKeys.payload); + + if (keysPayload.primaryIdentityPublicKeys.ed25519 === deviceID) { + continue; + } + const recipientDeviceID = keysPayload.primaryIdentityPublicKeys.ed25519; + if (!deviceKeys.oneTimeContentPrekey) { + console.log(`One-time key is missing for device ${recipientDeviceID}`); + continue; + } + try { + const encryptedContent = await nativeOutboundContentSessionCreator( + keysPayload.primaryIdentityPublicKeys, + { + prekey: deviceKeys.contentPrekey, + prekeySignature: deviceKeys.contentPrekeySignature, + oneTimeKey: deviceKeys.oneTimeContentPrekey, + }, + recipientDeviceID, + ); + + const sessionCreationMessage: OutboundSessionCreation = { + type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, + senderInfo: { + userID, + deviceID, + }, + encryptedContent, + }; + + await sendMessage({ + deviceID: recipientDeviceID, + payload: JSON.stringify(sessionCreationMessage), + }); + } catch (e) { + console.log( + 'Error creating outbound session with ' + + `device ${recipientDeviceID}: ${e.message}`, + ); + } + } +} + export { getContentSigningKey, nativeNotificationsSessionCreator, nativeInboundContentSessionCreator, + createOlmSessionsWithOwnDevices, };