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/lib/utils/olm-utils.js b/lib/utils/olm-utils.js --- a/lib/utils/olm-utils.js +++ b/lib/utils/olm-utils.js @@ -84,7 +84,7 @@ function getAccountOneTimeKeys( account: OlmAccount, - numberOfKeys: number, + numberOfKeys: number = ONE_TIME_KEYS_NUMBER, ): $ReadOnlyArray { let oneTimeKeys = getOneTimeKeyValuesFromBlob(account.one_time_keys()); if (oneTimeKeys.length < numberOfKeys) { 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 @@ -20,10 +20,16 @@ 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 { getConfig } from 'lib/utils/config.js'; -import { retrieveAccountKeysSet } from 'lib/utils/olm-utils.js'; +import { + retrieveIdentityKeysAndPrekeys, + getAccountOneTimeKeys, +} from 'lib/utils/olm-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { @@ -176,6 +182,78 @@ } function useGetNewDeviceKeyUpload(): () => Promise { + const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); + // `getExistingDeviceKeyUpload()` will initialize OLM, so no need to do it + // again + const getExistingDeviceKeyUpload = useGetExistingDeviceKeyUpload(); + const dispatch = useDispatch(); + + return React.useCallback(async () => { + const [ + { + keyPayload, + keyPayloadSignature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, + }, + cryptoStore, + ] = await Promise.all([ + getExistingDeviceKeyUpload(), + 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 contentOneTimeKeys = getAccountOneTimeKeys(primaryOLMAccount); + const notifOneTimeKeys = getAccountOneTimeKeys(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, + keyPayloadSignature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, + contentOneTimeKeys, + notifOneTimeKeys, + }; + }, [dispatch, getOrCreateCryptoStore, getExistingDeviceKeyUpload]); +} + +function useGetExistingDeviceKeyUpload(): () => Promise { const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); // `getSignedIdentityKeysBlob()` will initialize OLM, so no need to do it // again @@ -183,7 +261,10 @@ const dispatch = useDispatch(); return React.useCallback(async () => { - const [signedIdentityKeysBlob, cryptoStore] = await Promise.all([ + const [ + { payload: keyPayload, signature: keyPayloadSignature }, + cryptoStore, + ] = await Promise.all([ getSignedIdentityKeysBlob(), getOrCreateCryptoStore(), ]); @@ -199,10 +280,10 @@ cryptoStore.notificationAccount.pickledAccount, ); - const primaryAccountKeysSet = retrieveAccountKeysSet(primaryOLMAccount); - const notificationAccountKeysSet = retrieveAccountKeysSet( - notificationOLMAccount, - ); + const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } = + retrieveIdentityKeysAndPrekeys(primaryOLMAccount); + const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } = + retrieveIdentityKeysAndPrekeys(notificationOLMAccount); const pickledPrimaryAccount = primaryOLMAccount.pickle( cryptoStore.primaryAccount.picklingKey, @@ -227,14 +308,12 @@ dispatch({ type: setCryptoStore, payload: updatedCryptoStore }); return { - keyPayload: signedIdentityKeysBlob.payload, - keyPayloadSignature: signedIdentityKeysBlob.signature, - contentPrekey: primaryAccountKeysSet.prekey, - contentPrekeySignature: primaryAccountKeysSet.prekeySignature, - notifPrekey: notificationAccountKeysSet.prekey, - notifPrekeySignature: notificationAccountKeysSet.prekeySignature, - contentOneTimeKeys: primaryAccountKeysSet.oneTimeKeys, - notifOneTimeKeys: notificationAccountKeysSet.oneTimeKeys, + keyPayload, + keyPayloadSignature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, }; }, [dispatch, getOrCreateCryptoStore, getSignedIdentityKeysBlob]); } @@ -432,4 +511,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, };