Merge pull request #2036 from Iulian-Tudor/pqc-hybrid

Crypto Agility + PQC prototype
This commit is contained in:
Fabrice Mouhartem 2025-10-28 12:00:23 +01:00 committed by GitHub
commit bc8ef4c411
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 872 additions and 223 deletions

1
.gitignore vendored
View File

@ -272,3 +272,4 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
/.idea/

View File

@ -4,6 +4,8 @@
const Block = module.exports;
const Nacl = require("tweetnacl/nacl-fast");
const ml_kem = require("@noble/post-quantum/ml-kem");
const ml_dsa = require("@noble/post-quantum/ml-dsa");
const nThen = require("nthen");
const Util = require("../common-util");
const BlockStore = require("../storage/block");
@ -36,7 +38,7 @@ Block.isValidBlockId = id => {
author of the block, since we assume that the block will have been
encrypted with xsalsa20-poly1305 which is authenticated.
*/
Block.validateLoginBlock = function (Env, publicKey, signature, block, _cb) {
Block.validateLoginBlock = function (Env, publicKey, signature, block, pqPublicKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
// convert the public key to a Uint8Array and validate it
@ -68,10 +70,87 @@ Block.validateLoginBlock = function (Env, publicKey, signature, block, _cb) {
// take its hash
var hash = Nacl.hash(u8_block);
// validate the signature against the hash of the content
var verified = Nacl.sign.detached.verify(hash, u8_signature, u8_public_key);
// Check signature type - first byte indicates the format
var sigType = u8_signature[0];
var verified = false;
// existing authentication ensures that users cannot replay old blocks
if (sigType === 1 && u8_signature.length > 65) {
// Hybrid signature - check the classical part first
const classicalSig = u8_signature.subarray(1, 1 + 64); // Ed25519 signature is 64 bytes
const classicalVerified = Nacl.sign.detached.verify(hash, classicalSig, u8_public_key);
if (!classicalVerified) {
Env.Log.error('BLOCK_CLASSICAL_VERIFICATION_FAILED', {
blockId: publicKey
});
return void cb('E_COULD_NOT_VERIFY_CLASSICAL');
}
// Now check PQ signature - this is required for hybrid signatures
let pqVerified = false;
if (!ml_kem || !ml_dsa) {
Env.Log.error('BLOCK_PQ_VERIFICATION_ERROR', {
error: 'PQ libraries not available',
blockId: publicKey
});
return void cb('E_PQ_LIBRARIES_MISSING');
}
try {
// Get PQ signature length from the 4 bytes after classical signature
const pqSigLenView = new DataView(u8_signature.buffer, u8_signature.byteOffset + 65);
const pqSigLen = pqSigLenView.getUint32(0, false); // big-endian
// Extract PQ signature
const pqSig = u8_signature.subarray(1 + 64 + 4, 1 + 64 + 4 + pqSigLen);
// If PQ public key is provided, verify the PQ signature
let publicKeyToUse = pqPublicKey;
if (!publicKeyToUse && Env.blockInfo && Env.blockInfo[publicKey] && Env.blockInfo[publicKey].pqPublicKey) {
publicKeyToUse = Env.blockInfo[publicKey].pqPublicKey;
}
if (!publicKeyToUse) {
Env.Log.error('BLOCK_PQ_VERIFICATION_ERROR', {
error: 'PQ public key not available',
blockId: publicKey
});
return void cb('E_MISSING_PQ_PUBLIC_KEY');
}
const pqPublicKeyDecoded = Util.decodeBase64(publicKeyToUse);
pqVerified = ml_dsa.ml_dsa44.internal.verify(
pqPublicKeyDecoded,
hash,
pqSig
);
Env.Log.info('BLOCK_PQ_VERIFICATION_RESULT', {
blockId: publicKey,
result: pqVerified
});
if (!pqVerified) {
return void cb('E_COULD_NOT_VERIFY_PQ');
}
} catch (err) {
Env.Log.error('BLOCK_PQ_VERIFICATION_ERROR', {
error: err.message,
blockId: publicKey
});
return void cb('E_PQ_VERIFICATION_ERROR');
}
// Both classical and PQ signatures verified
verified = classicalVerified && pqVerified;
} else {
// Classical signature or unrecognized format - use normal verification
// For type 0 we need to skip the first byte
const classicalSig = sigType === 0 ? u8_signature.subarray(1) : u8_signature;
verified = Nacl.sign.detached.verify(hash, classicalSig, u8_public_key);
}
// call back with (err) if unsuccessful
if (!verified) { return void cb("E_COULD_NOT_VERIFY"); }
@ -81,16 +160,106 @@ Block.validateLoginBlock = function (Env, publicKey, signature, block, _cb) {
Block.validateAncestorProof = function (Env, proof, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
/* prove that you own an existing block by signing for its publicKey */
/* prove that you own an existing block by signing for its publicKey */
try {
var parsed = JSON.parse(proof);
var pub = parsed[0];
var u8_pub = Util.decodeBase64(pub);
var sig = parsed[1];
var u8_sig = Util.decodeBase64(sig);
// Extract PQ public key from the proof if it exists (3rd element)
var ancestorPqPublicKey = parsed.length > 2 ? parsed[2] : undefined;
var valid = false;
nThen(function (w) {
valid = Nacl.sign.detached.verify(u8_pub, u8_sig, u8_pub);
// Check signature type - first byte indicates the format
var sigType = u8_sig[0];
if (sigType === 1 && u8_sig.length > 65) {
// Hybrid signature - check the classical part first
const classicalSig = u8_sig.subarray(1, 1 + 64); // Ed25519 signature is 64 bytes
const classicalVerified = Nacl.sign.detached.verify(u8_pub, classicalSig, u8_pub);
if (!classicalVerified) {
Env.Log.error('ANCESTOR_CLASSICAL_VERIFICATION_FAILED', {
blockId: pub
});
w.abort();
return void cb('E_INVALID_ANCESTOR_PROOF_CLASSICAL');
}
// Now check PQ signature - this is required for hybrid signatures
let pqVerified = false;
if (!ml_kem || !ml_dsa) {
Env.Log.error('ANCESTOR_PQ_VERIFICATION_ERROR', {
error: 'PQ libraries not available',
blockId: pub
});
w.abort();
return void cb('E_PQ_LIBRARIES_MISSING');
}
try {
// Get PQ signature length from the 4 bytes after classical signature
const pqSigLenView = new DataView(u8_sig.buffer, u8_sig.byteOffset + 65);
const pqSigLen = pqSigLenView.getUint32(0, false); // big-endian
// Extract PQ signature
const pqSig = u8_sig.subarray(1 + 64 + 4, 1 + 64 + 4 + pqSigLen);
// If PQ public key is provided, verify the PQ signature
let pqPublicKey;
if (Env.blockInfo && Env.blockInfo[pub] && Env.blockInfo[pub].pqPublicKey) {
pqPublicKey = Env.blockInfo[pub].pqPublicKey;
} else if (ancestorPqPublicKey) {
pqPublicKey = ancestorPqPublicKey;
}
if (!pqPublicKey) {
Env.Log.error('ANCESTOR_PQ_VERIFICATION_ERROR', {
error: 'PQ public key not available',
blockId: pub
});
w.abort();
return void cb('E_MISSING_PQ_PUBLIC_KEY');
}
const pqPublicKeyDecoded = Util.decodeBase64(pqPublicKey);
pqVerified = ml_dsa.ml_dsa44.internal.verify(
pqPublicKeyDecoded,
u8_pub,
pqSig
);
Env.Log.info('ANCESTOR_PQ_VERIFICATION_RESULT', {
blockId: pub,
result: pqVerified
});
if (!pqVerified) {
w.abort();
return void cb('E_INVALID_ANCESTOR_PROOF_PQ');
}
} catch (err) {
Env.Log.error('ANCESTOR_PQ_VERIFICATION_ERROR', {
error: err.message,
blockId: pub
});
w.abort();
return void cb('E_PQ_VERIFICATION_ERROR');
}
// Both classical and PQ signatures verified
valid = classicalVerified && pqVerified;
} else {
// Classical signature or unrecognized format - use normal verification
// For type 0 we need to skip the first byte
const classicalSig = sigType === 0 ? u8_sig.subarray(1) : u8_sig;
valid = Nacl.sign.detached.verify(u8_pub, classicalSig, u8_pub);
}
if (!valid) {
w.abort();
return void cb('E_INVALID_ANCESTOR_PROOF');
@ -109,7 +278,7 @@ Block.validateAncestorProof = function (Env, proof, _cb) {
Block.writeLoginBlock = function (Env, msg, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
const { publicKey, signature, ciphertext, registrationProof, userData, inviteToken, isSSO } = msg;
const { publicKey, signature, ciphertext, registrationProof, userData, inviteToken, isSSO, pqPublicKey } = msg;
var previousKey;
var validatedBlock, path;
@ -144,7 +313,7 @@ Block.writeLoginBlock = function (Env, msg, _cb) {
previousKey = provenKey;
}));
}).nThen(function (w) {
Block.validateLoginBlock(Env, publicKey, signature, ciphertext, w(function (e, _validatedBlock) {
Block.validateLoginBlock(Env, publicKey, signature, ciphertext, pqPublicKey, w(function (e, _validatedBlock) {
if (e) {
w.abort();
return void cb(e);
@ -170,6 +339,20 @@ Block.writeLoginBlock = function (Env, msg, _cb) {
previousKey: previousKey,
path: path,
});
// Store PQ public key in blockInfo if provided
if (!err && pqPublicKey && typeof pqPublicKey === 'string') {
// Initialize blockInfo if it doesn't exist
if (!Env.blockInfo) { Env.blockInfo = {}; }
if (!Env.blockInfo[publicKey]) { Env.blockInfo[publicKey] = {}; }
// Store PQ public key for later verification
Env.blockInfo[publicKey].pqPublicKey = pqPublicKey;
Env.Log.info('BLOCK_PQ_KEY_STORED', {
blockId: publicKey
});
}
cb(err);
if (!err && registrationProof) {
Users.checkUpdate(Env, userData, publicKey, (err) => {
@ -255,4 +438,3 @@ Block.removeLoginBlock = function (Env, publicKey, reason, edPublic, _cb) {
SSOUtils.deleteBlock(Env, publicKey, () => {});
}
};

View File

@ -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);

View File

@ -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;
};

View File

@ -98,7 +98,7 @@ module.exports.create = function (config) {
protocol: new URL(httpUnsafeOrigin).protocol,
fileHost: config.fileHost || undefined,
fileHost: config.fileHost? new URL(config.fileHost).origin : undefined,
NO_SANDBOX: NO_SANDBOX,
httpSafePort: httpSafePort,
websocketPort: config.websocketPort,
@ -156,6 +156,7 @@ module.exports.create = function (config) {
adminEmail: config.adminEmail,
supportMailbox: config.supportMailboxPublicKey,
supportMailboxKey: undefined,
supportMailboxKemKey: undefined,
metadata_cache: {},
channel_cache: {},

View File

@ -107,6 +107,10 @@ HK.authenticateNetfluxSession = function (Env, netfluxId, unsafeKey) {
var user = Env.netfluxUsers[netfluxId] = Env.netfluxUsers[netfluxId] || {};
user[unsafeKey] = +new Date();
};
HK.unauthenticateNetfluxSession = function (Env, netfluxId, unsafeKey) {
var user = Env.netfluxUsers[netfluxId] = Env.netfluxUsers[netfluxId] || {};
delete user[unsafeKey];
};
HK.closeNetfluxSession = function (Env, netfluxId) {
delete Env.netfluxUsers[netfluxId];
@ -1032,7 +1036,7 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct, cb) {
}));
}).nThen(function (w) {
// if there's no validateKey present skip to the next block
if (!(metadata && metadata.validateKey)) { return; }
if (!(metadata && metadata.validateKey && metadata.dsaPublic)) { return; }
// trim the checkpoint indicator off the message if it's present
let signedMsg = (isCp) ? msgStruct[4].replace(CHECKPOINT_PATTERN, '') : msgStruct[4];
@ -1045,7 +1049,7 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct, cb) {
*/
var proceed = w();
Env.queueValidation(channel.id, function (next) {
Env.validateMessage(signedMsg, metadata.validateKey, function (err) {
Env.validateMessage(signedMsg, metadata.validateKey, metadata.dsaPublic, function (err) {
// always go on to the next item in the queue regardless of the outcome
next();
@ -1056,7 +1060,8 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct, cb) {
// we log this case, but not others for some reason
Log.info("HK_SIGNED_MESSAGE_REJECTED", {
channel: channel.id,
validateKey: metadata.validayKey,
validateKey: metadata.validateKey,
dsaPublic: metadata.dsaPublic,
message: signedMsg,
});
}

View File

@ -623,6 +623,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,

View File

@ -72,6 +72,7 @@ const AUTHENTICATED_USER_SCOPED = {
REMOVE_PINS: Pinning.removePins,
TRIM_PINS: Pinning.trimPins,
COOKIE: Core.haveACookie,
DESTROY: () => {}
};
var isAuthenticatedCall = function (call) {
@ -190,6 +191,11 @@ var rpc = function (Env, Server, userId, data, respond) {
if (command === 'COOKIE' && !hadSession && Env.logIP) {
Env.Log.info('NEW_RPC_SESSION', {userId: userId, publicKey: publicKey});
}
if (command === "DESTROY") {
HK.unauthenticateNetfluxSession(Env, userId, publicKey);
return; // No need to respond, user will close the session
}
HK.authenticateNetfluxSession(Env, userId, publicKey);
return void handleAuthenticatedMessage(Env, publicKey, msg, respond, Server);
});

View File

@ -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);

View File

@ -18,6 +18,7 @@ const Nacl = require('tweetnacl/nacl-fast');
const Eviction = require("../eviction");
const CPCrypto = require('../crypto');
const plugins = require("../plugin-manager");
const ml_dsa = require('@noble/post-quantum/ml-dsa');
const Env = {
Log: {},
@ -783,8 +784,52 @@ COMMANDS.INLINE = function (data, cb) {
} catch (e) {
return void cb("E_BADKEY");
}
// validate the message
//const validated = Nacl.sign.open(signedMsg, validateKey);
// Hybrid signature detection: [classical sig (64 bytes)] + [PQC sig length (4 bytes)] + [PQC sig] + [message]
if (signedMsg.length > 64 + 4) {
try {
// Parse hybrid signature
const classicalSig = signedMsg.slice(0, 64);
const pqSigLen = new DataView(signedMsg.buffer, signedMsg.byteOffset + 64, 4).getUint32(0, false);
const pqSig = signedMsg.slice(64 + 4, 64 + 4 + pqSigLen);
const message = signedMsg.slice(64 + 4 + pqSigLen);
// Classical verification
const classicalVerified = Nacl.sign.detached.verify(message, classicalSig, validateKey);
// PQC verification: get PQC public key from metadata if available
let pqcVerified = false;
let pqcPublicKey;
if (data.dsaPublic) {
try {
pqcPublicKey = Util.decodeBase64(data.dsaPublic);
} catch (e) {
return void cb("E_BAD_PQ_KEY");
}
} else {
// If not provided, fail (could be extended to fetch from metadata)
return void cb("E_NO_PQ_KEY");
}
try {
pqcVerified = ml_dsa.ml_dsa44.internal.verify(
pqcPublicKey,
message,
pqSig
);
} catch (e) {
pqcVerified = false;
}
if (!classicalVerified || !pqcVerified) {
return void cb("FAILED");
}
return cb();
} catch (e) {
return void cb("E_HYBRID_SIG_PARSE");
}
}
// Fallback: classical only
const validated = Env.crypto.open(signedMsg, validateKey);
if (!validated) {
return void cb("FAILED");

View File

@ -555,12 +555,14 @@ Workers.initialize = function (Env, config, _cb) {
};
// Synchronous crypto functions
Env.validateMessage = function (signedMsg, key, cb) {
sendCommand({
msg: signedMsg,
key: key,
Env.validateMessage = function (opts, cb) {
const cmd = {
msg: opts.msg,
key: opts.key,
command: 'INLINE',
}, cb);
};
if (opts.dsaPublic) cmd.dsaPublic = opts.dsaPublic;
sendCommand(cmd, cb);
};
Env.checkSignature = function (signedMsg, signature, publicKey, cb) {

25
package-lock.json generated
View File

@ -10,6 +10,7 @@
"license": "AGPL-3.0+",
"dependencies": {
"@mcrowe/minibloom": "^0.2.0",
"@noble/post-quantum": "^0.4.1",
"@node-saml/node-saml": "^4.0.5",
"alertify.js": "1.0.11",
"body-parser": "^1.20.2",
@ -517,6 +518,30 @@
"dev": true,
"license": "CC0-1.0"
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/post-quantum": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.4.1.tgz",
"integrity": "sha512-TRXjvnY9jAFNWbxOx+pKt21BNsCEWKFjMbIKwdx9CQXBudDanpY20EfOcooV7DIsRS/+Mf8D8utpUPjfGrQ8fA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.8.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@node-saml/node-saml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz",

View File

@ -13,6 +13,7 @@
},
"dependencies": {
"@mcrowe/minibloom": "^0.2.0",
"@noble/post-quantum": "^0.4.1",
"@node-saml/node-saml": "^4.0.5",
"alertify.js": "1.0.11",
"body-parser": "^1.20.2",
@ -89,7 +90,7 @@
"jquery": "3.6.0"
},
"scripts": {
"install:components": "node scripts/copy-components.js",
"install:components": "node scripts/copy-components.js && node scripts/pqc-browser-convert.js",
"start": "node server.js",
"dev": "DEV=1 node server.js",
"windev": "set DEV=1& node server.js",

View File

@ -4,7 +4,7 @@
const Fs = require('fs');
const nThen = require('nthen');
const Nacl = require('tweetnacl/nacl-fast');
const Crypto = require('chainpad-crypto/crypto');
const Path = require('path');
const Pins = require('../lib/pins');
const Util = require('../lib/common-util');
@ -28,7 +28,7 @@ if (dataIdx === -1) {
const ed = Util.decodeBase64(deleteData.toSign.edPublic);
const signed = Util.decodeUTF8(JSON.stringify(deleteData.toSign));
const proof = Util.decodeBase64(deleteData.proof);
if (!Nacl.sign.detached.verify(signed, proof, ed)) { return void console.error("Invalid signature"); }
if (!Crypto.CryptoAgility.verifyDetached(signed, proof, ed)) { return void console.error("Invalid signature"); }
edPublic = escapeKeyCharacters(deleteData.toSign.edPublic);
}

View File

@ -47,7 +47,8 @@ Fse.rmSync(oldComponentsPath, { recursive: true, force: true });
"netflux-websocket",
"drawio",
"pako",
"x2js"
"x2js",
"@noble"
].forEach(l => {
let s = l;
if (s === 'tweetnacl') {

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
const Nacl = require('tweetnacl/nacl-fast');
const Crypto = require('chainpad-crypto/crypto');
const Util = require('../lib/common-util');
const keyPair = Nacl.box.keyPair();
const keyPair = Crypto.CryptoAgility.curveKeyPair();
console.log("You've just generated a new key pair for your support mailbox.");
console.log("The public key should first be added to your config.js file ('supportMailboxPublicKey'), then save and restart the server.");

View File

@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
const { execSync } = require('child_process');
const fs = require('fs/promises');
const path = require('path');
// This content will be used for a temporary entry file for the bundle.
// It re-exports the necessary modules from the @noble/post-quantum package.
const entryPointContent = `
export * as ml_kem from '@noble/post-quantum/ml-kem';
export * as ml_dsa from '@noble/post-quantum/ml-dsa';
export * as utils from '@noble/post-quantum/utils';
`;
async function main() {
try {
console.log('Temporarily installing esbuild...');
execSync('npm install esbuild', { stdio: 'inherit' });
const esbuild = require('esbuild');
const outputDir = path.join('www', 'components', '@noble', 'post-quantum');
const entryPointPath = path.join(outputDir, '_pqc_entry.js');
const outfile = path.resolve(outputDir, 'index.js');
try {
await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(entryPointPath, entryPointContent.trim());
console.log('Bundling for browser with esbuild...');
await esbuild.build({
entryPoints: [entryPointPath],
bundle: true,
outfile,
format: 'iife',
globalName: 'PostQuantum',
platform: 'browser',
minify: false,
sourcemap: true,
});
const amdLoader = `\nif (typeof define === 'function' && define.amd) { define([], function() { return window.PostQuantum; }); }`;
await fs.appendFile(outfile, amdLoader);
console.log(`Bundle created successfully: ${outfile}`);
} finally {
// Clean up the temporary entry file
await fs.unlink(entryPointPath).catch(err => {
if (err.code !== 'ENOENT') {
console.error('Failed to remove temporary entry file:', err);
}
});
}
} catch (e) {
console.error('An error occurred during the process:');
console.error(e);
process.exit(1);
} finally {
console.log('Removing temporary esbuild dependency...');
try {
execSync('npm uninstall esbuild', { stdio: 'inherit' });
} catch (uninstallErr) {
console.error('Failed to uninstall esbuild. You may need to remove it manually.', uninstallErr);
}
}
}
console.log('Starting PQ crypto browser conversion...');
main().then(() => {
console.log('PQ crypto browser conversion complete!');
}).catch(() => {
process.exit(1);
});

View File

@ -3,17 +3,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
let SodiumNative = require('sodium-native');
let Nacl = require('tweetnacl/nacl-fast');
let Crypto = require('chainpad-crypto/crypto');
let LibSodium = require('libsodium-wrappers');
let Util = require('../lib/common-util');
let msgStr = "This is a test";
let keys = Nacl.sign.keyPair();
let keys = Crypto.CryptoAgility.signKeyPair();
let pub = keys.publicKey;
let msg = Util.decodeUTF8(msgStr);
let signedMsg = Nacl.sign(msg, keys.secretKey);
let signedMsg = Crypto.CryptoAgility.sign(msg, keys.secretKey);
let sig = signedMsg.subarray(0, 64);
LibSodium.ready.then(() => {
@ -57,8 +57,8 @@ console.log(LibSodium.crypto_sign_verify_detached(sig, msg, pub));
console.log('start tweetnacl');
a = +new Date();
for (let i = 0; i < n; i++) {
Nacl.sign.open(signedMsg, pub);
Nacl.sign.detached.verify(msg, sig, pub);
Crypto.CryptoAgility.signOpen(signedMsg, pub);
Crypto.CryptoAgility.verifyDetached(msg, sig, pub);
}
console.log('end tweetnacl ', (+new Date() - a), ' ms');

View File

@ -3,13 +3,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
var Client = require("../../lib/client");
var Nacl = require("tweetnacl/nacl-fast");
var nThen = require("nthen");
var CPNetflux = require("../../www/components/chainpad-netflux/chainpad-netflux");
var Hash = require("../../www/common/common-hash");
var Util = require("../../lib/common-util");
var Rpc = require("../../www/common/rpc");
var HK = require("../../lib/hk-util");
var Crypto = require("../../www/components/chainpad-crypto");
var identity = function (x) {
@ -70,7 +70,7 @@ nThen(function (w) {
//console.log(i);
if (i-- <= 0) { return void done(); }
var ciphertext = Util.encodeBase64(Nacl.randomBytes(256));
var ciphertext = Util.encodeBase64(Crypto.CryptoAgility.bytes(256));
client.anonRpc.send('WRITE_PRIVATE_MESSAGE', [
client.channel,

View File

@ -5,7 +5,6 @@
var Client = require("../../lib/client/");
var Crypto = require("../../www/components/chainpad-crypto");
var Mailbox = Crypto.Mailbox;
var Nacl = require("tweetnacl/nacl-fast");
var nThen = require("nthen");
var Pinpad = require("../../www/common/pinpad");
var Rpc = require("../../www/common/rpc");
@ -41,7 +40,7 @@ process.on('unhandledRejection', function (err) {
});
var makeCurveKeys = function () {
var pair = Nacl.box.keyPair();
var pair = Crypto.CryptoAgility.curveKeyPair();
return {
curvePrivate: Util.encodeBase64(pair.secretKey),
curvePublic: Util.encodeBase64(pair.publicKey),
@ -49,7 +48,7 @@ var makeCurveKeys = function () {
};
var makeEdKeys = function () {
var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength));
var keys = Crypto.CryptoAgility.signKeyPairFromSeed(Crypto.CryptoAgility.bytes(Crypto.CryptoAgility.signSeedLength()));
return {
edPrivate: Util.encodeBase64(keys.secretKey),
edPublic: Util.encodeBase64(keys.publicKey),

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(function (window) {
var factory = function (Util, Crypto, Keys, Nacl) {
var factory = function (Util, Crypto, Keys) {
var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex;
@ -15,33 +15,52 @@ var factory = function (Util, Crypto, Keys, Nacl) {
// This implementation must match that on the server
// it's used for a checksum
Hash.hashChannelList = function (list) {
return Util.encodeBase64(Nacl.hash(Util
return Util.encodeBase64(Crypto.CryptoAgility.createHash(Util
.decodeUTF8(JSON.stringify(list))));
};
Hash.generateSignPair = function () {
var ed = Nacl.sign.keyPair();
// Generate classical Ed25519 keypair
var ed = Crypto.CryptoAgility.signKeyPair();
var makeSafe = function (key) {
return Crypto.b64RemoveSlashes(key).replace(/=+$/g, '');
};
return {
var result = {
validateKey: Hash.encodeBase64(ed.publicKey),
signKey: Hash.encodeBase64(ed.secretKey),
safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)),
safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)),
};
// Add post-quantum DSA keypair if available
if (Crypto.PQC && Crypto.PQC.ml_dsa && Crypto.PQC.ml_dsa.ml_dsa44) {
try {
// Derive a deterministic seed from the Ed25519 secret key
var dsaSeed = Crypto.Nacl.hash(ed.secretKey).subarray(0, 32);
var dsaPair = Crypto.CryptoAgility.generateDsaKeypair(dsaSeed);
result.dsaPublic = Hash.encodeBase64(dsaPair.publicKey);
result.dsaPrivate = Hash.encodeBase64(dsaPair.secretKey);
result.safeDsaPublic = makeSafe(Hash.encodeBase64(dsaPair.publicKey));
result.safeDsaPrivate = makeSafe(Hash.encodeBase64(dsaPair.secretKey));
} catch (err) {
console.error("Failed to generate post-quantum DSA keys:", err);
}
}
return result;
};
Hash.getSignPublicFromPrivate = function (edPrivateSafeStr) {
var edPrivateStr = Crypto.b64AddSlashes(edPrivateSafeStr);
var privateKey = Util.decodeBase64(edPrivateStr);
var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey);
var keyPair = Crypto.CryptoAgility.signKeyPairFromSecretKey(privateKey);
return Util.encodeBase64(keyPair.publicKey);
};
Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) {
var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr);
var privateKey = Util.decodeBase64(curvePrivateStr);
var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey);
var keyPair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(privateKey);
return Util.encodeBase64(keyPair.publicKey);
};
@ -117,7 +136,7 @@ var factory = function (Util, Crypto, Keys, Nacl) {
Hash.ephemeralChannelLength = 34;
Hash.createChannelId = function (ephemeral) {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(ephemeral? 17: 16));
var id = uint8ArrayToHex(Crypto.CryptoAgility.bytes(ephemeral? 17: 16));
if ([32, 34].indexOf(id.length) === -1 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
@ -138,7 +157,7 @@ var factory = function (Util, Crypto, Keys, Nacl) {
Hash.getBoxPublicFromSecret = function (priv) {
if (!priv) { return; }
var u8_priv = Hash.decodeBase64(priv);
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
var pair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(u8_priv);
return Hash.encodeBase64(pair.publicKey);
};
@ -148,7 +167,7 @@ var factory = function (Util, Crypto, Keys, Nacl) {
Hash.checkBoxKeyPair = function (priv, pub) {
if (!pub || !priv) { return false; }
var u8_priv = Hash.decodeBase64(priv);
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
var pair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(u8_priv);
return pub === Hash.encodeBase64(pair.publicKey);
};
@ -653,7 +672,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
var keys = secret && secret.keys;
var secondary = keys && keys.secondaryKey;
if (!secondary) { return; }
var curvePair = Nacl.box.keyPair.fromSecretKey(Util.decodeUTF8(secondary).slice(0,32));
var curvePair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(Util.decodeUTF8(secondary).slice(0,32));
var ret = {};
ret.form_public = Util.encodeBase64(curvePair.publicKey);
var privateKey = ret.form_private = Util.encodeBase64(curvePair.secretKey);

View File

@ -3,21 +3,18 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(function (window) {
const factory = (NaclUtil) => {
const factory = (Crypto) => {
var Util = window.CryptPad_Util = {};
// polyfill for atob in case you're using this from node...
window.atob = window.atob || function (str) { return Buffer.from(str, 'base64').toString('binary'); };
window.btoa = window.btoa || function (str) { return Buffer.from(str, 'binary').toString('base64'); };
Util.encodeBase64 = NaclUtil.encodeBase64;
Util.decodeBase64 = str => {
let i = str.length % 4;
if (i) { str += '='.repeat(4-i); }
return NaclUtil.decodeBase64(str);
};
Util.encodeUTF8 = NaclUtil.encodeUTF8;
Util.decodeUTF8 = NaclUtil.decodeUTF8;
Util.encodeBase64 = Crypto.CryptoAgility.encodeBase64;
Util.decodeBase64 = Crypto.CryptoAgility.decodeBase64;
Util.encodeUTF8 = Crypto.CryptoAgility.encodeUTF8;
Util.decodeUTF8 = Crypto.CryptoAgility.decodeUTF8;
Util.slice = function (A, start, end) {
return Array.prototype.slice.call(A, start, end);
@ -871,10 +868,10 @@ const factory = (NaclUtil) => {
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require('tweetnacl-util'));
module.exports = factory(require('chainpad-crypto/crypto'));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/components/tweetnacl-util/nacl-util.min.js'], function () {
return factory(globalThis?.nacl?.util);
define(['/components/chainpad-crypto/crypto.js'], function (Crypto) {
return factory(Crypto);
});
} else {
// Unsupported initialization

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
const factory = (nThen, Util, ApiConfig = {}, Nacl, Crypto) => {
const getApiOrigin = function () {
if (!Object.keys(ApiConfig).length) { return; }
@ -26,7 +26,7 @@ const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
};
var clone = o => JSON.parse(JSON.stringify(o));
var randomToken = () => Util.encodeBase64(Nacl.randomBytes(24));
var randomToken = () => Util.encodeBase64(Crypto.CryptoAgility.bytes(24));
var postData = function (url, data, cb) {
var CB = Util.once(Util.mkAsync(cb));
fetch(url, {
@ -83,7 +83,7 @@ const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
copy.txid = txid;
copy.date = date;
var toSign = Util.decodeUTF8(JSON.stringify(copy));
var sig = Nacl.sign.detached(toSign, keypair.secretKey);
var sig = Crypto.CryptoAgility.signDetached(toSign, keypair.secretKey);
var encoded = Util.encodeBase64(sig);
var obj2 = {
sig: encoded,
@ -112,7 +112,8 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('nthen'),
require('../common-util'),
undefined,
require('tweetnacl/nacl-fast')
require('tweetnacl/nacl-fast'),
require('chainpad-crypto/crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
@ -120,8 +121,9 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/common-util.js',
'/api/config',
'/components/tweetnacl/nacl-fast.min.js',
], (nThen, Util, ApiConfig) => {
return factory(nThen, Util, ApiConfig, window.nacl);
'/components/chainpad-crypto/crypto.js',
], (nThen, Util, ApiConfig, Nacl, Crypto) => {
return factory(nThen, Util, ApiConfig, window.nacl, Crypto);
});
} else {
// unsupported initialization

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
const factory = (Util, ApiConfig = {}, ServerCommand, Nacl, Crypto) => {
var Block = {};
@ -23,7 +23,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
// [b64_public, b64_sig, b64_block [version, nonce, content]]
Block.seed = function () {
return Nacl.hash(Util.decodeUTF8('pewpewpew'));
return Crypto.CryptoAgility.createHash(Util.decodeUTF8('pewpewpew'));
};
// should be deterministic from a seed...
@ -35,23 +35,54 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
throw new Error('INVALID_SEED_LENGTH');
}
var signSeed = seed.subarray(0, Nacl.sign.seedLength);
var symmetric = seed.subarray(Nacl.sign.seedLength,
Nacl.sign.seedLength + Nacl.secretbox.keyLength);
var signSeed = seed.subarray(0, Crypto.CryptoAgility.signSeedLength());
var symmetric = seed.subarray(Crypto.CryptoAgility.signSeedLength(),
Crypto.CryptoAgility.signSeedLength() + Crypto.CryptoAgility.secretboxKeyLength());
// Generate standard keys using the existing method
var sign = Crypto.CryptoAgility.signKeyPairFromSeed(signSeed);
// Store the post-quantum keys separately for future use (no server validation issues)
var pqSignPair = null;
var hybridCapable = !!Crypto.PQC && !!Crypto.PQC.ml_dsa;
if (hybridCapable) {
try {
// Use separate seed for PQ keys derived from original seed
// ML-DSA requires a 32-byte seed, so we hash the seed to get a consistent 32-byte value
var pqSeed = Nacl.hash(seed).subarray(0, 32);
// Generate post-quantum keypair using the 32-byte hash
pqSignPair = Crypto.CryptoAgility.generateDsaKeypair(pqSeed);
} catch (err) {
console.error("Failed to generate post-quantum keys:", err);
pqSignPair = null;
}
}
return {
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
symmetric: symmetric, // 32 bytes ...
sign: sign,
pqSignPair: pqSignPair,
symmetric: symmetric,
// Store whether we have post-quantum capability
hasPQ: pqSignPair !== null
};
};
Block.keysToRPCFormat = function (keys) {
try {
var sign = keys.sign;
return {
var pqSignPair = keys.pqSignPair;
// Basic format with classical and pqc keys
var result = {
edPrivate: Util.encodeBase64(sign.secretKey),
edPublic: Util.encodeBase64(sign.publicKey),
dsaPrivate: Util.encodeBase64(pqSignPair.secretKey),
dsaPublic: Util.encodeBase64(pqSignPair.publicKey),
};
return result;
} catch (err) {
console.error(err);
return;
@ -61,21 +92,23 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
// (UTF8 content, keys object) => Uint8Array block
Block.encrypt = function (version, content, keys) {
var u8 = Util.decodeUTF8(content);
var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
var nonce = Crypto.CryptoAgility.bytes(Crypto.CryptoAgility.secretboxNonceLength());
return Block.join([
[0],
nonce,
Nacl.secretbox(u8, nonce, keys.symmetric)
Crypto.CryptoAgility.secretbox(u8, nonce, keys.symmetric)
]);
};
// (uint8Array block) => payload object
// src/common/outer/login-block.js
Block.decrypt = function (u8_content, keys) {
// version is currently ignored since there is only one
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
var nonceLength = Crypto.CryptoAgility.secretboxNonceLength();
var nonce = u8_content.subarray(1, 1 + nonceLength);
var box = u8_content.subarray(1 + nonceLength);
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
var plaintext = Crypto.CryptoAgility.secretboxOpen(box, nonce, keys.symmetric);
try {
return JSON.parse(Util.encodeUTF8(plaintext));
} catch (e) {
@ -86,7 +119,43 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
// (Uint8Array block) => signature
Block.sign = function (ciphertext, keys) {
return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
var hash = Crypto.CryptoAgility.createHash(ciphertext);
// Generate hybrid signature if post-quantum capabilities are available
if (keys.hasPQ && keys.pqSignPair) {
try {
// Generate classical signature (always required for server compatibility)
var classicalSig = Crypto.CryptoAgility.signDetached(hash, keys.sign.secretKey);
// Generate post-quantum signature
var pqSig = Crypto.PQC.ml_dsa.ml_dsa44.internal.sign(keys.pqSignPair.secretKey, hash);
console.log("Hybrid signature created successfully:");
// Create a hybrid signature with format:
// [1-byte type][64-byte classical sig][4-byte PQ sig length][PQ sig bytes]
var hybridSig = new Uint8Array(1 + classicalSig.length + 4 + pqSig.length);
hybridSig[0] = 1; // Type 1 indicates hybrid signature
hybridSig.set(classicalSig, 1);
// Set PQ signature length as 4-byte integer (big endian)
var view = new DataView(hybridSig.buffer);
view.setUint32(1 + classicalSig.length, pqSig.length, false);
// Add the PQ signature
hybridSig.set(pqSig, 1 + classicalSig.length + 4);
return hybridSig;
} catch (e) {
console.error("PQ signing failed, falling back to classical:", e);
}
}
// Classical signature with a type marker
classicalSig = Crypto.CryptoAgility.signDetached(hash, keys.sign.secretKey);
var taggedSig = new Uint8Array(classicalSig.length + 1);
taggedSig[0] = 0; // Type 0 indicates classical only
taggedSig.set(classicalSig, 1);
return taggedSig;
};
Block.serialize = function (content, keys) {
@ -97,21 +166,29 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
var sig = Block.sign(ciphertext, keys);
// serialize {publickey, sig, ciphertext}
return {
var result = {
publicKey: Util.encodeBase64(keys.sign.publicKey),
pqPublicKey: Util.encodeBase64(keys.pqSignPair.publicKey),
signature: Util.encodeBase64(sig),
ciphertext: Util.encodeBase64(ciphertext),
};
return result;
};
Block.proveAncestor = function (O /* oldBlockKeys, N, newBlockKeys */) {
var u8_pub = Util.find(O, ['sign', 'publicKey']);
var u8_secret = Util.find(O, ['sign', 'secretKey']);
try {
// sign your old publicKey with your old privateKey
var u8_sig = Nacl.sign.detached(u8_pub, u8_secret);
// return an array with the sig and the pubkey
return JSON.stringify([u8_pub, u8_sig].map(Util.encodeBase64));
// Use Block.sign to create a hybrid signature if available
var hybridSig = Block.sign(u8_pub, O);
let result = [u8_pub, hybridSig].map(Util.encodeBase64);
if (O.pqSignPair && O.pqSignPair.publicKey) {
result.push(Util.encodeBase64(O.pqSignPair.publicKey));
}
// Return an array with the signature and the pubkey
return JSON.stringify(result);
} catch (err) {
return void console.error(err);
}
@ -122,6 +199,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
};
Block.getBlockUrl = function (keys) {
// Use classical keys for backward compatibility with URL structure
var publicKey = urlSafeB64(keys.sign.publicKey);
// 'block/' here is hardcoded because it's hardcoded on the server
// if we want to make CryptPad work in server subfolders, we'll need
@ -172,9 +250,10 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
ServerCommand(blockKeys.sign, {
command: command,
auth: auth && auth.data
auth: auth && auth.data,
}, cb);
};
Block.writeLoginBlock = function (data, cb) {
const { content, blockKeys, oldBlockKeys, auth, pw, session, token, userData } = data;
@ -191,9 +270,10 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
ServerCommand(blockKeys.sign, {
command: command,
content: block,
session: session // sso session
session: session, // sso session
}, cb);
};
Block.removeLoginBlock = function (data, cb) {
const { reason, blockKeys, auth, edPublic } = data;
@ -204,7 +284,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
command: command,
auth: auth && auth.data,
edPublic: edPublic,
reason: reason
reason: reason,
}, cb);
};
@ -214,7 +294,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
ServerCommand(blockKeys.sign, {
command: 'SSO_UPDATE_BLOCK',
ancestorProof: oldProof
ancestorProof: oldProof,
}, cb);
};
@ -227,7 +307,8 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('../common-util'),
undefined,
require('./http-command'),
require('tweetnacl/nacl-fast')
require('tweetnacl/nacl-fast'),
require('chainpad-crypto/crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
@ -235,8 +316,9 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/api/config',
'/common/outer/http-command.js',
'/components/tweetnacl/nacl-fast.min.js',
], (Util, ApiConfig, ServerCommand) => {
return factory(Util, ApiConfig, ServerCommand, window.nacl);
'/components/chainpad-crypto/crypto.js',
], (Util, ApiConfig, ServerCommand, Nacl, Crypto) => {
return factory(Util, ApiConfig, ServerCommand, window.nacl, Crypto);
});
} else {
// unsupported initialization

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Nacl) {
var factory = function (Util, Nacl, Crypto) {
// we will send messages with a unique id for each RPC
// that id is returned with each response, indicating which call it was in response to
var uid = Util.uid;
@ -15,7 +15,7 @@ var factory = function (Util, Nacl) {
// this handles that in a generic way
var signMsg = function (data, signKey) {
var buffer = Util.decodeUTF8(JSON.stringify(data));
return Util.encodeBase64(Nacl.sign.detached(buffer, signKey));
return Util.encodeBase64(Crypto.CryptoAgility.signDetached(buffer, signKey));
};
// sendMsg takes a pre-formed message, does a little validation
@ -264,6 +264,8 @@ var factory = function (Util, Nacl) {
clearTimeout(to);
});
ctx.send('DESTROY', "", function () {});
// remove the ctx from the network's stack
var idx = networkContext.authenticated.indexOf(ctx);
if (idx === -1) { return; }
@ -408,13 +410,14 @@ var factory = function (Util, Nacl) {
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require("./common-util"), require("tweetnacl/nacl-fast"));
module.exports = factory(require("./common-util"), require("tweetnacl/nacl-fast"), require("chainpad-crypto/crypto"));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/components/tweetnacl/nacl-fast.min.js',
], function (Util) {
return factory(Util, window.nacl);
'/components/chainpad-crypto/crypto.js'
], function (Util, Nacl, Crypto) {
return factory(Util, window.nacl, Crypto);
});
} else {
// I'm not gonna bother supporting any other kind of instanciation

View File

@ -25,6 +25,7 @@ const factory = (Sortify, UserObject, ProxyManager,
const Saferphore = Util.Saferphore;
var onReadyEvt = Util.mkEvent(true);
var onCacheReadyEvt = Util.mkEvent(true);
var onDriveReadyEvt = Util.mkEvent(true);
var onPadRejectedEvt = Util.mkEvent(true);
const setCustomize = data => {
@ -422,8 +423,8 @@ const factory = (Sortify, UserObject, ProxyManager,
var initTempRpc = (clientId, cb) => {
if (store.rpc) { return void cb(store.rpc); }
var kp = Crypto.Nacl.sign.keyPair();
var keys = store.tempKeys = {
var kp = Crypto.CryptoAgility.signKeyPair();
var keys = {
edPublic: Util.encodeBase64(kp.publicKey),
edPrivate: Util.encodeBase64(kp.secretKey)
};
@ -586,7 +587,9 @@ const factory = (Sortify, UserObject, ProxyManager,
color: Store.getUserColor(),
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
curvePublic: proxy.curvePublic,
kemPublic: proxy.kemPublic,
edPublic: proxy.edPublic,
dsaPublic: proxy.dsaPublic,
netfluxId: store?.network?.webChannels?.[0]?.myID,
badge: Util.find(proxy, ['profile', 'badge'])
},
@ -595,6 +598,8 @@ const factory = (Sortify, UserObject, ProxyManager,
clientId: clientId,
edPublic: proxy.edPublic,
edPrivate: proxy.edPrivate,
dsaPublic: proxy.dsaPublic,
dsaPrivate: proxy.dsaPrivate,
friends: proxy.friends || {},
settings: proxy.settings || NEW_USER_SETTINGS,
thumbnails: disableThumbnails === false,
@ -881,7 +886,7 @@ const factory = (Sortify, UserObject, ProxyManager,
toSign.drive = store.driveChannel;
toSign.edPublic = edPublic;
var signKey = Util.decodeBase64(store.proxy.edPrivate);
var proof = Crypto.Nacl.sign.detached(Util.decodeUTF8(Sortify(toSign)), signKey);
var proof = Crypto.CryptoAgility.signDetached(Util.decodeUTF8(Sortify(toSign)), signKey);
var check = Crypto.Nacl.sign.detached.verify(Util.decodeUTF8(Sortify(toSign)),
proof,
@ -1608,7 +1613,7 @@ const factory = (Sortify, UserObject, ProxyManager,
if (!Array.isArray(allowed)) { return void cb('ERESTRICTED'); }
onPadRejectedEvt.fire();
onReadyEvt.reg(() => {
onDriveReadyEvt.reg(() => {
// There is an allow list: check if we can authenticate
if (!store.loggedIn || !store.proxy.edPublic) { return void cb('ERESTRICTED'); }
@ -1782,7 +1787,7 @@ const factory = (Sortify, UserObject, ProxyManager,
if (!channel || !ownerKey) { return void console.error("Can't delete BAR pad"); }
try {
var signKey = Hash.decodeBase64(ownerKey);
var pair = Crypto.Nacl.sign.keyPair.fromSecretKey(signKey);
var pair = Crypto.CryptoAgility.signKeyPairFromSecretKey(signKey);
Pinpad.create(store.network, {
edPublic: Hash.encodeBase64(pair.publicKey),
edPrivate: Hash.encodeBase64(pair.secretKey)
@ -2570,6 +2575,7 @@ const factory = (Sortify, UserObject, ProxyManager,
cacheCb(store.cacheReturned || store.returned);
});
onDriveReady(() => {
onDriveReadyEvt.fire();
cb(store.returned);
});

View File

@ -10,21 +10,30 @@ var factory = function (Util, Cred, Nacl, Crypto) {
// ed and curve keys can be random...
Invite.generateKeys = function () {
var ed = Nacl.sign.keyPair();
var curve = Nacl.box.keyPair();
var ed = Crypto.CryptoAgility.signKeyPair();
var curve = Crypto.CryptoAgility.curveKeyPair();
var kem = Crypto.CryptoAgility.generateKemKeypair();
var dsa = Crypto.CryptoAgility.generateDsaKeypair();
return {
edPublic: encode64(ed.publicKey),
edPrivate: encode64(ed.secretKey),
curvePublic: encode64(curve.publicKey),
curvePrivate: encode64(curve.secretKey),
kemPublic: encode64(kem.publicKey),
kemPrivate: encode64(kem.secretKey),
dsaPublic: encode64(dsa.publicKey),
dsaPrivate: encode64(dsa.secretKey),
};
};
Invite.generateSignPair = function () {
var ed = Nacl.sign.keyPair();
var ed = Crypto.CryptoAgility.signKeyPair();
var dsa = Crypto.CryptoAgility.generateKemKeypair();
return {
validateKey: encode64(ed.publicKey),
signKey: encode64(ed.secretKey),
dsaPublic: encode64(dsa.publicKey),
dsaPrivate: encode64(dsa.secretKey),
};
};
@ -32,7 +41,7 @@ var factory = function (Util, Cred, Nacl, Crypto) {
var dispense = Cred.dispenser(decode64(b64));
return {
channel: Util.uint8ArrayToHex(dispense(16)),
cryptKey: dispense(Nacl.secretbox.keyLength),
cryptKey: dispense(Crypto.CryptoAgility.secretboxKeyLength()),
};
};
@ -58,13 +67,13 @@ var factory = function (Util, Cred, Nacl, Crypto) {
var decodeUTF8 = Util.decodeUTF8;
Invite.encryptHash = function (data, seedStr) {
var array = decodeUTF8(seedStr);
var bytes = Nacl.hash(array);
var bytes = Crypto.CryptoAgility.createHash(array);
var cryptKey = bytes.subarray(0, 32);
return Crypto.encrypt(data, cryptKey);
};
Invite.decryptHash = function (encryptedStr, seedStr) {
var array = decodeUTF8(seedStr);
var bytes = Nacl.hash(array);
var bytes = Crypto.CryptoAgility.createHash(array);
var cryptKey = bytes.subarray(0, 32);
return Crypto.decrypt(encryptedStr, cryptKey);
};

View File

@ -5,13 +5,35 @@
const factory = (Crypto, Hash, Util, Constants, Realtime) => {
var Msg = {};
/*
* CRYPTOGRAPHIC IDENTITY NOTE:
*
* Throughout this module and the entire CryptPad application, we continue to use
* curve25519 public keys (curvePublic) as the primary identifier for users despite
* having post-quantum cryptography (PQC) keys (kemPublic) available.
*
* 1. Legacy compatibility: A complete migration to PQC for identification would require
* changing all existing user relationships and channels
*
* 2. System integration: The curvePublic key is deeply integrated into friend relationships,
* channel management, history keeper, and other core components
*
* 3. Risk management: Partially migrating identity systems can lead to inconsistencies
* across the application, potentially creating security vulnerabilities
*
* A future coordinated migration will be necessary to fully replace curve25519 with PQC,
* which will require careful planning to ensure all components are updated simultaneously.
*/
var createData = Msg.createData = function (proxy, hash) {
var data = {
channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'],
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
curvePublic: proxy.curvePublic, // Still used as primary ID throughout the system
dsaPublic: proxy.dsaPublic,
kemPublic: proxy.kemPublic, // PQC key available but not used as primary ID yet
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar,
badge: proxy.profile && proxy.profile.badge,
@ -21,6 +43,8 @@ const factory = (Crypto, Hash, Util, Constants, Realtime) => {
return data;
};
// Friend lookup still uses curvePublic as the key - changing this would require
// migrating all existing friendship relationships and updating all related code
var getFriend = Msg.getFriend = function (proxy, pubkey) {
if (!pubkey) { return; }
if (pubkey === proxy.curvePublic) {

View File

@ -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,

View File

@ -730,10 +730,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
var ciphertext = crypto.encrypt(Sortify(msg));
var id = getMessageId(ciphertext);
//console.log("Sending with id [%s]", id, msg);
//console.log();
response.expect(id, function (err, state) {
if (err) { return void cb(err); }
cb(void 0, state, id);
@ -879,8 +875,19 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
w.abort();
return void cb("NO_VALIDATE_KEY");
}
if (!config.keys.teamKemPublic && metadata.kemPublic) {
config.keys.teamKemPublic = metadata.kemPublic;
}
if (!config.keys.teamDsaPublic && metadata.dsaPublic) {
config.keys.teamDsaPublic = metadata.dsaPublic;
}
if (!config.keys.teamKemPublic && !config.keys.teamDsaPublic) {
w.abort();
return void cb("NO_PQC_KEYS");
}
try {
// Crypto.Team.createEncryptor now supports PQC keys
crypto = Crypto.Team.createEncryptor(config.keys);
} catch (err) {
w.abort();
@ -903,6 +910,7 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
crypto: crypto,
validateKey: config.keys.teamEdPublic,
dsaValidateKey: config.keys.teamDsaPublic, // PQC validation key for ML-DSA
owners: config.owners,

View File

@ -24,7 +24,16 @@ const factory = (Crypto) => {
msg: data.msg,
uid: data.uid,
};
chan.sendMsg(JSON.stringify(obj), cb);
if (obj.msg === 'ISAVE') {
ctx.pending[data.uid] = true;
}
chan.sendMsg(JSON.stringify(obj), obj => {
if (!ctx.pending[data.uid]) {
return void setTimeout(cb, 1000);
}
delete ctx.pending[data.uid];
cb(obj);
});
ctx.emit('MESSAGE', obj, chan.clients.filter(function (cl) {
return cl !== client;
}));
@ -90,6 +99,9 @@ const factory = (Crypto) => {
var parsed;
try {
parsed = JSON.parse(msg);
if (parsed.msg === "ISAVE") {
delete ctx.pending[parsed.uid];
}
ctx.emit('MESSAGE', parsed, chan.clients);
} catch (e) { console.error(e); }
});
@ -176,6 +188,7 @@ const factory = (Crypto) => {
store: cfg.store,
emit: emit,
channels: {},
pending: {}, // prevent ISAVE race condition
clients: {}
};

View File

@ -78,10 +78,12 @@ proxy.mailboxes = {
var getMyKeys = function (ctx) {
var proxy = ctx.store && ctx.store.proxy;
if (!proxy.curvePrivate || !proxy.curvePublic) { return; }
if (!proxy.curvePrivate || !proxy.curvePublic || !proxy.kemPublic || !proxy.kemPrivate) { return; }
return {
curvePrivate: proxy.curvePrivate,
curvePublic: proxy.curvePublic
curvePublic: proxy.curvePublic,
kemPrivate: proxy.kemPrivate,
kemPublic: proxy.kemPublic
};
};
@ -130,7 +132,10 @@ proxy.mailboxes = {
}
var text = JSON.stringify(obj);
var ciphertext = crypto.encrypt(text, user.curvePublic);
var ciphertext = crypto.encrypt(text, user.curvePublic, user.kemPublic);
// const bytes = Crypto.CryptoAgility.decodeBase64(ciphertext);
// if (bytes[0] !== 1) throw new Error("PQC not used for encryption");
// If we've sent this message to one of our teams' mailbox, we may want to "dismiss" it
// automatically
@ -158,17 +163,42 @@ proxy.mailboxes = {
});
};
Mailbox.sendToAnon = function (anonRpc, type, msg, user, cb) {
var Nacl = Crypto.Nacl;
var curveSeed = Nacl.randomBytes(32);
var curvePair = Nacl.box.keyPair.fromSecretKey(new Uint8Array(curveSeed));
var curveSeed = Crypto.CryptoAgility.bytes(32);
var curvePair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(new Uint8Array(curveSeed));
var curvePrivate = Util.encodeBase64(curvePair.secretKey);
var curvePublic = Util.encodeBase64(curvePair.publicKey);
// Generate ephemeral PQC keys if PQC is available
var kemPrivate, kemPublic;
if (Crypto.PQC && Crypto.PQC.ml_kem && Crypto.PQC.ml_kem.ml_kem512) {
try {
var kemSeed = Crypto.CryptoAgility.bytes(64);
var kemPair = Crypto.PQC.ml_kem.ml_kem512.keygen(new Uint8Array(kemSeed));
kemPrivate = Util.encodeBase64(kemPair.secretKey);
kemPublic = Util.encodeBase64(kemPair.publicKey);
} catch (e) {
console.warn('Failed to generate ephemeral PQC keys:', e);
}
}
var proxyData = {
curvePrivate: curvePrivate,
curvePublic: curvePublic
};
if (kemPrivate && kemPublic) {
proxyData.kemPrivate = kemPrivate;
proxyData.kemPublic = kemPublic;
}
sendTo({
store: {
anon_rpc: anonRpc,
proxy: {
curvePrivate: curvePrivate,
curvePublic: curvePublic
curvePublic: curvePublic,
kemPrivate: kemPrivate,
kemPublic: kemPublic,
}
}
}, type, msg, user, cb);

View File

@ -13,6 +13,26 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
Messages = data.Messages;
};
/*
* CRYPTOGRAPHIC IDENTITY NOTE:
*
* Throughout this module and the entire CryptPad application, we continue to use
* curve25519 public keys (curvePublic) as the primary identifier for users despite
* having post-quantum cryptography (PQC) keys (kemPublic) available.
*
* 1. Legacy compatibility: A complete migration to PQC for identification would require
* changing all existing user relationships and channels
*
* 2. System integration: The curvePublic key is deeply integrated into friend relationships,
* channel management, history keeper, and other core components
*
* 3. Risk management: Partially migrating identity systems can lead to inconsistencies
* across the application, potentially creating security vulnerabilities
*
* A future coordinated migration will be necessary to fully replace curve25519 with PQC,
* which will require careful planning to ensure all components are updated simultaneously.
*/
var Types = {
message: 'MSG',
unfriend: 'UNFRIEND',
@ -701,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,

View File

@ -11,7 +11,6 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt,
ApiConfig = data.ApiConfig;
};
var Nacl = Crypto.Nacl;
// UTILS
@ -40,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.kemPrivate,
theirKem: data.kemPublic,
notifKey: data.curvePublic
});
});
@ -54,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.kemPrivate,
theirKem: data.kemPublic || supportKemKey, // Needed for PQC, currently making messages unreadable
notifKey: supportKey
});
});
@ -64,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) {
@ -73,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,
@ -122,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
@ -133,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);
@ -228,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
@ -239,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);
@ -302,7 +315,7 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt,
if (!curvePrivate) { return void cb('EFORBIDDEN'); }
let edPrivate, edPublic;
try {
let pair = Nacl.sign.keyPair.fromSeed(Util.decodeBase64(curvePrivate));
let pair = Crypto.CryptoAgility.signKeyPairFromSeed(Util.decodeBase64(curvePrivate));
edPrivate = Util.encodeBase64(pair.secretKey);
edPublic = Util.encodeBase64(pair.publicKey);
} catch (e) {
@ -418,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') {
@ -830,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,
@ -996,17 +1011,17 @@ 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 = Nacl.sign.keyPair.fromSeed(Util.decodeBase64(curvePrivate));
let pair = Crypto.CryptoAgility.signKeyPairFromSeed(Util.decodeBase64(curvePrivate));
edPublic = Util.encodeBase64(pair.publicKey);
} catch (e) {
return void cb(e);
}
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,12 +1036,15 @@ const factory = (Util, Hash, Realtime, Pinpad, Crypt,
let proxy = ctx.store.proxy;
let edPublic = proxy.edPublic;
const keyPair = Nacl.box.keyPair();
const keyPair = Crypto.CryptoAgility.curveKeyPair();
const kemPair = Crypto.CryptoAgility.generateKemKeypair();
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;
@ -1092,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);
@ -1123,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);
});
}
@ -1205,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();

View File

@ -5,10 +5,9 @@
const factory = (Util, Hash, Constants, Realtime, ProxyManager,
UserObject, SF, Roster, Messaging, Feedback,
Invite, Crypt, Cache, Pinpad, Listmap, Crypto,
CpNetflux, ChainPad, nThen, Nacl) => {
CpNetflux, ChainPad, nThen) => {
const Team = {};
Nacl = Nacl || (typeof(window) !== "undefined" && window.nacl);
var onStoreReady = Util.mkEvent(true);
var openCachedTeamChat = function () {}; // Placeholder
@ -107,6 +106,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
try { team.roster.stop(); } catch (e) {}
team.proxy = {};
team.stopped = true;
team?.rpc?.destroy();
delete ctx.teams[teamId];
delete ctx.cache[teamId];
delete ctx.store.proxy.teams[teamId];
@ -449,7 +449,9 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
// Roster keys
var myKeys = {
curvePublic: ctx.store.proxy.curvePublic,
curvePrivate: ctx.store.proxy.curvePrivate
curvePrivate: ctx.store.proxy.curvePrivate,
kemPublic: ctx.store.proxy.kemPublic,
kemPrivate: ctx.store.proxy.kemPrivate,
};
var rosterData = keys.roster || {};
var rosterKeys = rosterData.edit ? Crypto.Team.deriveMemberKeys(rosterData.edit, myKeys)
@ -471,6 +473,11 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
if (k && !rosterKeys.teamEdPublic) {
rosterKeys.teamEdPublic = k;
}
// Also get DSA public key from cache if available
var dsaPublic = obj && obj.dsaPublic;
if (dsaPublic && !rosterKeys.teamDsaPublic) {
rosterKeys.teamDsaPublic = dsaPublic;
}
if (!c) {
waitFor.abort();
cb({error: 'NOCACHE'});
@ -654,14 +661,18 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
var hash = Hash.createRandomHash('team', password);
var secret = Hash.getSecrets('team', hash, password);
var roHash = Hash.getViewHashFromKeys(secret);
var keyPair = Nacl.sign.keyPair(); // keyPair.secretKey , keyPair.publicKey
var keyPair = Crypto.CryptoAgility.signKeyPair(); // ed25519
var curvePair = Crypto.CryptoAgility.curveKeyPair(); // Curve25519
var curvePair = Nacl.box.keyPair();
var kemPair = Crypto.CryptoAgility.generateKemKeypair();
var dsaPair = Crypto.CryptoAgility.generateKemKeypair();
var rosterSeed = Crypto.Team.createSeed();
var rosterKeys = Crypto.Team.deriveMemberKeys(rosterSeed, {
curvePublic: ctx.store.proxy.curvePublic,
curvePrivate: ctx.store.proxy.curvePrivate
curvePrivate: ctx.store.proxy.curvePrivate,
kemPublic: ctx.store.proxy.kemPublic,
kemPrivate: ctx.store.proxy.kemPrivate,
});
var roster;
@ -681,7 +692,6 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
owners: [ctx.store.proxy.edPublic]
};
nThen(function (waitFor) {
// Initialize the roster
Roster.create({
network: ctx.store.network || ctx.store.networkPromise,
channel: rosterKeys.channel, //sharedConfig.rosterChannel,
@ -694,7 +704,6 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}, waitFor(function (err, _roster) {
if (err) {
waitFor.abort();
console.error(err);
return void cb({error: 'ROSTER_ERROR'});
}
roster = _roster;
@ -708,7 +717,6 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}));
}));
// Add yourself as owner of the chat channel
var crypto = Crypto.createEncryptor(chatSecret.keys);
var chatCfg = {
network: ctx.store.network,
@ -751,19 +759,22 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
var proxy = lm.proxy;
proxy.version = 2; // No migration needed
proxy.on('ready', function () {
// Store keys in our drive
var keys = {
mailbox: {
channel: Hash.createChannelId(),
viewed: [],
keys: {
curvePrivate: Util.encodeBase64(curvePair.secretKey),
curvePublic: Util.encodeBase64(curvePair.publicKey)
curvePublic: Util.encodeBase64(curvePair.publicKey),
kemPrivate: Util.encodeBase64(kemPair.secretKey),
kemPublic: Util.encodeBase64(kemPair.publicKey),
}
},
drive: {
edPrivate: Util.encodeBase64(keyPair.secretKey),
edPublic: Util.encodeBase64(keyPair.publicKey)
edPublic: Util.encodeBase64(keyPair.publicKey),
dsaPrivate: Util.encodeBase64(dsaPair.secretKey),
dsaPublic: Util.encodeBase64(dsaPair.publicKey),
},
chat: {
edit: chatHashes.editHash,
@ -784,7 +795,6 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
roHash: roHash,
password: password,
keys: keys,
//members: membersHashes.editHash,
metadata: {
name: data.name
}
@ -803,13 +813,11 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
cb();
});
}).on('error', function (info) {
if (info && typeof (info.loaded) !== "undefined" && !info.loaded) {
if (info && typeof (info.loaded) !== "undefined" && !info.loaded) {
cb({error:'ECONNECT'});
}
if (info && info.error) {
if (info.error === "EDELETED") {
closeTeam(ctx, id);
}
if (info && info.error === "EDELETED") {
closeTeam(ctx, id);
}
});
});
@ -1674,6 +1682,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,
},
};
@ -1867,10 +1879,10 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
if (team.keys && team.keys.mailbox) { return team.keys.mailbox; }
var strSeed = Util.find(team, ['keys', 'roster', 'edit']);
if (!strSeed) { return; }
var hash = Nacl.hash(Util.decodeUTF8(strSeed));
var hash = Crypto.CryptoAgility.createHash(Util.decodeUTF8(strSeed));
var seed = hash.slice(0,32);
var mailboxChannel = Util.uint8ArrayToHex(hash.slice(32,48));
var curvePair = Nacl.box.keyPair.fromSecretKey(seed);
var curvePair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(seed);
return {
channel: mailboxChannel,
viewed: [],
@ -1919,7 +1931,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
if (!edPrivate || !edPublic) { return true; }
try {
var secretKey = Util.decodeBase64(edPrivate);
var pair = Nacl.sign.keyPair.fromSecretKey(secretKey);
var pair = Crypto.CryptoAgility.signKeyPairFromSecretKey(secretKey);
return Util.encodeBase64(pair.publicKey) === edPublic;
} catch (e) {
return false;

View File

@ -22,6 +22,7 @@ define([
'/api/instance',
'/lib/datepicker/flatpickr.js',
'/install/onboardscreen.js',
'/components/chainpad-crypto/crypto.js',
'css!/lib/datepicker/flatpickr.min.css',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
@ -47,11 +48,12 @@ define([
Instance,
Flatpickr,
Onboarding,
Crypto
) {
var APP = window.APP = {};
var Nacl = window.nacl;
//var Nacl = window.nacl;
var common;
var sFrameChan;
@ -2798,7 +2800,7 @@ define([
var msg = Util.decodeUTF8(Sortify(clone));
var sig = Util.decodeBase64(json.proof);
var pub = Util.decodeBase64(json.blockId);
return Nacl.sign.detached.verify(msg, sig, pub);
return Crypto.CryptoAgility.verifyDetached(msg, sig, pub);
};
// Msg.admin_totpRecoveryHint.admin_totpRecoveryTitle

View File

@ -21,11 +21,12 @@ define([
'/customize/messages.js',
'/components/tweetnacl/nacl-fast.min.js',
'/components/chainpad-crypto/crypto.js',
'less!/customize/src/less2/pages/page-assert.less',
], function ($, Hyperjson, Sortify, Drive, /*Test,*/ Hash, Util, Thumb, Wire, Flat, MediaTag, Block, ApiConfig, Assertions, h, Messages) {
], function ($, Hyperjson, Sortify, Drive, /*Test,*/ Hash, Util, Thumb, Wire, Flat, MediaTag, Block, ApiConfig, Assertions, h, Messages, Crypto) {
window.Hyperjson = Hyperjson;
window.Sortify = Sortify;
var Nacl = window.nacl;
//var Nacl = window.nacl;
var assert = Assertions();
@ -314,7 +315,7 @@ define([
}, "test that protocol relative URLs are rejected");
assert(function (cb) {
var keys = Block.genkeys(Nacl.randomBytes(64));
var keys = Block.genkeys(Crypto.CryptoAgility.bytes(64));
var hash = Block.getBlockHash(keys);
var parsed = Block.parseBlockHash(hash);

View File

@ -15,11 +15,12 @@ define([
'/lib/qrcode.min.js',
'/components/tweetnacl/nacl-fast.min.js',
'componenst/chainpad-cypto/crypto.js',
'less!/auth/app-auth.less',
], function ($, Util, h, UI, ServerCommand, Base32, Login, Block, LocalStore) {
], function ($, Util, h, UI, ServerCommand, Base32, Login, Block, LocalStore, Crypto) {
var QRCode = window.QRCode;
var Nacl = window.nacl;
//var Nacl = window.nacl;
var main = h('div.centered', [
@ -220,7 +221,7 @@ Note: This must currently be reversed manually (by deleting the mfa config file)
var $b32Secret = $('#base32-secret');
var randomSecret = () => {
var U8 = Nacl.randomBytes(20);
var U8 = Crypto.CryptoAgility.bytes(20);
return Base32.encode(U8);
};

View File

@ -24,10 +24,11 @@ define([
'/components/scrypt-async/scrypt-async.min.js', // better load speed
], function (Listmap, Crypto, Util, NetConfig, Cred, ChainPad, Realtime, Constants, UI,
Feedback, LocalStore, Messages, nThen, Block, Hash, ServerCommand) {
var Nacl = window.nacl;
//var Nacl = window.nacl;
var Exports = {
requiredBytes: 192,
// Increased required bytes to accommodate post-quantum keys
requiredBytes: 288, // Increased from original to accommodate post-quantum keys
};
var allocateBytes = Exports.allocateBytes = function (bytes) {
@ -42,23 +43,43 @@ define([
// 32 bytes for a curve key
var curveSeed = dispense(32);
var curvePair = Nacl.box.keyPair.fromSecretKey(new Uint8Array(curveSeed));
// KEM keys (post-quantum)
var kemSeed = dispense(64); // 64 bytes for post-quantum KEM keys
if (Crypto.PQC && Crypto.PQC.ml_kem && Crypto.PQC.ml_kem.ml_kem512) {
var pqKemPair = Crypto.CryptoAgility.generateKemKeypair(new Uint8Array(kemSeed));
opt.kemPrivate = Util.encodeBase64(pqKemPair.secretKey);
opt.kemPublic = Util.encodeBase64(pqKemPair.publicKey);
} else {
opt.kemPrivate = undefined;
opt.kemPublic = undefined;
}
var curvePair = Crypto.CryptoAgility.boxKeyPairFromSecretKey(new Uint8Array(curveSeed));
opt.curvePrivate = Util.encodeBase64(curvePair.secretKey);
opt.curvePublic = Util.encodeBase64(curvePair.publicKey);
// 32 more for a signing key
var edSeed = opt.edSeed = dispense(32);
// 64 more bytes to seed an additional signing key
var blockKeys = opt.blockKeys = Block.genkeys(new Uint8Array(dispense(64)));
// Allocate more bytes for block keys seed to accommodate PQ keys
var blockKeysSeed = dispense(96); // Increased from original to fit PQ keys
// Generate block keys
var blockKeys = opt.blockKeys = Block.genkeys(new Uint8Array(blockKeysSeed));
opt.blockHash = Block.getBlockHash(blockKeys);
// derive a private key from the ed seed
var signingKeypair = Nacl.sign.keyPair.fromSeed(new Uint8Array(edSeed));
var signingKeypair = Crypto.CryptoAgility.signKeyPairFromSeed(new Uint8Array(edSeed));
opt.edPrivate = Util.encodeBase64(signingKeypair.secretKey);
opt.edPublic = Util.encodeBase64(signingKeypair.publicKey);
// Store post-quantum keys if they're available
if (blockKeys.hasPQ && blockKeys.pqSignPair) {
opt.dsaPrivate = Util.encodeBase64(blockKeys.pqSignPair.secretKey);
opt.dsaPublic = Util.encodeBase64(blockKeys.pqSignPair.publicKey);
}
var keys = opt.keys = Crypto.createEditCryptor(null, encryptionSeed);
// 24 bytes of base64
@ -86,6 +107,9 @@ define([
opt.channelHex = parsed.channel;
opt.keys = parsed.keys;
opt.edPublic = blockInfo.edPublic;
opt.dsaPublic = blockInfo.dsaPublic;
return opt;
};
@ -171,10 +195,17 @@ define([
res.edPrivate = opt.edPrivate;
res.edPublic = opt.edPublic;
//export their post-quantum keys if available
res.dsaPrivate = opt.dsaPrivate;
res.dsaPublic = opt.dsaPublic;
// export their encryption key
res.curvePrivate = opt.curvePrivate;
res.curvePublic = opt.curvePublic;
res.kemPrivate = opt.kemPrivate;
res.kemPublic = opt.kemPublic;
// don't proceed past this async block.
// We have to call whenRealtimeSyncs asynchronously here because in the current
@ -201,7 +232,7 @@ define([
opt.userHash = blockInfo.User_hash;
} else {
console.log("allocating random bytes for a new user object");
opt = allocateBytes(Nacl.randomBytes(Exports.requiredBytes));
opt = allocateBytes(Crypto.CryptoAgility.bytes(Exports.requiredBytes));
// create a random v2 hash, since we don't need backwards compatibility
opt.userHash = Hash.createRandomHash('drive');
var secret = Hash.getSecrets('drive', opt.userHash);
@ -223,7 +254,7 @@ define([
var RT = rt;
var proxy = rt.proxy;
if (isRegister && !isProxyEmpty(proxy) && (!proxy.edPublic || !proxy.edPrivate)) {
if (isRegister && !isProxyEmpty(proxy) && (!proxy.edPublic || !proxy.edPrivate || !proxy.dsaPublic || !proxy.dsaPrivate)){
console.error("INVALID KEYS");
console.log(JSON.stringify(proxy));
return void cb(void 0, void 0, RT);
@ -245,7 +276,7 @@ define([
return void cb('NO_SUCH_USER');
}
if (!isProxyEmpty(rt.proxy) && res.auth_token && res.auth_token.bearer) {
if (!isRegister && !isProxyEmpty(rt.proxy) && res.auth_token && res.auth_token.bearer) {
LocalStore.setSessionToken(res.auth_token.bearer);
}
@ -268,8 +299,12 @@ define([
if (isRegister && isProxyEmpty(rt.proxy)) {
proxy.edPublic = opt.edPublic;
proxy.edPrivate = opt.edPrivate;
proxy.dsaPublic = opt.dsaPublic;
proxy.dsaPrivate = opt.dsaPrivate;
proxy.curvePublic = opt.curvePublic;
proxy.curvePrivate = opt.curvePrivate;
proxy.kemPublic = opt.kemPublic;
proxy.kemPrivate = opt.kemPrivate;
proxy.login_name = res.uname;
proxy[Constants.displayNameKey] = res.uname;
proxy.version = 11;

View File

@ -23,10 +23,11 @@ define([
'/customize/application_config.js',
'/components/nthen/index.js',
'/components/tweetnacl/nacl-fast.min.js'
'/components/tweetnacl/nacl-fast.min.js',
'/components/chainpad-crypto/crypto.js'
], function (Config, Broadcast, Messages, Util, Hash, Cache,
Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
Cred, Login, Store, Types, AppConfig, nThen) {
Cred, Login, Store, Types, AppConfig, nThen, Crypto) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
@ -251,8 +252,8 @@ define([
// Make proof
var curve = answer.curvePrivate;
var mySecret = Util.decodeBase64(curve);
var nonce = nacl.randomBytes(24);
var proofBytes = nacl.box(h, nonce, theirs, mySecret);
var nonce = Crypto.CryptoAgility.bytes(24);
var proofBytes = Crypto.CryptoAgility.box(h, nonce, theirs, mySecret);
var proof = Util.encodeBase64(nonce) +'|'+ Util.encodeBase64(proofBytes);
var lineData = {
channel: data.channel,

View File

@ -3,18 +3,18 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Nacl, Scrypt) {
var factory = function (Util, Nacl, Scrypt, Crypto) {
var Invite = {};
Invite.deriveSeeds = function (safeSeed) {
// take the hash of the provided seed
var seed = safeSeed.replace(/\-/g, '/');
var u8_seed = Nacl.hash(Util.decodeBase64(seed));
var u8_seed = Crypto.CryptoAgility.createHash(Util.decodeBase64(seed));
// hash the first half again for scrypt's input
var subseed1 = Nacl.hash(u8_seed.subarray(0, 32));
var subseed1 = Crypto.CryptoAgility.createHash(u8_seed.subarray(0, 32));
// hash the remainder for the invite content
var subseed2 = Nacl.hash(u8_seed.subarray(32));
var subseed2 = Crypto.CryptoAgility.createHash(u8_seed.subarray(32));
return {
scrypt: Util.encodeBase64(subseed1),
@ -44,15 +44,17 @@ var factory = function (Util, Nacl, Scrypt) {
module.exports = factory(
require("../common-util"),
require("tweetnacl/nacl-fast"),
require("scrypt-async")
require("scrypt-async"),
require("chainpad-crypto/crypto")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/components/tweetnacl/nacl-fast.min.js',
'/components/scrypt-async/scrypt-async.min.js',
], function (Util) {
return factory(Util, window.nacl, window.scrypt);
'/components/chainpad-crypto/crypto.js'
], function (Util, nacl, Scrypt, Crypto) {
return factory(Util, window.nacl, window.scrypt, Crypto);
});
}
}());

View File

@ -10,8 +10,9 @@ define([
'/components/nthen/index.js',
'/customize.dist/login.js',
'/common/common-util.js',
'/components/chainpad-crypto/crypto.js'
], function ($, Messages, h, UI, nThen, Login, Util) {
], function ($, Messages, h, UI, nThen, Login, Util, Crypto) {
const MFA = {};
MFA.totpSetup = function (common, config, content, enabled, cb) {
@ -161,7 +162,7 @@ define([
$(pwInput).prop('disabled', 'disabled');
$mfaSetupBtn.prop('disabled', 'disabled');
var Base32, QRCode, Nacl;
var Base32, QRCode;
var blockKeys;
var recoverySecret;
var ssoSeed;
@ -173,7 +174,6 @@ define([
], waitFor(function (_Base32) {
Base32 = _Base32;
QRCode = window.QRCode;
Nacl = window.nacl;
}));
}).nThen(function (waitFor) {
sframeChan.query("Q_SETTINGS_GET_SSO_SEED", {
@ -212,7 +212,7 @@ define([
}).nThen(function (waitFor) {
$content.empty();
var next = waitFor();
recoverySecret = Util.encodeBase64(Nacl.randomBytes(24));
recoverySecret = Util.encodeBase64(Crypto.CryptoAgility.bytes(24));
var button = h('button.btn.btn-primary', [
h('i.fa.fa-check'),
h('span', Messages.done)
@ -241,7 +241,7 @@ define([
});
}).nThen(function () {
var randomSecret = function () {
var U8 = Nacl.randomBytes(20);
var U8 = Crypto.CryptoAgility.bytes(20);
return Base32.encode(U8);
};
$content.empty();

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
(function (window) {
var factory = function (Util) {
var factory = function (Util, Crypto) {
var Promise = window.Promise;
var cache;
var cypherChunkLength = 131088;
@ -456,7 +456,7 @@ var factory = function (Util) {
var metadataLength = Decrypt.decodePrefix(prefix);
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = window.nacl.secretbox.open(metaBox, Decrypt.createNonce(), key);
var metaChunk = Crypto.CryptoAgility.secretboxOpen(metaBox, Decrypt.createNonce(), key);
try {
return JSON.parse(Util.encodeUTF8(metaChunk));
@ -478,7 +478,7 @@ var factory = function (Util) {
// Decrypts a Uint8Array with the given key.
var decrypt = function (u8, strKey, done, progressCb) {
var Nacl = window.nacl;
//var Nacl = window.nacl;
var progress = function (offset) {
progressCb((offset / u8.length) * 100);
@ -494,7 +494,7 @@ var factory = function (Util) {
// Get metadata
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
var metaChunk = Crypto.CryptoAgility.secretboxOpen(metaBox, nonce, key);
Decrypt.increment(nonce);
@ -513,7 +513,7 @@ var factory = function (Util) {
var box = new Uint8Array(u8.subarray(start, end));
// Decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
var plaintext = Crypto.CryptoAgility.secretboxOpen(box, nonce, key);
Decrypt.increment(nonce);
if (!plaintext) { return void cb('DECRYPTION_FAILURE'); }
@ -833,8 +833,8 @@ var factory = function (Util) {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/common/common-util.js'], function (Util) {
return factory(Util);
define(['/common/common-util.js', '/components/chainpad-crypto/crypto.js'], function (Util, Crypto) {
return factory(Util, Crypto);
});
} else {
// unsupported initialization

View File

@ -28,6 +28,8 @@ define([
'/common/onlyoffice/broken-formats.js',
'/components/file-saver/FileSaver.min.js',
'/components/chainpad-crypto/crypto.js',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'less!/components/components-font-awesome/css/font-awesome.min.css',
'less!/common/onlyoffice/app-oo.less',
@ -40,6 +42,7 @@ define([
UI,
Hash,
Util,
Crypto,
UIElements,
Feedback,
h,
@ -56,7 +59,7 @@ define([
BrokenFormats)
{
var saveAs = window.saveAs;
var Nacl = window.nacl;
//var Nacl = window.nacl;
var APP = window.APP = {
$: $,
urlArgs: Util.find(ApiConfig, ['requireConf', 'urlArgs'])
@ -2495,7 +2498,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
}
try {
debug("Decrypt with key " + data.key);
FileCrypto.decrypt(u8, Nacl.util.decodeBase64(data.key), function (err, res) {
FileCrypto.decrypt(u8, Crypto.CryptoAgility.decodeBase64(data.key), function (err, res) {
APP.loadingImage--;
if (err || !res.content) {
debug("Decrypting failed");

View File

@ -92,7 +92,7 @@ define([
if (state.me) { return; } // I have the lock: abort
if (state.other && state.other !== data.uid) { return; } // someone else has the lock
// If state.other === data.uid ==> save failed and someone else tries again
if (!state.changed) { return; }
if (!state.changed && state.other !== data.uid) { return; }
// If !state.other: nobody has the lock, give them
state.other = data.uid;
state.lastTmp = +new Date();
@ -103,6 +103,7 @@ define([
setStateChanged(false);
saveTo = setTimeout(function () {
// They weren't able to save in time, try ourselves
setStateChanged(true);
var id = state.other;
state.other = false;
save(id);
@ -151,7 +152,7 @@ define([
// is already saving
requestSave = function (id, cb) {
if (state.other || state.me) { return void cb(false); } // save in progress
debug('Integration send ISAVE');
debug('Integration send ISAVE', id);
alreadySaved = false; // someone may have saved while we were waiting for our callback
execCommand('SEND', {
msg: 'ISAVE',

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,9 @@
define([
'/common/common-util.js',
'/components/tweetnacl/nacl-fast.min.js',
], function (Util) {
var Nacl = window.nacl;
'/components/chainpad-crypto/crypto.js'
], function (Util, Crypto) {
//var Nacl = window.nacl;
//var PARANOIA = true;
var plainChunkLength = 128 * 1024;
@ -91,7 +92,7 @@ define([
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
var metaChunk = Crypto.CryptoAgility.secretboxOpen(metaBox, nonce, key);
increment(nonce);
try {
@ -116,7 +117,7 @@ define([
var box = new Uint8Array(u8.subarray(start, end));
// decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
var plaintext = Crypto.CryptoAgility.secretboxOpen(box, nonce, key);
increment(nonce);
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
@ -184,7 +185,7 @@ define([
if (state === 0) { // metadata...
part = new Uint8Array(plaintext);
box = Nacl.secretbox(part, nonce, key);
box = Crypto.CryptoAgility.secretbox(part, nonce, key);
increment(nonce);
if (box.length > 65535) {
@ -204,7 +205,7 @@ define([
end = start + plainChunkLength;
part = u8.subarray(start, end);
box = Nacl.secretbox(part, nonce, key);
box = Crypto.CryptoAgility.secretbox(part, nonce, key);
increment(nonce);
i++;

View File

@ -4,12 +4,13 @@
define([
'/common/common-util.js',
'/components/tweetnacl/nacl-fast.min.js'
], function (Util) {
'/components/tweetnacl/nacl-fast.min.js',
'/components/chainpad-crypto/crypto.js',
], function (Util, Nacl, Crypto) {
var Handler = {};
var Nacl = window.nacl;
//var Nacl = window.nacl;
Handler.formCommandHandlers = function(sframeChan, Utils, nThen, Cryptpad) {
sframeChan.on('EV_EXPORT_SHEET', function (data) {
if (!data || !Array.isArray(data.content)) { return; }
@ -33,8 +34,8 @@ define([
};
var anonProof = function (channel, theirPub, anonKeys) {
var u8_plain = Util.decodeUTF8(channel);
var u8_nonce = Nacl.randomBytes(Nacl.box.nonceLength);
var u8_cipher = Nacl.box(
var u8_nonce = Crypto.CryptoAgility.bytes(Crypto.CryptoAgility.boxNonceLength());
var u8_cipher = Crypto.CryptoAgility.box(
u8_plain,
u8_nonce,
Util.decodeBase64(theirPub),
@ -113,6 +114,7 @@ define([
var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, {
validateKey: data.validateKey,
ephemeral_private: Util.decodeBase64(answer.curvePrivate),
ephemeral_kem_private: Util.decodeBase64(answer.kemPrivate),
my_private: Util.decodeBase64(finalKeys.curvePrivate),
their_public: Util.decodeBase64(data.publicKey)
});
@ -143,9 +145,9 @@ define([
var proofTxt = proofObj.proof;
try {
var u8_bundle = Util.decodeBase64(proofTxt);
var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength);
var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength);
var u8_plain = Nacl.box.open(
var u8_nonce = u8_slice(u8_bundle, 0, Crypto.CryptoAgility.boxNonceLength());
var u8_cipher = u8_slice(u8_bundle, Crypto.CryptoAgility.boxNonceLength());
var u8_plain = Crypto.CryptoAgility.boxOpen(
u8_cipher,
u8_nonce,
Util.decodeBase64(pub),
@ -327,7 +329,7 @@ define([
var keys = Utils.secret && Utils.secret.keys;
myKeys.signingKey = keys.secondarySignKey;
var ephemeral_keypair = Nacl.box.keyPair();
var ephemeral_keypair = Crypto.CryptoAgility.curveKeyPair();
var ephemeral_private = Util.encodeBase64(ephemeral_keypair.secretKey);
myKeys.ephemeral_keypair = ephemeral_keypair;

View File

@ -20,14 +20,15 @@ define([
'/common/outer/http-command.js',
'/components/tweetnacl/nacl-fast.min.js',
'/components/chainpad-crypto/crypto.js',
'css!/components/components-font-awesome/css/font-awesome.min.css',
], function ($, Sortify, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback,
Clipboard, LocalStore, Block, ServerCommand) {
Clipboard, LocalStore, Block, ServerCommand, Crypto) {
if (window.top !== window) { return; }
var Messages = Cryptpad.Messages;
var Nacl = window.nacl;
//var Nacl = window.nacl;
$(function () {
if (LocalStore.isLoggedIn()) {
@ -78,7 +79,7 @@ define([
date: new Date().toISOString(),
blockId: Util.encodeBase64(pub),
};
var proof = Nacl.sign.detached(Util.decodeUTF8(Sortify(toSign)), sec);
var proof = Crypto.CryptoAgility.signDetached(Util.decodeUTF8(Sortify(toSign)), sec);
toSign.proof = Util.encodeBase64(proof);
proofStr = JSON.stringify(toSign, 0, 2);
$mfaProof.html(proofStr);

View File

@ -326,6 +326,7 @@ define([
channel: d.channel,
crypto: crypto,
validateKey: rosterKeys.teamEdPublic,
dsaValidateKey: rosterKeys.teamDsaPublic, // Add PQC validation key for ML-DSA
Cache: Cache,
noChainPad: true,
onCacheReady: onCacheReady,

View File

@ -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); }

View File

@ -355,6 +355,7 @@ define([
toolbar: APP.toolbar,
APP: driveAPP,
edPublic: APP.teamEdPublic,
dsaPublic: APP.teamDsaPublic,
editKey: teamData.secondaryKey
});
APP.drive = drive;