diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -171,6 +171,15 @@ +notifOneTimeKeys: $ReadOnlyArray, }; +export type IdentityExistingDeviceKeyUpload = { + +keyPayload: string, + +keyPayloadSignature: string, + +contentPrekey: string, + +contentPrekeySignature: string, + +notifPrekey: string, + +notifPrekeySignature: string, +}; + // Device list types export type RawDeviceList = { diff --git a/web/account/account-hooks.js b/web/account/account-hooks.js --- a/web/account/account-hooks.js +++ b/web/account/account-hooks.js @@ -16,9 +16,15 @@ OLMIdentityKeys, NotificationsOlmDataType, } from 'lib/types/crypto-types.js'; -import { type IdentityNewDeviceKeyUpload } from 'lib/types/identity-service-types.js'; +import { + type IdentityNewDeviceKeyUpload, + type IdentityExistingDeviceKeyUpload, +} from 'lib/types/identity-service-types.js'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; -import { retrieveAccountKeysSet } from 'lib/utils/olm-utils.js'; +import { + retrieveAccountKeysSet, + retrieveIdentityKeysAndPrekeys, +} from 'lib/utils/olm-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { @@ -234,6 +240,68 @@ }, [dispatch, getOrCreateCryptoStore, getSignedIdentityKeysBlob]); } +function useGetExistingDeviceKeyUpload(): () => Promise { + const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); + // `getSignedIdentityKeysBlob()` will initialize OLM, so no need to do it + // again + const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); + const dispatch = useDispatch(); + + return React.useCallback(async () => { + const [signedIdentityKeysBlob, cryptoStore] = await Promise.all([ + getSignedIdentityKeysBlob(), + getOrCreateCryptoStore(), + ]); + + const primaryOLMAccount = new olm.Account(); + const notificationOLMAccount = new olm.Account(); + primaryOLMAccount.unpickle( + cryptoStore.primaryAccount.picklingKey, + cryptoStore.primaryAccount.pickledAccount, + ); + notificationOLMAccount.unpickle( + cryptoStore.notificationAccount.picklingKey, + cryptoStore.notificationAccount.pickledAccount, + ); + + const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } = + retrieveIdentityKeysAndPrekeys(primaryOLMAccount); + const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } = + retrieveIdentityKeysAndPrekeys(notificationOLMAccount); + + const pickledPrimaryAccount = primaryOLMAccount.pickle( + cryptoStore.primaryAccount.picklingKey, + ); + const pickledNotificationAccount = notificationOLMAccount.pickle( + cryptoStore.notificationAccount.picklingKey, + ); + + const updatedCryptoStore = { + primaryAccount: { + picklingKey: cryptoStore.primaryAccount.picklingKey, + pickledAccount: pickledPrimaryAccount, + }, + primaryIdentityKeys: cryptoStore.primaryIdentityKeys, + notificationAccount: { + picklingKey: cryptoStore.notificationAccount.picklingKey, + pickledAccount: pickledNotificationAccount, + }, + notificationIdentityKeys: cryptoStore.notificationIdentityKeys, + }; + + dispatch({ type: setCryptoStore, payload: updatedCryptoStore }); + + return { + keyPayload: signedIdentityKeysBlob.payload, + keyPayloadSignature: signedIdentityKeysBlob.signature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, + }; + }, [dispatch, getOrCreateCryptoStore, getSignedIdentityKeysBlob]); +} + function OlmSessionCreatorProvider(props: Props): React.Node { const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); const currentCryptoStore = useSelector(state => state.cryptoStore); @@ -408,4 +476,5 @@ OlmSessionCreatorProvider, GetOrCreateCryptoStoreProvider, useGetNewDeviceKeyUpload, + useGetExistingDeviceKeyUpload, }; diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -19,6 +19,7 @@ type UserDevicesOlmOutboundKeys, type IdentityAuthResult, type IdentityNewDeviceKeyUpload, + type IdentityExistingDeviceKeyUpload, identityDeviceTypes, identityAuthResultValidator, type UserDevicesOlmInboundKeys, @@ -50,12 +51,14 @@ authClient: ?IdentityAuthClient.IdentityClientServicePromiseClient; unauthClient: IdentityUnauthClient.IdentityClientServicePromiseClient; getNewDeviceKeyUpload: () => Promise; + getExistingDeviceKeyUpload: () => Promise; constructor( platformDetails: PlatformDetails, overridedOpaqueFilepath: ?string, authLayer: ?IdentityServiceAuthLayer, getNewDeviceKeyUpload: () => Promise, + getExistingDeviceKeyUpload: () => Promise, ) { this.overridedOpaqueFilepath = overridedOpaqueFilepath; if (authLayer) { @@ -67,6 +70,7 @@ this.unauthClient = IdentityServiceClientWrapper.createUnauthClient(platformDetails); this.getNewDeviceKeyUpload = getNewDeviceKeyUpload; + this.getExistingDeviceKeyUpload = getExistingDeviceKeyUpload; } static determineSocketAddr(): string { @@ -354,14 +358,16 @@ } const [identityDeviceKeyUpload] = await Promise.all([ - this.getNewDeviceKeyUpload(), + this.getExistingDeviceKeyUpload(), initOpaque(this.overridedOpaqueFilepath), ]); const opaqueLogin = new Login(); const startRequestBytes = opaqueLogin.start(password); - const deviceKeyUpload = authNewDeviceKeyUpload(identityDeviceKeyUpload); + const deviceKeyUpload = authExistingDeviceKeyUpload( + identityDeviceKeyUpload, + ); const loginStartRequest = new OpaqueLoginStartRequest(); loginStartRequest.setUsername(username); @@ -409,8 +415,10 @@ siweMessage: string, siweSignature: string, ) => { - const identityDeviceKeyUpload = await this.getNewDeviceKeyUpload(); - const deviceKeyUpload = authNewDeviceKeyUpload(identityDeviceKeyUpload); + const identityDeviceKeyUpload = await this.getExistingDeviceKeyUpload(); + const deviceKeyUpload = authExistingDeviceKeyUpload( + identityDeviceKeyUpload, + ); const loginRequest = new WalletAuthRequest(); loginRequest.setSiweMessage(siweMessage); @@ -556,6 +564,39 @@ return deviceKeyUpload; } +function authExistingDeviceKeyUpload( + uploadData: IdentityExistingDeviceKeyUpload, +): DeviceKeyUpload { + const { + keyPayload, + keyPayloadSignature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, + } = uploadData; + + const identityKeyInfo = createIdentityKeyInfo( + keyPayload, + keyPayloadSignature, + ); + + const contentPrekeyUpload = createPrekey( + contentPrekey, + contentPrekeySignature, + ); + + const notifPrekeyUpload = createPrekey(notifPrekey, notifPrekeySignature); + + const deviceKeyUpload = createDeviceKeyUpload( + identityKeyInfo, + contentPrekeyUpload, + notifPrekeyUpload, + ); + + return deviceKeyUpload; +} + function createIdentityKeyInfo( keyPayload: string, keyPayloadSignature: string, diff --git a/web/grpc/identity-service-context-provider.react.js b/web/grpc/identity-service-context-provider.react.js --- a/web/grpc/identity-service-context-provider.react.js +++ b/web/grpc/identity-service-context-provider.react.js @@ -10,7 +10,10 @@ import { IdentityServiceClientSharedProxy } from './identity-service-client-proxy.js'; import { IdentityServiceClientWrapper } from './identity-service-client-wrapper.js'; -import { useGetNewDeviceKeyUpload } from '../account/account-hooks.js'; +import { + useGetNewDeviceKeyUpload, + useGetExistingDeviceKeyUpload, +} from '../account/account-hooks.js'; import { usingSharedWorker } from '../crypto/olm-api.js'; import { useSelector } from '../redux/redux-utils.js'; @@ -26,6 +29,7 @@ state => state.cryptoStore?.primaryIdentityKeys.ed25519, ); const getNewDeviceKeyUpload = useGetNewDeviceKeyUpload(); + const getExistingDeviceKeyUpload = useGetExistingDeviceKeyUpload(); const client = React.useMemo(() => { let authLayer = null; @@ -44,9 +48,16 @@ null, authLayer, getNewDeviceKeyUpload, + getExistingDeviceKeyUpload, ); } - }, [accessToken, deviceID, getNewDeviceKeyUpload, userID]); + }, [ + accessToken, + deviceID, + getNewDeviceKeyUpload, + getExistingDeviceKeyUpload, + userID, + ]); const getAuthMetadata = React.useCallback<() => Promise>( async () => ({ diff --git a/web/shared-worker/worker/identity-client.js b/web/shared-worker/worker/identity-client.js --- a/web/shared-worker/worker/identity-client.js +++ b/web/shared-worker/worker/identity-client.js @@ -1,6 +1,9 @@ // @flow -import { getNewDeviceKeyUpload } from './worker-crypto.js'; +import { + getNewDeviceKeyUpload, + getExistingDeviceKeyUpload, +} from './worker-crypto.js'; import { IdentityServiceClientWrapper } from '../../grpc/identity-service-client-wrapper.js'; import { type WorkerResponseMessage, @@ -26,6 +29,7 @@ message.opaqueWasmPath, message.authLayer, async () => getNewDeviceKeyUpload(), + async () => getExistingDeviceKeyUpload(), ); return undefined; } 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 @@ -9,8 +9,14 @@ IdentityKeysBlob, SignedIdentityKeysBlob, } from 'lib/types/crypto-types.js'; -import type { IdentityNewDeviceKeyUpload } from 'lib/types/identity-service-types.js'; -import { retrieveAccountKeysSet } from 'lib/utils/olm-utils.js'; +import type { + IdentityNewDeviceKeyUpload, + IdentityExistingDeviceKeyUpload, +} from 'lib/types/identity-service-types.js'; +import { + retrieveIdentityKeysAndPrekeys, + retrieveAccountKeysSet, +} from 'lib/utils/olm-utils.js'; import { getProcessingStoreOpsExceptionMessage } from './process-operations.js'; import { getDBModule, getSQLiteQueryExecutor } from './worker-database.js'; @@ -222,9 +228,35 @@ }; } +function getExistingDeviceKeyUpload(): IdentityExistingDeviceKeyUpload { + if (!cryptoStore) { + throw new Error('Crypto account not initialized'); + } + const { contentAccount, notificationAccount } = cryptoStore; + + const signedIdentityKeysBlob = getSignedIdentityKeysBlob(); + + const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } = + retrieveIdentityKeysAndPrekeys(contentAccount); + const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } = + retrieveIdentityKeysAndPrekeys(notificationAccount); + + persistCryptoStore(); + + return { + keyPayload: signedIdentityKeysBlob.payload, + keyPayloadSignature: signedIdentityKeysBlob.signature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, + }; +} + export { clearCryptoStore, processAppOlmApiRequest, getSignedIdentityKeysBlob, getNewDeviceKeyUpload, + getExistingDeviceKeyUpload, };