diff --git a/keyserver/src/creators/account-creator.js b/keyserver/src/creators/account-creator.js --- a/keyserver/src/creators/account-creator.js +++ b/keyserver/src/creators/account-creator.js @@ -13,10 +13,12 @@ import type { UserDetail, ReservedUsernameMessage, + IdentityKeysBlob, } from 'lib/types/crypto-types.js'; import { messageTypes } from 'lib/types/message-types-enum.js'; import type { RawMessageInfo } from 'lib/types/message-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; +import { identityKeysBlobValidator } from 'lib/utils/crypto-utils.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { ignorePromiseRejections } from 'lib/utils/promises.js'; @@ -91,10 +93,18 @@ // Olm sessions have to be created before createNewUserCookie is called, // to avoid propagating a user cookie in case session creation fails const olmNotifSession = await (async () => { - if (initialNotificationsEncryptedMessage) { + if (initialNotificationsEncryptedMessage && signedIdentityKeysBlob) { + const identityKeys: IdentityKeysBlob = JSON.parse( + signedIdentityKeysBlob.payload, + ); + if (!identityKeysBlobValidator.is(identityKeys)) { + throw new ServerError('invalid_identity_keys_blob'); + } + return await createOlmSession( initialNotificationsEncryptedMessage, 'notifications', + identityKeys.notificationIdentityPublicKeys.curve25519, ); } return null; diff --git a/keyserver/src/creators/olm-session-creator.js b/keyserver/src/creators/olm-session-creator.js --- a/keyserver/src/creators/olm-session-creator.js +++ b/keyserver/src/creators/olm-session-creator.js @@ -11,7 +11,7 @@ async function createOlmSession( initialEncryptedMessage: string, olmSessionType: 'content' | 'notifications', - theirCurve25519Key?: string, + theirCurve25519Key: string, ): Promise { const callback = (account: OlmAccount, picklingKey: string) => createPickledOlmSession( @@ -52,7 +52,7 @@ initialEncryptedMessage: string, olmSessionType: 'content' | 'notifications', cookieID: string, - theirCurve25519Key?: string, + theirCurve25519Key: string, ): Promise { const pickledOlmSession = await createOlmSession( initialEncryptedMessage, diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js --- a/keyserver/src/responders/user-responders.js +++ b/keyserver/src/responders/user-responders.js @@ -292,9 +292,16 @@ // to avoid propagating a user cookie in case session creation fails const olmNotifSession = await (async () => { if (initialNotificationsEncryptedMessage && signedIdentityKeysBlob) { + const identityKeys: IdentityKeysBlob = JSON.parse( + signedIdentityKeysBlob.payload, + ); + if (!identityKeysBlobValidator.is(identityKeys)) { + throw new ServerError('invalid_identity_keys_blob'); + } return await createOlmSession( initialNotificationsEncryptedMessage, 'notifications', + identityKeys.notificationIdentityPublicKeys.curve25519, ); } return null; diff --git a/keyserver/src/session/cookies.js b/keyserver/src/session/cookies.js --- a/keyserver/src/session/cookies.js +++ b/keyserver/src/session/cookies.js @@ -741,6 +741,18 @@ ); } +async function getSignedIdentityKeysBlobFromCookiesTable( + cookieID: string, +): Promise { + const query = SQL` + SELECT signed_identity_keys + FROM cookies + WHERE id = ${cookieID} + `; + const [queryResult] = await dbQuery(query); + return queryResult[0]?.signed_identity_keys; +} + async function isCookieMissingOlmNotificationsSession( viewer: Viewer, ): Promise { @@ -823,4 +835,5 @@ setCookiePlatform, setCookiePlatformDetails, isCookieMissingOlmNotificationsSession, + getSignedIdentityKeysBlobFromCookiesTable, }; diff --git a/keyserver/src/socket/session-utils.js b/keyserver/src/socket/session-utils.js --- a/keyserver/src/socket/session-utils.js +++ b/keyserver/src/socket/session-utils.js @@ -28,6 +28,8 @@ type ServerCheckStateServerRequest, } from 'lib/types/request-types.js'; import { sessionCheckFrequency } from 'lib/types/session-types.js'; +import { identityKeysBlobValidator } from 'lib/utils/crypto-utils.js'; +import { ServerError } from 'lib/utils/errors.js'; import { hash, values } from 'lib/utils/objects.js'; import { getOlmUtility } from 'lib/utils/olm-utility.js'; import { promiseAll, ignorePromiseRejections } from 'lib/utils/promises.js'; @@ -40,6 +42,7 @@ setCookiePlatform, setCookiePlatformDetails, setCookieSignedIdentityKeysBlob, + getSignedIdentityKeysBlobFromCookiesTable, } from '../session/cookies.js'; import type { Viewer } from '../session/viewer.js'; import { serverStateSyncSpecs } from '../shared/state-sync/state-sync-specs.js'; @@ -151,10 +154,23 @@ ); const { initialNotificationsEncryptedMessage } = clientResponse; try { + const signedIdentityKeysBlob = + await getSignedIdentityKeysBlobFromCookiesTable(viewer.cookieID); + if (!signedIdentityKeysBlob) { + continue; + } + const identityKeys: IdentityKeysBlob = JSON.parse( + signedIdentityKeysBlob, + ); + if (!identityKeysBlobValidator.is(identityKeys)) { + throw new ServerError('invalid_identity_keys_blob'); + } + await createAndPersistOlmSession( initialNotificationsEncryptedMessage, 'notifications', viewer.cookieID, + identityKeys.notificationIdentityPublicKeys.curve25519, ); } catch (e) { continue; diff --git a/keyserver/src/utils/olm-objects.js b/keyserver/src/utils/olm-objects.js --- a/keyserver/src/utils/olm-objects.js +++ b/keyserver/src/utils/olm-objects.js @@ -97,20 +97,16 @@ account: OlmAccount, accountPicklingKey: string, initialEncryptedMessage: string, - theirCurve25519Key?: 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); - } + session.create_inbound_from( + account, + theirCurve25519Key, + initialEncryptedMessage, + ); account.remove_one_time_keys(session); session.decrypt(olmEncryptedMessageTypes.PREKEY, initialEncryptedMessage);