diff --git a/lib/components/keyserver-connection-handler.js b/lib/components/keyserver-connection-handler.js --- a/lib/components/keyserver-connection-handler.js +++ b/lib/components/keyserver-connection-handler.js @@ -159,7 +159,8 @@ authActionSource, keyserverData: { [keyserverID]: { - initialContentEncryptedMessage: contentSession.message, + initialContentEncryptedMessage: + contentSession.encryptedData.message, initialNotificationsEncryptedMessage: notifsSession, }, }, diff --git a/lib/handlers/peer-to-peer-message-handler.js b/lib/handlers/peer-to-peer-message-handler.js --- a/lib/handlers/peer-to-peer-message-handler.js +++ b/lib/handlers/peer-to-peer-message-handler.js @@ -17,7 +17,7 @@ const { olmAPI } = getConfig(); if (message.type === peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION) { try { - const { senderInfo, encryptedData } = message; + const { senderInfo, encryptedData, sessionVersion } = message; const { keys } = await identityClient.getInboundKeysForUser( senderInfo.userID, ); @@ -34,10 +34,12 @@ const result = await olmAPI.contentInboundSessionCreator( deviceKeys.identityKeysBlob.primaryIdentityPublicKeys, encryptedData, + sessionVersion, ); console.log( 'Created inbound session with device ' + - `${message.senderInfo.deviceID}: ${result}`, + `${message.senderInfo.deviceID}: ${result}, ` + + `session version: ${sessionVersion}`, ); } catch (e) { console.log( diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -137,6 +137,11 @@ +signature: string, }; +export type OutboundSessionCreationResult = { + +encryptedData: EncryptedData, + +sessionVersion: number, +}; + export type OlmAPI = { +initializeCryptoAccount: () => Promise, +getUserPublicKey: () => Promise, @@ -145,11 +150,12 @@ +contentInboundSessionCreator: ( contentIdentityKeys: OLMIdentityKeys, initialEncryptedData: EncryptedData, + sessionVersion: number, ) => Promise, +contentOutboundSessionCreator: ( contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, - ) => Promise, + ) => Promise, +notificationsSessionCreator: ( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys, diff --git a/lib/types/tunnelbroker/peer-to-peer-message-types.js b/lib/types/tunnelbroker/peer-to-peer-message-types.js --- a/lib/types/tunnelbroker/peer-to-peer-message-types.js +++ b/lib/types/tunnelbroker/peer-to-peer-message-types.js @@ -31,12 +31,14 @@ +type: 'OutboundSessionCreation', +senderInfo: SenderInfo, +encryptedData: EncryptedData, + +sessionVersion: number, }; export const outboundSessionCreationValidator: TInterface = tShape({ type: tString(peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION), senderInfo: senderInfoValidator, encryptedData: encryptedDataValidator, + sessionVersion: t.Number, }); export type EncryptedMessage = { diff --git a/lib/utils/crypto-utils.js b/lib/utils/crypto-utils.js --- a/lib/utils/crypto-utils.js +++ b/lib/utils/crypto-utils.js @@ -78,10 +78,11 @@ continue; } try { - const encryptedData = await olmAPI.contentOutboundSessionCreator( - primaryIdentityPublicKeys, - keys.contentInitializationInfo, - ); + const { sessionVersion, encryptedData } = + await olmAPI.contentOutboundSessionCreator( + primaryIdentityPublicKeys, + keys.contentInitializationInfo, + ); const sessionCreationMessage: OutboundSessionCreation = { type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, @@ -90,6 +91,7 @@ deviceID, }, encryptedData, + sessionVersion, }; await sendMessage({ diff --git a/native/crypto/olm-api.js b/native/crypto/olm-api.js --- a/native/crypto/olm-api.js +++ b/native/crypto/olm-api.js @@ -19,6 +19,7 @@ getUserPublicKey: commCoreModule.getUserPublicKey, encrypt: commCoreModule.encrypt, decrypt: commCoreModule.decrypt, + // $FlowFixMe async contentInboundSessionCreator( contentIdentityKeys: OLMIdentityKeys, initialEncryptedData: EncryptedData, @@ -36,6 +37,7 @@ async contentOutboundSessionCreator( contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, + // $FlowFixMe ): Promise { const { prekey, prekeySignature, oneTimeKey } = contentInitializationInfo; const identityKeys = JSON.stringify({ diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js --- a/web/shared-worker/worker/worker-crypto.js +++ b/web/shared-worker/worker/worker-crypto.js @@ -16,6 +16,7 @@ type ClientPublicKeys, type NotificationsOlmDataType, type EncryptedData, + type OutboundSessionCreationResult, } from 'lib/types/crypto-types.js'; import type { IdentityNewDeviceKeyUpload, @@ -415,12 +416,20 @@ async contentInboundSessionCreator( contentIdentityKeys: OLMIdentityKeys, initialEncryptedData: EncryptedData, + sessionVersion: number, ): Promise { if (!cryptoStore) { throw new Error('Crypto account not initialized'); } const { contentAccount, contentSessions } = cryptoStore; + const existingSession = contentSessions[contentIdentityKeys.ed25519]; + if (existingSession && existingSession.version > sessionVersion) { + throw new Error('OLM_SESSION_ALREADY_CREATED'); + } else if (existingSession && existingSession.version === sessionVersion) { + throw new Error('OLM_SESSION_CREATION_RACE_CONDITION'); + } + const session = new olm.Session(); session.create_inbound_from( contentAccount, @@ -434,9 +443,10 @@ initialEncryptedData.message, ); + const newSessionVersion = existingSession ? existingSession.version + 1 : 1; contentSessions[contentIdentityKeys.ed25519] = { session, - version: 1, + version: newSessionVersion, }; persistCryptoStore(); @@ -445,11 +455,12 @@ async contentOutboundSessionCreator( contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, - ): Promise { + ): Promise { if (!cryptoStore) { throw new Error('Crypto account not initialized'); } const { contentAccount, contentSessions } = cryptoStore; + const existingSession = contentSessions[contentIdentityKeys.ed25519]; const session = new olm.Session(); session.create_outbound( @@ -464,13 +475,19 @@ JSON.stringify(initialEncryptedMessageContent), ); - contentSessions[contentIdentityKeys.ed25519] = { session, version: 1 }; + const newSessionVersion = existingSession ? existingSession.version + 1 : 1; + contentSessions[contentIdentityKeys.ed25519] = { + session, + version: newSessionVersion, + }; persistCryptoStore(); - return { + const encryptedData: EncryptedData = { message: initialEncryptedData.body, messageType: initialEncryptedData.type, }; + + return { encryptedData, sessionVersion: newSessionVersion }; }, async notificationsSessionCreator( cookie: ?string,