fca-riyad 37.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +325 -0
- package/DOCS.md +2712 -0
- package/README.md +410 -0
- package/func/checkUpdate.js +7 -0
- package/func/logAdapter.js +33 -0
- package/func/logger.js +48 -0
- package/index.d.ts +751 -0
- package/index.js +22 -0
- package/module/config.js +40 -0
- package/module/login.js +133 -0
- package/module/loginHelper.js +1359 -0
- package/module/options.js +44 -0
- package/package.json +95 -0
- package/src/api/action/addExternalModule.js +25 -0
- package/src/api/action/changeAvatar.js +137 -0
- package/src/api/action/changeBio.js +75 -0
- package/src/api/action/enableAutoSaveAppState.js +73 -0
- package/src/api/action/getCurrentUserID.js +7 -0
- package/src/api/action/handleFriendRequest.js +57 -0
- package/src/api/action/logout.js +76 -0
- package/src/api/action/refreshFb_dtsg.js +48 -0
- package/src/api/action/setPostReaction.js +106 -0
- package/src/api/action/unfriend.js +54 -0
- package/src/api/http/httpGet.js +46 -0
- package/src/api/http/httpPost.js +52 -0
- package/src/api/http/postFormData.js +47 -0
- package/src/api/messaging/addUserToGroup.js +68 -0
- package/src/api/messaging/changeAdminStatus.js +126 -0
- package/src/api/messaging/changeArchivedStatus.js +55 -0
- package/src/api/messaging/changeBlockedStatus.js +48 -0
- package/src/api/messaging/changeGroupImage.js +91 -0
- package/src/api/messaging/changeNickname.js +70 -0
- package/src/api/messaging/changeThreadColor.js +79 -0
- package/src/api/messaging/changeThreadEmoji.js +111 -0
- package/src/api/messaging/createNewGroup.js +88 -0
- package/src/api/messaging/createPoll.js +46 -0
- package/src/api/messaging/createThemeAI.js +98 -0
- package/src/api/messaging/deleteMessage.js +136 -0
- package/src/api/messaging/deleteThread.js +56 -0
- package/src/api/messaging/editMessage.js +90 -0
- package/src/api/messaging/forwardAttachment.js +57 -0
- package/src/api/messaging/forwardMessage.js +134 -0
- package/src/api/messaging/getEmojiUrl.js +29 -0
- package/src/api/messaging/getFriendsList.js +82 -0
- package/src/api/messaging/getMessage.js +829 -0
- package/src/api/messaging/getThemePictures.js +62 -0
- package/src/api/messaging/handleMessageRequest.js +65 -0
- package/src/api/messaging/markAsDelivered.js +57 -0
- package/src/api/messaging/markAsRead.js +88 -0
- package/src/api/messaging/markAsReadAll.js +49 -0
- package/src/api/messaging/markAsSeen.js +61 -0
- package/src/api/messaging/muteThread.js +50 -0
- package/src/api/messaging/pinMessage.js +115 -0
- package/src/api/messaging/removeUserFromGroup.js +62 -0
- package/src/api/messaging/resolvePhotoUrl.js +43 -0
- package/src/api/messaging/scheduler.js +264 -0
- package/src/api/messaging/searchForThread.js +53 -0
- package/src/api/messaging/sendBroadcast.js +93 -0
- package/src/api/messaging/sendMessage.js +269 -0
- package/src/api/messaging/sendTypingIndicator.js +74 -0
- package/src/api/messaging/sessionGuard.js +130 -0
- package/src/api/messaging/setMessageReaction.js +95 -0
- package/src/api/messaging/setTitle.js +124 -0
- package/src/api/messaging/shareContact.js +49 -0
- package/src/api/messaging/threadColors.js +128 -0
- package/src/api/messaging/unsendMessage.js +81 -0
- package/src/api/messaging/uploadAttachment.js +492 -0
- package/src/api/socket/OldMessage.js +186 -0
- package/src/api/socket/core/connectMqtt.js +269 -0
- package/src/api/socket/core/emitAuth.js +103 -0
- package/src/api/socket/core/getSeqID.js +321 -0
- package/src/api/socket/core/getTaskResponseData.js +25 -0
- package/src/api/socket/core/parseDelta.js +387 -0
- package/src/api/socket/detail/buildStream.js +215 -0
- package/src/api/socket/detail/constants.js +28 -0
- package/src/api/socket/e2ee/crypto.js +173 -0
- package/src/api/socket/e2ee/e2ee/crypto.js +173 -0
- package/src/api/socket/e2ee/e2ee/index.js +421 -0
- package/src/api/socket/e2ee/e2ee/proto/ArmadilloApplication.proto +281 -0
- package/src/api/socket/e2ee/e2ee/proto/ArmadilloICDC.proto +14 -0
- package/src/api/socket/e2ee/e2ee/proto/ConsumerApplication.proto +232 -0
- package/src/api/socket/e2ee/e2ee/proto/MessageApplication.proto +82 -0
- package/src/api/socket/e2ee/e2ee/proto/MessageTransport.proto +77 -0
- package/src/api/socket/e2ee/e2ee/proto/WACommon.proto +66 -0
- package/src/api/socket/e2ee/e2ee/proto/WAMediaTransport.proto +176 -0
- package/src/api/socket/e2ee/e2ee/proto/proto-writer.ts +76 -0
- package/src/api/socket/e2ee/e2ee/protocol.js +196 -0
- package/src/api/socket/e2ee/e2ee/ratchet.js +219 -0
- package/src/api/socket/e2ee/e2ee/store.js +182 -0
- package/src/api/socket/e2ee/e2ee/vendor/fb-e2ee.cjs +9162 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/ArmadilloApplication.proto +281 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/ArmadilloICDC.proto +14 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/ConsumerApplication.proto +232 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/MessageApplication.proto +82 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/MessageTransport.proto +77 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/WACommon.proto +66 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/WAMediaTransport.proto +176 -0
- package/src/api/socket/e2ee/e2ee/vendor/proto/proto-writer.ts +76 -0
- package/src/api/socket/e2ee/index.js +437 -0
- package/src/api/socket/e2ee/proto/ArmadilloApplication.proto +281 -0
- package/src/api/socket/e2ee/proto/ArmadilloICDC.proto +14 -0
- package/src/api/socket/e2ee/proto/ConsumerApplication.proto +232 -0
- package/src/api/socket/e2ee/proto/MessageApplication.proto +82 -0
- package/src/api/socket/e2ee/proto/MessageTransport.proto +77 -0
- package/src/api/socket/e2ee/proto/WACommon.proto +66 -0
- package/src/api/socket/e2ee/proto/WAMediaTransport.proto +176 -0
- package/src/api/socket/e2ee/proto/proto-writer.ts +76 -0
- package/src/api/socket/e2ee/protocol.js +196 -0
- package/src/api/socket/e2ee/ratchet.js +219 -0
- package/src/api/socket/e2ee/store.js +182 -0
- package/src/api/socket/e2ee/vendor/fb-e2ee.cjs +9162 -0
- package/src/api/socket/e2ee/vendor/proto/ArmadilloApplication.proto +281 -0
- package/src/api/socket/e2ee/vendor/proto/ArmadilloICDC.proto +14 -0
- package/src/api/socket/e2ee/vendor/proto/ConsumerApplication.proto +232 -0
- package/src/api/socket/e2ee/vendor/proto/MessageApplication.proto +82 -0
- package/src/api/socket/e2ee/vendor/proto/MessageTransport.proto +77 -0
- package/src/api/socket/e2ee/vendor/proto/WACommon.proto +66 -0
- package/src/api/socket/e2ee/vendor/proto/WAMediaTransport.proto +176 -0
- package/src/api/socket/e2ee/vendor/proto/proto-writer.ts +76 -0
- package/src/api/socket/listenE2EE.js +75 -0
- package/src/api/socket/listenMqtt.js +388 -0
- package/src/api/socket/middleware/index.js +216 -0
- package/src/api/socket/sendMessage.js +309 -0
- package/src/api/socket/sendMessageMqtt.js +69 -0
- package/src/api/threads/getThreadHistory.js +664 -0
- package/src/api/threads/getThreadInfo.js +296 -0
- package/src/api/threads/getThreadList.js +293 -0
- package/src/api/threads/getThreadPictures.js +78 -0
- package/src/api/users/getUserID.js +65 -0
- package/src/api/users/getUserInfo.js +402 -0
- package/src/api/users/getUserInfoV2.js +134 -0
- package/src/app/MessengerBot.js +209 -0
- package/src/app/MessengerContext.js +32 -0
- package/src/app/createFcaClient.js +136 -0
- package/src/app/threadInfoRealtimeSync.js +284 -0
- package/src/core/sendReqMqtt.js +96 -0
- package/src/database/helpers.js +53 -0
- package/src/database/models/index.js +88 -0
- package/src/database/models/thread.js +50 -0
- package/src/database/models/user.js +46 -0
- package/src/database/threadData.js +94 -0
- package/src/database/userData.js +98 -0
- package/src/remote/remoteClient.js +123 -0
- package/src/utils/broadcast.js +51 -0
- package/src/utils/client.js +10 -0
- package/src/utils/constants.js +23 -0
- package/src/utils/cookies.js +68 -0
- package/src/utils/format/attachment.js +357 -0
- package/src/utils/format/cookie.js +9 -0
- package/src/utils/format/date.js +50 -0
- package/src/utils/format/decode.js +44 -0
- package/src/utils/format/delta.js +194 -0
- package/src/utils/format/ids.js +64 -0
- package/src/utils/format/index.js +64 -0
- package/src/utils/format/message.js +88 -0
- package/src/utils/format/presence.js +132 -0
- package/src/utils/format/readTyp.js +44 -0
- package/src/utils/format/thread.js +42 -0
- package/src/utils/format/utils.js +141 -0
- package/src/utils/headers.js +115 -0
- package/src/utils/loginParser/autoLogin.js +125 -0
- package/src/utils/loginParser/helpers.js +43 -0
- package/src/utils/loginParser/index.js +10 -0
- package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
- package/src/utils/loginParser/textUtils.js +28 -0
- package/src/utils/nexca-logger.js +144 -0
- package/src/utils/nexca-utils.js +686 -0
- package/src/utils/request/client.js +26 -0
- package/src/utils/request/config.js +23 -0
- package/src/utils/request/defaults.js +46 -0
- package/src/utils/request/helpers.js +46 -0
- package/src/utils/request/index.js +17 -0
- package/src/utils/request/methods.js +163 -0
- package/src/utils/request/proxy.js +21 -0
- package/src/utils/request/retry.js +77 -0
- package/src/utils/request/sanitize.js +49 -0
- package/src/utils/versionCheck.js +47 -0
- package/src/vendor/fca-unofficial/.github/workflows/nodejs.yml +26 -0
- package/src/vendor/fca-unofficial/.github/workflows/npmpublish.yml +30 -0
- package/src/vendor/fca-unofficial/.travis.yml +6 -0
- package/src/vendor/fca-unofficial/DOCS.md +1731 -0
- package/src/vendor/fca-unofficial/LICENSE-MIT +21 -0
- package/src/vendor/fca-unofficial/README.md +225 -0
- package/src/vendor/fca-unofficial/index.js +619 -0
- package/src/vendor/fca-unofficial/package.json +76 -0
- package/src/vendor/fca-unofficial/src/addExternalModule.js +19 -0
- package/src/vendor/fca-unofficial/src/addUserToGroup.js +113 -0
- package/src/vendor/fca-unofficial/src/changeAdminStatus.js +79 -0
- package/src/vendor/fca-unofficial/src/changeArchivedStatus.js +55 -0
- package/src/vendor/fca-unofficial/src/changeBio.js +77 -0
- package/src/vendor/fca-unofficial/src/changeBlockedStatus.js +47 -0
- package/src/vendor/fca-unofficial/src/changeGroupImage.js +129 -0
- package/src/vendor/fca-unofficial/src/changeNickname.js +59 -0
- package/src/vendor/fca-unofficial/src/changeThreadColor.js +71 -0
- package/src/vendor/fca-unofficial/src/changeThreadEmoji.js +55 -0
- package/src/vendor/fca-unofficial/src/createNewGroup.js +86 -0
- package/src/vendor/fca-unofficial/src/createPoll.js +71 -0
- package/src/vendor/fca-unofficial/src/deleteMessage.js +56 -0
- package/src/vendor/fca-unofficial/src/deleteThread.js +56 -0
- package/src/vendor/fca-unofficial/src/forwardAttachment.js +60 -0
- package/src/vendor/fca-unofficial/src/getCurrentUserID.js +7 -0
- package/src/vendor/fca-unofficial/src/getEmojiUrl.js +29 -0
- package/src/vendor/fca-unofficial/src/getFriendsList.js +84 -0
- package/src/vendor/fca-unofficial/src/getThreadHistory.js +645 -0
- package/src/vendor/fca-unofficial/src/getThreadHistoryDeprecated.js +93 -0
- package/src/vendor/fca-unofficial/src/getThreadInfo.js +201 -0
- package/src/vendor/fca-unofficial/src/getThreadInfoDeprecated.js +80 -0
- package/src/vendor/fca-unofficial/src/getThreadList.js +238 -0
- package/src/vendor/fca-unofficial/src/getThreadListDeprecated.js +75 -0
- package/src/vendor/fca-unofficial/src/getThreadPictures.js +79 -0
- package/src/vendor/fca-unofficial/src/getUserID.js +66 -0
- package/src/vendor/fca-unofficial/src/getUserInfo.js +72 -0
- package/src/vendor/fca-unofficial/src/handleFriendRequest.js +61 -0
- package/src/vendor/fca-unofficial/src/handleMessageRequest.js +65 -0
- package/src/vendor/fca-unofficial/src/httpGet.js +52 -0
- package/src/vendor/fca-unofficial/src/httpPost.js +52 -0
- package/src/vendor/fca-unofficial/src/listenMqtt.js +788 -0
- package/src/vendor/fca-unofficial/src/logout.js +75 -0
- package/src/vendor/fca-unofficial/src/markAsDelivered.js +58 -0
- package/src/vendor/fca-unofficial/src/markAsRead.js +80 -0
- package/src/vendor/fca-unofficial/src/markAsReadAll.js +50 -0
- package/src/vendor/fca-unofficial/src/markAsSeen.js +59 -0
- package/src/vendor/fca-unofficial/src/muteThread.js +52 -0
- package/src/vendor/fca-unofficial/src/removeUserFromGroup.js +79 -0
- package/src/vendor/fca-unofficial/src/resolvePhotoUrl.js +45 -0
- package/src/vendor/fca-unofficial/src/searchForThread.js +53 -0
- package/src/vendor/fca-unofficial/src/sendMessage.js +459 -0
- package/src/vendor/fca-unofficial/src/sendTypingIndicator.js +103 -0
- package/src/vendor/fca-unofficial/src/setMessageReaction.js +117 -0
- package/src/vendor/fca-unofficial/src/setPostReaction.js +76 -0
- package/src/vendor/fca-unofficial/src/setTitle.js +86 -0
- package/src/vendor/fca-unofficial/src/threadColors.js +57 -0
- package/src/vendor/fca-unofficial/src/unfriend.js +52 -0
- package/src/vendor/fca-unofficial/src/unsendMessage.js +49 -0
- package/src/vendor/fca-unofficial/test/data/shareAttach.js +146 -0
- package/src/vendor/fca-unofficial/test/data/something.mov +0 -0
- package/src/vendor/fca-unofficial/test/data/test.png +0 -0
- package/src/vendor/fca-unofficial/test/data/test.txt +7 -0
- package/src/vendor/fca-unofficial/test/example-config.json +18 -0
- package/src/vendor/fca-unofficial/test/test-page.js +140 -0
- package/src/vendor/fca-unofficial/test/test.js +385 -0
- package/src/vendor/fca-unofficial/utils.js +1358 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NEXCA E2EE Crypto — All primitives using Node.js built-in crypto only.
|
|
5
|
+
* Implements: X25519 DH, HKDF-SHA256, AES-256-GCM, HMAC-SHA256
|
|
6
|
+
* Node.js >= 15 required (crypto.hkdfSync, crypto.generateKeyPairSync x25519).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
|
|
11
|
+
// ─────────────────────────────────────────────────────────
|
|
12
|
+
// X25519 Key Management
|
|
13
|
+
// ─────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function generateKeyPair() {
|
|
16
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("x25519", {
|
|
17
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" },
|
|
18
|
+
publicKeyEncoding: { type: "spki", format: "der" }
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
privateKey,
|
|
22
|
+
publicKey,
|
|
23
|
+
publicKeyRaw: exportRawPublicKey(publicKey)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exportRawPublicKey(spkiDer) {
|
|
28
|
+
// X25519 SPKI DER: 12 bytes header + 32 bytes key
|
|
29
|
+
return Buffer.from(spkiDer).slice(-32);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function importPublicKey(raw32bytes) {
|
|
33
|
+
if (raw32bytes.length !== 32) throw new Error("X25519 public key must be 32 bytes");
|
|
34
|
+
// Build minimal SPKI wrapper for X25519
|
|
35
|
+
const header = Buffer.from("302a300506032b656e032100", "hex");
|
|
36
|
+
return Buffer.concat([header, raw32bytes]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function dh(privateKeyPkcs8Der, peerPublicRaw32) {
|
|
40
|
+
const privKey = crypto.createPrivateKey({ key: Buffer.from(privateKeyPkcs8Der), format: "der", type: "pkcs8" });
|
|
41
|
+
const pubKeyDer = importPublicKey(Buffer.from(peerPublicRaw32));
|
|
42
|
+
const pubKey = crypto.createPublicKey({ key: pubKeyDer, format: "der", type: "spki" });
|
|
43
|
+
return crypto.diffieHellman({ privateKey: privKey, publicKey: pubKey });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─────────────────────────────────────────────────────────
|
|
47
|
+
// HKDF-SHA256
|
|
48
|
+
// ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function hkdf(ikm, length, salt, info) {
|
|
51
|
+
if (!salt) salt = Buffer.alloc(32, 0);
|
|
52
|
+
if (!info) info = Buffer.alloc(0);
|
|
53
|
+
if (typeof info === "string") info = Buffer.from(info, "utf8");
|
|
54
|
+
return Buffer.from(crypto.hkdfSync("sha256", ikm, salt, info, length));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hkdfExtract(salt, ikm) {
|
|
58
|
+
return crypto.createHmac("sha256", salt).update(ikm).digest();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hkdfExpand(prk, info, length) {
|
|
62
|
+
const results = [];
|
|
63
|
+
let t = Buffer.alloc(0);
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (results.reduce((s, b) => s + b.length, 0) < length) {
|
|
66
|
+
i++;
|
|
67
|
+
const hmac = crypto.createHmac("sha256", prk);
|
|
68
|
+
hmac.update(t);
|
|
69
|
+
if (info) hmac.update(info);
|
|
70
|
+
hmac.update(Buffer.from([i]));
|
|
71
|
+
t = hmac.digest();
|
|
72
|
+
results.push(t);
|
|
73
|
+
}
|
|
74
|
+
return Buffer.concat(results).slice(0, length);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─────────────────────────────────────────────────────────
|
|
78
|
+
// AES-256-GCM
|
|
79
|
+
// ─────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function encrypt(key32, plaintext, aad) {
|
|
82
|
+
if (key32.length !== 32) throw new Error("AES-GCM key must be 32 bytes");
|
|
83
|
+
const iv = crypto.randomBytes(12);
|
|
84
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key32, iv);
|
|
85
|
+
if (aad) cipher.setAAD(aad);
|
|
86
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
87
|
+
const authTag = cipher.getAuthTag();
|
|
88
|
+
return Buffer.concat([iv, authTag, encrypted]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function decrypt(key32, ciphertext, aad) {
|
|
92
|
+
if (key32.length !== 32) throw new Error("AES-GCM key must be 32 bytes");
|
|
93
|
+
if (ciphertext.length < 28) throw new Error("Ciphertext too short");
|
|
94
|
+
const iv = ciphertext.slice(0, 12);
|
|
95
|
+
const authTag = ciphertext.slice(12, 28);
|
|
96
|
+
const data = ciphertext.slice(28);
|
|
97
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key32, iv);
|
|
98
|
+
decipher.setAuthTag(authTag);
|
|
99
|
+
if (aad) decipher.setAAD(aad);
|
|
100
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─────────────────────────────────────────────────────────
|
|
104
|
+
// HMAC-SHA256
|
|
105
|
+
// ─────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function hmacSha256(key, data) {
|
|
108
|
+
return crypto.createHmac("sha256", key).update(data).digest();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function constantTimeEqual(a, b) {
|
|
112
|
+
if (a.length !== b.length) return false;
|
|
113
|
+
return crypto.timingSafeEqual(a, b);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────
|
|
117
|
+
// X3DH Key Agreement (Extended Triple Diffie-Hellman)
|
|
118
|
+
// ─────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Sender side of X3DH.
|
|
122
|
+
* @param {object} senderIdKey - { privateKey, publicKey, publicKeyRaw } of sender identity key
|
|
123
|
+
* @param {object} ephemeralKey - { privateKey, publicKey, publicKeyRaw } of ephemeral key
|
|
124
|
+
* @param {Buffer} recipientIdPub - recipient's identity public key (raw 32 bytes)
|
|
125
|
+
* @param {Buffer} recipientSpkPub - recipient's signed pre-key (raw 32 bytes)
|
|
126
|
+
* @param {Buffer} [recipientOpkPub] - recipient's one-time pre-key (raw 32 bytes), optional
|
|
127
|
+
* @returns {Buffer} 32-byte shared master secret
|
|
128
|
+
*/
|
|
129
|
+
function x3dhSend(senderIdKey, ephemeralKey, recipientIdPub, recipientSpkPub, recipientOpkPub) {
|
|
130
|
+
const dh1 = dh(senderIdKey.privateKey, recipientSpkPub);
|
|
131
|
+
const dh2 = dh(ephemeralKey.privateKey, recipientIdPub);
|
|
132
|
+
const dh3 = dh(ephemeralKey.privateKey, recipientSpkPub);
|
|
133
|
+
const parts = recipientOpkPub
|
|
134
|
+
? [dh1, dh2, dh3, dh(ephemeralKey.privateKey, recipientOpkPub)]
|
|
135
|
+
: [dh1, dh2, dh3];
|
|
136
|
+
const ikm = Buffer.concat(parts);
|
|
137
|
+
const F = Buffer.alloc(32, 0xFF);
|
|
138
|
+
const salt = Buffer.alloc(32, 0);
|
|
139
|
+
return hkdf(Buffer.concat([F, ikm]), 32, salt, Buffer.from("WhisperText", "utf8"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Receiver side of X3DH.
|
|
144
|
+
*/
|
|
145
|
+
function x3dhReceive(recipientIdKey, recipientSpkKey, recipientOpkKey, senderIdPub, senderEphemeralPub) {
|
|
146
|
+
const dh1 = dh(recipientSpkKey.privateKey, senderIdPub);
|
|
147
|
+
const dh2 = dh(recipientIdKey.privateKey, senderEphemeralPub);
|
|
148
|
+
const dh3 = dh(recipientSpkKey.privateKey, senderEphemeralPub);
|
|
149
|
+
const parts = recipientOpkKey
|
|
150
|
+
? [dh1, dh2, dh3, dh(recipientOpkKey.privateKey, senderEphemeralPub)]
|
|
151
|
+
: [dh1, dh2, dh3];
|
|
152
|
+
const ikm = Buffer.concat(parts);
|
|
153
|
+
const F = Buffer.alloc(32, 0xFF);
|
|
154
|
+
const salt = Buffer.alloc(32, 0);
|
|
155
|
+
return hkdf(Buffer.concat([F, ikm]), 32, salt, Buffer.from("WhisperText", "utf8"));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
generateKeyPair,
|
|
160
|
+
exportRawPublicKey,
|
|
161
|
+
importPublicKey,
|
|
162
|
+
dh,
|
|
163
|
+
hkdf,
|
|
164
|
+
hkdfExtract,
|
|
165
|
+
hkdfExpand,
|
|
166
|
+
encrypt,
|
|
167
|
+
decrypt,
|
|
168
|
+
hmacSha256,
|
|
169
|
+
constantTimeEqual,
|
|
170
|
+
x3dhSend,
|
|
171
|
+
x3dhReceive,
|
|
172
|
+
randomBytes: crypto.randomBytes.bind(crypto)
|
|
173
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NEXCA E2EE Crypto — All primitives using Node.js built-in crypto only.
|
|
5
|
+
* Implements: X25519 DH, HKDF-SHA256, AES-256-GCM, HMAC-SHA256
|
|
6
|
+
* Node.js >= 15 required (crypto.hkdfSync, crypto.generateKeyPairSync x25519).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
|
|
11
|
+
// ─────────────────────────────────────────────────────────
|
|
12
|
+
// X25519 Key Management
|
|
13
|
+
// ─────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function generateKeyPair() {
|
|
16
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("x25519", {
|
|
17
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" },
|
|
18
|
+
publicKeyEncoding: { type: "spki", format: "der" }
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
privateKey,
|
|
22
|
+
publicKey,
|
|
23
|
+
publicKeyRaw: exportRawPublicKey(publicKey)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exportRawPublicKey(spkiDer) {
|
|
28
|
+
// X25519 SPKI DER: 12 bytes header + 32 bytes key
|
|
29
|
+
return Buffer.from(spkiDer).slice(-32);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function importPublicKey(raw32bytes) {
|
|
33
|
+
if (raw32bytes.length !== 32) throw new Error("X25519 public key must be 32 bytes");
|
|
34
|
+
// Build minimal SPKI wrapper for X25519
|
|
35
|
+
const header = Buffer.from("302a300506032b656e032100", "hex");
|
|
36
|
+
return Buffer.concat([header, raw32bytes]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function dh(privateKeyPkcs8Der, peerPublicRaw32) {
|
|
40
|
+
const privKey = crypto.createPrivateKey({ key: Buffer.from(privateKeyPkcs8Der), format: "der", type: "pkcs8" });
|
|
41
|
+
const pubKeyDer = importPublicKey(Buffer.from(peerPublicRaw32));
|
|
42
|
+
const pubKey = crypto.createPublicKey({ key: pubKeyDer, format: "der", type: "spki" });
|
|
43
|
+
return crypto.diffieHellman({ privateKey: privKey, publicKey: pubKey });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─────────────────────────────────────────────────────────
|
|
47
|
+
// HKDF-SHA256
|
|
48
|
+
// ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function hkdf(ikm, length, salt, info) {
|
|
51
|
+
if (!salt) salt = Buffer.alloc(32, 0);
|
|
52
|
+
if (!info) info = Buffer.alloc(0);
|
|
53
|
+
if (typeof info === "string") info = Buffer.from(info, "utf8");
|
|
54
|
+
return Buffer.from(crypto.hkdfSync("sha256", ikm, salt, info, length));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hkdfExtract(salt, ikm) {
|
|
58
|
+
return crypto.createHmac("sha256", salt).update(ikm).digest();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hkdfExpand(prk, info, length) {
|
|
62
|
+
const results = [];
|
|
63
|
+
let t = Buffer.alloc(0);
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (results.reduce((s, b) => s + b.length, 0) < length) {
|
|
66
|
+
i++;
|
|
67
|
+
const hmac = crypto.createHmac("sha256", prk);
|
|
68
|
+
hmac.update(t);
|
|
69
|
+
if (info) hmac.update(info);
|
|
70
|
+
hmac.update(Buffer.from([i]));
|
|
71
|
+
t = hmac.digest();
|
|
72
|
+
results.push(t);
|
|
73
|
+
}
|
|
74
|
+
return Buffer.concat(results).slice(0, length);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─────────────────────────────────────────────────────────
|
|
78
|
+
// AES-256-GCM
|
|
79
|
+
// ─────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function encrypt(key32, plaintext, aad) {
|
|
82
|
+
if (key32.length !== 32) throw new Error("AES-GCM key must be 32 bytes");
|
|
83
|
+
const iv = crypto.randomBytes(12);
|
|
84
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key32, iv);
|
|
85
|
+
if (aad) cipher.setAAD(aad);
|
|
86
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
87
|
+
const authTag = cipher.getAuthTag();
|
|
88
|
+
return Buffer.concat([iv, authTag, encrypted]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function decrypt(key32, ciphertext, aad) {
|
|
92
|
+
if (key32.length !== 32) throw new Error("AES-GCM key must be 32 bytes");
|
|
93
|
+
if (ciphertext.length < 28) throw new Error("Ciphertext too short");
|
|
94
|
+
const iv = ciphertext.slice(0, 12);
|
|
95
|
+
const authTag = ciphertext.slice(12, 28);
|
|
96
|
+
const data = ciphertext.slice(28);
|
|
97
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key32, iv);
|
|
98
|
+
decipher.setAuthTag(authTag);
|
|
99
|
+
if (aad) decipher.setAAD(aad);
|
|
100
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─────────────────────────────────────────────────────────
|
|
104
|
+
// HMAC-SHA256
|
|
105
|
+
// ─────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function hmacSha256(key, data) {
|
|
108
|
+
return crypto.createHmac("sha256", key).update(data).digest();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function constantTimeEqual(a, b) {
|
|
112
|
+
if (a.length !== b.length) return false;
|
|
113
|
+
return crypto.timingSafeEqual(a, b);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────
|
|
117
|
+
// X3DH Key Agreement (Extended Triple Diffie-Hellman)
|
|
118
|
+
// ─────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Sender side of X3DH.
|
|
122
|
+
* @param {object} senderIdKey - { privateKey, publicKey, publicKeyRaw } of sender identity key
|
|
123
|
+
* @param {object} ephemeralKey - { privateKey, publicKey, publicKeyRaw } of ephemeral key
|
|
124
|
+
* @param {Buffer} recipientIdPub - recipient's identity public key (raw 32 bytes)
|
|
125
|
+
* @param {Buffer} recipientSpkPub - recipient's signed pre-key (raw 32 bytes)
|
|
126
|
+
* @param {Buffer} [recipientOpkPub] - recipient's one-time pre-key (raw 32 bytes), optional
|
|
127
|
+
* @returns {Buffer} 32-byte shared master secret
|
|
128
|
+
*/
|
|
129
|
+
function x3dhSend(senderIdKey, ephemeralKey, recipientIdPub, recipientSpkPub, recipientOpkPub) {
|
|
130
|
+
const dh1 = dh(senderIdKey.privateKey, recipientSpkPub);
|
|
131
|
+
const dh2 = dh(ephemeralKey.privateKey, recipientIdPub);
|
|
132
|
+
const dh3 = dh(ephemeralKey.privateKey, recipientSpkPub);
|
|
133
|
+
const parts = recipientOpkPub
|
|
134
|
+
? [dh1, dh2, dh3, dh(ephemeralKey.privateKey, recipientOpkPub)]
|
|
135
|
+
: [dh1, dh2, dh3];
|
|
136
|
+
const ikm = Buffer.concat(parts);
|
|
137
|
+
const F = Buffer.alloc(32, 0xFF);
|
|
138
|
+
const salt = Buffer.alloc(32, 0);
|
|
139
|
+
return hkdf(Buffer.concat([F, ikm]), 32, salt, Buffer.from("WhisperText", "utf8"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Receiver side of X3DH.
|
|
144
|
+
*/
|
|
145
|
+
function x3dhReceive(recipientIdKey, recipientSpkKey, recipientOpkKey, senderIdPub, senderEphemeralPub) {
|
|
146
|
+
const dh1 = dh(recipientSpkKey.privateKey, senderIdPub);
|
|
147
|
+
const dh2 = dh(recipientIdKey.privateKey, senderEphemeralPub);
|
|
148
|
+
const dh3 = dh(recipientSpkKey.privateKey, senderEphemeralPub);
|
|
149
|
+
const parts = recipientOpkKey
|
|
150
|
+
? [dh1, dh2, dh3, dh(recipientOpkKey.privateKey, senderEphemeralPub)]
|
|
151
|
+
: [dh1, dh2, dh3];
|
|
152
|
+
const ikm = Buffer.concat(parts);
|
|
153
|
+
const F = Buffer.alloc(32, 0xFF);
|
|
154
|
+
const salt = Buffer.alloc(32, 0);
|
|
155
|
+
return hkdf(Buffer.concat([F, ikm]), 32, salt, Buffer.from("WhisperText", "utf8"));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
generateKeyPair,
|
|
160
|
+
exportRawPublicKey,
|
|
161
|
+
importPublicKey,
|
|
162
|
+
dh,
|
|
163
|
+
hkdf,
|
|
164
|
+
hkdfExtract,
|
|
165
|
+
hkdfExpand,
|
|
166
|
+
encrypt,
|
|
167
|
+
decrypt,
|
|
168
|
+
hmacSha256,
|
|
169
|
+
constantTimeEqual,
|
|
170
|
+
x3dhSend,
|
|
171
|
+
x3dhReceive,
|
|
172
|
+
randomBytes: crypto.randomBytes.bind(crypto)
|
|
173
|
+
};
|