diff --git a/lib/components/peer-olm-session-creator-provider.react.js b/lib/components/peer-olm-session-creator-provider.react.js index 536fde3c3..8bdc0181a 100644 --- a/lib/components/peer-olm-session-creator-provider.react.js +++ b/lib/components/peer-olm-session-creator-provider.react.js @@ -1,87 +1,96 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; -import { createOlmSessionWithPeer } from '../utils/crypto-utils.js'; +import { + createOlmSessionWithPeer, + type SessionCreationOptions, +} from '../utils/crypto-utils.js'; export type PeerOlmSessionCreatorContextType = { +createOlmSessionsWithPeer: ( userID: string, deviceID: string, + sessionCreationOptions?: SessionCreationOptions, ) => Promise, }; const PeerOlmSessionCreatorContext: React.Context = React.createContext(); type Props = { +children: React.Node, }; function PeerOlmSessionCreatorProvider(props: Props): React.Node { const identityContext = React.useContext(IdentityClientContext); invariant(identityContext, 'Identity context should be set'); const { identityClient, getAuthMetadata } = identityContext; const { sendMessageToDevice } = useTunnelbroker(); const runningPromises = React.useRef<{ [userID: string]: { [deviceID: string]: ?Promise }, }>({}); const createOlmSessionsWithPeer = React.useCallback( - (userID: string, deviceID: string) => { + ( + userID: string, + deviceID: string, + sessionCreationOptions?: SessionCreationOptions, + ) => { if ( runningPromises.current[userID] && runningPromises.current[userID][deviceID] ) { return runningPromises.current[userID][deviceID]; } const promise = (async () => { const authMetadata = await getAuthMetadata(); await createOlmSessionWithPeer( authMetadata, identityClient, sendMessageToDevice, userID, deviceID, + sessionCreationOptions, ); runningPromises.current[userID][deviceID] = null; })(); if (!runningPromises.current[userID]) { runningPromises.current[userID] = {}; } runningPromises.current[userID][deviceID] = promise; return promise; }, [identityClient, sendMessageToDevice, getAuthMetadata], ); const peerOlmSessionCreatorContextValue: PeerOlmSessionCreatorContextType = React.useMemo( () => ({ createOlmSessionsWithPeer }), [createOlmSessionsWithPeer], ); return ( {props.children} ); } function usePeerOlmSessionsCreatorContext(): PeerOlmSessionCreatorContextType { const context = React.useContext(PeerOlmSessionCreatorContext); invariant(context, 'PeerOlmSessionsCreatorContext should be set'); return context; } export { PeerOlmSessionCreatorProvider, usePeerOlmSessionsCreatorContext }; diff --git a/lib/utils/crypto-utils.js b/lib/utils/crypto-utils.js index 25652b759..b45aad685 100644 --- a/lib/utils/crypto-utils.js +++ b/lib/utils/crypto-utils.js @@ -1,206 +1,221 @@ // @flow import t from 'tcomb'; import { type TInterface } from 'tcomb'; import { getConfig } from './config.js'; import { primaryIdentityPublicKeyRegex } from './siwe-utils.js'; import { tRegex, tShape } from './validation-utils.js'; import type { AuthMetadata } from '../shared/identity-client-context.js'; import type { TunnelbrokerClientMessageToDevice } from '../tunnelbroker/tunnelbroker-context.js'; import type { IdentityKeysBlob, OLMIdentityKeys, OutboundSessionCreationResult, SignedIdentityKeysBlob, } from '../types/crypto-types.js'; import type { IdentityServiceClient } from '../types/identity-service-types'; import { type OutboundSessionCreation, peerToPeerMessageTypes, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; const signedIdentityKeysBlobValidator: TInterface = tShape({ payload: t.String, signature: t.String, }); const olmIdentityKeysValidator: TInterface = tShape({ ed25519: tRegex(primaryIdentityPublicKeyRegex), curve25519: tRegex(primaryIdentityPublicKeyRegex), }); const identityKeysBlobValidator: TInterface = tShape({ primaryIdentityPublicKeys: olmIdentityKeysValidator, notificationIdentityPublicKeys: olmIdentityKeysValidator, }); async function getContentSigningKey(): Promise { const { olmAPI } = getConfig(); await olmAPI.initializeCryptoAccount(); const { primaryIdentityPublicKeys: { ed25519 }, } = await olmAPI.getUserPublicKey(); return ed25519; } async function createOlmSessionsWithOwnDevices( authMetadata: AuthMetadata, identityClient: IdentityServiceClient, sendMessage: (message: TunnelbrokerClientMessageToDevice) => Promise, ): Promise { const { olmAPI } = getConfig(); const { userID, deviceID, accessToken } = authMetadata; await olmAPI.initializeCryptoAccount(); if (!userID || !deviceID || !accessToken) { throw new Error('CommServicesAuthMetadata is missing'); } const keysResponse = await identityClient.getOutboundKeysForUser(userID); for (const deviceKeys of keysResponse) { const { keys } = deviceKeys; if (!keys) { console.log(`Keys missing for device ${deviceKeys.deviceID}`); continue; } const { primaryIdentityPublicKeys } = keys.identityKeysBlob; if (primaryIdentityPublicKeys.ed25519 === deviceID) { continue; } const recipientDeviceID = primaryIdentityPublicKeys.ed25519; try { const { sessionVersion, encryptedData } = await olmAPI.contentOutboundSessionCreator( primaryIdentityPublicKeys, keys.contentInitializationInfo, ); const sessionCreationMessage: OutboundSessionCreation = { type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, senderInfo: { userID, deviceID, }, encryptedData, sessionVersion, }; await sendMessage({ deviceID: recipientDeviceID, payload: JSON.stringify(sessionCreationMessage), }); console.log( `Request to create a session with device ${recipientDeviceID} sent.`, ); } catch (e) { console.log( 'Error creating outbound session with ' + `device ${recipientDeviceID}: ${e.message}`, ); } } } +export type SessionCreationOptions = { + +overwriteNotifSession?: boolean, + +overwriteContentSession?: boolean, +}; async function createOlmSessionWithPeer( authMetadata: AuthMetadata, identityClient: IdentityServiceClient, sendMessage: (message: TunnelbrokerClientMessageToDevice) => Promise, userID: string, deviceID: string, + options?: SessionCreationOptions, ): Promise { const { olmAPI } = getConfig(); await olmAPI.initializeCryptoAccount(); const [hasContentSession, hasNotifsSession] = await Promise.all([ - olmAPI.isContentSessionInitialized(deviceID), - olmAPI.isDeviceNotificationsSessionInitialized(deviceID), + (async () => { + if (options?.overwriteContentSession) { + return false; + } + return await olmAPI.isContentSessionInitialized(deviceID); + })(), + (async () => { + if (options?.overwriteNotifSession) { + return false; + } + return await olmAPI.isDeviceNotificationsSessionInitialized(deviceID); + })(), ]); if (hasContentSession && hasNotifsSession) { return; } const { userID: authUserID, deviceID: authDeviceID, accessToken, } = authMetadata; if (!authUserID || !authDeviceID || !accessToken) { throw new Error('CommServicesAuthMetadata is missing'); } const keysResponse = await identityClient.getOutboundKeysForUser(userID); const deviceKeysResponse = keysResponse.find( keys => keys.deviceID === deviceID, ); if (!deviceKeysResponse || !deviceKeysResponse.keys) { throw new Error(`Keys missing for device ${deviceID}`); } const { keys } = deviceKeysResponse; const { primaryIdentityPublicKeys, notificationIdentityPublicKeys } = keys.identityKeysBlob; const recipientDeviceID = primaryIdentityPublicKeys.ed25519; if (hasContentSession) { await olmAPI.notificationsOutboundSessionCreator( recipientDeviceID, notificationIdentityPublicKeys, keys.notifInitializationInfo, ); return; } let outboundSessionCreationResult: OutboundSessionCreationResult; if (hasNotifsSession) { outboundSessionCreationResult = await olmAPI.contentOutboundSessionCreator( primaryIdentityPublicKeys, keys.contentInitializationInfo, ); } else { [outboundSessionCreationResult] = await Promise.all([ await olmAPI.contentOutboundSessionCreator( primaryIdentityPublicKeys, keys.contentInitializationInfo, ), olmAPI.notificationsOutboundSessionCreator( recipientDeviceID, notificationIdentityPublicKeys, keys.notifInitializationInfo, ), ]); } const { sessionVersion, encryptedData } = outboundSessionCreationResult; const sessionCreationMessage: OutboundSessionCreation = { type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, senderInfo: { userID: authUserID, deviceID: authDeviceID, }, encryptedData, sessionVersion, }; await sendMessage({ deviceID: recipientDeviceID, payload: JSON.stringify(sessionCreationMessage), }); console.log( `Request to create a session with device ${recipientDeviceID} sent.`, ); } export { signedIdentityKeysBlobValidator, identityKeysBlobValidator, getContentSigningKey, createOlmSessionsWithOwnDevices, createOlmSessionWithPeer, };