diff --git a/lib/commands/core.js b/lib/commands/core.js index 6c92af74f..afd5233e3 100644 --- a/lib/commands/core.js +++ b/lib/commands/core.js @@ -19,6 +19,10 @@ Core.isValidPublicKey = function (owner) { return typeof(owner) === 'string' && owner.length === 44; }; +Core.isValidKemPublicKey = function (kemPublic) { + return typeof(kemPublic) === 'string' && kemPublic.length === 800; +}; + var makeToken = Core.makeToken = function () { return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) .toString(16); diff --git a/lib/decrees.js b/lib/decrees.js index ba5f57a95..a3a503e72 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -264,14 +264,16 @@ commands.DISABLE_APPS = function (Env, args) { commands.SET_SUPPORT_KEYS = function (Env, args) { const curvePublic = args[0]; // Support mailbox key const edPublic = args[1]; // Support pin log + const kemPublic = args[2]; // Support KEM key let validated = typeof(curvePublic) === "string" && (Core.isValidPublicKey(curvePublic) || !curvePublic) && typeof(edPublic) === "string" && (Core.isValidPublicKey(edPublic) || !edPublic); if (!validated) { throw new Error('INVALID_ARGS'); } - if (Env.supportMailboxKey === curvePublic && Env.supportPinKey === edPublic) { return false; } + if (Env.supportMailboxKey === curvePublic && Env.supportPinKey === edPublic && Env.supportMailboxKemKey === kemPublic) { return false; } Env.supportMailboxKey = curvePublic; Env.supportPinKey = edPublic; + Env.supportMailboxKemKey = kemPublic; return true; }; diff --git a/lib/env.js b/lib/env.js index 737355ede..f9e71a220 100644 --- a/lib/env.js +++ b/lib/env.js @@ -156,6 +156,7 @@ module.exports.create = function (config) { adminEmail: config.adminEmail, supportMailbox: config.supportMailboxPublicKey, supportMailboxKey: undefined, + supportMailboxKemKey: undefined, metadata_cache: {}, channel_cache: {}, diff --git a/lib/http-worker.js b/lib/http-worker.js index ac435260a..1386b30dc 100644 --- a/lib/http-worker.js +++ b/lib/http-worker.js @@ -620,6 +620,7 @@ var serveConfig = makeRouteCache(function () { inactiveTime: Env.inactiveTime, supportMailbox: Env.supportMailbox, supportMailboxKey: Env.supportMailboxKey, + supportMailboxKemKey: Env.supportMailboxKemKey, defaultStorageLimit: Env.defaultStorageLimit, maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, diff --git a/lib/stats.js b/lib/stats.js index c2e148977..1f0526cc1 100644 --- a/lib/stats.js +++ b/lib/stats.js @@ -45,6 +45,7 @@ Stats.instanceData = function (Env) { // we expect that you enable your support mailbox data.supportMailbox = Boolean(Env.supportMailbox); data.supportMailboxKey = Boolean(Env.supportMailboxKey); + data.supportMailboxKemKey = Boolean(Env.supportMailboxKemKey); // do you allow registration? data.restrictRegistration = Boolean(Env.restrictRegistration); diff --git a/src/worker/components/migrate-user-object.js b/src/worker/components/migrate-user-object.js index a6d71cbe2..e627563ed 100644 --- a/src/worker/components/migrate-user-object.js +++ b/src/worker/components/migrate-user-object.js @@ -297,7 +297,7 @@ const factory = (Feedback, Hash, Util, * 3.b. No ==> post our mailbox data to the messenger channel */ network.join(friend.channel).then(function (wc) { - var keys = Crypto.Curve.deriveKeys(friend.curvePublic, userObject.curvePrivate); + var keys = Crypto.Curve.deriveKeys(friend.curvePublic, userObject.curvePrivate, friend.kemPublic, userObject.kemPublic); var encryptor = Crypto.Curve.createEncryptor(keys); channels[friend.channel] = { wc: wc, diff --git a/src/worker/modules/messenger.js b/src/worker/modules/messenger.js index f1dba7fb1..fe8b6bca2 100644 --- a/src/worker/modules/messenger.js +++ b/src/worker/modules/messenger.js @@ -721,7 +721,7 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging, } var proxy = ctx.store.proxy; - var keys = Curve.deriveKeys(friend.curvePublic, proxy.curvePrivate); + var keys = Curve.deriveKeys(friend.curvePublic, proxy.curvePrivate, friend.kemPublic, proxy.kemPublic); var data = { keys: keys, channel: friend.channel, diff --git a/src/worker/modules/support.js b/src/worker/modules/support.js index eb892a4db..8885c3024 100644 --- a/src/worker/modules/support.js +++ b/src/worker/modules/support.js @@ -39,13 +39,18 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, return void cb('EFORBIDDEN'); } + var supportKemKey = NewConfig.supportMailboxKemKey; + if (isAdmin) { return ctx.adminRdyEvt.reg(() => { cb(null, { supportKey: supportKey, + supportKemKey: supportKemKey, myCurve: data.adminCurvePrivate || Util.find(ctx.store.proxy, [ 'mailboxes', 'supportteam', 'keys', 'curvePrivate']), theirPublic: data.curvePublic, + myKem: ctx.store.proxy.kemPublic, + theirKem: data.kemPublic, notifKey: data.curvePublic }); }); @@ -53,8 +58,11 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, cb(null, { supportKey: supportKey, + supportKemKey: supportKemKey, myCurve: ctx.store.proxy.curvePrivate, theirPublic: data.curvePublic || supportKey, // old tickets may use deprecated key + myKem: ctx.store.proxy.kemPublic, + theirKem: data.kemPublic, // || supportKemKey, // Needed for PQC, currently making messages unreadable notifKey: supportKey }); }); @@ -63,7 +71,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, // Get the content of a ticket mailbox and close it var getContent = function (ctx, data, isAdmin, _cb) { var cb = Util.once(Util.mkAsync(_cb)); - var theirPublic, myCurve; + var theirPublic, myCurve, theirKem, myKem; nThen((waitFor) => { getKeys(ctx, isAdmin, data, waitFor((err, obj) => { if (err) { @@ -72,9 +80,11 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, } theirPublic = obj.theirPublic; myCurve = obj.myCurve; + theirKem = obj.theirKem; + myKem = obj.myKem; })); }).nThen(() => { - var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve); + var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve, theirKem, myKem); var crypto = Crypto.Curve.createEncryptor(keys); var cfg = { network: ctx.store.network, @@ -121,7 +131,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, var channel = data.channel; var title = data.title; var ticket = data.ticket; - var supportKey, theirPublic, myCurve; + var supportKey, theirPublic, myCurve, theirKem, myKem; var time = +new Date(); nThen((waitFor) => { // Send ticket to the admins and call back @@ -132,13 +142,15 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, } supportKey = obj.supportKey; theirPublic = obj.theirPublic; + theirKem = obj.theirKem; + myKem = obj.myKem; myCurve = obj.myCurve; // No need for notifKey here: users can only create tickets for the // currently used key })); }).nThen((waitFor) => { // Create ticket mailbox - var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve); + var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve, theirKem, myKem); var crypto = Crypto.Curve.createEncryptor(keys); var text = JSON.stringify(ticket); var ciphertext = crypto.encrypt(text); @@ -227,7 +239,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, if (!mailbox) { return void cb('E_NOT_READY'); } if (!anonRpc) { return void cb("anonymous rpc session not ready"); } if (!data?.ticket) { return void cb('E_NO_DATA'); } - var theirPublic, myCurve, notifKey; + var theirPublic, myCurve, notifKey, myKem, theirKem; var time; nThen((waitFor) => { // Get correct keys @@ -238,11 +250,13 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, } theirPublic = obj.theirPublic; myCurve = obj.myCurve; + theirKem = obj.theirKem; + myKem = obj.myKem; notifKey = obj.notifKey; })); }).nThen((waitFor) => { // Send message - var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve); + var keys = Crypto.Curve.deriveKeys(theirPublic, myCurve, theirKem, myKem); var crypto = Crypto.Curve.createEncryptor(keys); var text = JSON.stringify(data.ticket); var ciphertext = crypto.encrypt(text); @@ -417,7 +431,8 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, var t = Util.clone(ctx.supportData[ticket]); getContent(ctx, { channel: ticket, - curvePublic: t.curvePublic + curvePublic: t.curvePublic, + kemPublic: t.kemPublic }, false, waitFor((err, messages) => { if (err) { if (err.type === 'EDELETED') { @@ -829,6 +844,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, name: Util.find(first, ['sender', 'name']), notifications: Util.find(first, ['sender', 'notifications']), curvePublic: Util.find(first, ['sender', 'curvePublic']), + kemPublic: Util.find(first, ['sender', 'kemPublic']), channel: Hash.createChannelId(), title: first.title, time: last.time, @@ -995,7 +1011,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, // ADMIN COMMANDS - let updateServerKey = (ctx, curvePublic, curvePrivate, cb) => { + let updateServerKey = (ctx, curvePublic, curvePrivate, kemPublic, cb) => { let edPublic; try { let pair = Crypto.CryptoAgility.signKeyPairFromSeed(Util.decodeBase64(curvePrivate)); @@ -1005,7 +1021,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, } ctx.Store.adminRpc(null, { cmd: 'ADMIN_DECREE', - data: ['SET_SUPPORT_KEYS', [curvePublic, edPublic]] + data: ['SET_SUPPORT_KEYS', [curvePublic, edPublic, kemPublic]] }, cb); }; let getModerators = (ctx, data, cId, cb) => { @@ -1021,11 +1037,14 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, let edPublic = proxy.edPublic; const keyPair = Crypto.CryptoAgility.curveKeyPair(); + const kemPair = Crypto.PQC.ml_kem.ml_kem512.keygen(); const newKeyPub = Util.encodeBase64(keyPair.publicKey); const newKey = Util.encodeBase64(keyPair.secretKey); + const newKemPublic = Util.encodeBase64(kemPair.publicKey); const oldKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']); const oldKeyPub = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']); + const oldKemPublic = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'kemPublic']); if (!newKey || !newKeyPub) { return void cb({ error: 'INVALID_KEY' }); } let oldAdminChan; @@ -1091,7 +1110,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, }); }).nThen((waitFor) => { // Send new key to server - updateServerKey(ctx, newKeyPub, newKey, waitFor((obj) => { + updateServerKey(ctx, newKeyPub, newKey, newKemPublic, waitFor((obj) => { if (obj && obj.error) { waitFor.abort(); return void cb(obj); @@ -1122,7 +1141,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, waitFor.abort(); if (oldSupportKey) { // If we weren't able to store the new key, abort and restore old keys - return updateServerKey(ctx, oldKeyPub, oldKey, () => { + return updateServerKey(ctx, oldKeyPub, oldKey, oldKemPublic,() => { return void cb(obj); }); } @@ -1204,7 +1223,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt, }).nThen((waitFor) => { ctx.Store.adminRpc(null, { cmd: 'ADMIN_DECREE', - data: ['SET_SUPPORT_KEYS', ['', '']] + data: ['SET_SUPPORT_KEYS', ['', '', '']] }, waitFor(function (obj) { if (obj && obj.error) { waitFor.abort(); diff --git a/src/worker/modules/team.js b/src/worker/modules/team.js index 61ac70b37..6c5c6f3d1 100644 --- a/src/worker/modules/team.js +++ b/src/worker/modules/team.js @@ -1681,6 +1681,10 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager, edPrivate: ephemeralKeys.edPrivate, curvePublic: ephemeralKeys.curvePublic, curvePrivate: ephemeralKeys.curvePrivate, + kemPublic: ephemeralKeys.kemPublic, + kemPrivate: ephemeralKeys.kemPrivate, + dsaPublic: ephemeralKeys.dsaPublic, + dsaPrivate: ephemeralKeys.dsaPrivate, }, }; diff --git a/www/support/inner.js b/www/support/inner.js index 7ac5d1da8..b122ff9db 100644 --- a/www/support/inner.js +++ b/www/support/inner.js @@ -105,6 +105,7 @@ define([ APP.supportModule.execCommand('CLOSE_TICKET', { channel: channel, curvePublic: data.curvePublic, // Support curve public for this ticket + kemPublic: data.kemPublic, // Support kem public for this ticket ticket: APP.support.getDebuggingData({ close: true }) }, function (obj) { if (obj && obj.error) { return void UI.warn(Messages.error); } @@ -116,6 +117,7 @@ define([ APP.supportModule.execCommand('REPLY_TICKET', { channel: channel, curvePublic: data.curvePublic, // Support curve public for this ticket + kemPublic: data.kemPublic, // Support kem public for this ticket ticket: formData }, function (obj) { if (obj && obj.error) { return void UI.warn(Messages.error); }