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 @@ -3,13 +3,21 @@ 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 { ClientMessageToDevice } from '../tunnelbroker/tunnelbroker-context.js'; import type { IdentityKeysBlob, OLMIdentityKeys, SignedIdentityKeysBlob, } from '../types/crypto-types'; +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({ @@ -27,4 +35,82 @@ notificationIdentityPublicKeys: olmIdentityKeysValidator, }); -export { signedIdentityKeysBlobValidator, identityKeysBlobValidator }; +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: ClientMessageToDevice) => 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; + + if (!keys.contentInitializationInfo.oneTimeKey) { + console.log(`One-time key is missing for device ${recipientDeviceID}`); + continue; + } + try { + const encryptedContent = await olmAPI.contentOutboundSessionCreator( + primaryIdentityPublicKeys, + keys.contentInitializationInfo, + ); + + const sessionCreationMessage: OutboundSessionCreation = { + type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, + senderInfo: { + userID, + deviceID, + }, + encryptedContent, + }; + + 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 { + signedIdentityKeysBlobValidator, + identityKeysBlobValidator, + getContentSigningKey, + createOlmSessionsWithOwnDevices, +}; diff --git a/native/account/siwe-panel.react.js b/native/account/siwe-panel.react.js --- a/native/account/siwe-panel.react.js +++ b/native/account/siwe-panel.react.js @@ -18,6 +18,7 @@ import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { SIWEWebViewMessage, SIWEResult } from 'lib/types/siwe-types.js'; import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; +import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; @@ -26,7 +27,6 @@ import type { BottomSheetRef } from '../types/bottom-sheet.js'; import { UnknownErrorAlertDetails } from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; -import { getContentSigningKey } from '../utils/crypto-utils.js'; import { defaultLandingURLPrefix } from '../utils/url-utils.js'; const commSIWE = `${defaultLandingURLPrefix}/siwe`; diff --git a/native/backup/use-client-backup.js b/native/backup/use-client-backup.js --- a/native/backup/use-client-backup.js +++ b/native/backup/use-client-backup.js @@ -3,11 +3,11 @@ import * as React from 'react'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; +import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import { fetchNativeKeychainCredentials } from '../account/native-credentials.js'; import { commCoreModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; -import { getContentSigningKey } from '../utils/crypto-utils.js'; type ClientBackup = { +uploadBackupProtocol: () => Promise, diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -25,12 +25,12 @@ deviceOlmInboundKeysValidator, userDeviceOlmInboundKeysValidator, } from 'lib/types/identity-service-types.js'; +import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import { assertWithValidator } from 'lib/utils/validation-utils.js'; import { getCommServicesAuthMetadataEmitter } from '../event-emitters/csa-auth-metadata-emitter.js'; import { commCoreModule, commRustModule } from '../native-modules.js'; import { useSelector } from '../redux/redux-utils.js'; -import { getContentSigningKey } from '../utils/crypto-utils.js'; type Props = { +children: React.Node, diff --git a/native/profile/tunnelbroker-menu.react.js b/native/profile/tunnelbroker-menu.react.js --- a/native/profile/tunnelbroker-menu.react.js +++ b/native/profile/tunnelbroker-menu.react.js @@ -5,24 +5,25 @@ import { Text, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; import type { TunnelbrokerMessage } from 'lib/types/tunnelbroker/messages.js'; import { type EncryptedMessage, peerToPeerMessageTypes, } from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; +import { + createOlmSessionsWithOwnDevices, + getContentSigningKey, +} from 'lib/utils/crypto-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import Button from '../components/button.react.js'; import TextInput from '../components/text-input.react.js'; -import { commCoreModule } from '../native-modules.js'; +import { olmAPI } from '../crypto/olm-api.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useColors, useStyles } from '../themes/colors.js'; -import { - createOlmSessionsWithOwnDevices, - getContentSigningKey, -} from '../utils/crypto-utils.js'; type Props = { +navigation: ProfileNavigationProp<'TunnelbrokerMenu'>, @@ -35,6 +36,7 @@ const currentUserID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); + const identityContext = React.useContext(IdentityClientContext); const { connected, addListener, sendMessage, removeListener } = useTunnelbroker(); @@ -61,20 +63,28 @@ }, [message, recipient, sendMessage]); const onCreateSessions = React.useCallback(async () => { + if (!identityContext) { + return; + } + const authMetadata = await identityContext.getAuthMetadata(); try { - await createOlmSessionsWithOwnDevices(sendMessage); + await createOlmSessionsWithOwnDevices( + authMetadata, + identityContext.identityClient, + sendMessage, + ); } catch (e) { console.log(`Error creating olm sessions with own devices: ${e.message}`); } - }, [sendMessage]); + }, [identityContext, sendMessage]); const onSendEncryptedMessage = React.useCallback(async () => { try { if (!currentUserID) { return; } - await commCoreModule.initializeCryptoAccount(); - const encrypted = await commCoreModule.encrypt( + await olmAPI.initializeCryptoAccount(); + const encrypted = await olmAPI.encrypt( `Encrypted message to ${recipient}`, recipient, ); diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js --- a/native/qr-code/qr-code-screen.react.js +++ b/native/qr-code/qr-code-screen.react.js @@ -15,6 +15,7 @@ NonceChallenge, SignedMessage, } from 'lib/types/identity-service-types.js'; +import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js'; import { @@ -26,7 +27,6 @@ import { useStyles } from '../themes/colors.js'; import * as AES from '../utils/aes-crypto-module.js'; import Alert from '../utils/alert.js'; -import { getContentSigningKey } from '../utils/crypto-utils.js'; type QRCodeScreenProps = { +navigation: QRCodeSignInNavigationProp<'QRCodeScreen'>, diff --git a/native/utils/crypto-utils.js b/native/utils/crypto-utils.js --- a/native/utils/crypto-utils.js +++ b/native/utils/crypto-utils.js @@ -1,18 +1,9 @@ // @flow -import { type ClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js'; -import type { - IdentityKeysBlob, - OLMIdentityKeys, -} from 'lib/types/crypto-types.js'; -import type { OutboundKeyInfoResponse } from 'lib/types/identity-service-types'; +import type { OLMIdentityKeys } from 'lib/types/crypto-types.js'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; -import { - type OutboundSessionCreation, - peerToPeerMessageTypes, -} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; -import { commCoreModule, commRustModule } from '../native-modules.js'; +import { commCoreModule } from '../native-modules.js'; function nativeNotificationsSessionCreator( notificationsIdentityKeys: OLMIdentityKeys, @@ -30,14 +21,6 @@ ); } -async function getContentSigningKey(): Promise { - await commCoreModule.initializeCryptoAccount(); - const { - primaryIdentityPublicKeys: { ed25519 }, - } = await commCoreModule.getUserPublicKey(); - return ed25519; -} - function nativeOutboundContentSessionCreator( contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, @@ -58,75 +41,7 @@ ); } -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'); - } - - await commCoreModule.initializeCryptoAccount(); - 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), - }); - 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 { - getContentSigningKey, nativeNotificationsSessionCreator, - createOlmSessionsWithOwnDevices, nativeOutboundContentSessionCreator, }; diff --git a/web/settings/account-settings.react.js b/web/settings/account-settings.react.js --- a/web/settings/account-settings.react.js +++ b/web/settings/account-settings.react.js @@ -7,7 +7,9 @@ import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import { useStringForUser } from 'lib/hooks/ens-cache.js'; import { accountHasPassword } from 'lib/shared/account-utils.js'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; +import { createOlmSessionsWithOwnDevices } from 'lib/utils/crypto-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import css from './account-settings.css'; @@ -30,6 +32,7 @@ () => dispatchActionPromise(logOutActionTypes, sendLogoutRequest()), [dispatchActionPromise, sendLogoutRequest], ); + const identityContext = React.useContext(IdentityClientContext); const { pushModal, popModal } = useModalContext(); const showPasswordChangeModal = React.useCallback( @@ -77,6 +80,22 @@ [addListener, popModal, pushModal, removeListener], ); + const onCreateOlmSessions = React.useCallback(async () => { + if (!identityContext) { + return; + } + const authMetadata = await identityContext.getAuthMetadata(); + try { + await createOlmSessionsWithOwnDevices( + authMetadata, + identityContext.identityClient, + sendMessage, + ); + } catch (e) { + console.log(`Error creating olm sessions with own devices: ${e.message}`); + } + }, [identityContext, sendMessage]); + const openBackupTestRestoreModal = React.useCallback( () => pushModal(), [popModal, pushModal], @@ -147,6 +166,12 @@

Show list

+
  • + Create session with own devices + +
  • diff --git a/web/settings/tunnelbroker-test.react.js b/web/settings/tunnelbroker-test.react.js --- a/web/settings/tunnelbroker-test.react.js +++ b/web/settings/tunnelbroker-test.react.js @@ -4,11 +4,18 @@ import * as React from 'react'; import { type ClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js'; +import { + type EncryptedMessage, + peerToPeerMessageTypes, +} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; +import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import css from './tunnelbroker-test.css'; import Button from '../components/button.react.js'; +import { olmAPI } from '../crypto/olm-api.js'; import Input from '../modals/input.react.js'; import Modal from '../modals/modal.react.js'; +import { useSelector } from '../redux/redux-utils.js'; type Props = { +sendMessage: (message: ClientMessageToDevice) => Promise, @@ -24,6 +31,10 @@ const recipientInput = React.useRef(null); const messageInput = React.useRef(null); + const currentUserID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + const onSubmit = React.useCallback( async (event: SyntheticEvent) => { event.preventDefault(); @@ -39,6 +50,42 @@ [message, recipient, sendMessage], ); + const onSubmitEncrypted = React.useCallback( + async (event: SyntheticEvent) => { + event.preventDefault(); + + if (!currentUserID) { + return; + } + + setLoading(true); + try { + await olmAPI.initializeCryptoAccount(); + const encrypted = await olmAPI.encrypt( + `Encrypted message to ${recipient}`, + recipient, + ); + const deviceID = await getContentSigningKey(); + const encryptedMessage: EncryptedMessage = { + type: peerToPeerMessageTypes.ENCRYPTED_MESSAGE, + senderInfo: { + deviceID, + userID: currentUserID, + }, + encryptedContent: encrypted, + }; + await sendMessage({ + deviceID: recipient, + payload: JSON.stringify(encryptedMessage), + }); + } catch (e) { + setErrorMessage(e.message); + } + setLoading(false); + }, + [currentUserID, recipient, sendMessage], + ); + let errorMsg; if (errorMessage) { errorMsg =
    {errorMessage}
    ; @@ -86,6 +133,17 @@ {errorMsg} +
    + + {errorMsg} +
    );