diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js --- a/keyserver/src/endpoints.js +++ b/keyserver/src/endpoints.js @@ -18,7 +18,10 @@ calendarQueryUpdateResponder, } from './responders/entry-responders.js'; import type { JSONResponder } from './responders/handlers.js'; -import { getSessionPublicKeysResponder } from './responders/keys-responders.js'; +import { + getSessionPublicKeysResponder, + getOlmSessionInitializationDataResponder, +} from './responders/keys-responders.js'; import { createOrUpdatePublicLinkResponder, disableInviteLinkResponder, @@ -303,6 +306,10 @@ responder: uploadMediaMetadataResponder, requiredPolicies: baseLegalPolicies, }, + get_olm_session_initialization_data: { + responder: getOlmSessionInitializationDataResponder, + requiredPolicies: [], + }, }; export { jsonEndpoints }; diff --git a/keyserver/src/responders/keys-responders.js b/keyserver/src/responders/keys-responders.js --- a/keyserver/src/responders/keys-responders.js +++ b/keyserver/src/responders/keys-responders.js @@ -1,18 +1,32 @@ // @flow +import type { Account as OlmAccount } from '@commapp/olm'; import t, { type TUnion } from 'tcomb'; -import type { GetSessionPublicKeysArgs } from 'lib/types/request-types.js'; +import type { + OlmSessionInitializationInfo, + GetOlmSessionInitializationDataResponse, + GetSessionPublicKeysArgs, +} from 'lib/types/request-types.js'; import { type SessionPublicKeys, sessionPublicKeysValidator, } from 'lib/types/session-types.js'; +import { ServerError } from 'lib/utils/errors.js'; import { tShape, tNull } from 'lib/utils/validation-utils.js'; import { fetchSessionPublicKeys } from '../fetchers/key-fetchers.js'; +import { verifyClientSupported } from '../session/version.js'; import type { Viewer } from '../session/viewer.js'; +import { fetchCallUpdateOlmAccount } from '../updaters/olm-account-updater.js'; +import { validateAccountPrekey } from '../utils/olm-utils.js'; import { validateInput, validateOutput } from '../utils/validation-utils.js'; +type AccountKeysSet = { + +identityKeys: string, + ...OlmSessionInitializationInfo, +}; + const getSessionPublicKeysInputValidator = tShape({ session: t.String, }); @@ -41,4 +55,95 @@ ); } -export { getSessionPublicKeysResponder }; +async function retrieveAccountKeysSet( + account: OlmAccount, +): Promise { + const identityKeys = account.identity_keys(); + + await validateAccountPrekey(account); + const prekey = account.prekey(); + // Until transfer of prekeys to the identity service is implemented + // prekeys will be marked as published each time it is accessed + // to establish olm notifs session to mitigate the risk of prekeys + // being in use for long enough to cause security concerns + account.mark_prekey_as_published(); + const prekeySignature = account.prekey_signature(); + + if (!prekeySignature) { + throw new ServerError('prekey_validation_failure'); + } + + account.generate_one_time_keys(1); + const oneTimeKey = account.one_time_keys(); + account.mark_keys_as_published(); + + return { identityKeys, oneTimeKey, prekey, prekeySignature }; +} + +async function getOlmSessionInitializationDataResponder( + viewer: Viewer, +): Promise { + await verifyClientSupported(viewer); + + const { + identityKeys: notificationsIdentityKeys, + prekey: notificationsPrekey, + prekeySignature: notificationsPrekeySignature, + oneTimeKey: notificationsOneTimeKey, + } = await fetchCallUpdateOlmAccount('notifications', retrieveAccountKeysSet); + + const contentAccountCallback = async (account: OlmAccount) => { + const { + identityKeys: contentIdentityKeys, + oneTimeKey, + prekey, + prekeySignature, + } = await retrieveAccountKeysSet(account); + + const identityKeysBlob = { + primaryIdentityPublicKeys: JSON.parse(contentIdentityKeys), + notificationIdentityPublicKeys: JSON.parse(notificationsIdentityKeys), + }; + const identityKeysBlobPayload = JSON.stringify(identityKeysBlob); + const signedIdentityKeysBlob = { + payload: identityKeysBlobPayload, + signature: account.sign(identityKeysBlobPayload), + }; + + return { + signedIdentityKeysBlob, + oneTimeKey, + prekey, + prekeySignature, + }; + }; + + const { + signedIdentityKeysBlob, + prekey: contentPrekey, + prekeySignature: contentPrekeySignature, + oneTimeKey: contentOneTimeKey, + } = await fetchCallUpdateOlmAccount('content', contentAccountCallback); + + const notifInitializationInfo = { + prekey: notificationsPrekey, + prekeySignature: notificationsPrekeySignature, + oneTimeKey: notificationsOneTimeKey, + }; + const contentInitializationInfo = { + prekey: contentPrekey, + prekeySignature: contentPrekeySignature, + oneTimeKey: contentOneTimeKey, + }; + + return { + signedIdentityKeysBlob, + contentInitializationInfo, + notifInitializationInfo, + }; +} + +export { + getSessionPublicKeysResponder, + getOlmSessionInitializationDataResponder, +}; diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -14,7 +14,10 @@ UpdateUserAvatarRequest, UpdateUserAvatarResponse, } from '../types/avatar-types.js'; -import type { GetSessionPublicKeysArgs } from '../types/request-types.js'; +import type { + GetSessionPublicKeysArgs, + GetOlmSessionInitializationDataResponse, +} from '../types/request-types.js'; import type { UserSearchResult, ExactUserSearchResult, @@ -256,6 +259,20 @@ return await callServerEndpoint('get_session_public_keys', data); }; +const getOlmSessionInitializationDataActionTypes = Object.freeze({ + started: 'GET_OLM_SESSION_INITIALIZATION_DATA_STARTED', + success: 'GET_OLM_SESSION_INITIALIZATION_DATA_SUCCESS', + failed: 'GET_OLM_SESSION_INITIALIZATION_DATA_FAILED', +}); + +const getOlmSessionInitializationData = + ( + callServerEndpoint: CallServerEndpoint, + ): (() => Promise) => + async () => { + return await callServerEndpoint('get_olm_session_initialization_data', {}); + }; + const policyAcknowledgmentActionTypes = Object.freeze({ started: 'POLICY_ACKNOWLEDGMENT_STARTED', success: 'POLICY_ACKNOWLEDGMENT_SUCCESS', @@ -294,6 +311,8 @@ deleteAccount, deleteAccountActionTypes, getSessionPublicKeys, + getOlmSessionInitializationDataActionTypes, + getOlmSessionInitializationData, mergeUserInfos, logIn, logInActionTypes, diff --git a/lib/types/endpoints.js b/lib/types/endpoints.js --- a/lib/types/endpoints.js +++ b/lib/types/endpoints.js @@ -94,6 +94,7 @@ UPDATE_USER_AVATAR: 'update_user_avatar', UPLOAD_MEDIA_METADATA: 'upload_media_metadata', SEARCH_MESSAGES: 'search_messages', + GET_OLM_SESSION_INITIALIZATION_DATA: 'get_olm_session_initialization_data', }); type SocketPreferredEndpoint = $Values; diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -77,7 +77,10 @@ QueueReportsPayload, ReportStore, } from './report-types.js'; -import type { ProcessServerRequestsPayload } from './request-types.js'; +import type { + ProcessServerRequestsPayload, + GetOlmSessionInitializationDataResponse, +} from './request-types.js'; import type { UserSearchResult, ExactUserSearchResult, @@ -1115,6 +1118,22 @@ +error: true, +payload: Error, +loadingInfo: LoadingInfo, + } + | { + +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_STARTED', + +loadingInfo?: LoadingInfo, + +payload?: void, + } + | { + +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_SUCCESS', + +payload: GetOlmSessionInitializationDataResponse, + +loadingInfo: LoadingInfo, + } + | { + +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_FAILED', + +error: true, + +payload: Error, + +loadingInfo: LoadingInfo, }; export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string); diff --git a/lib/types/request-types.js b/lib/types/request-types.js --- a/lib/types/request-types.js +++ b/lib/types/request-types.js @@ -259,3 +259,15 @@ export type GetSessionPublicKeysArgs = { +session: string, }; + +export type OlmSessionInitializationInfo = { + +prekey: string, + +prekeySignature: string, + +oneTimeKey: string, +}; + +export type GetOlmSessionInitializationDataResponse = { + +signedIdentityKeysBlob: SignedIdentityKeysBlob, + +contentInitializationInfo: OlmSessionInitializationInfo, + +notifInitializationInfo: OlmSessionInitializationInfo, +};