diff --git a/keyserver/src/utils/identity-utils.js b/keyserver/src/utils/identity-utils.js index 327d2d42b..0655799c9 100644 --- a/keyserver/src/utils/identity-utils.js +++ b/keyserver/src/utils/identity-utils.js @@ -1,78 +1,98 @@ // @flow import { getRustAPI } from 'rust-node-addon'; import type { UserIdentitiesResponse } from 'lib/types/identity-service-types.js'; import { getContentSigningKey } from './olm-utils.js'; import type { IdentityInfo } from '../user/identity.js'; import { verifyUserLoggedIn } from '../user/login.js'; async function findUserIdentities( userIDs: $ReadOnlyArray, ): Promise { const [rustAPI, identityInfo, deviceID] = await Promise.all([ getRustAPI(), verifyUserLoggedIn(), getContentSigningKey(), ]); return await rustAPI.findUserIdentities( identityInfo.userId, deviceID, identityInfo.accessToken, userIDs, ); } async function privilegedDeleteUsers( userIDs: $ReadOnlyArray, ): Promise { const [rustAPI, identityInfo, deviceID] = await Promise.all([ getRustAPI(), verifyUserLoggedIn(), getContentSigningKey(), ]); await rustAPI.privilegedDeleteUsers( identityInfo.userId, deviceID, identityInfo.accessToken, userIDs, ); } async function privilegedResetUserPassword( username: string, password: string, ): Promise { const [rustAPI, identityInfo, deviceID] = await Promise.all([ getRustAPI(), verifyUserLoggedIn(), getContentSigningKey(), ]); await rustAPI.privilegedResetUserPassword( identityInfo.userId, deviceID, identityInfo.accessToken, username, password, ); } async function syncPlatformDetails(identityInfo: IdentityInfo): Promise { const [rustAPI, deviceID] = await Promise.all([ getRustAPI(), getContentSigningKey(), ]); return rustAPI.syncPlatformDetails( identityInfo.userId, deviceID, identityInfo.accessToken, ); } +async function uploadOneTimeKeys( + identityInfo: IdentityInfo, + contentOneTimeKeys: $ReadOnlyArray, + notifOneTimeKeys: $ReadOnlyArray, +): Promise { + const [rustAPI, deviceID] = await Promise.all([ + getRustAPI(), + getContentSigningKey(), + ]); + + await rustAPI.uploadOneTimeKeys( + identityInfo.userId, + deviceID, + identityInfo.accessToken, + contentOneTimeKeys, + notifOneTimeKeys, + ); +} + export { findUserIdentities, privilegedDeleteUsers, privilegedResetUserPassword, syncPlatformDetails, + uploadOneTimeKeys, }; diff --git a/keyserver/src/utils/olm-utils.js b/keyserver/src/utils/olm-utils.js index eaa560598..f5e56d42a 100644 --- a/keyserver/src/utils/olm-utils.js +++ b/keyserver/src/utils/olm-utils.js @@ -1,339 +1,329 @@ // @flow import olm from '@commapp/olm'; import type { Account as OlmAccount, Utility as OlmUtility, Session as OlmSession, } from '@commapp/olm'; import invariant from 'invariant'; import { getRustAPI } from 'rust-node-addon'; import uuid from 'uuid'; import { getOneTimeKeyValuesFromBlob } from 'lib/shared/crypto-utils.js'; import { olmEncryptedMessageTypes } from 'lib/types/crypto-types.js'; import type { IdentityNewDeviceKeyUpload } from 'lib/types/identity-service-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { getAccountPrekeysSet, shouldForgetPrekey, shouldRotatePrekey, retrieveAccountKeysSet, } from 'lib/utils/olm-utils.js'; +import { uploadOneTimeKeys } from './identity-utils.js'; import { fetchCallUpdateOlmAccount, fetchOlmAccount, } from '../updaters/olm-account-updater.js'; import { verifyUserLoggedIn } from '../user/login.js'; export type PickledOlmAccount = { +picklingKey: string, +pickledAccount: string, }; async function createPickledOlmAccount(): Promise { await olm.init(); const account = new olm.Account(); account.create(); const picklingKey = uuid.v4(); const pickledAccount = account.pickle(picklingKey); return { picklingKey: picklingKey, pickledAccount: pickledAccount, }; } async function unpickleOlmAccount( pickledOlmAccount: PickledOlmAccount, ): Promise { await olm.init(); const account = new olm.Account(); account.unpickle( pickledOlmAccount.picklingKey, pickledOlmAccount.pickledAccount, ); return account; } async function createPickledOlmSession( account: OlmAccount, accountPicklingKey: string, initialEncryptedMessage: string, theirCurve25519Key?: string, ): Promise { await olm.init(); const session = new olm.Session(); if (theirCurve25519Key) { session.create_inbound_from( account, theirCurve25519Key, initialEncryptedMessage, ); } else { session.create_inbound(account, initialEncryptedMessage); } account.remove_one_time_keys(session); session.decrypt(olmEncryptedMessageTypes.PREKEY, initialEncryptedMessage); return session.pickle(accountPicklingKey); } async function unpickleOlmSession( pickledSession: string, picklingKey: string, ): Promise { await olm.init(); const session = new olm.Session(); session.unpickle(picklingKey, pickledSession); return session; } let cachedOLMUtility: OlmUtility; function getOlmUtility(): OlmUtility { if (cachedOLMUtility) { return cachedOLMUtility; } cachedOLMUtility = new olm.Utility(); return cachedOLMUtility; } async function markPrekeysAsPublished(): Promise { await Promise.all([ fetchCallUpdateOlmAccount('content', (contentAccount: OlmAccount) => { contentAccount.mark_prekey_as_published(); }), fetchCallUpdateOlmAccount('notifications', (notifAccount: OlmAccount) => { notifAccount.mark_prekey_as_published(); }), ]); } async function getNewDeviceKeyUpload(): Promise { let contentIdentityKeys: string; let contentOneTimeKeys: $ReadOnlyArray; let contentPrekey: string; let contentPrekeySignature: string; let notifIdentityKeys: string; let notifOneTimeKeys: $ReadOnlyArray; let notifPrekey: string; let notifPrekeySignature: string; let contentAccountInfo: OlmAccount; await Promise.all([ fetchCallUpdateOlmAccount('content', (contentAccount: OlmAccount) => { const { identityKeys, oneTimeKeys, prekey, prekeySignature } = retrieveAccountKeysSet(contentAccount); contentIdentityKeys = identityKeys; contentOneTimeKeys = oneTimeKeys; contentPrekey = prekey; contentPrekeySignature = prekeySignature; contentAccountInfo = contentAccount; contentAccount.mark_keys_as_published(); }), fetchCallUpdateOlmAccount('notifications', (notifAccount: OlmAccount) => { const { identityKeys, oneTimeKeys, prekey, prekeySignature } = retrieveAccountKeysSet(notifAccount); notifIdentityKeys = identityKeys; notifOneTimeKeys = oneTimeKeys; notifPrekey = prekey; notifPrekeySignature = prekeySignature; notifAccount.mark_keys_as_published(); }), ]); invariant( contentIdentityKeys, 'content identity keys not set after fetchCallUpdateOlmAccount', ); invariant( notifIdentityKeys, 'notif identity keys not set after fetchCallUpdateOlmAccount', ); invariant( contentPrekey, 'content prekey not set after fetchCallUpdateOlmAccount', ); invariant( notifPrekey, 'notif prekey not set after fetchCallUpdateOlmAccount', ); invariant( contentPrekeySignature, 'content prekey signature not set after fetchCallUpdateOlmAccount', ); invariant( notifPrekeySignature, 'notif prekey signature not set after fetchCallUpdateOlmAccount', ); invariant( contentOneTimeKeys, 'content one-time keys not set after fetchCallUpdateOlmAccount', ); invariant( notifOneTimeKeys, 'notif one-time keys not set after fetchCallUpdateOlmAccount', ); invariant( contentAccountInfo, 'content account info not set after fetchCallUpdateOlmAccount', ); const identityKeysBlob = { primaryIdentityPublicKeys: JSON.parse(contentIdentityKeys), notificationIdentityPublicKeys: JSON.parse(notifIdentityKeys), }; const keyPayload = JSON.stringify(identityKeysBlob); const keyPayloadSignature = contentAccountInfo.sign(keyPayload); return { keyPayload, keyPayloadSignature, contentPrekey, contentPrekeySignature, notifPrekey, notifPrekeySignature, contentOneTimeKeys, notifOneTimeKeys, }; } async function uploadNewOneTimeKeys(numberOfKeys: number) { - const [rustAPI, identityInfo, deviceID] = await Promise.all([ - getRustAPI(), - verifyUserLoggedIn(), - getContentSigningKey(), - ]); - + const identityInfo = await verifyUserLoggedIn(); if (!identityInfo) { throw new ServerError('missing_identity_info'); } let contentOneTimeKeys: ?$ReadOnlyArray; let notifOneTimeKeys: ?$ReadOnlyArray; await Promise.all([ fetchCallUpdateOlmAccount('content', (contentAccount: OlmAccount) => { contentAccount.generate_one_time_keys(numberOfKeys); contentOneTimeKeys = getOneTimeKeyValuesFromBlob( contentAccount.one_time_keys(), ); contentAccount.mark_keys_as_published(); }), fetchCallUpdateOlmAccount('notifications', (notifAccount: OlmAccount) => { notifAccount.generate_one_time_keys(numberOfKeys); notifOneTimeKeys = getOneTimeKeyValuesFromBlob( notifAccount.one_time_keys(), ); notifAccount.mark_keys_as_published(); }), ]); invariant( contentOneTimeKeys, 'content one-time keys not set after fetchCallUpdateOlmAccount', ); invariant( notifOneTimeKeys, 'notif one-time keys not set after fetchCallUpdateOlmAccount', ); - await rustAPI.uploadOneTimeKeys( - identityInfo.userId, - deviceID, - identityInfo.accessToken, - contentOneTimeKeys, - notifOneTimeKeys, - ); + await uploadOneTimeKeys(identityInfo, contentOneTimeKeys, notifOneTimeKeys); } async function getContentSigningKey(): Promise { const accountInfo = await fetchOlmAccount('content'); return JSON.parse(accountInfo.account.identity_keys()).ed25519; } function validateAndUploadAccountPrekeys( contentAccount: OlmAccount, notifAccount: OlmAccount, ): Promise { if (contentAccount.unpublished_prekey()) { return publishPrekeysToIdentity(contentAccount, notifAccount); } // Since keys are rotated synchronously, only check validity of one if (shouldRotatePrekey(contentAccount)) { contentAccount.generate_prekey(); notifAccount.generate_prekey(); return publishPrekeysToIdentity(contentAccount, notifAccount); } if (shouldForgetPrekey(contentAccount)) { contentAccount.forget_old_prekey(); notifAccount.forget_old_prekey(); } return Promise.resolve(); } async function publishPrekeysToIdentity( contentAccount: OlmAccount, notifAccount: OlmAccount, ): Promise { const rustAPIPromise = getRustAPI(); const verifyUserLoggedInPromise = verifyUserLoggedIn(); const deviceID = JSON.parse(contentAccount.identity_keys()).ed25519; const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } = getAccountPrekeysSet(contentAccount); const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } = getAccountPrekeysSet(notifAccount); if (!contentPrekeySignature || !notifPrekeySignature) { console.warn('Unable to create valid signature for a prekey'); return; } const [rustAPI, identityInfo] = await Promise.all([ rustAPIPromise, verifyUserLoggedInPromise, ]); if (!identityInfo) { console.warn( 'Attempted to refresh prekeys before registering with Identity service', ); return; } await rustAPI.publishPrekeys( identityInfo.userId, deviceID, identityInfo.accessToken, contentPrekey, contentPrekeySignature, notifPrekey, notifPrekeySignature, ); contentAccount.mark_prekey_as_published(); notifAccount.mark_prekey_as_published(); } export { createPickledOlmAccount, createPickledOlmSession, getOlmUtility, unpickleOlmAccount, unpickleOlmSession, uploadNewOneTimeKeys, getContentSigningKey, validateAndUploadAccountPrekeys, publishPrekeysToIdentity, getNewDeviceKeyUpload, markPrekeysAsPublished, };