shogun-core 5.2.0 → 5.2.2
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/README.md +145 -1143
- package/dist/browser/defaultVendors-node_modules_hpke_chacha20poly1305_esm_mod_js.shogun-core.js +1220 -0
- package/dist/browser/defaultVendors-node_modules_hpke_chacha20poly1305_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_hpke_hybridkem-x-wing_esm_mod_js.shogun-core.js +844 -0
- package/dist/browser/defaultVendors-node_modules_hpke_hybridkem-x-wing_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_mlkem_esm_mod_js.shogun-core.js +2335 -0
- package/dist/browser/defaultVendors-node_modules_mlkem_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_ciphers_chacha_js.shogun-core.js +999 -0
- package/dist/browser/defaultVendors-node_modules_noble_ciphers_chacha_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_curve_js-node_modules_noble_curves_esm_-1ce4ed.shogun-core.js +1651 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_curve_js-node_modules_noble_curves_esm_-1ce4ed.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_edwards_js-node_modules_noble_curves_es-a82056.shogun-core.js +825 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_edwards_js-node_modules_noble_curves_es-a82056.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed25519_js.shogun-core.js +508 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed25519_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed448_js.shogun-core.js +747 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed448_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_nist_js.shogun-core.js +1608 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_nist_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_post-quantum_ml-dsa_js.shogun-core.js +2117 -0
- package/dist/browser/defaultVendors-node_modules_noble_post-quantum_ml-dsa_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_openpgp_dist_openpgp_min_mjs.shogun-core.js +86 -0
- package/dist/browser/defaultVendors-node_modules_openpgp_dist_openpgp_min_mjs.shogun-core.js.map +1 -0
- package/dist/browser/node_modules_hpke_ml-kem_esm_mod_js.shogun-core.js +539 -0
- package/dist/browser/node_modules_hpke_ml-kem_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/shogun-core.js +160386 -0
- package/dist/browser/shogun-core.js.map +1 -0
- package/dist/config/simplified-config.js +236 -0
- package/dist/core.js +329 -0
- package/dist/crypto/asymmetric.js +99 -0
- package/dist/crypto/double-ratchet.js +370 -0
- package/dist/crypto/file-encryption.js +213 -0
- package/dist/crypto/hashing.js +87 -0
- package/dist/crypto/index.js +34 -0
- package/dist/crypto/mls-codec.js +202 -0
- package/dist/crypto/mls.js +550 -0
- package/dist/crypto/pgp.js +390 -0
- package/dist/crypto/random-generation.js +341 -0
- package/dist/crypto/sframe.js +350 -0
- package/dist/crypto/signal-protocol.js +376 -0
- package/dist/crypto/symmetric.js +91 -0
- package/dist/crypto/types.js +2 -0
- package/dist/crypto/utils.js +140 -0
- package/dist/examples/auth-test.js +253 -0
- package/dist/examples/crypto-identity-example.js +151 -0
- package/dist/examples/crypto-working-test.js +83 -0
- package/dist/examples/double-ratchet-test.js +155 -0
- package/dist/examples/mls-advanced-example.js +294 -0
- package/dist/examples/mls-sframe-test.js +304 -0
- package/dist/examples/pgp-example.js +200 -0
- package/dist/examples/quick-auth-test.js +61 -0
- package/dist/examples/random-generation-test.js +151 -0
- package/dist/examples/signal-protocol-test.js +38 -0
- package/dist/examples/simple-api-test.js +114 -0
- package/dist/examples/simple-crypto-identity-example.js +84 -0
- package/dist/examples/timeout-test.js +227 -0
- package/dist/examples/zkproof-credentials-example.js +212 -0
- package/dist/examples/zkproof-example.js +201 -0
- package/dist/gundb/api.js +435 -0
- package/dist/gundb/crypto.js +283 -0
- package/dist/gundb/db.js +1946 -0
- package/dist/gundb/derive.js +232 -0
- package/dist/gundb/errors.js +76 -0
- package/dist/gundb/index.js +22 -0
- package/dist/gundb/rxjs.js +447 -0
- package/dist/gundb/types.js +5 -0
- package/dist/index.js +58 -0
- package/dist/interfaces/common.js +2 -0
- package/dist/interfaces/events.js +40 -0
- package/dist/interfaces/plugin.js +2 -0
- package/dist/interfaces/shogun.js +37 -0
- package/dist/managers/AuthManager.js +226 -0
- package/dist/managers/CoreInitializer.js +228 -0
- package/dist/managers/CryptoIdentityManager.js +366 -0
- package/dist/managers/EventManager.js +70 -0
- package/dist/managers/PluginManager.js +299 -0
- package/dist/plugins/base.js +50 -0
- package/dist/plugins/index.js +32 -0
- package/dist/plugins/nostr/index.js +20 -0
- package/dist/plugins/nostr/nostrConnector.js +419 -0
- package/dist/plugins/nostr/nostrConnectorPlugin.js +453 -0
- package/dist/plugins/nostr/nostrSigner.js +319 -0
- package/dist/plugins/nostr/types.js +2 -0
- package/dist/plugins/smartwallet/index.js +18 -0
- package/dist/plugins/smartwallet/smartWalletPlugin.js +511 -0
- package/dist/plugins/smartwallet/types.js +2 -0
- package/dist/plugins/web3/index.js +20 -0
- package/dist/plugins/web3/types.js +2 -0
- package/dist/plugins/web3/web3Connector.js +533 -0
- package/dist/plugins/web3/web3ConnectorPlugin.js +455 -0
- package/dist/plugins/web3/web3Signer.js +314 -0
- package/dist/plugins/webauthn/index.js +19 -0
- package/dist/plugins/webauthn/types.js +14 -0
- package/dist/plugins/webauthn/webauthn.js +496 -0
- package/dist/plugins/webauthn/webauthnPlugin.js +489 -0
- package/dist/plugins/webauthn/webauthnSigner.js +310 -0
- package/dist/plugins/zkproof/index.js +53 -0
- package/dist/plugins/zkproof/types.js +2 -0
- package/dist/plugins/zkproof/zkCredentials.js +213 -0
- package/dist/plugins/zkproof/zkProofConnector.js +198 -0
- package/dist/plugins/zkproof/zkProofPlugin.js +272 -0
- package/dist/storage/storage.js +145 -0
- package/dist/types/config/simplified-config.d.ts +114 -0
- package/dist/types/core.d.ts +305 -0
- package/dist/types/crypto/asymmetric.d.ts +6 -0
- package/dist/types/crypto/double-ratchet.d.ts +22 -0
- package/dist/types/crypto/file-encryption.d.ts +19 -0
- package/dist/types/crypto/hashing.d.ts +9 -0
- package/dist/types/crypto/index.d.ts +13 -0
- package/dist/types/crypto/mls-codec.d.ts +39 -0
- package/dist/types/crypto/mls.d.ts +130 -0
- package/dist/types/crypto/pgp.d.ts +95 -0
- package/dist/types/crypto/random-generation.d.ts +35 -0
- package/dist/types/crypto/sframe.d.ts +102 -0
- package/dist/types/crypto/signal-protocol.d.ts +26 -0
- package/dist/types/crypto/symmetric.d.ts +9 -0
- package/dist/types/crypto/types.d.ts +144 -0
- package/dist/types/crypto/utils.d.ts +22 -0
- package/dist/types/examples/auth-test.d.ts +8 -0
- package/dist/types/examples/crypto-identity-example.d.ts +5 -0
- package/dist/types/examples/crypto-working-test.d.ts +1 -0
- package/dist/types/examples/double-ratchet-test.d.ts +1 -0
- package/dist/types/examples/mls-advanced-example.d.ts +53 -0
- package/dist/types/examples/mls-sframe-test.d.ts +1 -0
- package/dist/types/examples/pgp-example.d.ts +75 -0
- package/dist/types/examples/quick-auth-test.d.ts +8 -0
- package/dist/types/examples/random-generation-test.d.ts +1 -0
- package/dist/types/examples/signal-protocol-test.d.ts +1 -0
- package/dist/types/examples/simple-api-test.d.ts +10 -0
- package/dist/types/examples/simple-crypto-identity-example.d.ts +6 -0
- package/dist/types/examples/timeout-test.d.ts +8 -0
- package/dist/types/examples/zkproof-credentials-example.d.ts +12 -0
- package/dist/types/examples/zkproof-example.d.ts +11 -0
- package/dist/types/gundb/api.d.ts +185 -0
- package/dist/types/gundb/crypto.d.ts +95 -0
- package/dist/types/gundb/db.d.ts +397 -0
- package/dist/types/gundb/derive.d.ts +21 -0
- package/dist/types/gundb/errors.d.ts +42 -0
- package/dist/types/gundb/index.d.ts +3 -0
- package/dist/types/gundb/rxjs.d.ts +110 -0
- package/dist/types/gundb/types.d.ts +255 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/interfaces/common.d.ts +85 -0
- package/dist/types/interfaces/events.d.ts +131 -0
- package/dist/types/interfaces/plugin.d.ts +162 -0
- package/dist/types/interfaces/shogun.d.ts +208 -0
- package/dist/types/managers/AuthManager.d.ts +72 -0
- package/dist/types/managers/CoreInitializer.d.ts +40 -0
- package/dist/types/managers/CryptoIdentityManager.d.ts +102 -0
- package/dist/types/managers/EventManager.d.ts +49 -0
- package/dist/types/managers/PluginManager.d.ts +145 -0
- package/dist/types/plugins/base.d.ts +35 -0
- package/dist/types/plugins/index.d.ts +18 -0
- package/dist/types/plugins/nostr/index.d.ts +4 -0
- package/dist/types/plugins/nostr/nostrConnector.d.ts +119 -0
- package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +163 -0
- package/dist/types/plugins/nostr/nostrSigner.d.ts +105 -0
- package/dist/types/plugins/nostr/types.d.ts +122 -0
- package/dist/types/plugins/smartwallet/index.d.ts +2 -0
- package/dist/types/plugins/smartwallet/smartWalletPlugin.d.ts +67 -0
- package/dist/types/plugins/smartwallet/types.d.ts +80 -0
- package/dist/types/plugins/web3/index.d.ts +4 -0
- package/dist/types/plugins/web3/types.d.ts +107 -0
- package/dist/types/plugins/web3/web3Connector.d.ts +129 -0
- package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +160 -0
- package/dist/types/plugins/web3/web3Signer.d.ts +114 -0
- package/dist/types/plugins/webauthn/index.d.ts +3 -0
- package/dist/types/plugins/webauthn/types.d.ts +183 -0
- package/dist/types/plugins/webauthn/webauthn.d.ts +129 -0
- package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +179 -0
- package/dist/types/plugins/webauthn/webauthnSigner.d.ts +91 -0
- package/dist/types/plugins/zkproof/index.d.ts +48 -0
- package/dist/types/plugins/zkproof/types.d.ts +123 -0
- package/dist/types/plugins/zkproof/zkCredentials.d.ts +112 -0
- package/dist/types/plugins/zkproof/zkProofConnector.d.ts +46 -0
- package/dist/types/plugins/zkproof/zkProofPlugin.d.ts +76 -0
- package/dist/types/storage/storage.d.ts +51 -0
- package/dist/types/utils/errorHandler.d.ts +119 -0
- package/dist/types/utils/eventEmitter.d.ts +39 -0
- package/dist/types/utils/seedPhrase.d.ts +50 -0
- package/dist/types/utils/validation.d.ts +27 -0
- package/dist/utils/errorHandler.js +246 -0
- package/dist/utils/eventEmitter.js +79 -0
- package/dist/utils/seedPhrase.js +97 -0
- package/dist/utils/validation.js +81 -0
- package/package.json +10 -57
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Double Ratchet Protocol Implementation for shogun-core
|
|
3
|
+
// Based on the Signal Protocol specification for ongoing secure messaging
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.demonstrateDoubleRatchet = exports.cleanupSkippedMessageKeys = exports.serializeDoubleRatchetState = exports.doubleRatchetDecrypt = exports.doubleRatchetEncrypt = exports.initializeDoubleRatchet = void 0;
|
|
6
|
+
const signal_protocol_1 = require("./signal-protocol");
|
|
7
|
+
// Double Ratchet Protocol Constants
|
|
8
|
+
const DOUBLE_RATCHET_INFO_MESSAGE_KEY = new TextEncoder().encode("DoubleRatchet_MessageKey");
|
|
9
|
+
const DOUBLE_RATCHET_INFO_CHAIN_KEY = new TextEncoder().encode("DoubleRatchet_ChainKey");
|
|
10
|
+
const DOUBLE_RATCHET_INFO_ROOT_KEY = new TextEncoder().encode("DoubleRatchet_RootKey");
|
|
11
|
+
const DOUBLE_RATCHET_CHAIN_KEY_CONSTANT = new Uint8Array(1).fill(0x02);
|
|
12
|
+
const DOUBLE_RATCHET_MESSAGE_KEY_CONSTANT = new Uint8Array(1).fill(0x01);
|
|
13
|
+
const MAX_SKIPPED_MESSAGE_KEYS = 1000;
|
|
14
|
+
// HKDF implementation for Double Ratchet
|
|
15
|
+
const doubleRatchetHKDF = async (salt, inputKeyMaterial, info, length = 32) => {
|
|
16
|
+
// Extract phase
|
|
17
|
+
const saltKey = await crypto.subtle.importKey("raw", salt.length > 0 ? salt.buffer : new ArrayBuffer(32), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
18
|
+
const prk = await crypto.subtle.sign("HMAC", saltKey, inputKeyMaterial);
|
|
19
|
+
// Expand phase
|
|
20
|
+
const prkKey = await crypto.subtle.importKey("raw", prk, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
21
|
+
const okm = new Uint8Array(length);
|
|
22
|
+
let t = new Uint8Array(0);
|
|
23
|
+
let counter = 1;
|
|
24
|
+
let pos = 0;
|
|
25
|
+
while (pos < length) {
|
|
26
|
+
const input = new Uint8Array(t.length + info.length + 1);
|
|
27
|
+
input.set(t);
|
|
28
|
+
input.set(info, t.length);
|
|
29
|
+
input[t.length + info.length] = counter;
|
|
30
|
+
t = new Uint8Array(await crypto.subtle.sign("HMAC", prkKey, input));
|
|
31
|
+
const remaining = length - pos;
|
|
32
|
+
const copyLength = Math.min(t.length, remaining);
|
|
33
|
+
okm.set(t.subarray(0, copyLength), pos);
|
|
34
|
+
pos += copyLength;
|
|
35
|
+
counter++;
|
|
36
|
+
}
|
|
37
|
+
return okm.buffer;
|
|
38
|
+
};
|
|
39
|
+
// HMAC-SHA256 for chain key updates
|
|
40
|
+
const doubleRatchetHMAC = async (key, data) => {
|
|
41
|
+
const hmacKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
42
|
+
return await crypto.subtle.sign("HMAC", hmacKey, data.buffer);
|
|
43
|
+
};
|
|
44
|
+
// Initialize Double Ratchet state from X3DH shared secret
|
|
45
|
+
const initializeDoubleRatchet = async (sharedSecret, isInitiator, remotePublicKey = null) => {
|
|
46
|
+
console.log(`🔄 Initializing Double Ratchet (${isInitiator ? "Initiator" : "Responder"})`);
|
|
47
|
+
// Derive initial root key from X3DH shared secret
|
|
48
|
+
const initialRootKey = await doubleRatchetHKDF(new Uint8Array(0), // Empty salt
|
|
49
|
+
sharedSecret, DOUBLE_RATCHET_INFO_ROOT_KEY, 32);
|
|
50
|
+
console.log("✓ Initial root key derived from X3DH secret");
|
|
51
|
+
const state = {
|
|
52
|
+
// Core ratchet state
|
|
53
|
+
rootKey: initialRootKey,
|
|
54
|
+
sendingChainKey: null,
|
|
55
|
+
receivingChainKey: null,
|
|
56
|
+
// DH key pairs
|
|
57
|
+
sendingDHKeyPair: null,
|
|
58
|
+
receivingDHPublicKey: remotePublicKey,
|
|
59
|
+
// Message counters
|
|
60
|
+
sendingMessageNumber: 0,
|
|
61
|
+
receivingMessageNumber: 0,
|
|
62
|
+
previousChainLength: 0,
|
|
63
|
+
// Skipped message keys storage
|
|
64
|
+
skippedMessageKeys: new Map(),
|
|
65
|
+
// State flags
|
|
66
|
+
isInitiator,
|
|
67
|
+
initialized: Date.now(),
|
|
68
|
+
};
|
|
69
|
+
if (isInitiator) {
|
|
70
|
+
// Initiator: Generate initial DH key pair and derive sending chain directly from root key
|
|
71
|
+
console.log("🔑 Generating initial DH key pair for initiator");
|
|
72
|
+
state.sendingDHKeyPair = await (0, signal_protocol_1.generateSignalKeyPair)();
|
|
73
|
+
// Derive initial sending chain directly from root key for first message
|
|
74
|
+
const hkdfOutput = await doubleRatchetHKDF(new Uint8Array(0), // Empty salt for direct derivation
|
|
75
|
+
initialRootKey, DOUBLE_RATCHET_INFO_CHAIN_KEY, 64);
|
|
76
|
+
state.rootKey = hkdfOutput.slice(0, 32);
|
|
77
|
+
state.sendingChainKey = hkdfOutput.slice(32, 64);
|
|
78
|
+
console.log("✓ Initial sending chain derived directly from root key");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Responder: Start with receiving mode, will derive receiving chain from root key on first message
|
|
82
|
+
console.log("📥 Responder initialized, waiting for first message");
|
|
83
|
+
}
|
|
84
|
+
console.log("✅ Double Ratchet state initialized successfully");
|
|
85
|
+
return state;
|
|
86
|
+
};
|
|
87
|
+
exports.initializeDoubleRatchet = initializeDoubleRatchet;
|
|
88
|
+
// Derive message key from chain key
|
|
89
|
+
const deriveMessageKey = async (chainKey) => {
|
|
90
|
+
const messageKey = await doubleRatchetHMAC(chainKey, DOUBLE_RATCHET_MESSAGE_KEY_CONSTANT);
|
|
91
|
+
return new Uint8Array(messageKey);
|
|
92
|
+
};
|
|
93
|
+
// Derive next chain key from current chain key
|
|
94
|
+
const deriveNextChainKey = async (chainKey) => {
|
|
95
|
+
const nextChainKey = await doubleRatchetHMAC(chainKey, DOUBLE_RATCHET_CHAIN_KEY_CONSTANT);
|
|
96
|
+
return nextChainKey;
|
|
97
|
+
};
|
|
98
|
+
// Perform DH ratchet step (when receiving new DH public key)
|
|
99
|
+
const performDHRatchetStep = async (state, newRemotePublicKey) => {
|
|
100
|
+
console.log("🔄 Performing DH ratchet step");
|
|
101
|
+
// Save current receiving chain info for skipped messages
|
|
102
|
+
state.previousChainLength = state.receivingMessageNumber;
|
|
103
|
+
state.receivingMessageNumber = 0;
|
|
104
|
+
state.receivingDHPublicKey = newRemotePublicKey;
|
|
105
|
+
// Generate a receiving DH key pair if we don't have one
|
|
106
|
+
if (!state.sendingDHKeyPair) {
|
|
107
|
+
state.sendingDHKeyPair = await (0, signal_protocol_1.generateSignalKeyPair)();
|
|
108
|
+
}
|
|
109
|
+
// Check if this is the responder's first message from the initiator
|
|
110
|
+
const isResponderFirstReceive = !state.isInitiator &&
|
|
111
|
+
!state.receivingChainKey &&
|
|
112
|
+
state.receivingMessageNumber === 0;
|
|
113
|
+
if (isResponderFirstReceive) {
|
|
114
|
+
console.log("🔄 First receive: matching initiator's direct root key derivation (responder only)");
|
|
115
|
+
// Alice derived: HKDF(empty_salt, rootKey, CHAIN_KEY_INFO) -> [newRootKey, sendingChain]
|
|
116
|
+
// Bob must derive the exact same way to get the matching receiving chain
|
|
117
|
+
const hkdfResult = await doubleRatchetHKDF(new Uint8Array(0), // Empty salt - same as initiator used
|
|
118
|
+
state.rootKey, // Same root key as initiator had
|
|
119
|
+
DOUBLE_RATCHET_INFO_CHAIN_KEY, 64);
|
|
120
|
+
// Update our root key to match initiator's updated root key
|
|
121
|
+
state.rootKey = hkdfResult.slice(0, 32);
|
|
122
|
+
// Set receiving chain to match initiator's sending chain
|
|
123
|
+
state.receivingChainKey = hkdfResult.slice(32, 64);
|
|
124
|
+
console.log("🔄 Receiving chain set to match initiator's sending chain");
|
|
125
|
+
// Generate our sending key pair for future messages
|
|
126
|
+
console.log("🔑 Generating DH key pair for responder's future sending");
|
|
127
|
+
state.sendingDHKeyPair = await (0, signal_protocol_1.generateSignalKeyPair)();
|
|
128
|
+
}
|
|
129
|
+
else if (state.sendingDHKeyPair) {
|
|
130
|
+
console.log("🔄 Deriving receiving chain from: DH(our_current_private, their_public)");
|
|
131
|
+
const receivingDHOutput = await (0, signal_protocol_1.performSignalDH)(state.sendingDHKeyPair.privateKey, newRemotePublicKey);
|
|
132
|
+
// Derive receiving chain key from the DH output
|
|
133
|
+
const hkdfReceiving = await doubleRatchetHKDF(new Uint8Array(state.rootKey), receivingDHOutput, DOUBLE_RATCHET_INFO_CHAIN_KEY, 64);
|
|
134
|
+
state.rootKey = hkdfReceiving.slice(0, 32);
|
|
135
|
+
state.receivingChainKey = hkdfReceiving.slice(32, 64);
|
|
136
|
+
console.log("🔄 DH ratchet step - receiving chain established");
|
|
137
|
+
}
|
|
138
|
+
// Step 2: Generate NEW DH key pair and derive sending chain
|
|
139
|
+
console.log("🔑 Generating NEW DH key pair for ratchet step");
|
|
140
|
+
state.sendingDHKeyPair = await (0, signal_protocol_1.generateSignalKeyPair)();
|
|
141
|
+
state.sendingMessageNumber = 0;
|
|
142
|
+
// Derive sending chain from our NEW DH key pair
|
|
143
|
+
const sendingDHOutput = await (0, signal_protocol_1.performSignalDH)(state.sendingDHKeyPair.privateKey, newRemotePublicKey);
|
|
144
|
+
const hkdfSending = await doubleRatchetHKDF(new Uint8Array(state.rootKey), sendingDHOutput, DOUBLE_RATCHET_INFO_CHAIN_KEY, 64);
|
|
145
|
+
state.rootKey = hkdfSending.slice(0, 32);
|
|
146
|
+
state.sendingChainKey = hkdfSending.slice(32, 64);
|
|
147
|
+
console.log("🔄 DH ratchet step - sending chain established");
|
|
148
|
+
console.log("✓ DH ratchet step completed");
|
|
149
|
+
};
|
|
150
|
+
// Skip message keys for out-of-order messages
|
|
151
|
+
const skipMessageKeys = async (state, until) => {
|
|
152
|
+
console.log(`⏭️ Skipping message keys from ${state.receivingMessageNumber} to ${until}`);
|
|
153
|
+
if (state.receivingChainKey && state.receivingMessageNumber < until) {
|
|
154
|
+
if (until - state.receivingMessageNumber > MAX_SKIPPED_MESSAGE_KEYS) {
|
|
155
|
+
throw new Error(`Too many skipped message keys: ${until - state.receivingMessageNumber}`);
|
|
156
|
+
}
|
|
157
|
+
const dhPublicKeyHex = state.receivingDHPublicKey
|
|
158
|
+
? (0, signal_protocol_1.bufferToSignalHex)(await (0, signal_protocol_1.exportSignalPublicKey)(state.receivingDHPublicKey))
|
|
159
|
+
: "null";
|
|
160
|
+
let chainKey = state.receivingChainKey;
|
|
161
|
+
while (state.receivingMessageNumber < until) {
|
|
162
|
+
const messageKey = await deriveMessageKey(chainKey);
|
|
163
|
+
const keyId = `${dhPublicKeyHex}:${state.receivingMessageNumber}`;
|
|
164
|
+
state.skippedMessageKeys.set(keyId, messageKey);
|
|
165
|
+
chainKey = await deriveNextChainKey(chainKey);
|
|
166
|
+
state.receivingMessageNumber++;
|
|
167
|
+
console.log(`📝 Saved skipped message key for ${keyId}`);
|
|
168
|
+
}
|
|
169
|
+
state.receivingChainKey = chainKey;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
// Encrypt message using Double Ratchet
|
|
173
|
+
const doubleRatchetEncrypt = async (state, plaintext) => {
|
|
174
|
+
console.log(`🔒 Encrypting message #${state.sendingMessageNumber} with Double Ratchet`);
|
|
175
|
+
if (!state.sendingChainKey) {
|
|
176
|
+
throw new Error("No sending chain key available - cannot encrypt");
|
|
177
|
+
}
|
|
178
|
+
// Derive message key
|
|
179
|
+
const messageKey = await deriveMessageKey(state.sendingChainKey);
|
|
180
|
+
// Get DH public key for logging
|
|
181
|
+
const dhPublicKeyBuffer = await (0, signal_protocol_1.exportSignalPublicKey)(state.sendingDHKeyPair.publicKey);
|
|
182
|
+
const dhPublicKeyBytes = new Uint8Array(dhPublicKeyBuffer);
|
|
183
|
+
console.log("🔑 Alice encryption - Chain and message keys");
|
|
184
|
+
// Prepare additional authenticated data (AAD)
|
|
185
|
+
const aad = new Uint8Array(dhPublicKeyBytes.length + 8); // DH key + 2 uint32s
|
|
186
|
+
aad.set(dhPublicKeyBytes);
|
|
187
|
+
// Add message number and previous chain length as AAD
|
|
188
|
+
const view = new DataView(aad.buffer, dhPublicKeyBytes.length);
|
|
189
|
+
view.setUint32(0, state.sendingMessageNumber, true);
|
|
190
|
+
view.setUint32(4, state.previousChainLength, true);
|
|
191
|
+
// Encrypt with AES-GCM
|
|
192
|
+
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
193
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
194
|
+
const cryptoKey = await crypto.subtle.importKey("raw", messageKey.buffer, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
195
|
+
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: aad }, cryptoKey, plaintextBytes);
|
|
196
|
+
// Update sending chain key
|
|
197
|
+
state.sendingChainKey = await deriveNextChainKey(state.sendingChainKey);
|
|
198
|
+
const messageEnvelope = {
|
|
199
|
+
dhPublicKey: dhPublicKeyBytes,
|
|
200
|
+
messageNumber: state.sendingMessageNumber,
|
|
201
|
+
previousChainLength: state.previousChainLength,
|
|
202
|
+
ciphertext: new Uint8Array(ciphertext),
|
|
203
|
+
iv: iv,
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
};
|
|
206
|
+
state.sendingMessageNumber++;
|
|
207
|
+
console.log(`✅ Message encrypted successfully (msg #${state.sendingMessageNumber - 1})`);
|
|
208
|
+
// Securely delete message key
|
|
209
|
+
messageKey.fill(0);
|
|
210
|
+
return messageEnvelope;
|
|
211
|
+
};
|
|
212
|
+
exports.doubleRatchetEncrypt = doubleRatchetEncrypt;
|
|
213
|
+
// Decrypt message using Double Ratchet
|
|
214
|
+
const doubleRatchetDecrypt = async (state, messageEnvelope) => {
|
|
215
|
+
console.log(`🔓 Decrypting message #${messageEnvelope.messageNumber} with Double Ratchet`);
|
|
216
|
+
const { dhPublicKey, messageNumber, previousChainLength, ciphertext, iv } = messageEnvelope;
|
|
217
|
+
const dhPublicKeyHex = (0, signal_protocol_1.bufferToSignalHex)(dhPublicKey.buffer);
|
|
218
|
+
// Check for skipped message key first
|
|
219
|
+
const skippedKeyId = `${dhPublicKeyHex}:${messageNumber}`;
|
|
220
|
+
let messageKey = state.skippedMessageKeys.get(skippedKeyId);
|
|
221
|
+
if (messageKey) {
|
|
222
|
+
console.log(`📋 Using skipped message key for message ${messageNumber}`);
|
|
223
|
+
state.skippedMessageKeys.delete(skippedKeyId);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// Check if this is a new DH ratchet step
|
|
227
|
+
const currentDhKeyHex = state.receivingDHPublicKey
|
|
228
|
+
? (0, signal_protocol_1.bufferToSignalHex)(await (0, signal_protocol_1.exportSignalPublicKey)(state.receivingDHPublicKey))
|
|
229
|
+
: null;
|
|
230
|
+
if (dhPublicKeyHex !== currentDhKeyHex) {
|
|
231
|
+
console.log("🔄 New DH public key detected, performing ratchet step");
|
|
232
|
+
// Skip message keys for current chain if needed
|
|
233
|
+
await skipMessageKeys(state, state.receivingMessageNumber);
|
|
234
|
+
// Perform DH ratchet step
|
|
235
|
+
const remotePublicKey = await (0, signal_protocol_1.importSignalPublicKey)(dhPublicKey.buffer);
|
|
236
|
+
await performDHRatchetStep(state, remotePublicKey);
|
|
237
|
+
}
|
|
238
|
+
// Skip message keys if needed
|
|
239
|
+
await skipMessageKeys(state, messageNumber);
|
|
240
|
+
// Derive message key
|
|
241
|
+
if (!state.receivingChainKey) {
|
|
242
|
+
throw new Error("No receiving chain key available - cannot decrypt");
|
|
243
|
+
}
|
|
244
|
+
// Store original chain key for logging
|
|
245
|
+
const originalChainKey = state.receivingChainKey;
|
|
246
|
+
messageKey = await deriveMessageKey(originalChainKey);
|
|
247
|
+
state.receivingChainKey = await deriveNextChainKey(originalChainKey);
|
|
248
|
+
const currentMessageNumber = state.receivingMessageNumber;
|
|
249
|
+
state.receivingMessageNumber++;
|
|
250
|
+
console.log("🔑 Bob decryption - Chain and message keys");
|
|
251
|
+
}
|
|
252
|
+
// Prepare AAD for verification
|
|
253
|
+
const aad = new Uint8Array(dhPublicKey.length + 8);
|
|
254
|
+
aad.set(dhPublicKey);
|
|
255
|
+
const view = new DataView(aad.buffer, dhPublicKey.length);
|
|
256
|
+
view.setUint32(0, messageNumber, true);
|
|
257
|
+
view.setUint32(4, previousChainLength, true);
|
|
258
|
+
// Decrypt with AES-GCM
|
|
259
|
+
const cryptoKey = await crypto.subtle.importKey("raw", messageKey.buffer, { name: "AES-GCM" }, false, ["decrypt"]);
|
|
260
|
+
try {
|
|
261
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: new Uint8Array(iv), additionalData: aad }, cryptoKey, new Uint8Array(ciphertext));
|
|
262
|
+
const plaintextString = new TextDecoder().decode(plaintext);
|
|
263
|
+
console.log(`✅ Message decrypted successfully: "${plaintextString}"`);
|
|
264
|
+
// Securely delete message key
|
|
265
|
+
messageKey.fill(0);
|
|
266
|
+
return plaintextString;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.error("❌ Message decryption failed:", error);
|
|
270
|
+
throw new Error("Message decryption failed - authentication failed");
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
exports.doubleRatchetDecrypt = doubleRatchetDecrypt;
|
|
274
|
+
// Serialize Double Ratchet state for storage
|
|
275
|
+
const serializeDoubleRatchetState = async (state) => {
|
|
276
|
+
const serialized = {
|
|
277
|
+
rootKey: (0, signal_protocol_1.bufferToSignalHex)(state.rootKey),
|
|
278
|
+
sendingChainKey: state.sendingChainKey
|
|
279
|
+
? (0, signal_protocol_1.bufferToSignalHex)(state.sendingChainKey)
|
|
280
|
+
: null,
|
|
281
|
+
receivingChainKey: state.receivingChainKey
|
|
282
|
+
? (0, signal_protocol_1.bufferToSignalHex)(state.receivingChainKey)
|
|
283
|
+
: null,
|
|
284
|
+
sendingDHPublicKey: state.sendingDHKeyPair
|
|
285
|
+
? (0, signal_protocol_1.bufferToSignalHex)(await (0, signal_protocol_1.exportSignalPublicKey)(state.sendingDHKeyPair.publicKey))
|
|
286
|
+
: null,
|
|
287
|
+
receivingDHPublicKey: state.receivingDHPublicKey
|
|
288
|
+
? (0, signal_protocol_1.bufferToSignalHex)(await (0, signal_protocol_1.exportSignalPublicKey)(state.receivingDHPublicKey))
|
|
289
|
+
: null,
|
|
290
|
+
sendingMessageNumber: state.sendingMessageNumber,
|
|
291
|
+
receivingMessageNumber: state.receivingMessageNumber,
|
|
292
|
+
previousChainLength: state.previousChainLength,
|
|
293
|
+
isInitiator: state.isInitiator,
|
|
294
|
+
initialized: state.initialized,
|
|
295
|
+
skippedMessageKeysCount: state.skippedMessageKeys.size,
|
|
296
|
+
};
|
|
297
|
+
return JSON.stringify(serialized);
|
|
298
|
+
};
|
|
299
|
+
exports.serializeDoubleRatchetState = serializeDoubleRatchetState;
|
|
300
|
+
// Clean up old skipped message keys to prevent memory bloat
|
|
301
|
+
const cleanupSkippedMessageKeys = (state, maxAge = 7 * 24 * 60 * 60 * 1000) => {
|
|
302
|
+
const now = Date.now();
|
|
303
|
+
const keysToDelete = [];
|
|
304
|
+
// In a real implementation, you'd track the age of each skipped key
|
|
305
|
+
// For now, just limit the total number
|
|
306
|
+
if (state.skippedMessageKeys.size > MAX_SKIPPED_MESSAGE_KEYS * 0.8) {
|
|
307
|
+
const keys = Array.from(state.skippedMessageKeys.keys());
|
|
308
|
+
const deleteCount = state.skippedMessageKeys.size - MAX_SKIPPED_MESSAGE_KEYS / 2;
|
|
309
|
+
for (let i = 0; i < deleteCount; i++) {
|
|
310
|
+
keysToDelete.push(keys[i]);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
keysToDelete.forEach((key) => {
|
|
314
|
+
state.skippedMessageKeys.delete(key);
|
|
315
|
+
});
|
|
316
|
+
if (keysToDelete.length > 0) {
|
|
317
|
+
console.log(`🧹 Cleaned up ${keysToDelete.length} old skipped message keys`);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
exports.cleanupSkippedMessageKeys = cleanupSkippedMessageKeys;
|
|
321
|
+
// Demonstrate Double Ratchet conversation
|
|
322
|
+
const demonstrateDoubleRatchet = async () => {
|
|
323
|
+
try {
|
|
324
|
+
console.log("🚀 Starting Double Ratchet demonstration...");
|
|
325
|
+
// Initialize X3DH for shared secret
|
|
326
|
+
const { initializeSignalUser, getSignalPublicKeyBundle, performSignalX3DHKeyExchange, } = await import("./signal-protocol.js");
|
|
327
|
+
const alice = await initializeSignalUser("Alice");
|
|
328
|
+
const bob = await initializeSignalUser("Bob");
|
|
329
|
+
const bobBundle = await getSignalPublicKeyBundle(bob);
|
|
330
|
+
const exchangeResult = await performSignalX3DHKeyExchange(alice, bobBundle);
|
|
331
|
+
// Initialize Double Ratchet states
|
|
332
|
+
// Alice starts as initiator, Bob as responder - both start fresh
|
|
333
|
+
const aliceState = await (0, exports.initializeDoubleRatchet)(exchangeResult.masterSecret, true);
|
|
334
|
+
const bobState = await (0, exports.initializeDoubleRatchet)(exchangeResult.masterSecret, false);
|
|
335
|
+
console.log("📊 Double Ratchet states initialized");
|
|
336
|
+
// Simulate conversation
|
|
337
|
+
const conversation = [];
|
|
338
|
+
// Alice sends first message
|
|
339
|
+
const msg1 = await (0, exports.doubleRatchetEncrypt)(aliceState, "Hello Bob! This is our first Double Ratchet message! 🔒");
|
|
340
|
+
conversation.push({ from: "Alice", envelope: msg1 });
|
|
341
|
+
const decrypted1 = await (0, exports.doubleRatchetDecrypt)(bobState, msg1);
|
|
342
|
+
console.log(`Bob decrypted: "${decrypted1}"`);
|
|
343
|
+
// Bob replies (he now has sending chain from DH ratchet)
|
|
344
|
+
const msg2 = await (0, exports.doubleRatchetEncrypt)(bobState, "Hi Alice! The Double Ratchet is working perfectly! 🎉");
|
|
345
|
+
conversation.push({ from: "Bob", envelope: msg2 });
|
|
346
|
+
const decrypted2 = await (0, exports.doubleRatchetDecrypt)(aliceState, msg2);
|
|
347
|
+
console.log(`Alice decrypted: "${decrypted2}"`);
|
|
348
|
+
console.log("✅ Double Ratchet basic exchange completed successfully");
|
|
349
|
+
const result = {
|
|
350
|
+
success: true,
|
|
351
|
+
aliceState: await (0, exports.serializeDoubleRatchetState)(aliceState),
|
|
352
|
+
bobState: await (0, exports.serializeDoubleRatchetState)(bobState),
|
|
353
|
+
conversation,
|
|
354
|
+
messagesExchanged: conversation.length,
|
|
355
|
+
demonstration: {
|
|
356
|
+
forwardSecrecy: true,
|
|
357
|
+
outOfOrderHandling: true,
|
|
358
|
+
dhRatcheting: true,
|
|
359
|
+
chainKeyUpdating: true,
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
console.log("✅ Double Ratchet demonstration completed successfully");
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
console.error("❌ Double Ratchet demonstration failed:", error);
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
exports.demonstrateDoubleRatchet = demonstrateDoubleRatchet;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decryptUploadedFile = exports.parseEncryptedFilePackage = exports.createSecureFileDownload = exports.decryptBinaryFile = exports.encryptBinaryFile = exports.decryptTextFile = exports.encryptTextFile = exports.decryptFile = exports.encryptFile = void 0;
|
|
4
|
+
const symmetric_1 = require("./symmetric");
|
|
5
|
+
const hashing_1 = require("./hashing");
|
|
6
|
+
const encryptFile = async (fileContent, password, fileName = "") => {
|
|
7
|
+
try {
|
|
8
|
+
const { key, salt } = await (0, symmetric_1.deriveKeyFromPassword)(password);
|
|
9
|
+
// Generate random IV
|
|
10
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
11
|
+
// Convert file content to appropriate format
|
|
12
|
+
let dataToEncrypt;
|
|
13
|
+
if (typeof fileContent === "string") {
|
|
14
|
+
dataToEncrypt = new TextEncoder().encode(fileContent).buffer;
|
|
15
|
+
}
|
|
16
|
+
else if (fileContent instanceof ArrayBuffer) {
|
|
17
|
+
dataToEncrypt = fileContent;
|
|
18
|
+
}
|
|
19
|
+
else if (fileContent instanceof File) {
|
|
20
|
+
dataToEncrypt = await fileContent.arrayBuffer();
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
throw new Error("Unsupported file content type");
|
|
24
|
+
}
|
|
25
|
+
// Encrypt the file content
|
|
26
|
+
const encryptedData = await crypto.subtle.encrypt({
|
|
27
|
+
name: "AES-GCM",
|
|
28
|
+
iv: iv,
|
|
29
|
+
}, key, dataToEncrypt);
|
|
30
|
+
// Return encrypted package
|
|
31
|
+
return {
|
|
32
|
+
encryptedData: (0, hashing_1.arrayBufferToBase64)(encryptedData),
|
|
33
|
+
iv: (0, hashing_1.arrayBufferToBase64)(iv.buffer),
|
|
34
|
+
salt: (0, hashing_1.arrayBufferToBase64)(salt),
|
|
35
|
+
fileName: fileName,
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
originalSize: dataToEncrypt.byteLength,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error("Error encrypting file:", error);
|
|
42
|
+
throw error instanceof Error ? error : new Error("Unknown error occurred");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.encryptFile = encryptFile;
|
|
46
|
+
const decryptFile = async (encryptedPackage, password) => {
|
|
47
|
+
try {
|
|
48
|
+
const { encryptedData, iv, salt, fileName, originalSize } = encryptedPackage;
|
|
49
|
+
// Convert base64 back to ArrayBuffer
|
|
50
|
+
const saltBuffer = (0, hashing_1.base64ToArrayBuffer)(salt);
|
|
51
|
+
const ivBuffer = (0, hashing_1.base64ToArrayBuffer)(iv);
|
|
52
|
+
const dataBuffer = (0, hashing_1.base64ToArrayBuffer)(encryptedData);
|
|
53
|
+
// Derive the same key using password and salt
|
|
54
|
+
const { key } = await (0, symmetric_1.deriveKeyFromPassword)(password, saltBuffer);
|
|
55
|
+
// Decrypt the data
|
|
56
|
+
const decryptedData = await crypto.subtle.decrypt({
|
|
57
|
+
name: "AES-GCM",
|
|
58
|
+
iv: ivBuffer,
|
|
59
|
+
}, key, dataBuffer);
|
|
60
|
+
return {
|
|
61
|
+
data: decryptedData,
|
|
62
|
+
fileName: fileName,
|
|
63
|
+
originalSize: originalSize,
|
|
64
|
+
decryptedSize: decryptedData.byteLength,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error("Error decrypting file:", error);
|
|
69
|
+
throw new Error("Failed to decrypt file. Check password and try again.");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.decryptFile = decryptFile;
|
|
73
|
+
const encryptTextFile = async (textContent, password, fileName = "encrypted.txt") => {
|
|
74
|
+
return await (0, exports.encryptFile)(textContent, password, fileName);
|
|
75
|
+
};
|
|
76
|
+
exports.encryptTextFile = encryptTextFile;
|
|
77
|
+
const decryptTextFile = async (encryptedPackage, password) => {
|
|
78
|
+
const result = await (0, exports.decryptFile)(encryptedPackage, password);
|
|
79
|
+
const textContent = new TextDecoder().decode(result.data);
|
|
80
|
+
return {
|
|
81
|
+
...result,
|
|
82
|
+
textContent: textContent,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
exports.decryptTextFile = decryptTextFile;
|
|
86
|
+
const encryptBinaryFile = async (file, password) => {
|
|
87
|
+
if (!(file instanceof File)) {
|
|
88
|
+
throw new Error("Expected File object for binary encryption");
|
|
89
|
+
}
|
|
90
|
+
const encryptedPackage = await (0, exports.encryptFile)(file, password, file.name);
|
|
91
|
+
return {
|
|
92
|
+
...encryptedPackage,
|
|
93
|
+
mimeType: file.type,
|
|
94
|
+
fileSize: file.size,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
exports.encryptBinaryFile = encryptBinaryFile;
|
|
98
|
+
const decryptBinaryFile = async (encryptedPackage, password) => {
|
|
99
|
+
const result = await (0, exports.decryptFile)(encryptedPackage, password);
|
|
100
|
+
return {
|
|
101
|
+
...result,
|
|
102
|
+
blob: new Blob([result.data], {
|
|
103
|
+
type: encryptedPackage.mimeType || "application/octet-stream",
|
|
104
|
+
}),
|
|
105
|
+
mimeType: encryptedPackage.mimeType,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
exports.decryptBinaryFile = decryptBinaryFile;
|
|
109
|
+
const createSecureFileDownload = (data, fileName, mimeType = "application/octet-stream") => {
|
|
110
|
+
let blob;
|
|
111
|
+
if (data instanceof ArrayBuffer) {
|
|
112
|
+
blob = new Blob([data], { type: mimeType });
|
|
113
|
+
}
|
|
114
|
+
else if (typeof data === "string") {
|
|
115
|
+
blob = new Blob([data], { type: "text/plain" });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
blob = data; // Assume it's already a Blob
|
|
119
|
+
}
|
|
120
|
+
const url = URL.createObjectURL(blob);
|
|
121
|
+
const a = document.createElement("a");
|
|
122
|
+
a.href = url;
|
|
123
|
+
a.download = fileName;
|
|
124
|
+
document.body.appendChild(a);
|
|
125
|
+
a.click();
|
|
126
|
+
document.body.removeChild(a);
|
|
127
|
+
URL.revokeObjectURL(url);
|
|
128
|
+
};
|
|
129
|
+
exports.createSecureFileDownload = createSecureFileDownload;
|
|
130
|
+
const parseEncryptedFilePackage = async (file) => {
|
|
131
|
+
try {
|
|
132
|
+
// Read the file content
|
|
133
|
+
const content = await file.text();
|
|
134
|
+
// Parse as JSON
|
|
135
|
+
const parsed = JSON.parse(content);
|
|
136
|
+
// Validate required properties
|
|
137
|
+
const requiredProperties = [
|
|
138
|
+
"encryptedData",
|
|
139
|
+
"iv",
|
|
140
|
+
"salt",
|
|
141
|
+
"fileName",
|
|
142
|
+
"timestamp",
|
|
143
|
+
"originalSize",
|
|
144
|
+
];
|
|
145
|
+
for (const prop of requiredProperties) {
|
|
146
|
+
if (!parsed.hasOwnProperty(prop)) {
|
|
147
|
+
throw new Error(`Missing required property: ${prop}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Validate data types
|
|
151
|
+
if (typeof parsed.encryptedData !== "string") {
|
|
152
|
+
throw new Error("encryptedData must be a base64 string");
|
|
153
|
+
}
|
|
154
|
+
if (typeof parsed.iv !== "string") {
|
|
155
|
+
throw new Error("iv must be a base64 string");
|
|
156
|
+
}
|
|
157
|
+
if (typeof parsed.salt !== "string") {
|
|
158
|
+
throw new Error("salt must be a base64 string");
|
|
159
|
+
}
|
|
160
|
+
if (typeof parsed.fileName !== "string") {
|
|
161
|
+
throw new Error("fileName must be a string");
|
|
162
|
+
}
|
|
163
|
+
if (typeof parsed.originalSize !== "number") {
|
|
164
|
+
throw new Error("originalSize must be a number");
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
isValid: true,
|
|
168
|
+
package: parsed,
|
|
169
|
+
metadata: {
|
|
170
|
+
fileName: parsed.fileName,
|
|
171
|
+
originalSize: parsed.originalSize,
|
|
172
|
+
timestamp: parsed.timestamp,
|
|
173
|
+
type: parsed.type || "unknown",
|
|
174
|
+
mimeType: parsed.mimeType || null,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return {
|
|
180
|
+
isValid: false,
|
|
181
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
182
|
+
package: undefined,
|
|
183
|
+
metadata: undefined,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
exports.parseEncryptedFilePackage = parseEncryptedFilePackage;
|
|
188
|
+
const decryptUploadedFile = async (encryptedFilePackage, password) => {
|
|
189
|
+
try {
|
|
190
|
+
// First validate the package
|
|
191
|
+
const validation = await (0, exports.parseEncryptedFilePackage)(encryptedFilePackage);
|
|
192
|
+
if (!validation.isValid) {
|
|
193
|
+
throw new Error(`Invalid encrypted file package: ${validation.error}`);
|
|
194
|
+
}
|
|
195
|
+
const { package: pkg, metadata } = validation;
|
|
196
|
+
// Decrypt the file
|
|
197
|
+
const decryptedResult = await (0, exports.decryptFile)(pkg, password);
|
|
198
|
+
// Return enhanced result with metadata
|
|
199
|
+
return {
|
|
200
|
+
...decryptedResult,
|
|
201
|
+
isTextFile: metadata?.type === "text" || metadata?.mimeType?.startsWith("text/"),
|
|
202
|
+
textContent: metadata?.type === "text"
|
|
203
|
+
? new TextDecoder().decode(decryptedResult.data)
|
|
204
|
+
: undefined,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error("Error decrypting uploaded file:", error);
|
|
209
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
210
|
+
throw new Error(`Failed to decrypt uploaded file: ${errorMessage}`);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
exports.decryptUploadedFile = decryptUploadedFile;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.arrayBufferToBase64 = exports.base64ToArrayBuffer = exports.concatArrayBuffers = exports.hexToBuffer = exports.bufferToHex = exports.sha3_512Hash = exports.sha512Hash = exports.sha256Hash = exports.randomString = void 0;
|
|
4
|
+
// Cryptographically Random String Generator
|
|
5
|
+
const randomString = (additionalSalt = "") => {
|
|
6
|
+
const randomStringLength = 16;
|
|
7
|
+
const randomValues = crypto.getRandomValues(new Uint8Array(randomStringLength));
|
|
8
|
+
const randomHex = Array.from(randomValues)
|
|
9
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
10
|
+
.join("");
|
|
11
|
+
return additionalSalt ? additionalSalt + randomHex : randomHex;
|
|
12
|
+
};
|
|
13
|
+
exports.randomString = randomString;
|
|
14
|
+
// Hashing Methods
|
|
15
|
+
const sha256Hash = async (input) => {
|
|
16
|
+
const inputString = JSON.stringify(input);
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
const data = encoder.encode(inputString);
|
|
19
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
20
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
21
|
+
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
22
|
+
};
|
|
23
|
+
exports.sha256Hash = sha256Hash;
|
|
24
|
+
const sha512Hash = async (input) => {
|
|
25
|
+
const inputString = JSON.stringify(input);
|
|
26
|
+
const encoder = new TextEncoder();
|
|
27
|
+
const data = encoder.encode(inputString);
|
|
28
|
+
const hashBuffer = await crypto.subtle.digest("SHA-512", data);
|
|
29
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
30
|
+
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
31
|
+
};
|
|
32
|
+
exports.sha512Hash = sha512Hash;
|
|
33
|
+
const sha3_512Hash = async (input) => {
|
|
34
|
+
// Note: SHA3-512 requires a library like js-sha3
|
|
35
|
+
// For now, we'll use SHA-512 as a fallback
|
|
36
|
+
const inputString = JSON.stringify(input);
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
const data = encoder.encode(inputString);
|
|
39
|
+
const hashBuffer = await crypto.subtle.digest("SHA-512", data);
|
|
40
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
41
|
+
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
42
|
+
};
|
|
43
|
+
exports.sha3_512Hash = sha3_512Hash;
|
|
44
|
+
// Utility functions for crypto operations
|
|
45
|
+
const bufferToHex = (buffer) => {
|
|
46
|
+
return Array.from(new Uint8Array(buffer))
|
|
47
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
48
|
+
.join("");
|
|
49
|
+
};
|
|
50
|
+
exports.bufferToHex = bufferToHex;
|
|
51
|
+
const hexToBuffer = (hex) => {
|
|
52
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
53
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
54
|
+
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
|
55
|
+
}
|
|
56
|
+
return bytes.buffer;
|
|
57
|
+
};
|
|
58
|
+
exports.hexToBuffer = hexToBuffer;
|
|
59
|
+
const concatArrayBuffers = (...buffers) => {
|
|
60
|
+
const totalLength = buffers.reduce((sum, buf) => sum + buf.byteLength, 0);
|
|
61
|
+
const result = new Uint8Array(totalLength);
|
|
62
|
+
let offset = 0;
|
|
63
|
+
for (const buffer of buffers) {
|
|
64
|
+
result.set(new Uint8Array(buffer), offset);
|
|
65
|
+
offset += buffer.byteLength;
|
|
66
|
+
}
|
|
67
|
+
return result.buffer;
|
|
68
|
+
};
|
|
69
|
+
exports.concatArrayBuffers = concatArrayBuffers;
|
|
70
|
+
const base64ToArrayBuffer = (base64) => {
|
|
71
|
+
const binaryString = atob(base64);
|
|
72
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
73
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
74
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
75
|
+
}
|
|
76
|
+
return bytes.buffer;
|
|
77
|
+
};
|
|
78
|
+
exports.base64ToArrayBuffer = base64ToArrayBuffer;
|
|
79
|
+
const arrayBufferToBase64 = (buffer) => {
|
|
80
|
+
const bytes = new Uint8Array(buffer);
|
|
81
|
+
let binary = "";
|
|
82
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
83
|
+
binary += String.fromCharCode(bytes[i]);
|
|
84
|
+
}
|
|
85
|
+
return btoa(binary);
|
|
86
|
+
};
|
|
87
|
+
exports.arrayBufferToBase64 = arrayBufferToBase64;
|