Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33018459
D15547.1768382151.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
102 KB
Referenced Files
None
Subscribers
None
D15547.1768382151.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D15547: [lib][web][keyserver] add Vodozemac library and `.wasm` file handling
Attached
Detach File
Event Timeline
Log In to Comment