diff --git a/keyserver/src/user/login.js b/keyserver/src/user/login.js index 16d47cedc..fdc3c42d3 100644 --- a/keyserver/src/user/login.js +++ b/keyserver/src/user/login.js @@ -1,171 +1,170 @@ // @flow import type { Account as OlmAccount } from '@commapp/olm'; import { getRustAPI } from 'rust-node-addon'; +import { getOneTimeKeyValuesFromBlob } from 'lib/shared/crypto-utils.js'; import { getCommConfig } from 'lib/utils/comm-config.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { saveIdentityInfo, fetchIdentityInfo, type IdentityInfo, } from './identity.js'; import { getMessageForException } from '../responders/utils.js'; import { fetchCallUpdateOlmAccount } from '../updaters/olm-account-updater.js'; -import { - getOneTimeKeyValues, - validateAccountPrekey, -} from '../utils/olm-utils.js'; +import { validateAccountPrekey } from '../utils/olm-utils.js'; type UserCredentials = { +username: string, +password: string }; export type AccountKeysSet = { +identityKeys: string, +prekey: string, +prekeySignature: string, - +oneTimeKey: $ReadOnlyArray, + +oneTimeKeys: $ReadOnlyArray, }; function retrieveAccountKeysSet(account: OlmAccount): AccountKeysSet { const identityKeys = account.identity_keys(); validateAccountPrekey(account); const prekeyMap = JSON.parse(account.prekey()).curve25519; const [prekey] = values(prekeyMap); const prekeySignature = account.prekey_signature(); if (!prekeySignature || !prekey) { throw new ServerError('invalid_prekey'); } - if (getOneTimeKeyValues(account.one_time_keys()).length < 10) { + let oneTimeKeys = getOneTimeKeyValuesFromBlob(account.one_time_keys()); + + if (oneTimeKeys.length < 10) { account.generate_one_time_keys(10); + oneTimeKeys = getOneTimeKeyValuesFromBlob(account.one_time_keys()); } - const oneTimeKey = getOneTimeKeyValues(account.one_time_keys()); - - return { identityKeys, oneTimeKey, prekey, prekeySignature }; + return { identityKeys, oneTimeKeys, prekey, prekeySignature }; } // After register or login is successful function markKeysAsPublished(account: OlmAccount) { account.mark_prekey_as_published(); account.mark_keys_as_published(); } async function verifyUserLoggedIn(): Promise { const result = await fetchIdentityInfo(); if (result) { return result; } const identityInfo = await registerOrLogin(); await saveIdentityInfo(identityInfo); return identityInfo; } async function registerOrLogin(): Promise { const rustAPIPromise = getRustAPI(); const userInfo = await getCommConfig({ folder: 'secrets', name: 'user_credentials', }); if (!userInfo) { throw new ServerError('missing_user_credentials'); } const { identityKeys: notificationsIdentityKeys, prekey: notificationsPrekey, prekeySignature: notificationsPrekeySignature, - oneTimeKey: notificationsOneTimeKey, + oneTimeKeys: notificationsOneTimeKeys, } = await fetchCallUpdateOlmAccount('notifications', retrieveAccountKeysSet); const contentAccountCallback = async (account: OlmAccount) => { const { identityKeys: contentIdentityKeys, - oneTimeKey, + oneTimeKeys, 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, + oneTimeKeys, prekey, prekeySignature, }; }; const [ rustAPI, { signedIdentityKeysBlob, prekey: contentPrekey, prekeySignature: contentPrekeySignature, - oneTimeKey: contentOneTimeKey, + oneTimeKeys: contentOneTimeKeys, }, ] = await Promise.all([ rustAPIPromise, fetchCallUpdateOlmAccount('content', contentAccountCallback), ]); try { const identity_info = await rustAPI.loginUser( userInfo.username, userInfo.password, signedIdentityKeysBlob, contentPrekey, contentPrekeySignature, notificationsPrekey, notificationsPrekeySignature, - contentOneTimeKey, - notificationsOneTimeKey, + contentOneTimeKeys, + notificationsOneTimeKeys, ); await Promise.all([ fetchCallUpdateOlmAccount('content', markKeysAsPublished), fetchCallUpdateOlmAccount('notifications', markKeysAsPublished), ]); return identity_info; } catch (e) { console.warn('Failed to login user: ' + getMessageForException(e)); try { const identity_info = await rustAPI.registerUser( userInfo.username, userInfo.password, signedIdentityKeysBlob, contentPrekey, contentPrekeySignature, notificationsPrekey, notificationsPrekeySignature, - contentOneTimeKey, - notificationsOneTimeKey, + contentOneTimeKeys, + notificationsOneTimeKeys, ); await Promise.all([ fetchCallUpdateOlmAccount('content', markKeysAsPublished), fetchCallUpdateOlmAccount('notifications', markKeysAsPublished), ]); return identity_info; } catch (err) { console.warn('Failed to register user: ' + getMessageForException(err)); throw new ServerError('identity_auth_failed'); } } } export { verifyUserLoggedIn }; diff --git a/keyserver/src/utils/olm-utils.js b/keyserver/src/utils/olm-utils.js index 8fe9658b4..114ebaf43 100644 --- a/keyserver/src/utils/olm-utils.js +++ b/keyserver/src/utils/olm-utils.js @@ -1,165 +1,155 @@ // @flow import olm from '@commapp/olm'; import type { Account as OlmAccount, Utility as OlmUtility, Session as OlmSession, } from '@commapp/olm'; import { getRustAPI } from 'rust-node-addon'; import uuid from 'uuid'; -import { - olmEncryptedMessageTypes, - type OLMOneTimeKeys, -} from 'lib/types/crypto-types.js'; +import { getOneTimeKeyValuesFromBlob } from 'lib/shared/crypto-utils.js'; +import { olmEncryptedMessageTypes } from 'lib/types/crypto-types.js'; import { ServerError } from 'lib/utils/errors.js'; -import { values } from 'lib/utils/objects.js'; import { fetchCallUpdateOlmAccount } from '../updaters/olm-account-updater.js'; import { fetchIdentityInfo } from '../user/identity.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; } function validateAccountPrekey(account: OlmAccount) { 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(); } } -function getOneTimeKeyValues(keyBlob: string): $ReadOnlyArray { - const content: OLMOneTimeKeys = JSON.parse(keyBlob); - const keys: $ReadOnlyArray = values(content.curve25519); - return keys; -} - async function uploadNewOneTimeKeys(numberOfKeys: number) { const [rustAPI, identityInfo] = await Promise.all([ getRustAPI(), fetchIdentityInfo(), ]); if (!identityInfo) { throw new ServerError('missing_identity_info'); } await fetchCallUpdateOlmAccount('content', (contentAccount: OlmAccount) => { contentAccount.generate_one_time_keys(numberOfKeys); - const contentOneTimeKeys = getOneTimeKeyValues( + const contentOneTimeKeys = getOneTimeKeyValuesFromBlob( contentAccount.one_time_keys(), ); const deviceID = JSON.parse(contentAccount.identity_keys()).curve25519; return fetchCallUpdateOlmAccount( 'notifications', async (notifAccount: OlmAccount) => { notifAccount.generate_one_time_keys(numberOfKeys); - const notifOneTimeKeys = getOneTimeKeyValues( + const notifOneTimeKeys = getOneTimeKeyValuesFromBlob( notifAccount.one_time_keys(), ); await rustAPI.uploadOneTimeKeys( identityInfo.userId, deviceID, identityInfo.accessToken, contentOneTimeKeys, notifOneTimeKeys, ); notifAccount.mark_keys_as_published(); contentAccount.mark_keys_as_published(); }, ); }); } export { createPickledOlmAccount, createPickledOlmSession, getOlmUtility, unpickleOlmAccount, unpickleOlmSession, validateAccountPrekey, - getOneTimeKeyValues, uploadNewOneTimeKeys, }; diff --git a/lib/shared/crypto-utils.js b/lib/shared/crypto-utils.js new file mode 100644 index 000000000..d5b70fac1 --- /dev/null +++ b/lib/shared/crypto-utils.js @@ -0,0 +1,17 @@ +//@flow + +import type { OLMOneTimeKeys } from '../types/crypto-types'; +import { values } from '../utils/objects.js'; + +function getOneTimeKeyValues( + oneTimeKeys: OLMOneTimeKeys, +): $ReadOnlyArray { + return values(oneTimeKeys.curve25519); +} + +function getOneTimeKeyValuesFromBlob(keyBlob: string): $ReadOnlyArray { + const oneTimeKeys: OLMOneTimeKeys = JSON.parse(keyBlob); + return getOneTimeKeyValues(oneTimeKeys); +} + +export { getOneTimeKeyValues, getOneTimeKeyValuesFromBlob };