mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-12 19:49:59 +05:00
Merge pull request #2036 from Iulian-Tudor/pqc-hybrid
Crypto Agility + PQC prototype
This commit is contained in:
commit
bc8ef4c411
1
.gitignore
vendored
1
.gitignore
vendored
@ -272,3 +272,4 @@ $RECYCLE.BIN/
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
/.idea/
|
||||
|
||||
@ -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, () => {});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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: {},
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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
25
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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.");
|
||||
|
||||
76
scripts/pqc-browser-convert.js
Normal file
76
scripts/pqc-browser-convert.js
Normal 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);
|
||||
});
|
||||
@ -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');
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
@ -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: {}
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
}());
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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',
|
||||
|
||||
2
www/common/worker.bundle.min.js
vendored
2
www/common/worker.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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++;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -355,6 +355,7 @@ define([
|
||||
toolbar: APP.toolbar,
|
||||
APP: driveAPP,
|
||||
edPublic: APP.teamEdPublic,
|
||||
dsaPublic: APP.teamDsaPublic,
|
||||
editKey: teamData.secondaryKey
|
||||
});
|
||||
APP.drive = drive;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user