Page MenuHomePhorge

D15547.1768382151.diff
No OneTemporary

Size
102 KB
Referenced Files
None
Subscribers
None

D15547.1768382151.diff

diff --git a/keyserver/package.json b/keyserver/package.json
--- a/keyserver/package.json
+++ b/keyserver/package.json
@@ -49,6 +49,7 @@
"@babel/runtime": "^7.28.3",
"@commapp/olm": "0.2.6",
"@hono/node-server": "^1.13.7",
+ "@commapp/vodozemac": "0.1.0",
"@parse/node-apn": "^3.2.0",
"@vingle/bmp-js": "^0.2.5",
"bad-words": "^3.0.4",
@@ -117,7 +118,7 @@
]
},
"transformIgnorePatterns": [
- "/node_modules/(?!@babel/runtime)"
+ "/node_modules/(?!(@babel/runtime|@commapp/vodozemac))"
],
"setupFiles": [
"<rootDir>/jest-setup.js"
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
@@ -1,6 +1,6 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import type { Account as OlmAccount } from '@commapp/vodozemac';
import { ServerError } from 'lib/utils/errors.js';
diff --git a/keyserver/src/cron/cron.js b/keyserver/src/cron/cron.js
--- a/keyserver/src/cron/cron.js
+++ b/keyserver/src/cron/cron.js
@@ -1,15 +1,9 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
-import olm from '@commapp/olm';
+import type { Account as OlmAccount } from '@commapp/vodozemac';
import cluster from 'cluster';
import schedule from 'node-schedule';
-import {
- getOlmMemory,
- compareAndLogOlmMemory,
-} from 'lib/utils/olm-memory-utils.js';
-
import { backupDB } from './backups.js';
import { createDailyUpdatesThread } from './daily-updates.js';
import { postMetrics } from './metrics.js';
@@ -88,7 +82,6 @@
schedule.scheduleJob(
'0 0 * * *', // every day at midnight in the keyserver's timezone
async () => {
- const memBefore = getOlmMemory();
try {
await fetchCallUpdateOlmAccount(
'content',
@@ -101,15 +94,12 @@
);
} catch (e) {
console.warn('encountered error while trying to validate prekeys', e);
- } finally {
- compareAndLogOlmMemory(memBefore, 'prekey upload cronjob');
}
},
);
schedule.scheduleJob(
'0 2 * * *', // every day at 2:00 AM in the keyserver's timezone
async () => {
- const memBefore = getOlmMemory();
try {
await synchronizeInviteLinksWithBlobs();
} catch (e) {
@@ -117,24 +107,6 @@
'encountered an error while trying to synchronize invite links with blobs',
e,
);
- } finally {
- compareAndLogOlmMemory(memBefore, 'invite links cronjob');
- }
- },
- );
- schedule.scheduleJob(
- '0,15,30,45 * * * *', // every 15 minutes
- async () => {
- const memBefore = getOlmMemory();
- try {
- await olm.init();
- } catch (e) {
- console.warn(
- 'encountered an error while executing olm init cron job',
- e,
- );
- } finally {
- compareAndLogOlmMemory(memBefore, 'olm init cronjob');
}
},
);
diff --git a/keyserver/src/database/migration-config.js b/keyserver/src/database/migration-config.js
--- a/keyserver/src/database/migration-config.js
+++ b/keyserver/src/database/migration-config.js
@@ -1,6 +1,6 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import type { Account as OlmAccount } from '@commapp/vodozemac';
import fs from 'fs';
import bots from 'lib/facts/bots.js';
diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js
--- a/keyserver/src/keyserver.js
+++ b/keyserver/src/keyserver.js
@@ -1,6 +1,6 @@
// @flow
-import olm from '@commapp/olm';
+import initVodozemac from '@commapp/vodozemac';
import cluster from 'cluster';
import compression from 'compression';
import cookieParser from 'cookie-parser';
@@ -72,7 +72,7 @@
void (async () => {
const [webAppCorsConfig] = await Promise.all([
getWebAppCorsConfig(),
- olm.init(),
+ initVodozemac(),
prefetchAllURLFacts(),
initENSCache(),
initFCCache(),
diff --git a/keyserver/src/push/encrypted-notif-utils-api.js b/keyserver/src/push/encrypted-notif-utils-api.js
--- a/keyserver/src/push/encrypted-notif-utils-api.js
+++ b/keyserver/src/push/encrypted-notif-utils-api.js
@@ -1,7 +1,6 @@
// @flow
-import type { EncryptResult } from '@commapp/olm';
-
+import type { EncryptResult } from 'lib/types/encrypted-type.js';
import type { EncryptedNotifUtilsAPI } from 'lib/types/notif-types.js';
import { getOlmUtility } from 'lib/utils/olm-utility.js';
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,6 +1,6 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import type { Account as OlmAccount } from '@commapp/vodozemac';
import type {
OlmSessionInitializationInfo,
@@ -18,20 +18,39 @@
function retrieveSessionInitializationKeysSet(
account: OlmAccount,
): SessionInitializationKeysSet {
- const identityKeys = account.identity_keys();
+ const identityKeys = JSON.stringify({
+ ed25519: account.ed25519_key,
+ curve25519: account.curve25519_key,
+ });
const prekey = account.prekey();
- const prekeySignature = account.prekey_signature();
+ if (!prekey) {
+ throw new ServerError('missing_prekey');
+ }
+
+ // Wrap prekey in old Olm format to match expected structure on all clients
+ const prekeyWrapped = JSON.stringify({
+ curve25519: { AAAAAA: prekey },
+ });
+ const prekeySignature = account.prekey_signature();
if (!prekeySignature) {
throw new ServerError('invalid_prekey');
}
account.generate_one_time_keys(1);
- const oneTimeKey = account.one_time_keys();
+ const oneTimeKeysMap = account.one_time_keys();
+ const oneTimeKeysEntries = Array.from(oneTimeKeysMap.entries());
+ const oneTimeKeysObject = Object.fromEntries(oneTimeKeysEntries);
+ const oneTimeKey = JSON.stringify({ curve25519: oneTimeKeysObject });
account.mark_keys_as_published();
- return { identityKeys, oneTimeKey, prekey, prekeySignature };
+ return {
+ identityKeys,
+ oneTimeKey,
+ prekey: prekeyWrapped,
+ prekeySignature,
+ };
}
async function getOlmSessionInitializationDataResponder(): Promise<GetOlmSessionInitializationDataResponse> {
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
@@ -1,6 +1,6 @@
// @flow
-import type { Utility as OlmUtility } from '@commapp/olm';
+import type { Utility as OlmUtility } from '@commapp/vodozemac';
import invariant from 'invariant';
import { SiweErrorType, SiweMessage } from 'siwe';
import t, { type TInterface } from 'tcomb';
diff --git a/keyserver/src/responders/website-responders.js b/keyserver/src/responders/website-responders.js
--- a/keyserver/src/responders/website-responders.js
+++ b/keyserver/src/responders/website-responders.js
@@ -41,6 +41,7 @@
+fontsURL: string,
+cssInclude: string,
+olmFilename: string,
+ +vodozemacFilename: string,
+commQueryExecutorFilename: string,
+backupClientFilename: string,
+webworkersOpaqueFilename: string,
@@ -57,6 +58,7 @@
fontsURL,
cssInclude: '',
olmFilename: '',
+ vodozemacFilename: '',
commQueryExecutorFilename: '',
backupClientFilename: '',
webworkersOpaqueFilename: '',
@@ -82,6 +84,7 @@
/>
`,
olmFilename: manifest['olm.wasm'],
+ vodozemacFilename: manifest['vodozemac.wasm'],
commQueryExecutorFilename: webworkersManifest['comm-query-executor.wasm'],
backupClientFilename: webworkersManifest['backup-client-wasm_bg.wasm'],
webworkersOpaqueFilename: webworkersManifest['comm_opaque2_wasm_bg.wasm'],
@@ -136,6 +139,7 @@
fontsURL,
cssInclude,
olmFilename,
+ vodozemacFilename,
commQueryExecutorFilename,
backupClientFilename,
webworkersOpaqueFilename,
@@ -195,6 +199,7 @@
var keyserverURL = "${keyserverURL}";
var baseURL = "${baseURL}";
var olmFilename = "${olmFilename}";
+ var vodozemacFilename = "${vodozemacFilename}";
var commQueryExecutorFilename = "${commQueryExecutorFilename}";
var backupClientFilename = "${backupClientFilename}";
var webworkersOpaqueFilename = "${webworkersOpaqueFilename}"
diff --git a/keyserver/src/socket/tunnelbroker.js b/keyserver/src/socket/tunnelbroker.js
--- a/keyserver/src/socket/tunnelbroker.js
+++ b/keyserver/src/socket/tunnelbroker.js
@@ -44,10 +44,6 @@
convertObjToBytes,
} from 'lib/utils/conversion-utils.js';
import { getMessageForException } from 'lib/utils/errors.js';
-import {
- compareAndLogOlmMemory,
- getOlmMemory,
-} from 'lib/utils/olm-memory-utils.js';
import sleep from 'lib/utils/sleep.js';
import {
@@ -391,14 +387,11 @@
refreshOneTimeKeys: (numberOfKeys: number) => void = numberOfKeys => {
const oldOneTimeKeysPromise = this.oneTimeKeysPromise;
this.oneTimeKeysPromise = (async () => {
- const memBefore = getOlmMemory();
try {
await oldOneTimeKeysPromise;
await uploadNewOneTimeKeys(numberOfKeys);
} catch (e) {
console.error('Encountered error when trying to upload new OTKs:', e);
- } finally {
- compareAndLogOlmMemory(memBefore, 'otk refresh');
}
})();
};
diff --git a/keyserver/src/updaters/olm-account-updater.js b/keyserver/src/updaters/olm-account-updater.js
--- a/keyserver/src/updaters/olm-account-updater.js
+++ b/keyserver/src/updaters/olm-account-updater.js
@@ -1,6 +1,6 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import { type Account as OlmAccount } from '@commapp/vodozemac';
import { ServerError } from 'lib/utils/errors.js';
import sleep from 'lib/utils/sleep.js';
diff --git a/keyserver/src/updaters/olm-session-updater.js b/keyserver/src/updaters/olm-session-updater.js
--- a/keyserver/src/updaters/olm-session-updater.js
+++ b/keyserver/src/updaters/olm-session-updater.js
@@ -1,7 +1,8 @@
// @flow
-import type { EncryptResult, Session as OlmSession } from '@commapp/olm';
+import type { Session as OlmSession } from '@commapp/vodozemac';
+import type { EncryptResult } from 'lib/types/encrypted-type.js';
import { ServerError } from 'lib/utils/errors.js';
import sleep from 'lib/utils/sleep.js';
@@ -56,9 +57,11 @@
},
(olmSession: OlmSession) => {
for (const messageName in messagesToEncrypt) {
- encryptedMessages[messageName] = olmSession.encrypt(
- messagesToEncrypt[messageName],
- );
+ const olmMessage = olmSession.encrypt(messagesToEncrypt[messageName]);
+ encryptedMessages[messageName] = {
+ type: olmMessage.message_type,
+ body: olmMessage.ciphertext,
+ };
}
},
);
diff --git a/keyserver/src/user/login.js b/keyserver/src/user/login.js
--- a/keyserver/src/user/login.js
+++ b/keyserver/src/user/login.js
@@ -1,6 +1,6 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import type { Account as OlmAccount } from '@commapp/vodozemac';
import { getRustAPI } from 'rust-node-addon';
import { getCommConfig } from 'lib/utils/comm-config.js';
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
@@ -1,13 +1,20 @@
// @flow
-import olm, {
+import initVodozemac, {
type Account as OlmAccount,
+ Account,
+ OlmMessage,
type Session as OlmSession,
-} from '@commapp/olm';
+} from '@commapp/vodozemac';
import uuid from 'uuid';
import { olmEncryptedMessageTypes } from 'lib/types/crypto-types.js';
import { ServerError } from 'lib/utils/errors.js';
+import {
+ getVodozemacPickleKey,
+ unpickleVodozemacAccount,
+ unpickleVodozemacSession,
+} from 'lib/utils/vodozemac-utils.js';
import { getMessageForException } from '../responders/utils.js';
@@ -20,16 +27,14 @@
pickledOlmAccount: PickledOlmAccount,
callback: (account: OlmAccount, picklingKey: string) => Promise<T> | T,
): Promise<{ +result: T, +pickledOlmAccount: PickledOlmAccount }> {
- const { picklingKey, pickledAccount } = pickledOlmAccount;
+ await initVodozemac();
- await olm.init();
-
- const account = new olm.Account();
- account.unpickle(picklingKey, pickledAccount);
+ const { picklingKey } = pickledOlmAccount;
+ const account = unpickleVodozemacAccount(pickledOlmAccount);
try {
const result = await callback(account, picklingKey);
- const updatedAccount = account.pickle(picklingKey);
+ const updatedAccount = account.pickle(getVodozemacPickleKey(picklingKey));
return {
result,
pickledOlmAccount: {
@@ -45,14 +50,12 @@
}
async function createPickledOlmAccount(): Promise<PickledOlmAccount> {
- await olm.init();
+ await initVodozemac();
- const account = new olm.Account();
- account.create();
+ const account = new Account();
const picklingKey = uuid.v4();
- const pickledAccount = account.pickle(picklingKey);
-
+ const pickledAccount = account.pickle(getVodozemacPickleKey(picklingKey));
account.free();
return {
@@ -69,16 +72,14 @@
pickledOlmSession: PickledOlmSession,
callback: (session: OlmSession) => Promise<T> | T,
): Promise<{ +result: T, +pickledOlmSession: PickledOlmSession }> {
- const { picklingKey, pickledSession } = pickledOlmSession;
-
- await olm.init();
+ await initVodozemac();
- const session = new olm.Session();
- session.unpickle(picklingKey, pickledSession);
+ const { picklingKey } = pickledOlmSession;
+ const session = unpickleVodozemacSession(pickledOlmSession);
try {
const result = await callback(session);
- const updatedSession = session.pickle(picklingKey);
+ const updatedSession = session.pickle(getVodozemacPickleKey(picklingKey));
return {
result,
pickledOlmSession: {
@@ -99,19 +100,22 @@
initialEncryptedMessage: string,
theirCurve25519Key: string,
): Promise<string> {
- await olm.init();
- const session = new olm.Session();
+ await initVodozemac();
- session.create_inbound_from(
- account,
- theirCurve25519Key,
+ const olmMessage = new OlmMessage(
+ olmEncryptedMessageTypes.PREKEY,
initialEncryptedMessage,
);
-
- account.remove_one_time_keys(session);
- session.decrypt(olmEncryptedMessageTypes.PREKEY, initialEncryptedMessage);
- const pickledSession = session.pickle(accountPicklingKey);
-
+ const inboundCreationResult = account.create_inbound_session(
+ theirCurve25519Key,
+ olmMessage,
+ );
+ // into_session() is consuming object.
+ // There is no need to call free() on inboundCreationResult
+ const session = inboundCreationResult.into_session();
+ const pickledSession = session.pickle(
+ getVodozemacPickleKey(accountPicklingKey),
+ );
session.free();
return pickledSession;
diff --git a/keyserver/src/utils/olm-utils.js b/keyserver/src/utils/olm-utils.js
--- a/keyserver/src/utils/olm-utils.js
+++ b/keyserver/src/utils/olm-utils.js
@@ -1,9 +1,8 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import { type Account as OlmAccount } from '@commapp/vodozemac';
import invariant from 'invariant';
-import { getOneTimeKeyValuesFromBlob } from 'lib/shared/crypto-utils.js';
import type { IdentityNewDeviceKeyUpload } from 'lib/types/identity-service-types.js';
import { ServerError } from 'lib/utils/errors.js';
import {
@@ -134,16 +133,12 @@
await Promise.all([
fetchCallUpdateOlmAccount('content', (contentAccount: OlmAccount) => {
contentAccount.generate_one_time_keys(numberOfKeys);
- contentOneTimeKeys = getOneTimeKeyValuesFromBlob(
- contentAccount.one_time_keys(),
- );
+ contentOneTimeKeys = [...contentAccount.one_time_keys().values()];
contentAccount.mark_keys_as_published();
}),
fetchCallUpdateOlmAccount('notifications', (notifAccount: OlmAccount) => {
notifAccount.generate_one_time_keys(numberOfKeys);
- notifOneTimeKeys = getOneTimeKeyValuesFromBlob(
- notifAccount.one_time_keys(),
- );
+ notifOneTimeKeys = [...notifAccount.one_time_keys().values()];
notifAccount.mark_keys_as_published();
}),
]);
@@ -164,7 +159,7 @@
const pickledOlmAccount = await fetchPickledOlmAccount('content');
const getAccountEd25519Key: (account: OlmAccount) => string = (
account: OlmAccount,
- ) => JSON.parse(account.identity_keys()).ed25519;
+ ) => account.ed25519_key;
const { result } = await unpickleAccountAndUseCallback(
pickledOlmAccount,
@@ -211,7 +206,7 @@
contentAccount: OlmAccount,
notifAccount: OlmAccount,
): Promise<void> {
- const deviceID = JSON.parse(contentAccount.identity_keys()).ed25519;
+ const deviceID = contentAccount.ed25519_key;
const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } =
getAccountPrekeysSet(contentAccount);
diff --git a/keyserver/src/utils/olm-utils.test.js b/keyserver/src/utils/olm-utils.test.js
deleted file mode 100644
--- a/keyserver/src/utils/olm-utils.test.js
+++ /dev/null
@@ -1,337 +0,0 @@
-// @flow
-
-import olm from '@commapp/olm';
-
-import { getOlmUtility } from 'lib/utils/olm-utility.js';
-
-describe('olm.Account', () => {
- const alphabet =
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ';
-
- const randomString = (length: number) =>
- Array.from(
- { length },
- () => alphabet[Math.floor(Math.random() * alphabet.length)],
- ).join('');
-
- const initAccount = (mark_prekey_published: boolean = true) => {
- const account = new olm.Account();
- account.create();
- account.generate_prekey();
- account.generate_one_time_keys(1);
- if (mark_prekey_published) {
- account.mark_prekey_as_published();
- }
- return account;
- };
-
- const createSession = (
- aliceSession: olm.Session,
- aliceAccount: olm.Account,
- bobAccount: olm.Account,
- regen: boolean = false,
- forget: boolean = false,
- invalid_sign: boolean = false,
- ) => {
- const bobOneTimeKeys = JSON.parse(bobAccount.one_time_keys()).curve25519;
- bobAccount.mark_keys_as_published();
- const otk_id = Object.keys(bobOneTimeKeys)[0];
-
- if (regen) {
- bobAccount.generate_prekey();
- if (forget) {
- bobAccount.forget_old_prekey();
- }
- }
-
- if (invalid_sign) {
- try {
- aliceSession.create_outbound(
- aliceAccount,
- JSON.parse(bobAccount.identity_keys()).curve25519,
- JSON.parse(bobAccount.identity_keys()).ed25519,
- String(Object.values(JSON.parse(bobAccount.prekey()).curve25519)[0]),
- bobAccount.sign(randomString(32)),
- bobOneTimeKeys[otk_id],
- );
- } catch (error) {
- expect(error.message).toBe('OLM.BAD_SIGNATURE');
- return false;
- }
-
- try {
- aliceSession.create_outbound(
- aliceAccount,
- JSON.parse(bobAccount.identity_keys()).curve25519,
- JSON.parse(bobAccount.identity_keys()).ed25519,
- String(Object.values(JSON.parse(bobAccount.prekey()).curve25519)[0]),
- randomString(43),
- bobOneTimeKeys[otk_id],
- );
- } catch (error) {
- expect(error.message).toBe('OLM.INVALID_BASE64');
- return false;
- }
- }
-
- aliceSession.create_outbound(
- aliceAccount,
- JSON.parse(bobAccount.identity_keys()).curve25519,
- JSON.parse(bobAccount.identity_keys()).ed25519,
- String(Object.values(JSON.parse(bobAccount.prekey()).curve25519)[0]),
- String(bobAccount.prekey_signature()),
- bobOneTimeKeys[otk_id],
- );
-
- return aliceSession;
- };
-
- const createSessionWithoutOTK = (
- aliceSession: olm.Session,
- aliceAccount: olm.Account,
- bobAccount: olm.Account,
- ) => {
- aliceSession.create_outbound_without_otk(
- aliceAccount,
- JSON.parse(bobAccount.identity_keys()).curve25519,
- JSON.parse(bobAccount.identity_keys()).ed25519,
- String(Object.values(JSON.parse(bobAccount.prekey()).curve25519)[0]),
- String(bobAccount.prekey_signature()),
- );
-
- return aliceSession;
- };
-
- const testRatchet = (
- aliceSession: olm.Session,
- bobSession: olm.Session,
- bobAccount: olm.Account,
- num_msg: number = 1,
- ) => {
- let test_text = randomString(40);
- let encrypted = aliceSession.encrypt(test_text);
- expect(encrypted.type).toEqual(0);
-
- try {
- bobSession.create_inbound(bobAccount, encrypted.body);
- } catch (error) {
- expect(error.message).toBe('OLM.BAD_MESSAGE_KEY_ID');
- return false;
- }
-
- bobAccount.remove_one_time_keys(bobSession);
- let decrypted = bobSession.decrypt(encrypted.type, encrypted.body);
- expect(decrypted).toEqual(test_text);
-
- test_text = randomString(40);
- encrypted = bobSession.encrypt(test_text);
- expect(encrypted.type).toEqual(1);
- decrypted = aliceSession.decrypt(encrypted.type, encrypted.body);
- expect(decrypted).toEqual(test_text);
-
- const aliceEncrypted = aliceSession.encrypt(test_text);
- expect(() =>
- aliceSession.decrypt(aliceEncrypted.type, aliceEncrypted.body),
- ).toThrow('OLM.BAD_MESSAGE_MAC');
-
- for (let index = 1; index < num_msg; index++) {
- test_text = randomString(40);
- encrypted = aliceSession.encrypt(test_text);
- expect(encrypted.type).toEqual(1);
- decrypted = bobSession.decrypt(encrypted.type, encrypted.body);
- expect(decrypted).toEqual(test_text);
-
- test_text = randomString(40);
- encrypted = bobSession.encrypt(test_text);
- expect(encrypted.type).toEqual(1);
- decrypted = aliceSession.decrypt(encrypted.type, encrypted.body);
- expect(decrypted).toEqual(test_text);
- }
-
- expect(() =>
- aliceSession.decrypt_sequential(encrypted.type, encrypted.body),
- ).toThrow('OLM.OLM_ALREADY_DECRYPTED_OR_KEYS_SKIPPED');
-
- return true;
- };
-
- const testRatchetSequential = (
- aliceSession: olm.Session,
- bobSession: olm.Session,
- bobAccount: olm.Account,
- ) => {
- let test_text = randomString(40);
- let encrypted = aliceSession.encrypt(test_text);
- expect(encrypted.type).toEqual(0);
-
- try {
- bobSession.create_inbound(bobAccount, encrypted.body);
- } catch (error) {
- expect(error.message).toBe('OLM.BAD_MESSAGE_KEY_ID');
- return false;
- }
-
- bobAccount.remove_one_time_keys(bobSession);
- let decrypted = bobSession.decrypt(encrypted.type, encrypted.body);
- expect(decrypted).toEqual(test_text);
-
- test_text = randomString(40);
- encrypted = bobSession.encrypt(test_text);
- expect(encrypted.type).toEqual(1);
- decrypted = aliceSession.decrypt(encrypted.type, encrypted.body);
- expect(decrypted).toEqual(test_text);
-
- const testText1 = 'message1';
- const encrypted1 = bobSession.encrypt(testText1);
- const testText2 = 'message2';
- const encrypted2 = bobSession.encrypt(testText2);
-
- // encrypt message using alice session and trying to decrypt with
- // the same session => `BAD_MESSAGE_MAC`
- const aliceEncrypted = aliceSession.encrypt(test_text);
- expect(() =>
- aliceSession.decrypt_sequential(aliceEncrypted.type, aliceEncrypted.body),
- ).toThrow('OLM.BAD_MESSAGE_MAC');
-
- // decrypting encrypted2 before encrypted1 using
- // decrypt_sequential() => OLM_MESSAGE_OUT_OF_ORDER
- expect(() =>
- aliceSession.decrypt_sequential(encrypted2.type, encrypted2.body),
- ).toThrow('OLM.OLM_MESSAGE_OUT_OF_ORDER');
-
- // test correct order
- const decrypted1 = aliceSession.decrypt_sequential(
- encrypted1.type,
- encrypted1.body,
- );
- expect(decrypted1).toEqual(testText1);
- const decrypted2 = aliceSession.decrypt_sequential(
- encrypted2.type,
- encrypted2.body,
- );
- expect(decrypted2).toEqual(testText2);
-
- // try to decrypt second time
- // the same message => OLM_ALREADY_DECRYPTED_OR_KEYS_SKIPPED
- expect(() =>
- aliceSession.decrypt_sequential(encrypted2.type, encrypted2.body),
- ).toThrow('OLM.OLM_ALREADY_DECRYPTED_OR_KEYS_SKIPPED');
-
- return true;
- };
-
- it('should get Olm Utility', async () => {
- await olm.init();
- const utility = getOlmUtility();
- expect(utility).toBeDefined();
- });
-
- it('should generate, regenerate, forget, and publish prekey', async () => {
- await olm.init();
- const account = initAccount(false);
-
- expect(account.last_prekey_publish_time()).toEqual(0);
- expect(account.prekey()).toBeDefined();
- expect(account.unpublished_prekey()).toBeDefined();
- account.mark_prekey_as_published();
- const last_published = account.last_prekey_publish_time();
- expect(last_published).toBeGreaterThan(0);
-
- try {
- console.log(account.unpublished_prekey());
- } catch (error) {
- expect(error.message).toContain('NO_UNPUBLISHED_PREKEY');
- }
- account.forget_old_prekey();
-
- account.generate_prekey();
- expect(account.prekey()).toBeDefined();
- expect(account.unpublished_prekey()).toBeDefined();
-
- expect(account.last_prekey_publish_time()).toEqual(last_published);
- account.mark_prekey_as_published();
- expect(account.last_prekey_publish_time()).toBeGreaterThanOrEqual(
- last_published,
- );
- account.forget_old_prekey();
- });
-
- it('should encrypt and decrypt', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
- const bobSession = new olm.Session();
-
- createSession(aliceSession, aliceAccount, bobAccount);
- expect(testRatchet(aliceSession, bobSession, bobAccount)).toBeTrue;
- });
-
- it('should encrypt and decrypt sequential', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
- const bobSession = new olm.Session();
-
- createSession(aliceSession, aliceAccount, bobAccount);
- expect(testRatchetSequential(aliceSession, bobSession, bobAccount))
- .toBeTrue;
- });
-
- it('should encrypt and decrypt, even after a prekey is rotated', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
- const bobSession = new olm.Session();
-
- createSession(aliceSession, aliceAccount, bobAccount, true);
- expect(testRatchet(aliceSession, bobSession, bobAccount)).toBeTrue;
- });
-
- it('should not encrypt and decrypt, after the old prekey is forgotten', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
- const bobSession = new olm.Session();
-
- createSession(aliceSession, aliceAccount, bobAccount, true, true);
- expect(testRatchet(aliceSession, bobSession, bobAccount)).toBeFalse;
- });
-
- it('should encrypt and decrypt repeatedly', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
- const bobSession = new olm.Session();
-
- createSession(aliceSession, aliceAccount, bobAccount, false, false);
- expect(testRatchet(aliceSession, bobSession, bobAccount, 100)).toBeTrue;
- });
-
- it('should not encrypt and decrypt if prekey is not signed correctly', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
-
- expect(
- createSession(aliceSession, aliceAccount, bobAccount, false, false, true),
- ).toBeFalse;
- });
-
- it('should create session without one-time key', async () => {
- await olm.init();
- const aliceAccount = initAccount();
- const bobAccount = initAccount();
- const aliceSession = new olm.Session();
- const bobSession = new olm.Session();
-
- expect(createSessionWithoutOTK(aliceSession, aliceAccount, bobAccount))
- .toBeTrue;
- expect(testRatchet(aliceSession, bobSession, bobAccount, 100)).toBeTrue;
- });
-});
diff --git a/lib/flow-typed/npm/@commapp/vodozemac_vx.x.x.js b/lib/flow-typed/npm/@commapp/vodozemac_vx.x.x.js
new file mode 100644
--- /dev/null
+++ b/lib/flow-typed/npm/@commapp/vodozemac_vx.x.x.js
@@ -0,0 +1,91 @@
+// flow-typed signature: 39f28f3c9faafcadb70d2bbe7f61d07e
+// flow-typed version: <<STUB>>/@commapp/vodozemac_v0.1.0/flow_v0.269.1
+
+declare module '@commapp/vodozemac' {
+ declare export default function init(opts?: Object): Promise<void>;
+ declare export function initSync(options: { module: Buffer | Uint8Array }): void;
+
+ declare export class Account {
+ constructor(): Account;
+ free(): void;
+
+ +ed25519_key: string;
+ +curve25519_key: string;
+ sign(message: string): string;
+
+ one_time_keys(): Map<string, string>;
+ mark_keys_as_published(): void;
+ max_number_of_one_time_keys(): number;
+ generate_one_time_keys(count: number): void;
+
+ generate_prekey(): void;
+ prekey(): ?string;
+ unpublished_prekey(): ?string;
+ prekey_signature(): ?string;
+ forget_old_prekey(): void;
+
+ mark_prekey_as_published(): boolean;
+ last_prekey_publish_time(): bigint;
+
+ pickle(pickle_key: Uint8Array): string;
+ static from_pickle(pickle: string, pickle_key: Uint8Array): Account;
+ static from_libolm_pickle(pickle: string, pickle_key: Uint8Array): Account;
+
+ create_outbound_session(
+ identity_key: string,
+ signing_key: string,
+ one_time_key: ?string,
+ pre_key: string,
+ pre_key_signature: string,
+ olm_compatibility_mode: boolean,
+ ): Session;
+
+ create_inbound_session(
+ identity_key: string,
+ message: OlmMessage,
+ ): InboundCreationResult;
+ }
+
+ declare export class Session {
+ free(): void;
+
+ pickle(pickle_key: Uint8Array): string;
+ static from_pickle(pickle: string, pickle_key: Uint8Array): Session;
+ static from_libolm_pickle(pickle: string, pickle_key: Uint8Array): Session;
+
+ +session_id: string;
+ has_received_message(): boolean;
+ is_sender_chain_empty(): boolean;
+ session_matches(message: OlmMessage): boolean;
+
+ encrypt(plaintext: string): OlmMessage;
+ decrypt(message: OlmMessage): string;
+ }
+
+ declare export class OlmMessage {
+ constructor(message_type: number, ciphertext: string): OlmMessage;
+ free(): void;
+
+ +ciphertext: string;
+ +message_type: 0 | 1, // 0: PreKey, 1: Message
+ }
+
+ declare export class InboundCreationResult {
+ free(): void;
+
+ +plaintext: string;
+ into_session(): Session;
+ }
+
+ declare export class Utility {
+ constructor(): void;
+ free(): void;
+
+ sha256(input: string | Uint8Array): string;
+ ed25519_verify(
+ key: string,
+ message: string | Uint8Array,
+ signature: string,
+ ): void;
+ }
+}
diff --git a/lib/package.json b/lib/package.json
--- a/lib/package.json
+++ b/lib/package.json
@@ -38,6 +38,7 @@
"dependencies": {
"@commapp/olm": "0.2.6",
"@ensdomains/ensjs": "^4.0.1",
+ "@commapp/vodozemac": "0.1.0",
"@khanacademy/simple-markdown": "^2.1.0",
"@rainbow-me/rainbowkit": "^2.0.7",
"base-64": "^0.1.0",
diff --git a/lib/shared/crypto-utils.js b/lib/shared/crypto-utils.js
--- a/lib/shared/crypto-utils.js
+++ b/lib/shared/crypto-utils.js
@@ -84,6 +84,10 @@
);
}
+// Methods below are now considered to be deprecated. Vodozemac uses a different
+// API and there is no need to parse prekey and OTKs. The only exception is
+// `get_olm_session_initialization_data` which still returns keys in the old
+// format to support older clients.
function getOneTimeKeyValues(
oneTimeKeys: OLMOneTimeKeys,
): $ReadOnlyArray<string> {
diff --git a/lib/types/encrypted-type.js b/lib/types/encrypted-type.js
new file mode 100644
--- /dev/null
+++ b/lib/types/encrypted-type.js
@@ -0,0 +1,6 @@
+// @flow
+
+export type EncryptResult = {
+ +type: 0 | 1, // 0: PreKey, 1: Message
+ +body: string,
+};
diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js
--- a/lib/types/notif-types.js
+++ b/lib/types/notif-types.js
@@ -1,8 +1,8 @@
// @flow
-import type { EncryptResult } from '@commapp/olm';
import t, { type TInterface, type TUnion } from 'tcomb';
+import type { EncryptResult } from './encrypted-type.js';
import type { MessageData, RawMessageInfo } from './message-types.js';
import type { ThickRawThreadInfos } from './thread-types.js';
import type { EntityText, ThreadEntity } from '../utils/entity-text.js';
diff --git a/lib/utils/olm-memory-utils.js b/lib/utils/olm-memory-utils.js
deleted file mode 100644
--- a/lib/utils/olm-memory-utils.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// @flow
-
-import olm from '@commapp/olm';
-
-let olmTotalMemory = null,
- olmUsedMemory = null;
-
-function verifyMemoryUsage(method: string) {
- try {
- if (olmTotalMemory === null && olmUsedMemory === null) {
- olmTotalMemory = olm.get_total_memory();
- olmUsedMemory = olm.get_used_memory();
- console.error(
- `Olm first time memory check - Total: ${olmTotalMemory ?? -1}, Used: ${
- olmUsedMemory ?? -1
- }`,
- );
- return;
- }
-
- const currentTotalMemory = olm.get_total_memory();
- if (currentTotalMemory !== olmTotalMemory) {
- console.error(
- `Olm's total memory changed from ${olmTotalMemory ?? -1} ` +
- `to ${currentTotalMemory} after executing ${method} method`,
- );
- olmTotalMemory = currentTotalMemory;
- }
-
- const currentUsedMemory = olm.get_used_memory();
- if (currentUsedMemory !== olmUsedMemory) {
- console.error(
- `Olm's used memory changed from ${olmUsedMemory ?? -1} ` +
- `to ${currentUsedMemory} after executing ${method} method`,
- );
- olmUsedMemory = currentUsedMemory;
- }
- } catch (e) {
- console.error('Encountered error while trying log Olm memory', e);
- }
-}
-
-type OlmMemory = {
- +total: ?number,
- +used: ?number,
-};
-
-function getOlmMemory(): OlmMemory {
- try {
- const total = olm.get_total_memory();
- const used = olm.get_used_memory();
- return { total, used };
- } catch (e) {
- console.error('Encountered error in getOlmMemory:', e);
- return { total: null, used: null };
- }
-}
-
-function compareAndLogOlmMemory(previous: OlmMemory, method: string) {
- const current = getOlmMemory();
- if (current.total !== previous.total) {
- console.error(
- `Olm's total memory changed from ${previous.total ?? -1} ` +
- `to ${current.total ?? -1} during execution of ${method} method`,
- );
- }
- if (current.used !== previous.used) {
- console.error(
- `Olm's used memory changed from ${previous.used ?? -1} ` +
- `to ${current.used ?? -1} during execution of ${method} method`,
- );
- }
-}
-
-export { verifyMemoryUsage, getOlmMemory, compareAndLogOlmMemory };
diff --git a/lib/utils/olm-utility.js b/lib/utils/olm-utility.js
--- a/lib/utils/olm-utility.js
+++ b/lib/utils/olm-utility.js
@@ -1,16 +1,15 @@
// @flow
-import type { Utility as OlmUtility } from '@commapp/olm';
-import olm from '@commapp/olm';
+import { Utility } from '@commapp/vodozemac';
-let cachedOlmUtility: OlmUtility;
-function getOlmUtility(): OlmUtility {
+let cachedOlmUtility: Utility;
+function getOlmUtility(): Utility {
if (cachedOlmUtility) {
return cachedOlmUtility;
}
- // This `olm.Utility` is created once and is cached for the entire
+ // This `Utility` is created once and is cached for the entire
// program lifetime, there is no need to free the memory.
- cachedOlmUtility = new olm.Utility();
+ cachedOlmUtility = new Utility();
return cachedOlmUtility;
}
diff --git a/lib/utils/olm-utils.js b/lib/utils/olm-utils.js
--- a/lib/utils/olm-utils.js
+++ b/lib/utils/olm-utils.js
@@ -1,11 +1,7 @@
// @flow
-import type { Account as OlmAccount } from '@commapp/olm';
+import type { Account as VodozemacAccount } from '@commapp/vodozemac';
-import {
- getOneTimeKeyValuesFromBlob,
- getPrekeyValueFromBlob,
-} from '../shared/crypto-utils.js';
import { ONE_TIME_KEYS_NUMBER } from '../types/identity-service-types.js';
const maxPublishedPrekeyAge = 30 * 24 * 60 * 60 * 1000; // 30 days
@@ -18,7 +14,7 @@
+oneTimeKeys: $ReadOnlyArray<string>,
};
-function validateAccountPrekey(account: OlmAccount) {
+function validateAccountPrekey(account: VodozemacAccount) {
if (shouldRotatePrekey(account)) {
account.generate_prekey();
}
@@ -27,7 +23,7 @@
}
}
-function shouldRotatePrekey(account: OlmAccount): boolean {
+function shouldRotatePrekey(account: VodozemacAccount): boolean {
// Our fork of Olm only remembers two prekeys at a time.
// If the new one hasn't been published, then the old one is still active.
// In that scenario, we need to avoid rotating the prekey because it will
@@ -45,7 +41,7 @@
);
}
-function shouldForgetPrekey(account: OlmAccount): boolean {
+function shouldForgetPrekey(account: VodozemacAccount): boolean {
// Our fork of Olm only remembers two prekeys at a time.
// We have to hold onto the old one until the new one is published.
if (account.unpublished_prekey()) {
@@ -60,36 +56,42 @@
);
}
-function getLastPrekeyPublishTime(account: OlmAccount): Date {
- const olmLastPrekeyPublishTime = account.last_prekey_publish_time();
+function getLastPrekeyPublishTime(account: VodozemacAccount): Date {
+ const vodozemacLastPrekeyPublishTime = account.last_prekey_publish_time();
- // Olm uses seconds, while the Date() constructor expects milliseconds.
- return new Date(olmLastPrekeyPublishTime * 1000);
+ // Vodozemac uses seconds, while the Date() constructor expects milliseconds.
+ return new Date(Number(vodozemacLastPrekeyPublishTime) * 1000);
}
-function getAccountPrekeysSet(account: OlmAccount): {
+function getAccountPrekeysSet(account: VodozemacAccount): {
+prekey: string,
+prekeySignature: ?string,
} {
- const prekey = getPrekeyValueFromBlob(account.prekey());
+ const prekey = account.prekey();
+ if (!prekey) {
+ throw Error('Prekey is missing');
+ }
const prekeySignature = account.prekey_signature();
return { prekey, prekeySignature };
}
function getAccountOneTimeKeys(
- account: OlmAccount,
+ account: VodozemacAccount,
numberOfKeys: number = ONE_TIME_KEYS_NUMBER,
): $ReadOnlyArray<string> {
- let oneTimeKeys = getOneTimeKeyValuesFromBlob(account.one_time_keys());
+ let oneTimeKeys = [...account.one_time_keys().values()];
if (oneTimeKeys.length < numberOfKeys) {
account.generate_one_time_keys(numberOfKeys - oneTimeKeys.length);
- oneTimeKeys = getOneTimeKeyValuesFromBlob(account.one_time_keys());
+ oneTimeKeys = [...account.one_time_keys().values()];
}
return oneTimeKeys;
}
-function retrieveAccountKeysSet(account: OlmAccount): AccountKeysSet {
- const identityKeys = account.identity_keys();
+function retrieveAccountKeysSet(account: VodozemacAccount): AccountKeysSet {
+ const identityKeys = JSON.stringify({
+ ed25519: account.ed25519_key,
+ curve25519: account.curve25519_key,
+ });
validateAccountPrekey(account);
const { prekey, prekeySignature } = getAccountPrekeysSet(account);
@@ -128,8 +130,7 @@
// the corresponding .cpp file
// at `native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp`.
invalidSessionVersion: 'INVALID_SESSION_VERSION',
- alreadyDecrypted:
- OLM_SESSION_ERROR_PREFIX + `ALREADY_DECRYPTED_OR_KEYS_SKIPPED`,
+ alreadyDecrypted: `The message key with the given key can't be created`,
});
function hasHigherDeviceID(
diff --git a/lib/utils/vodozemac-utils.js b/lib/utils/vodozemac-utils.js
new file mode 100644
--- /dev/null
+++ b/lib/utils/vodozemac-utils.js
@@ -0,0 +1,61 @@
+// @flow
+
+import { Account, Session } from '@commapp/vodozemac';
+
+// Helper function to get 32-byte pickle key for vodozemac
+function getVodozemacPickleKey(picklingKey: string): Uint8Array {
+ const fullKeyBytes = new TextEncoder().encode(picklingKey);
+ // NOTE: vodozemac works only with 32-byte keys.
+ // We have sessions pickled with 64-byte keys. Additionally, this key
+ // is used in backup, so it can't simply be migrated. Instead, we're going
+ // to just use the first 32 bytes of the existing secret key.
+ return fullKeyBytes.slice(0, 32);
+}
+
+function unpickleVodozemacAccount({
+ picklingKey,
+ pickledAccount,
+}: {
+ +picklingKey: string,
+ +pickledAccount: string,
+}): Account {
+ const fullKeyBytes = new TextEncoder().encode(picklingKey);
+ const keyBytes = getVodozemacPickleKey(picklingKey);
+ try {
+ // First try vodozemac native format
+ return Account.from_pickle(pickledAccount, keyBytes);
+ } catch (e) {
+ console.log(
+ 'Failed to unpickle account with vodozemac format, falling back to libolm:',
+ e.message,
+ );
+ return Account.from_libolm_pickle(pickledAccount, fullKeyBytes);
+ }
+}
+
+function unpickleVodozemacSession({
+ picklingKey,
+ pickledSession,
+}: {
+ +picklingKey: string,
+ +pickledSession: string,
+}): Session {
+ const fullKeyBytes = new TextEncoder().encode(picklingKey);
+ const keyBytes = getVodozemacPickleKey(picklingKey);
+ try {
+ // First try vodozemac native format
+ return Session.from_pickle(pickledSession, keyBytes);
+ } catch (e) {
+ console.log(
+ 'Failed to unpickle session with vodozemac format, falling back to libolm:',
+ e.message,
+ );
+ return Session.from_libolm_pickle(pickledSession, fullKeyBytes);
+ }
+}
+
+export {
+ getVodozemacPickleKey,
+ unpickleVodozemacAccount,
+ unpickleVodozemacSession,
+};
diff --git a/native/native_rust_library/Cargo.lock b/native/native_rust_library/Cargo.lock
--- a/native/native_rust_library/Cargo.lock
+++ b/native/native_rust_library/Cargo.lock
@@ -1418,6 +1418,7 @@
name = "native_rust_library"
version = "0.1.0"
dependencies = [
+ "anyhow",
"argon2",
"backup_client",
"base64 0.21.7",
@@ -1431,6 +1432,7 @@
"regex",
"serde",
"serde_json",
+ "sha2",
"siwe",
"tokio",
"tokio-util",
@@ -2870,7 +2872,7 @@
[[package]]
name = "vodozemac"
version = "0.9.0"
-source = "git+https://github.com/CommE2E/vodozemac#3c142a35f114fcc9edb9dc766b051716c5160150"
+source = "git+https://github.com/CommE2E/vodozemac#040c9875016241680cb89387a3d6aa1af93b1214"
dependencies = [
"aes",
"arrayvec",
diff --git a/native/native_rust_library/Cargo.toml b/native/native_rust_library/Cargo.toml
--- a/native/native_rust_library/Cargo.toml
+++ b/native/native_rust_library/Cargo.toml
@@ -22,6 +22,8 @@
base64 = "0.21"
regex = "1.10"
vodozemac = { git = "https://github.com/CommE2E/vodozemac", features = ["libolm-compat"] }
+anyhow = "1.0.97"
+sha2 = "0.10"
[target.'cfg(target_os = "android")'.dependencies]
backup_client = { path = "../../shared/backup_client", default-features = false, features = [
diff --git a/native/native_rust_library/build.rs b/native/native_rust_library/build.rs
--- a/native/native_rust_library/build.rs
+++ b/native/native_rust_library/build.rs
@@ -197,7 +197,7 @@
.expect("Couldn't write backup service config");
println!("cargo:rerun-if-changed=src/lib.rs");
- println!("cargo:rerun-if-changed=src/session.rs");
+ println!("cargo:rerun-if-changed=src/vodozemac.rs");
println!("cargo:rerun-if-changed={}", IdentityServiceConfig::FILEPATH);
println!("cargo:rerun-if-changed={}", BackupServiceConfig::FILEPATH);
}
diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs
--- a/native/native_rust_library/src/lib.rs
+++ b/native/native_rust_library/src/lib.rs
@@ -10,12 +10,16 @@
mod backup;
mod constants;
mod identity;
-mod session;
mod utils;
+mod vodozemac;
use crate::argon2_tools::compute_backup_key_str;
use crate::utils::jsi_callbacks::handle_string_result_as_callback;
-use session::{session_from_pickle, EncryptResult, VodozemacSession};
+use vodozemac::{
+ account_from_pickle, account_new, encrypt_result_new, session_from_pickle,
+ sha256, verify_ed25519_signature, verify_prekey_signature, EncryptResult,
+ InboundCreationResult, VodozemacAccount, VodozemacSession,
+};
mod generated {
// We get the CODE_VERSION from this generated file
@@ -568,36 +572,100 @@
}
// Vodozemac crypto functions
+ // NOTE: We use `not(target_os = "ios")` to target Android instead of
+ // checking for Android directly due to problems with setting the Android
+ // target OS on CI.
+ #[cfg(not(target_os = "ios"))]
extern "Rust" {
// NOTE: Keep in sync with Vodozemac crypto functions block
// in native/vodozemac_bindings/src/lib.rs.
- #[cfg(target_os = "android")]
- type VodozemacSession;
- #[cfg(target_os = "android")]
+
+ // EncryptResult type
type EncryptResult;
- #[cfg(target_os = "android")]
- fn pickle(self: &VodozemacSession, pickle_key: &[u8; 32]) -> String;
- #[cfg(target_os = "android")]
+ fn encrypt_result_new(
+ encrypted_message: String,
+ message_type: u32,
+ ) -> Box<EncryptResult>;
fn encrypted_message(self: &EncryptResult) -> String;
- #[cfg(target_os = "android")]
fn message_type(self: &EncryptResult) -> u32;
- #[cfg(target_os = "android")]
+
+ // VodozemacSession type
+ type VodozemacSession;
+ fn pickle(self: &VodozemacSession, pickle_key: &[u8; 32]) -> String;
fn encrypt(
self: &mut VodozemacSession,
plaintext: &str,
) -> Result<Box<EncryptResult>>;
- #[cfg(target_os = "android")]
fn decrypt(
self: &mut VodozemacSession,
encrypted_message: String,
message_type: u32,
) -> Result<String>;
+ fn has_received_message(self: &VodozemacSession) -> bool;
+ fn is_sender_chain_empty(self: &VodozemacSession) -> bool;
- #[cfg(target_os = "android")]
pub fn session_from_pickle(
session_state: String,
session_key: String,
) -> Result<Box<VodozemacSession>>;
+
+ // VodozemacAccount type
+ type VodozemacAccount;
+ fn pickle(self: &VodozemacAccount, pickle_key: &[u8; 32]) -> String;
+ fn ed25519_key(self: &VodozemacAccount) -> String;
+ fn curve25519_key(self: &VodozemacAccount) -> String;
+ fn sign(self: &VodozemacAccount, message: &str) -> String;
+ fn generate_one_time_keys(self: &mut VodozemacAccount, count: usize);
+ fn one_time_keys(self: &VodozemacAccount) -> Vec<String>;
+ fn mark_keys_as_published(self: &mut VodozemacAccount);
+ fn max_number_of_one_time_keys(self: &VodozemacAccount) -> usize;
+ fn mark_prekey_as_published(self: &mut VodozemacAccount) -> bool;
+ fn generate_prekey(self: &mut VodozemacAccount);
+ fn forget_old_prekey(self: &mut VodozemacAccount);
+ fn last_prekey_publish_time(self: &mut VodozemacAccount) -> u64;
+ fn prekey(self: &VodozemacAccount) -> String;
+ fn unpublished_prekey(self: &VodozemacAccount) -> String;
+ fn prekey_signature(self: &VodozemacAccount) -> String;
+ fn create_outbound_session(
+ self: &VodozemacAccount,
+ identity_key: &str,
+ signing_key: &str,
+ one_time_key: &str,
+ pre_key: &str,
+ pre_key_signature: &str,
+ olm_compatibility_mode: bool,
+ ) -> Result<Box<VodozemacSession>>;
+ fn create_inbound_session(
+ self: &mut VodozemacAccount,
+ identity_key: &str,
+ message: &EncryptResult,
+ ) -> Result<Box<InboundCreationResult>>;
+
+ pub fn account_new() -> Box<VodozemacAccount>;
+
+ pub fn account_from_pickle(
+ account_state: String,
+ session_key: String,
+ ) -> Result<Box<VodozemacAccount>>;
+
+ pub fn verify_ed25519_signature(
+ public_key: &str,
+ message: &str,
+ signature: &str,
+ ) -> Result<()>;
+
+ pub fn verify_prekey_signature(
+ public_key: &str,
+ prekey_base64: &str,
+ signature: &str,
+ ) -> Result<()>;
+
+ pub fn sha256(input: &[u8]) -> String;
+
+ // InboundCreationResult type
+ type InboundCreationResult;
+ fn plaintext(self: &InboundCreationResult) -> String;
+ fn take_session(self: &mut InboundCreationResult) -> Box<VodozemacSession>;
}
}
diff --git a/native/native_rust_library/src/session.rs b/native/native_rust_library/src/session.rs
deleted file mode 100644
--- a/native/native_rust_library/src/session.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-use vodozemac::olm::{Session, SessionPickle};
-use vodozemac::{olm, PickleError};
-
-pub struct VodozemacSession(pub(crate) vodozemac::olm::Session);
-
-impl From<Session> for VodozemacSession {
- fn from(session: Session) -> Self {
- VodozemacSession(session)
- }
-}
-
-pub struct EncryptResult {
- pub encrypted_message: String,
- pub message_type: u32,
-}
-
-impl EncryptResult {
- pub fn encrypted_message(&self) -> String {
- self.encrypted_message.clone()
- }
-
- pub fn message_type(&self) -> u32 {
- self.message_type
- }
-}
-
-impl VodozemacSession {
- pub fn pickle(&self, pickle_key: &[u8; 32]) -> String {
- self.0.pickle().encrypt(pickle_key)
- }
-
- pub fn encrypt(
- &mut self,
- plaintext: &str,
- ) -> Result<Box<EncryptResult>, String> {
- let olm_message = self.0.encrypt(plaintext.as_bytes());
-
- let (message_type, encrypted_message) = match olm_message {
- olm::OlmMessage::Normal(msg) => (1, msg.to_base64()),
- olm::OlmMessage::PreKey(msg) => (0, msg.to_base64()),
- };
-
- Ok(Box::from(EncryptResult {
- encrypted_message,
- message_type: message_type as u32,
- }))
- }
-
- pub fn decrypt(
- &mut self,
- encrypted_message: String,
- message_type: u32,
- ) -> Result<String, String> {
- let olm_message: vodozemac::olm::OlmMessage = match message_type {
- 0 => olm::PreKeyMessage::from_base64(encrypted_message.as_str())
- .map_err(|e| e.to_string())?
- .into(),
- 1 => olm::Message::from_base64(encrypted_message.as_str())
- .map_err(|e| e.to_string())?
- .into(),
- _ => return Err("wrong message type".to_string()),
- };
-
- let result = self.0.decrypt(&olm_message).map_err(|e| e.to_string())?;
- let plaintext = String::from_utf8(result).expect("Invalid UTF-8");
-
- Ok(plaintext)
- }
-}
-
-pub fn session_from_pickle(
- session_state: String,
- session_key: String,
-) -> Result<Box<VodozemacSession>, String> {
- let key_bytes = session_key.as_bytes();
-
- //NOTE: vvodozemac works only with 32-byte keys.
- // We have sessions pickled with 64-byte keys. Additionally, this key
- // is used in backup, so it can't simply be migrated. Instead, we're going
- // to just use the first 32 bytes of the existing secret key.
- let key: &[u8; 32] = &key_bytes[0..32]
- .try_into()
- .expect("String must be at least 32 bytes");
-
- let session_pickle =
- match SessionPickle::from_encrypted(session_state.as_str(), key) {
- Ok(pickle) => Some(pickle),
- Err(e) => {
- match e {
- PickleError::Base64(base64_error) => {
- return Err(base64_error.to_string());
- }
- //TODO: Use only specific error type
- PickleError::Decryption(_) => {
- println!("Decryption error, will try from_libolm_pickle");
- None
- }
- PickleError::Serialization(serialization_error) => {
- return Err(serialization_error.to_string());
- }
- }
- }
- };
-
- let session: VodozemacSession = if let Some(pickle) = session_pickle {
- Session::from_pickle(pickle).into()
- } else {
- Session::from_libolm_pickle(&session_state, session_key.as_bytes())
- .map_err(|e| e.to_string())?
- .into()
- };
-
- Ok(Box::from(session))
-}
diff --git a/native/native_rust_library/src/vodozemac.rs b/native/native_rust_library/src/vodozemac.rs
new file mode 100644
--- /dev/null
+++ b/native/native_rust_library/src/vodozemac.rs
@@ -0,0 +1,409 @@
+use sha2::{Digest, Sha256};
+use vodozemac::olm::{Account, AccountPickle, Session, SessionPickle};
+use vodozemac::{olm, PickleError};
+
+pub struct VodozemacSession(pub(crate) vodozemac::olm::Session);
+
+impl From<Session> for VodozemacSession {
+ fn from(session: Session) -> Self {
+ VodozemacSession(session)
+ }
+}
+
+pub struct EncryptResult {
+ pub encrypted_message: String,
+ pub message_type: u32,
+}
+
+pub fn encrypt_result_new(
+ encrypted_message: String,
+ message_type: u32,
+) -> Box<EncryptResult> {
+ Box::new(EncryptResult {
+ encrypted_message,
+ message_type,
+ })
+}
+
+impl EncryptResult {
+ pub fn encrypted_message(&self) -> String {
+ self.encrypted_message.clone()
+ }
+
+ pub fn message_type(&self) -> u32 {
+ self.message_type
+ }
+}
+
+impl TryFrom<&EncryptResult> for olm::OlmMessage {
+ type Error = anyhow::Error;
+
+ fn try_from(message: &EncryptResult) -> Result<Self, Self::Error> {
+ match message.message_type {
+ 0 => {
+ let prekey =
+ olm::PreKeyMessage::from_base64(&message.encrypted_message)?;
+ Ok(prekey.into())
+ }
+ 1 => {
+ let msg = olm::Message::from_base64(&message.encrypted_message)?;
+ Ok(msg.into())
+ }
+ _ => anyhow::bail!("Invalid message type: {}", message.message_type),
+ }
+ }
+}
+
+impl VodozemacSession {
+ pub fn pickle(&self, pickle_key: &[u8; 32]) -> String {
+ self.0.pickle().encrypt(pickle_key)
+ }
+
+ pub fn encrypt(
+ &mut self,
+ plaintext: &str,
+ ) -> Result<Box<EncryptResult>, String> {
+ let olm_message = self.0.encrypt(plaintext.as_bytes());
+
+ let (message_type, encrypted_message) = match olm_message {
+ olm::OlmMessage::Normal(msg) => (1, msg.to_base64()),
+ olm::OlmMessage::PreKey(msg) => (0, msg.to_base64()),
+ };
+
+ Ok(Box::from(EncryptResult {
+ encrypted_message,
+ message_type: message_type as u32,
+ }))
+ }
+
+ pub fn decrypt(
+ &mut self,
+ encrypted_message: String,
+ message_type: u32,
+ ) -> Result<String, String> {
+ let encrypted_result = EncryptResult {
+ encrypted_message,
+ message_type,
+ };
+ let olm_message: olm::OlmMessage = (&encrypted_result)
+ .try_into()
+ .map_err(|e: anyhow::Error| e.to_string())?;
+
+ let result = self.0.decrypt(&olm_message).map_err(|e| e.to_string())?;
+ let plaintext = String::from_utf8(result).expect("Invalid UTF-8");
+
+ Ok(plaintext)
+ }
+
+ pub fn has_received_message(&self) -> bool {
+ self.0.has_received_message()
+ }
+
+ pub fn is_sender_chain_empty(&self) -> bool {
+ self.0.is_sender_chain_empty()
+ }
+}
+
+pub fn session_from_pickle(
+ session_state: String,
+ session_key: String,
+) -> Result<Box<VodozemacSession>, String> {
+ let key_bytes = session_key.as_bytes();
+
+ // NOTE: vodozemac works only with 32-byte keys.
+ // We have sessions pickled with 64-byte keys. Additionally, this key
+ // is used in backup, so it can't simply be migrated. Instead, we're going
+ // to just use the first 32 bytes of the existing secret key.
+ let key: &[u8; 32] = &key_bytes[0..32]
+ .try_into()
+ .expect("String must be at least 32 bytes");
+
+ let session_pickle =
+ match SessionPickle::from_encrypted(session_state.as_str(), key) {
+ Ok(pickle) => Some(pickle),
+ Err(e) => match e {
+ PickleError::Base64(base64_error) => {
+ return Err(base64_error.to_string());
+ }
+ PickleError::Decryption(_) => None,
+ PickleError::Serialization(serialization_error) => {
+ return Err(serialization_error.to_string());
+ }
+ },
+ };
+
+ let session: VodozemacSession = if let Some(pickle) = session_pickle {
+ Session::from_pickle(pickle).into()
+ } else {
+ Session::from_libolm_pickle(&session_state, session_key.as_bytes())
+ .map_err(|e| e.to_string())?
+ .into()
+ };
+
+ Ok(Box::from(session))
+}
+
+pub struct InboundCreationResult {
+ session: Option<VodozemacSession>,
+ plaintext: String,
+}
+
+impl From<vodozemac::olm::InboundCreationResult> for InboundCreationResult {
+ fn from(result: vodozemac::olm::InboundCreationResult) -> Self {
+ InboundCreationResult {
+ session: Some(VodozemacSession(result.session)),
+ plaintext: String::from_utf8(result.plaintext)
+ .expect("Invalid UTF-8 in plaintext"),
+ }
+ }
+}
+
+impl InboundCreationResult {
+ pub fn plaintext(&self) -> String {
+ self.plaintext.clone()
+ }
+
+ pub fn take_session(&mut self) -> Box<VodozemacSession> {
+ Box::new(self.session.take().expect("Session has already been taken"))
+ }
+}
+
+pub struct VodozemacAccount(pub(crate) vodozemac::olm::Account);
+
+impl From<Account> for VodozemacAccount {
+ fn from(account: Account) -> Self {
+ VodozemacAccount(account)
+ }
+}
+
+impl VodozemacAccount {
+ pub fn pickle(&self, pickle_key: &[u8; 32]) -> String {
+ self.0.pickle().encrypt(pickle_key)
+ }
+
+ pub fn ed25519_key(&self) -> String {
+ self.0.ed25519_key().to_base64()
+ }
+
+ pub fn curve25519_key(&self) -> String {
+ self.0.curve25519_key().to_base64()
+ }
+
+ pub fn sign(&self, message: &str) -> String {
+ self.0.sign(message).to_base64()
+ }
+
+ pub fn generate_one_time_keys(&mut self, count: usize) {
+ self.0.generate_one_time_keys(count);
+ }
+
+ pub fn one_time_keys(&self) -> Vec<String> {
+ self
+ .0
+ .one_time_keys()
+ .into_values()
+ .map(|v| v.to_base64())
+ .collect()
+ }
+
+ pub fn mark_keys_as_published(&mut self) {
+ self.0.mark_keys_as_published()
+ }
+
+ pub fn max_number_of_one_time_keys(&self) -> usize {
+ self.0.max_number_of_one_time_keys()
+ }
+
+ pub fn mark_prekey_as_published(&mut self) -> bool {
+ self.0.mark_prekey_as_published()
+ }
+
+ pub fn generate_prekey(&mut self) {
+ self.0.generate_prekey()
+ }
+
+ pub fn forget_old_prekey(&mut self) {
+ self.0.forget_old_prekey()
+ }
+
+ pub fn last_prekey_publish_time(&mut self) -> u64 {
+ self.0.get_last_prekey_publish_time()
+ }
+
+ pub fn prekey(&self) -> String {
+ self
+ .0
+ .prekey()
+ .map(|key| key.to_base64())
+ .unwrap_or_default()
+ }
+
+ pub fn unpublished_prekey(&self) -> String {
+ self
+ .0
+ .unpublished_prekey()
+ .map(|key| key.to_base64())
+ .unwrap_or_default()
+ }
+
+ pub fn prekey_signature(&self) -> String {
+ self.0.get_prekey_signature().unwrap_or_default()
+ }
+
+ pub fn create_outbound_session(
+ &self,
+ identity_key: &str,
+ signing_key: &str,
+ one_time_key: &str,
+ pre_key: &str,
+ pre_key_signature: &str,
+ olm_compatibility_mode: bool,
+ ) -> Result<Box<VodozemacSession>, String> {
+ let session_config = vodozemac::olm::SessionConfig::version_1();
+ let identity_key =
+ vodozemac::Curve25519PublicKey::from_base64(identity_key)
+ .map_err(|e| e.to_string())?;
+ let signing_key = vodozemac::Ed25519PublicKey::from_base64(signing_key)
+ .map_err(|e| e.to_string())?;
+ // NOTE: We use an empty string to represent None because cxx doesn't
+ // support Option<&str> in FFI function signatures.
+ let one_time_key = if one_time_key.is_empty() {
+ None
+ } else {
+ Some(
+ vodozemac::Curve25519PublicKey::from_base64(one_time_key)
+ .map_err(|e| e.to_string())?,
+ )
+ };
+ let pre_key = vodozemac::Curve25519PublicKey::from_base64(pre_key)
+ .map_err(|e| e.to_string())?;
+
+ let session = self
+ .0
+ .create_outbound_session(
+ session_config,
+ identity_key,
+ signing_key,
+ one_time_key,
+ pre_key,
+ pre_key_signature.to_string(),
+ olm_compatibility_mode,
+ )
+ .map_err(|e| e.to_string())?;
+
+ Ok(Box::new(VodozemacSession(session)))
+ }
+
+ pub fn create_inbound_session(
+ &mut self,
+ identity_key: &str,
+ message: &EncryptResult,
+ ) -> Result<Box<InboundCreationResult>, String> {
+ let identity_key =
+ vodozemac::Curve25519PublicKey::from_base64(identity_key)
+ .map_err(|e| e.to_string())?;
+ let olm_message: olm::OlmMessage = message
+ .try_into()
+ .map_err(|e: anyhow::Error| e.to_string())?;
+
+ if let olm::OlmMessage::PreKey(message) = olm_message {
+ Ok(Box::new(
+ self
+ .0
+ .create_inbound_session(identity_key, &message)
+ .map_err(|e| e.to_string())?
+ .into(),
+ ))
+ } else {
+ Err("Invalid message type, a pre-key message is required".to_string())
+ }
+ }
+}
+
+pub fn account_new() -> Box<VodozemacAccount> {
+ let account = Account::new();
+ Box::new(VodozemacAccount(account))
+}
+
+pub fn account_from_pickle(
+ account_state: String,
+ account_key: String,
+) -> Result<Box<VodozemacAccount>, String> {
+ let key_bytes = account_key.as_bytes();
+
+ // NOTE: vodozemac works only with 32-byte keys.
+ // We have sessions pickled with 64-byte keys. Additionally, this key
+ // is used in backup, so it can't simply be migrated. Instead, we're going
+ // to just use the first 32 bytes of the existing secret key.
+ let key: &[u8; 32] = &key_bytes[0..32]
+ .try_into()
+ .expect("String must be at least 32 bytes");
+
+ let account_pickle =
+ match AccountPickle::from_encrypted(account_state.as_str(), key) {
+ Ok(pickle) => Some(pickle),
+ Err(e) => match e {
+ PickleError::Base64(base64_error) => {
+ return Err(base64_error.to_string());
+ }
+ PickleError::Decryption(_) => None,
+ PickleError::Serialization(serialization_error) => {
+ return Err(serialization_error.to_string());
+ }
+ },
+ };
+
+ let account: VodozemacAccount = if let Some(pickle) = account_pickle {
+ Account::from_pickle(pickle).into()
+ } else {
+ Account::from_libolm_pickle(&account_state, account_key.as_bytes())
+ .map_err(|e| e.to_string())?
+ .into()
+ };
+
+ Ok(Box::from(account))
+}
+
+pub fn verify_ed25519_signature(
+ public_key: &str,
+ message: &str,
+ signature: &str,
+) -> Result<(), String> {
+ let public_key = vodozemac::Ed25519PublicKey::from_base64(public_key)
+ .map_err(|e| e.to_string())?;
+
+ let signature = vodozemac::Ed25519Signature::from_base64(signature)
+ .map_err(|e| e.to_string())?;
+
+ public_key
+ .verify(message.as_bytes(), &signature)
+ .map_err(|e| e.to_string())
+}
+
+pub fn verify_prekey_signature(
+ public_key: &str,
+ prekey_base64: &str,
+ signature: &str,
+) -> Result<(), String> {
+ let public_key = vodozemac::Ed25519PublicKey::from_base64(public_key)
+ .map_err(|e| e.to_string())?;
+
+ let signature = vodozemac::Ed25519Signature::from_base64(signature)
+ .map_err(|e| e.to_string())?;
+
+ // Decode the base64 prekey to raw bytes for verification
+ let prekey_bytes =
+ vodozemac::base64_decode(prekey_base64).map_err(|e| e.to_string())?;
+
+ public_key
+ .verify(&prekey_bytes, &signature)
+ .map_err(|e| e.to_string())
+}
+
+pub fn sha256(input: &[u8]) -> String {
+ let mut hasher = Sha256::new();
+ hasher.update(&input);
+ let hash = hasher.finalize();
+
+ vodozemac::base64_encode(hash)
+}
diff --git a/native/vodozemac_bindings/Cargo.lock b/native/vodozemac_bindings/Cargo.lock
--- a/native/vodozemac_bindings/Cargo.lock
+++ b/native/vodozemac_bindings/Cargo.lock
@@ -897,7 +897,7 @@
[[package]]
name = "vodozemac"
version = "0.9.0"
-source = "git+https://github.com/CommE2E/vodozemac#3c142a35f114fcc9edb9dc766b051716c5160150"
+source = "git+https://github.com/CommE2E/vodozemac#040c9875016241680cb89387a3d6aa1af93b1214"
dependencies = [
"aes",
"arrayvec",
@@ -934,6 +934,7 @@
"regex",
"serde",
"serde_json",
+ "sha2",
"vodozemac",
]
diff --git a/native/vodozemac_bindings/Cargo.toml b/native/vodozemac_bindings/Cargo.toml
--- a/native/vodozemac_bindings/Cargo.toml
+++ b/native/vodozemac_bindings/Cargo.toml
@@ -10,6 +10,7 @@
serde_json = "1.0"
derive_more = "0.99"
anyhow = "1.0.97"
+sha2 = "0.10"
[build-dependencies]
cxx-build = "=1.0.75"
diff --git a/native/vodozemac_bindings/build.rs b/native/vodozemac_bindings/build.rs
--- a/native/vodozemac_bindings/build.rs
+++ b/native/vodozemac_bindings/build.rs
@@ -3,4 +3,5 @@
cxx_build::bridge("src/lib.rs").flag_if_supported("-std=c++17");
println!("cargo:rerun-if-changed=src/lib.rs");
+ println!("cargo:rerun-if-changed=src/vodozemac.rs");
}
diff --git a/native/vodozemac_bindings/src/lib.rs b/native/vodozemac_bindings/src/lib.rs
--- a/native/vodozemac_bindings/src/lib.rs
+++ b/native/vodozemac_bindings/src/lib.rs
@@ -1,23 +1,31 @@
use std::error::Error as StdError;
-mod session;
+mod vodozemac;
-use session::{session_from_pickle, VodozemacSession};
+use vodozemac::{session_from_pickle, VodozemacSession};
-use crate::session::*;
+use crate::vodozemac::*;
#[cxx::bridge]
pub mod ffi {
// Vodozemac crypto functions
- // NOTE: Keep in sync with Vodozemac crypto functions block
- // in native/native_rust_library/src/lib.rs
extern "Rust" {
- type VodozemacSession;
+ // NOTE: Keep in sync with Vodozemac crypto functions block
+ // in native/native_rust_library/src/lib.rs
+
+ // EncryptResult type
type EncryptResult;
- fn pickle(self: &VodozemacSession, pickle_key: &[u8; 32]) -> String;
+ fn encrypt_result_new(
+ encrypted_message: String,
+ message_type: u32,
+ ) -> Box<EncryptResult>;
fn encrypted_message(self: &EncryptResult) -> String;
fn message_type(self: &EncryptResult) -> u32;
+
+ // VodozemacSession type
+ type VodozemacSession;
+ fn pickle(self: &VodozemacSession, pickle_key: &[u8; 32]) -> String;
fn encrypt(
self: &mut VodozemacSession,
plaintext: &str,
@@ -27,11 +35,71 @@
encrypted_message: String,
message_type: u32,
) -> Result<String>;
+ fn has_received_message(self: &VodozemacSession) -> bool;
+ fn is_sender_chain_empty(self: &VodozemacSession) -> bool;
pub fn session_from_pickle(
session_state: String,
session_key: String,
) -> Result<Box<VodozemacSession>>;
+
+ // VodozemacAccount type
+ type VodozemacAccount;
+ fn pickle(self: &VodozemacAccount, pickle_key: &[u8; 32]) -> String;
+ fn ed25519_key(self: &VodozemacAccount) -> String;
+ fn curve25519_key(self: &VodozemacAccount) -> String;
+ fn sign(self: &VodozemacAccount, message: &str) -> String;
+ fn generate_one_time_keys(self: &mut VodozemacAccount, count: usize);
+ fn one_time_keys(self: &VodozemacAccount) -> Vec<String>;
+ fn mark_keys_as_published(self: &mut VodozemacAccount);
+ fn max_number_of_one_time_keys(self: &VodozemacAccount) -> usize;
+ fn mark_prekey_as_published(self: &mut VodozemacAccount) -> bool;
+ fn generate_prekey(self: &mut VodozemacAccount);
+ fn forget_old_prekey(self: &mut VodozemacAccount);
+ fn last_prekey_publish_time(self: &mut VodozemacAccount) -> u64;
+ fn prekey(self: &VodozemacAccount) -> String;
+ fn unpublished_prekey(self: &VodozemacAccount) -> String;
+ fn prekey_signature(self: &VodozemacAccount) -> String;
+ fn create_outbound_session(
+ self: &VodozemacAccount,
+ identity_key: &str,
+ signing_key: &str,
+ one_time_key: &str,
+ pre_key: &str,
+ pre_key_signature: &str,
+ olm_compatibility_mode: bool,
+ ) -> Result<Box<VodozemacSession>>;
+ fn create_inbound_session(
+ self: &mut VodozemacAccount,
+ identity_key: &str,
+ message: &EncryptResult,
+ ) -> Result<Box<InboundCreationResult>>;
+
+ pub fn account_new() -> Box<VodozemacAccount>;
+
+ pub fn account_from_pickle(
+ account_state: String,
+ session_key: String,
+ ) -> Result<Box<VodozemacAccount>>;
+
+ pub fn verify_ed25519_signature(
+ public_key: &str,
+ message: &str,
+ signature: &str,
+ ) -> Result<()>;
+
+ pub fn verify_prekey_signature(
+ public_key: &str,
+ prekey_base64: &str,
+ signature: &str,
+ ) -> Result<()>;
+
+ pub fn sha256(input: &[u8]) -> String;
+
+ // InboundCreationResult type
+ type InboundCreationResult;
+ fn plaintext(self: &InboundCreationResult) -> String;
+ fn take_session(self: &mut InboundCreationResult) -> Box<VodozemacSession>;
}
}
diff --git a/native/vodozemac_bindings/src/session.rs b/native/vodozemac_bindings/src/session.rs
deleted file mode 120000
--- a/native/vodozemac_bindings/src/session.rs
+++ /dev/null
@@ -1 +0,0 @@
-../../native_rust_library/src/session.rs
\ No newline at end of file
diff --git a/native/vodozemac_bindings/src/vodozemac.rs b/native/vodozemac_bindings/src/vodozemac.rs
new file mode 120000
--- /dev/null
+++ b/native/vodozemac_bindings/src/vodozemac.rs
@@ -0,0 +1 @@
+../../native_rust_library/src/vodozemac.rs
\ No newline at end of file
diff --git a/web/crypto/olm-api.js b/web/crypto/olm-api.js
--- a/web/crypto/olm-api.js
+++ b/web/crypto/olm-api.js
@@ -3,7 +3,10 @@
import { type OlmAPI } from 'lib/types/crypto-types.js';
import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js';
-import { getOlmWasmPath } from '../shared-worker/utils/constants.js';
+import {
+ getOlmWasmPath,
+ getVodozemacWasmPath,
+} from '../shared-worker/utils/constants.js';
import {
workerRequestMessageTypes,
workerResponseMessageTypes,
@@ -41,6 +44,7 @@
await sharedWorker.schedule({
type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT,
olmWasmPath: getOlmWasmPath(),
+ vodozemacWasmPath: getVodozemacWasmPath(),
});
},
getUserPublicKey: proxyToWorker('getUserPublicKey'),
diff --git a/web/olm/olm-utils.js b/web/olm/olm-utils.js
deleted file mode 100644
--- a/web/olm/olm-utils.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// @flow
-
-import olm from '@commapp/olm';
-
-declare var olmFilename: string;
-
-async function initOlm(): Promise<void> {
- if (!olmFilename) {
- return await olm.init();
- }
- const locateFile = (wasmFilename: string, httpAssetsHost: string) =>
- httpAssetsHost + olmFilename;
- return await olm.init({ locateFile });
-}
-
-export { initOlm };
diff --git a/web/olm/olm.test.js b/web/olm/olm.test.js
deleted file mode 100644
--- a/web/olm/olm.test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// @flow
-
-import olm from '@commapp/olm';
-
-describe('olm.Account', () => {
- it('should construct an empty olm.Account', async () => {
- await olm.init();
- const account = new olm.Account();
- expect(account).toBeDefined();
- });
- it('should be able to generate and return prekey', async () => {
- await olm.init();
- const account = new olm.Account();
- account.create();
- account.generate_prekey();
- expect(account.prekey()).toBeDefined();
- });
- it('should be able to generate and return one-time keys', async () => {
- await olm.init();
- const account = new olm.Account();
- account.create();
- account.generate_one_time_keys(5);
- const oneTimeKeysObject = JSON.parse(account.one_time_keys());
- expect(oneTimeKeysObject).toBeDefined();
- const oneTimeKeys = oneTimeKeysObject.curve25519;
- expect(Object.keys(oneTimeKeys).length).toBe(5);
- });
-});
diff --git a/web/package.json b/web/package.json
--- a/web/package.json
+++ b/web/package.json
@@ -46,6 +46,7 @@
"@babel/runtime": "^7.28.3",
"@commapp/olm": "0.2.6",
"@commapp/opaque-ke-wasm": "npm:@commapp/opaque-ke-wasm@^0.0.4",
+ "@commapp/vodozemac": "0.1.0",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@fortawesome/fontawesome-svg-core": "1.2.25",
diff --git a/web/push-notif/notif-crypto-utils.js b/web/push-notif/notif-crypto-utils.js
--- a/web/push-notif/notif-crypto-utils.js
+++ b/web/push-notif/notif-crypto-utils.js
@@ -1,7 +1,9 @@
// @flow
-import olm from '@commapp/olm';
-import type { EncryptResult } from '@commapp/olm';
+import initVodozemac, {
+ OlmMessage,
+ type Account as VodozemacAccount,
+} from '@commapp/vodozemac';
import invariant from 'invariant';
import localforage from 'localforage';
import uuid from 'uuid';
@@ -13,6 +15,7 @@
type OlmEncryptedMessageTypes,
} from 'lib/types/crypto-types.js';
import { olmEncryptedMessageTypesValidator } from 'lib/types/crypto-types.js';
+import type { EncryptResult } from 'lib/types/encrypted-type.js';
import type {
PlainTextWebNotification,
EncryptedWebNotification,
@@ -22,6 +25,11 @@
import { getMessageForException } from 'lib/utils/errors.js';
import { promiseAll } from 'lib/utils/promises.js';
import { assertWithValidator } from 'lib/utils/validation-utils.js';
+import {
+ getVodozemacPickleKey,
+ unpickleVodozemacAccount,
+ unpickleVodozemacSession,
+} from 'lib/utils/vodozemac-utils.js';
import {
fetchAuthMetadata,
@@ -37,8 +45,8 @@
encryptedAESDataValidator,
extendedCryptoKeyValidator,
} from '../crypto/aes-gcm-crypto-utils.js';
-import { initOlm } from '../olm/olm-utils.js';
import {
+ getVodozemacWasmPath,
NOTIFICATIONS_OLM_DATA_CONTENT,
NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY,
} from '../shared-worker/utils/constants.js';
@@ -52,11 +60,12 @@
export type WebNotifsServiceUtilsData = {
+olmWasmPath: string,
+ +vodozemacWasmPath: ?string,
+staffCanSee: boolean,
};
export type NotificationAccountWithPicklingKey = {
- +notificationAccount: olm.Account,
+ +notificationAccount: VodozemacAccount,
+picklingKey: string,
+synchronizationValue: ?string,
+accountEncryptionKey?: CryptoKey,
@@ -366,10 +375,11 @@
WEB_NOTIFS_SERVICE_UTILS_KEY,
);
- if (!utilsData) {
+ if (!utilsData || !utilsData.vodozemacWasmPath) {
return { id, error: 'Necessary data not found in IndexedDB' };
}
- const { olmWasmPath, staffCanSee } = (utilsData: WebNotifsServiceUtilsData);
+ const { vodozemacWasmPath, staffCanSee } =
+ (utilsData: WebNotifsServiceUtilsData);
let notifsAccountWithOlmData;
try {
@@ -404,7 +414,7 @@
encryptedOlmAccount,
accountEncryptionKey,
),
- olm.init({ locateFile: () => olmWasmPath }),
+ initVodozemac(vodozemacWasmPath),
]);
let decryptedNotification;
@@ -517,7 +527,7 @@
try {
[notifsAccountWithOlmData] = await Promise.all([
getNotifsAccountWithOlmData(senderDeviceDescriptor),
- initOlm(),
+ initVodozemac(getVodozemacWasmPath()),
]);
} catch (e) {
return {
@@ -720,11 +730,10 @@
if (notificationsOlmData) {
// Memory is freed below in this condition.
- const session = new olm.Session();
- session.unpickle(
- notificationsOlmData.picklingKey,
- notificationsOlmData.pendingSessionUpdate,
- );
+ const session = unpickleVodozemacSession({
+ picklingKey: notificationsOlmData.picklingKey,
+ pickledSession: notificationsOlmData.pendingSessionUpdate,
+ });
isSenderChainEmpty = session.is_sender_chain_empty();
hasReceivedMessage = session.has_received_message();
@@ -758,15 +767,13 @@
authMetadata,
);
- // Memory is freed below after pickling.
- const account = new olm.Account();
- const session = new olm.Session();
+ // Memory is freed in finally block.
+ let account;
+ let session;
+ let olmMessage;
try {
- account.unpickle(
- notificationAccount.picklingKey,
- notificationAccount.pickledAccount,
- );
+ account = unpickleVodozemacAccount(notificationAccount);
if (notifInboundKeys.error) {
throw new Error(notifInboundKeys.error);
@@ -777,20 +784,25 @@
'curve25519 must be present in notifs inbound keys',
);
- session.create_inbound_from(
- account,
+ olmMessage = new OlmMessage(messageType, encryptedPayload);
+ const inboundCreationResult = account.create_inbound_session(
notifInboundKeys.curve25519,
- encryptedPayload,
+ olmMessage,
);
- account.remove_one_time_keys(session);
+ const decryptedString = inboundCreationResult.plaintext;
+ // into_session() is consuming object.
+ // There is no need to call free() on inboundCreationResult
+ session = inboundCreationResult.into_session();
- const decryptedNotification: T = JSON.parse(
- session.decrypt(messageType, encryptedPayload),
- );
+ const decryptedNotification: T = JSON.parse(decryptedString);
- const pickledOlmSession = session.pickle(notificationAccount.picklingKey);
- const pickledAccount = account.pickle(notificationAccount.picklingKey);
+ const pickledOlmSession = session.pickle(
+ getVodozemacPickleKey(notificationAccount.picklingKey),
+ );
+ const pickledAccount = account.pickle(
+ getVodozemacPickleKey(notificationAccount.picklingKey),
+ );
// session reset attempt or session initialization - handled the same
const sessionResetAttempt =
@@ -831,8 +843,9 @@
// any session state
return { decryptedNotification };
} finally {
- session.free();
- account.free();
+ olmMessage?.free();
+ session?.free();
+ account?.free();
}
}
@@ -842,22 +855,32 @@
encryptedPayload: string,
type: OlmEncryptedMessageTypes,
): DecryptionResult<T> {
- // Memory is freed below after pickling.
- const session = new olm.Session();
- session.unpickle(picklingKey, pickledSession);
- const decryptedNotification: T = JSON.parse(
- session.decrypt(type, encryptedPayload),
- );
- const newPendingSessionUpdate = session.pickle(picklingKey);
- session.free();
+ // Memory is freed in finally block.
+ let session;
+ let olmMessage;
- const newUpdateCreationTimestamp = Date.now();
+ try {
+ session = unpickleVodozemacSession({ picklingKey, pickledSession });
- return {
- decryptedNotification,
- newUpdateCreationTimestamp,
- newPendingSessionUpdate,
- };
+ olmMessage = new OlmMessage(type, encryptedPayload);
+ const decryptedString = session.decrypt(olmMessage);
+
+ const decryptedNotification: T = JSON.parse(decryptedString);
+ const newPendingSessionUpdate = session.pickle(
+ getVodozemacPickleKey(picklingKey),
+ );
+
+ const newUpdateCreationTimestamp = Date.now();
+
+ return {
+ decryptedNotification,
+ newUpdateCreationTimestamp,
+ newPendingSessionUpdate,
+ };
+ } finally {
+ olmMessage?.free();
+ session?.free();
+ }
}
function decryptWithPendingSession<T>(
@@ -967,10 +990,22 @@
);
// Memory is freed below after pickling.
- const session = new olm.Session();
- session.unpickle(picklingKey, pendingSessionUpdate);
- const encryptedNotification = session.encrypt(payload);
- const newPendingSessionUpdate = session.pickle(picklingKey);
+
+ const session = unpickleVodozemacSession({
+ picklingKey,
+ pickledSession: pendingSessionUpdate,
+ });
+
+ const olmMessage = session.encrypt(payload);
+ const encryptedNotification = {
+ body: olmMessage.ciphertext,
+ type: olmMessage.message_type,
+ };
+ olmMessage.free();
+
+ const newPendingSessionUpdate = session.pickle(
+ getVodozemacPickleKey(picklingKey),
+ );
session.free();
const updatedOlmData: NotificationsOlmDataType = {
@@ -1037,10 +1072,9 @@
validatedNotifsAccountEncryptionKey,
);
- const { pickledAccount, picklingKey } = pickledOLMAccount;
+ const { picklingKey } = pickledOLMAccount;
- const notificationAccount = new olm.Account();
- notificationAccount.unpickle(picklingKey, pickledAccount);
+ const notificationAccount = unpickleVodozemacAccount(pickledOLMAccount);
return {
notificationAccount,
diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js
--- a/web/push-notif/push-notifs-handler.js
+++ b/web/push-notif/push-notifs-handler.js
@@ -32,7 +32,10 @@
import PushNotifModal from '../modals/push-notif-modal.react.js';
import { updateNavInfoActionType } from '../redux/action-types.js';
import { useSelector } from '../redux/redux-utils.js';
-import { getOlmWasmPath } from '../shared-worker/utils/constants.js';
+import {
+ getOlmWasmPath,
+ getVodozemacWasmPath,
+} from '../shared-worker/utils/constants.js';
import { useStaffCanSee } from '../utils/staff-utils.js';
function useCreateDesktopPushSubscription() {
@@ -162,6 +165,7 @@
workerRegistration.active?.postMessage({
olmWasmPath: getOlmWasmPath(),
+ vodozemacWasmPath: getVodozemacWasmPath(),
staffCanSee,
authMetadata,
});
diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js
--- a/web/push-notif/service-worker.js
+++ b/web/push-notif/service-worker.js
@@ -29,6 +29,7 @@
declare class CommAppMessage extends ExtendableEvent {
+data: {
+olmWasmPath?: string,
+ +vodozemacWasmPath?: string,
+staffCanSee?: boolean,
+authMetadata?: AuthMetadata,
};
@@ -71,14 +72,21 @@
localforage.config(localforageConfig);
event.waitUntil(
(async () => {
- const { olmWasmPath, staffCanSee, authMetadata } = event.data;
-
- if (!olmWasmPath || staffCanSee === undefined || !authMetadata) {
+ const { olmWasmPath, vodozemacWasmPath, staffCanSee, authMetadata } =
+ event.data;
+
+ if (
+ !olmWasmPath ||
+ !vodozemacWasmPath ||
+ staffCanSee === undefined ||
+ !authMetadata
+ ) {
return;
}
const webNotifsServiceUtils: WebNotifsServiceUtilsData = {
olmWasmPath: olmWasmPath,
+ vodozemacWasmPath: vodozemacWasmPath,
staffCanSee: staffCanSee,
};
diff --git a/web/redux/persist.js b/web/redux/persist.js
--- a/web/redux/persist.js
+++ b/web/redux/persist.js
@@ -86,7 +86,10 @@
import { legacyUnshimClientDB, unshimClientDB } from './unshim-utils.js';
import { authoritativeKeyserverID } from '../authoritative-keyserver.js';
import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js';
-import { getOlmWasmPath } from '../shared-worker/utils/constants.js';
+import {
+ getOlmWasmPath,
+ getVodozemacWasmPath,
+} from '../shared-worker/utils/constants.js';
import { isSQLiteSupported } from '../shared-worker/utils/db-utils.js';
import { workerRequestMessageTypes } from '../types/worker-types.js';
@@ -341,6 +344,7 @@
await sharedWorker.schedule({
type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT,
olmWasmPath: getOlmWasmPath(),
+ vodozemacWasmPath: getVodozemacWasmPath(),
initialCryptoStore: cryptoStore,
});
return rest;
diff --git a/web/shared-worker/utils/constants.js b/web/shared-worker/utils/constants.js
--- a/web/shared-worker/utils/constants.js
+++ b/web/shared-worker/utils/constants.js
@@ -15,6 +15,7 @@
export const DEFAULT_BACKUP_CLIENT_FILENAME = 'backup-client-wasm_bg.wasm';
export const DEFAULT_OLM_FILENAME = 'olm.wasm';
+export const DEFAULT_VODOZEMAC_FILENAME = 'vodozemac_bg.wasm';
export const DEFAULT_WEBWORKERS_OPAQUE_FILENAME = 'comm_opaque2_wasm_bg.wasm';
@@ -69,3 +70,13 @@
: DEFAULT_WEBWORKERS_OPAQUE_FILENAME;
return `${opaqueWasmDirPath}/${opaqueWasmFilename}`;
}
+
+declare var vodozemacFilename: string;
+export function getVodozemacWasmPath(): string {
+ const origin = window.location.origin;
+ const vodozemacWasmDirPath = `${origin}${baseURL}${WORKERS_MODULES_DIR_PATH}`;
+ const vodozemacWasmFilename = vodozemacFilename
+ ? vodozemacFilename
+ : DEFAULT_VODOZEMAC_FILENAME;
+ return `${vodozemacWasmDirPath}/${vodozemacWasmFilename}`;
+}
diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js
--- a/web/shared-worker/worker/worker-crypto.js
+++ b/web/shared-worker/worker/worker-crypto.js
@@ -1,9 +1,12 @@
// @flow
-import olm, {
- type Account as OlmAccount,
- type Utility as OlmUtility,
-} from '@commapp/olm';
+import initVodozemac, {
+ Account,
+ type Account as VodozemacAccount,
+ OlmMessage,
+ Session,
+ Utility,
+} from '@commapp/vodozemac';
import base64 from 'base-64';
import localforage from 'localforage';
import uuid from 'uuid';
@@ -11,16 +14,16 @@
import { initialEncryptedMessageContent } from 'lib/shared/crypto-utils.js';
import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
import {
- type OLMIdentityKeys,
- type PickledOLMAccount,
+ type ClientPublicKeys,
+ type EncryptedData,
type IdentityKeysBlob,
- type SignedIdentityKeysBlob,
+ type NotificationsOlmDataType,
type OlmAPI,
+ type OLMIdentityKeys,
type OneTimeKeysResultValues,
- type ClientPublicKeys,
- type NotificationsOlmDataType,
- type EncryptedData,
type OutboundSessionCreationResult,
+ type PickledOLMAccount,
+ type SignedIdentityKeysBlob,
} from 'lib/types/crypto-types.js';
import type { PlatformDetails } from 'lib/types/device-types.js';
import type { IdentityNewDeviceKeyUpload } from 'lib/types/identity-service-types.js';
@@ -28,52 +31,56 @@
import type { InboundP2PMessage } from 'lib/types/sqlite-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { entries } from 'lib/utils/objects.js';
-import { verifyMemoryUsage } from 'lib/utils/olm-memory-utils.js';
import { getOlmUtility } from 'lib/utils/olm-utility.js';
import {
- retrieveAccountKeysSet,
getAccountOneTimeKeys,
getAccountPrekeysSet,
+ OLM_ERROR_FLAG,
+ olmSessionErrors,
+ retrieveAccountKeysSet,
shouldForgetPrekey,
shouldRotatePrekey,
- olmSessionErrors,
- OLM_ERROR_FLAG,
} from 'lib/utils/olm-utils.js';
+import {
+ getVodozemacPickleKey,
+ unpickleVodozemacAccount,
+ unpickleVodozemacSession,
+} from 'lib/utils/vodozemac-utils.js';
import { getIdentityClient } from './identity-client.js';
import { getProcessingStoreOpsExceptionMessage } from './process-operations.js';
import {
getDBModule,
- getSQLiteQueryExecutor,
getPlatformDetails,
+ getSQLiteQueryExecutor,
} from './worker-database.js';
import {
+ encryptNotification,
+ getNotifsCryptoAccount_WITH_MANUAL_MEMORY_MANAGEMENT,
getOlmDataKeyForCookie,
- getOlmEncryptionKeyDBLabelForCookie,
getOlmDataKeyForDeviceID,
+ getOlmEncryptionKeyDBLabelForCookie,
getOlmEncryptionKeyDBLabelForDeviceID,
- encryptNotification,
type NotificationAccountWithPicklingKey,
- getNotifsCryptoAccount_WITH_MANUAL_MEMORY_MANAGEMENT,
persistNotifsAccountWithOlmData,
} from '../../push-notif/notif-crypto-utils.js';
import {
+ type LegacyCryptoStore,
type WorkerRequestMessage,
- type WorkerResponseMessage,
workerRequestMessageTypes,
+ type WorkerResponseMessage,
workerResponseMessageTypes,
- type LegacyCryptoStore,
} from '../../types/worker-types.js';
import type { OlmPersistSession } from '../types/sqlite-query-executor.js';
-type OlmSession = { +session: olm.Session, +version: number };
+type OlmSession = { +session: Session, +version: number };
type OlmSessions = {
[deviceID: string]: OlmSession,
};
type WorkerCryptoStore = {
+contentAccountPickleKey: string,
- +contentAccount: olm.Account,
+ +contentAccount: VodozemacAccount,
+contentSessions: OlmSessions,
};
@@ -113,14 +120,18 @@
const pickledContentAccount: PickledOLMAccount = {
picklingKey: contentAccountPickleKey,
- pickledAccount: contentAccount.pickle(contentAccountPickleKey),
+ pickledAccount: contentAccount.pickle(
+ getVodozemacPickleKey(contentAccountPickleKey),
+ ),
};
const pickledContentSessions: OlmPersistSession[] = entries(
contentSessions,
).map(([targetDeviceID, sessionData]) => ({
targetDeviceID,
- sessionData: sessionData.session.pickle(contentAccountPickleKey),
+ sessionData: sessionData.session.pickle(
+ getVodozemacPickleKey(contentAccountPickleKey),
+ ),
version: sessionData.version,
}));
@@ -143,7 +154,9 @@
accountEncryptionKey,
} = notifsCryptoAccount;
- const pickledAccount = notificationAccount.pickle(picklingKey);
+ const pickledAccount = notificationAccount.pickle(
+ getVodozemacPickleKey(picklingKey),
+ );
const accountWithPicklingKey: PickledOLMAccount = {
pickledAccount,
picklingKey,
@@ -189,31 +202,23 @@
const notificationsPrekey = notificationsInitializationInfo.prekey;
// Memory is freed below after persisting.
- const session = new olm.Session();
- if (notificationsInitializationInfo.oneTimeKey) {
- session.create_outbound(
- notificationAccount,
- notificationsIdentityKeys.curve25519,
- notificationsIdentityKeys.ed25519,
- notificationsPrekey,
- notificationsInitializationInfo.prekeySignature,
- notificationsInitializationInfo.oneTimeKey,
- );
- } else {
- session.create_outbound_without_otk(
- notificationAccount,
- notificationsIdentityKeys.curve25519,
- notificationsIdentityKeys.ed25519,
- notificationsPrekey,
- notificationsInitializationInfo.prekeySignature,
- );
- }
- const { body: message, type: messageType } = session.encrypt(
+ const session = notificationAccount.create_outbound_session(
+ notificationsIdentityKeys.curve25519,
+ notificationsIdentityKeys.ed25519,
+ notificationsInitializationInfo.oneTimeKey || '',
+ notificationsPrekey,
+ notificationsInitializationInfo.prekeySignature,
+ true, // olmCompatibilityMode
+ );
+
+ const encryptedMessage = session.encrypt(
JSON.stringify(initialEncryptedMessageContent),
);
+ const message = encryptedMessage.ciphertext;
+ const messageType = encryptedMessage.message_type;
const mainSession = session.pickle(
- notificationAccountWithPicklingKey.picklingKey,
+ getVodozemacPickleKey(notificationAccountWithPicklingKey.picklingKey),
);
const notificationsOlmData: NotificationsOlmDataType = {
mainSession,
@@ -222,7 +227,9 @@
picklingKey,
};
- const pickledAccount = notificationAccount.pickle(picklingKey);
+ const pickledAccount = notificationAccount.pickle(
+ getVodozemacPickleKey(picklingKey),
+ );
const accountWithPicklingKey: PickledOLMAccount = {
pickledAccount,
picklingKey,
@@ -246,7 +253,7 @@
async function getOrCreateOlmAccount(accountIDInDB: number): Promise<{
+picklingKey: string,
- +account: olm.Account,
+ +account: VodozemacAccount,
+synchronizationValue?: ?string,
}> {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
@@ -288,18 +295,18 @@
};
}
- // This `olm.Account` is created once and is cached for the entire
+ // This `Account` is created once and is cached for the entire
// program lifetime. Freeing is done as part of `clearCryptoStore`.
- const account = new olm.Account();
+ let account;
let picklingKey;
if (!accountDBString) {
picklingKey = uuid.v4();
- account.create();
+ account = new Account();
} else {
const dbAccount: PickledOLMAccount = JSON.parse(accountDBString);
picklingKey = dbAccount.picklingKey;
- account.unpickle(picklingKey, dbAccount.pickledAccount);
+ account = unpickleVodozemacAccount(dbAccount);
}
if (accountIDInDB === sqliteQueryExecutor.getNotifsAccountID()) {
@@ -328,10 +335,12 @@
const sessionsData: OlmSessions = {};
for (const persistedSession: OlmPersistSession of dbSessionsData) {
const { sessionData, version } = persistedSession;
- // This `olm.Session` is created once and is cached for the entire
+ // This `Session` is created once and is cached for the entire
// program lifetime. Freeing is done as part of `clearCryptoStore`.
- const session = new olm.Session();
- session.unpickle(picklingKey, sessionData);
+ const session = unpickleVodozemacSession({
+ picklingKey,
+ pickledSession: sessionData,
+ });
sessionsData[persistedSession.targetDeviceID] = {
session,
version,
@@ -341,19 +350,8 @@
return sessionsData;
}
-function unpickleInitialCryptoStoreAccount(
- account: PickledOLMAccount,
-): olm.Account {
- const { picklingKey, pickledAccount } = account;
- // This `olm.Account` is created once and is cached for the entire
- // program lifetime. Freeing is done as part of `clearCryptoStore`.
- const olmAccount = new olm.Account();
- olmAccount.unpickle(picklingKey, pickledAccount);
- return olmAccount;
-}
-
async function initializeCryptoAccount(
- olmWasmPath: string,
+ vodozemacWasmPath: string,
initialCryptoStore: ?LegacyCryptoStore,
) {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
@@ -361,20 +359,20 @@
throw new Error('Database not initialized');
}
- await olm.init({ locateFile: () => olmWasmPath });
+ await initVodozemac(vodozemacWasmPath);
if (initialCryptoStore) {
clearCryptoStore();
cryptoStore = {
contentAccountPickleKey: initialCryptoStore.primaryAccount.picklingKey,
- contentAccount: unpickleInitialCryptoStoreAccount(
+ contentAccount: unpickleVodozemacAccount(
initialCryptoStore.primaryAccount,
),
contentSessions: {},
};
const notifsCryptoAccount = {
picklingKey: initialCryptoStore.notificationAccount.picklingKey,
- notificationAccount: unpickleInitialCryptoStoreAccount(
+ notificationAccount: unpickleVodozemacAccount(
initialCryptoStore.notificationAccount,
),
synchronizationValue: uuid.v4(),
@@ -391,10 +389,9 @@
): Promise<?WorkerResponseMessage> {
if (message.type === workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT) {
await initializeCryptoAccount(
- message.olmWasmPath,
+ message.vodozemacWasmPath,
message.initialCryptoStore,
);
- verifyMemoryUsage('INITIALIZE_CRYPTO_ACCOUNT');
} else if (message.type === workerRequestMessageTypes.CALL_OLM_API_METHOD) {
const method: (...$ReadOnlyArray<mixed>) => mixed = (olmAPI[
message.method
@@ -402,7 +399,6 @@
// Flow doesn't allow us to bind the (stringified) method name with
// the argument types so we need to pass the args as mixed.
const result = await method(...message.args);
- verifyMemoryUsage(message.method);
return {
type: workerResponseMessageTypes.CALL_OLM_API_METHOD,
result,
@@ -421,10 +417,14 @@
await getNotifsCryptoAccount_WITH_MANUAL_MEMORY_MANAGEMENT();
const identityKeysBlob: IdentityKeysBlob = {
- notificationIdentityPublicKeys: JSON.parse(
- notificationAccount.identity_keys(),
- ),
- primaryIdentityPublicKeys: JSON.parse(contentAccount.identity_keys()),
+ notificationIdentityPublicKeys: {
+ ed25519: notificationAccount.ed25519_key,
+ curve25519: notificationAccount.curve25519_key,
+ },
+ primaryIdentityPublicKeys: {
+ ed25519: contentAccount.ed25519_key,
+ curve25519: contentAccount.curve25519_key,
+ },
};
const payloadToBeSigned: string = JSON.stringify(identityKeysBlob);
@@ -444,13 +444,14 @@
signature: string,
signingPublicKey: string,
) {
- const olmUtility: OlmUtility = getOlmUtility();
+ const olmUtility: Utility = getOlmUtility();
try {
olmUtility.ed25519_verify(signingPublicKey, message, signature);
return true;
} catch (err) {
- const isSignatureInvalid =
- getMessageForException(err)?.includes('BAD_MESSAGE_MAC');
+ const isSignatureInvalid = getMessageForException(err)?.includes(
+ 'The signature was invalid',
+ );
if (isSignatureInvalid) {
return false;
}
@@ -458,8 +459,8 @@
}
}
-function isPrekeySignatureValid(account: OlmAccount): boolean {
- const signingPublicKey = JSON.parse(account.identity_keys()).ed25519;
+function isPrekeySignatureValid(account: VodozemacAccount): boolean {
+ const signingPublicKey = account.ed25519_key;
const { prekey, prekeySignature } = getAccountPrekeysSet(account);
if (!prekey || !prekeySignature) {
return false;
@@ -586,10 +587,14 @@
);
const result = {
- primaryIdentityPublicKeys: JSON.parse(contentAccount.identity_keys()),
- notificationIdentityPublicKeys: JSON.parse(
- notificationAccount.identity_keys(),
- ),
+ primaryIdentityPublicKeys: {
+ ed25519: contentAccount.ed25519_key,
+ curve25519: contentAccount.curve25519_key,
+ },
+ notificationIdentityPublicKeys: {
+ ed25519: notificationAccount.ed25519_key,
+ curve25519: notificationAccount.curve25519_key,
+ },
blobPayload: payload,
signature,
};
@@ -605,15 +610,17 @@
if (!olmSession) {
throw new Error(olmSessionErrors.sessionDoesNotExist);
}
- const encryptedContent = olmSession.session.encrypt(content);
+ const olmMessage = olmSession.session.encrypt(content);
+ const encryptedData = {
+ message: olmMessage.ciphertext,
+ messageType: olmMessage.message_type,
+ sessionVersion: olmSession.version,
+ };
+ olmMessage.free();
await persistCryptoStore();
- return {
- message: encryptedContent.body,
- messageType: encryptedContent.type,
- sessionVersion: olmSession.version,
- };
+ return encryptedData;
},
async encryptAndPersist(
content: string,
@@ -627,8 +634,12 @@
if (!olmSession) {
throw new Error(olmSessionErrors.sessionDoesNotExist);
}
-
- const encryptedContent = olmSession.session.encrypt(content);
+ const olmMessage = olmSession.session.encrypt(content);
+ const encryptedContent = {
+ body: olmMessage.ciphertext,
+ type: olmMessage.message_type,
+ };
+ olmMessage.free();
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
@@ -690,14 +701,18 @@
throw new Error(olmSessionErrors.invalidSessionVersion);
}
+ const olmMessage = new OlmMessage(
+ encryptedData.messageType,
+ encryptedData.message,
+ );
+
let result;
try {
- result = olmSession.session.decrypt(
- encryptedData.messageType,
- encryptedData.message,
- );
+ result = olmSession.session.decrypt(olmMessage);
} catch (e) {
throw new Error(`error decrypt => ${OLM_ERROR_FLAG} ` + e.message);
+ } finally {
+ olmMessage.free();
}
await persistCryptoStore();
@@ -726,14 +741,18 @@
throw new Error(olmSessionErrors.invalidSessionVersion);
}
+ const olmMessage = new OlmMessage(
+ encryptedData.messageType,
+ encryptedData.message,
+ );
+
let result;
try {
- result = olmSession.session.decrypt(
- encryptedData.messageType,
- encryptedData.message,
- );
+ result = olmSession.session.decrypt(olmMessage);
} catch (e) {
throw new Error(`error decrypt => ${OLM_ERROR_FLAG} ` + e.message);
+ } finally {
+ olmMessage.free();
}
const sqliteQueryExecutor = getSQLiteQueryExecutor();
@@ -784,27 +803,30 @@
}
}
- // This `olm.Session` is created once and is cached for the entire
+ // This `Session` is created once and is cached for the entire
// program lifetime. Freeing is done as part of `clearCryptoStore`.
- const session = new olm.Session();
- session.create_inbound_from(
- contentAccount,
- contentIdentityKeys.curve25519,
+ const olmMessage = new OlmMessage(
+ initialEncryptedData.messageType,
initialEncryptedData.message,
);
- contentAccount.remove_one_time_keys(session);
- let initialEncryptedMessage;
+ let inboundCreationResult;
try {
- initialEncryptedMessage = session.decrypt(
- initialEncryptedData.messageType,
- initialEncryptedData.message,
+ inboundCreationResult = contentAccount.create_inbound_session(
+ contentIdentityKeys.curve25519,
+ olmMessage,
);
} catch (e) {
- session.free();
throw new Error(`error decrypt => ${OLM_ERROR_FLAG} ` + e.message);
+ } finally {
+ olmMessage.free();
}
+ // into_session() is consuming object.
+ // There is no need to call free() on inboundCreationResult
+ const initialEncryptedMessage = inboundCreationResult.plaintext;
+ const session = inboundCreationResult.into_session();
+
if (existingSession) {
existingSession.session.free();
}
@@ -826,30 +848,25 @@
const { contentAccount, contentSessions } = cryptoStore;
const existingSession = contentSessions[contentIdentityKeys.ed25519];
- // This `olm.Session` is created once and is cached for the entire
+ // This `Session` is created once and is cached for the entire
// program lifetime. Freeing is done as part of `clearCryptoStore`.
- const session = new olm.Session();
- if (contentInitializationInfo.oneTimeKey) {
- session.create_outbound(
- contentAccount,
- contentIdentityKeys.curve25519,
- contentIdentityKeys.ed25519,
- contentInitializationInfo.prekey,
- contentInitializationInfo.prekeySignature,
- contentInitializationInfo.oneTimeKey,
- );
- } else {
- session.create_outbound_without_otk(
- contentAccount,
- contentIdentityKeys.curve25519,
- contentIdentityKeys.ed25519,
- contentInitializationInfo.prekey,
- contentInitializationInfo.prekeySignature,
- );
- }
- const initialEncryptedData = session.encrypt(
+ const session = contentAccount.create_outbound_session(
+ contentIdentityKeys.curve25519,
+ contentIdentityKeys.ed25519,
+ contentInitializationInfo.oneTimeKey || '',
+ contentInitializationInfo.prekey,
+ contentInitializationInfo.prekeySignature,
+ true, // olmCompatibilityMode
+ );
+
+ const olmMessage = session.encrypt(
JSON.stringify(initialEncryptedMessageContent),
);
+ const initialEncryptedData = {
+ body: olmMessage.ciphertext,
+ type: olmMessage.message_type,
+ };
+ olmMessage.free();
const newSessionVersion = existingSession ? existingSession.version + 1 : 1;
if (existingSession) {
diff --git a/web/types/worker-types.js b/web/types/worker-types.js
--- a/web/types/worker-types.js
+++ b/web/types/worker-types.js
@@ -169,6 +169,7 @@
export type InitializeCryptoAccountRequestMessage = {
+type: 12,
+olmWasmPath: string,
+ +vodozemacWasmPath: string,
+initialCryptoStore?: LegacyCryptoStore,
};
diff --git a/web/webpack.config.cjs b/web/webpack.config.cjs
--- a/web/webpack.config.cjs
+++ b/web/webpack.config.cjs
@@ -134,6 +134,14 @@
},
],
}),
+ new CopyPlugin({
+ patterns: [
+ {
+ from: 'node_modules/@commapp/vodozemac/wasm/vodozemac_bg.wasm',
+ to: path.join(__dirname, 'dist', 'webworkers'),
+ },
+ ],
+ }),
new CopyPlugin({
patterns: [
{
@@ -181,6 +189,19 @@
},
],
}),
+ new CopyPlugin({
+ patterns: [
+ {
+ from: 'node_modules/@commapp/vodozemac/wasm/vodozemac_bg.wasm',
+ to: path.join(
+ __dirname,
+ 'dist',
+ 'webworkers',
+ 'vodozemac.[contenthash:12].wasm',
+ ),
+ },
+ ],
+ }),
new CopyPlugin({
patterns: [
{
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -1495,6 +1495,11 @@
resolved "https://registry.yarnpkg.com/@commapp/sqlcipher-amalgamation/-/sqlcipher-amalgamation-4.5.5-e.tgz#05245e7c1f07ed46cdc652ebeeb7e1a923fa748a"
integrity sha512-f+fuT/mbN/cB+Z4+QhTEmuRSbOLvaTdE3R2emcTK0P4gCPtwtgdZ3BGyxDLHkQPjlDaJ9G0Z6/+WqDJJLD0NnA==
+"@commapp/vodozemac@0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@commapp/vodozemac/-/vodozemac-0.1.0.tgz#8a3016bb4354915600d0915a979bfa04b1db5cd7"
+ integrity sha512-JEUeFrVMLYXACxKqOze+6aqITGt46QrsSGZ19+Nrpx3Wo85Gml7nUm47HF+Pkc3iNS1uiNYhK3pliq9N/SMomQ==
+
"@commapp/windowspush@file:desktop/addons/windows-pushnotifications":
version "0.0.1"
dependencies:

File Metadata

Mime Type
text/plain
Expires
Wed, Jan 14, 9:15 AM (58 m, 30 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5930795
Default Alt Text
D15547.1768382151.diff (102 KB)

Event Timeline