diff --git a/keyserver/src/creators/olm-session-creator.js b/keyserver/src/creators/olm-session-creator.js new file mode 100644 index 000000000..e53c0277c --- /dev/null +++ b/keyserver/src/creators/olm-session-creator.js @@ -0,0 +1,43 @@ +// @flow + +import type { Account as OlmAccount } from '@commapp/olm'; + +import { ServerError } from 'lib/utils/errors.js'; + +import { dbQuery, SQL } from '../database/database.js'; +import { fetchCallUpdateOlmAccount } from '../updaters/olm-account-updater.js'; +import { createPickledOlmSession } from '../utils/olm-utils.js'; + +async function createOlmSession( + initialEncryptedMessage: string, + olmSessionType: 'content' | 'notifications', + cookieID: string, +): Promise { + const callback = (account: OlmAccount, picklingKey: string) => + createPickledOlmSession(account, picklingKey, initialEncryptedMessage); + + let pickledOlmSession; + try { + pickledOlmSession = await fetchCallUpdateOlmAccount( + olmSessionType, + callback, + ); + } catch (e) { + throw new ServerError('olm_session_creation_failure'); + } + + const isContent = olmSessionType === 'content'; + // We match the native client behavior here where olm session is overwritten + // in case it is initialized twice for the same pair of identities + await dbQuery( + SQL` + INSERT INTO olm_sessions (cookie_id, is_content, + version, pickled_olm_session) + VALUES (${cookieID}, ${isContent}, 0, ${pickledOlmSession}) + ON DUPLICATE KEY UPDATE + pickled_olm_session = ${pickledOlmSession} + `, + ); +} + +export { createOlmSession }; diff --git a/keyserver/src/utils/olm-utils.js b/keyserver/src/utils/olm-utils.js index 9b37d1fc6..14f6a573d 100644 --- a/keyserver/src/utils/olm-utils.js +++ b/keyserver/src/utils/olm-utils.js @@ -1,81 +1,109 @@ // @flow import olm from '@commapp/olm'; import type { Account as OlmAccount, Utility as OlmUtility, + Session as OlmSession, } from '@commapp/olm'; import uuid from 'uuid'; +import { olmEncryptedMessageTypes } from 'lib/types/crypto-types.js'; + type PickledOlmAccount = { +picklingKey: string, +pickledAccount: string, }; const maxPublishedPrekeyAge = 30 * 24 * 60 * 60 * 1000; const maxOldPrekeyAge = 24 * 60 * 60 * 1000; 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, +): Promise { + await olm.init(); + const session = new olm.Session(); + 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 validateAccountPrekey(account: OlmAccount): Promise { const currentDate = new Date(); const lastPrekeyPublishDate = new Date(account.last_prekey_publish_time()); const prekeyPublished = !account.unpublished_prekey(); if ( prekeyPublished && currentDate - lastPrekeyPublishDate > maxPublishedPrekeyAge ) { // If there is no prekey or the current prekey is older than month // we need to generate new one. account.generate_prekey(); } if ( prekeyPublished && currentDate - lastPrekeyPublishDate >= maxOldPrekeyAge ) { account.forget_old_prekey(); } } export { createPickledOlmAccount, + createPickledOlmSession, getOlmUtility, unpickleOlmAccount, + unpickleOlmSession, validateAccountPrekey, }; diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js index f687ba5dd..c333cc57f 100644 --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -1,39 +1,44 @@ // @flow export type OLMIdentityKeys = { +ed25519: string, +curve25519: string, }; export type OLMPrekey = { +curve25519: { +id: string, +key: string, }, }; export type OLMOneTimeKeys = { +curve25519: { +[string]: string }, }; export type PickledOLMAccount = { +picklingKey: string, +pickledAccount: string, }; export type CryptoStore = { +primaryAccount: ?PickledOLMAccount, +primaryIdentityKeys: ?OLMIdentityKeys, +notificationAccount: ?PickledOLMAccount, +notificationIdentityKeys: ?OLMIdentityKeys, }; export type IdentityKeysBlob = { +primaryIdentityPublicKeys: OLMIdentityKeys, +notificationIdentityPublicKeys: OLMIdentityKeys, }; export type SignedIdentityKeysBlob = { +payload: string, +signature: string, }; + +export const olmEncryptedMessageTypes = Object.freeze({ + PREKEY: 0, + TEXT: 1, +});