facebook / modules /crypto.js
Althnayi's picture
Upload 27 files
2a196ac verified
Raw
History Blame Contribute Delete
31.6 kB
// ============================================================================
// File: modules/crypto.js
// ============================================================================
(function(global) {
'use strict';
const CURRENT_CRYPTO_VERSION = "3.0.0-AES-256-GCM-SHA-256-Strict-WebCrypto-RTL";
const SECURE_HEADER_V3 = "FTE_SECURE_PACKET_SHA256_V3:";
const TOKEN_HEADER = "FTE_TOKEN_SHA256_V3:";
const BACKUP_HEADER = "";
// Constant security salting bound to SHA-256 algorithm
const HASH_KEY_SALT = "FritreeKeySalt_2026_StrictSHA256_MaximumHardenSalt_EnterpriseForce";
const DERIVATION_SALT = "FritreeDerivationSalt_2026_StrictSHA256_SolidStateSymmetricSalt";
const PBKDF2_ITERATIONS = 120000;
let cachedMasterKey = null;
const derivedKeyCache = new Map();
const derivedHmacKeyCache = new Map();
/**
* Clears sensitive arrays from memory when no longer required
* @param {ArrayBuffer|TypedArray} buffer - Target memory buffer
*/
function secureWipe(buffer) {
if (!buffer) return;
if (buffer instanceof ArrayBuffer) {
new Uint8Array(buffer).fill(0);
} else if (ArrayBuffer.isView(buffer)) {
buffer.fill(0);
}
}
/**
* Generates cryptographically secure random bytes
* @param {number} bytesLength - Size of random bytes array
* @returns {Uint8Array} Secure random bytes
*/
function generateSecureRandomBytes(bytesLength) {
const buffer = new Uint8Array(bytesLength);
crypto.getRandomValues(buffer);
return buffer;
}
/**
* Generates a secure unique alphanumeric identifier
* @param {number} length - Desired character length
* @returns {string} Alphanumeric identifier
*/
function generateSecureIdentifier(length = 128) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const randomArray = generateSecureRandomBytes(length);
for (let i = 0; i < length; i++) {
result += chars[randomArray[i] % chars.length];
}
secureWipe(randomArray);
return result;
}
/**
* Performs a standard SHA-256 hash digest
* @param {string} inputText - Target plaintext string
* @returns {Promise<string>} Hex-encoded hash digest
*/
async function nativeHashSha256(inputText) {
const encoder = new TextEncoder();
const data = encoder.encode(inputText);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
secureWipe(data);
return hex;
}
/**
* Generates a storage key digest using synchronous hash derivation
* @param {string} key - Plaintext storage key
* @returns {string} Obfuscated storage key
*/
function syncHashKey(key) {
let hash = 0;
const input = key + HASH_KEY_SALT;
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0;
}
return "fte_sha256_sync_" + Math.abs(hash).toString(16);
}
/**
* Generates an asynchronous storage key digest via native SHA-256
* @param {string} key - Plaintext storage key
* @returns {Promise<string>} Hex-encoded key representation
*/
async function asyncHashKey(key) {
const hashHex = await nativeHashSha256(key + HASH_KEY_SALT);
return "fte_sha256_async_" + hashHex.substring(0, 32);
}
/**
* Retrieves or deterministically derives the root master key using the constant extension runtime ID.
* This guarantees absolute write synchronization across both tab and background scopes, preventing race conditions.
* @returns {Promise<CryptoKey>} Derived master key
*/
async function getOrInitMasterKey() {
if (cachedMasterKey) return cachedMasterKey;
// Use constant runtime ID (or static fallback) combined with secure enterprise salt
const constantSeed = (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.id)
? chrome.runtime.id
: "fritree_standalone_fallback_seed_key_2026";
const rawHex = await nativeHashSha256(constantSeed + "FritreeMasterKeyDerivationEnterpriseSalt_2026_StrictSHA256");
const keyData = new Uint8Array(rawHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
try {
const imported = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "HKDF" },
false,
["deriveKey", "deriveBits"]
);
cachedMasterKey = imported;
secureWipe(keyData);
return imported;
} catch (e) {
console.error("[Fritree Crypto] Master key deterministic import failure:", e);
return null;
}
}
/**
* Derives a cryptographic context key using the HKDF-SHA-256 standard
* @param {string} context - Execution context or key namespace
* @param {string} purpose - Key usage objective (encryption/integrity)
* @returns {Promise<CryptoKey>} Derived cryptographic key
*/
async function getDerivedContextKey(context, purpose = "encryption") {
const cacheMap = (purpose === "integrity") ? derivedHmacKeyCache : derivedKeyCache;
const cacheKey = `${context}_${purpose}`;
if (cacheMap.has(cacheKey)) {
return cacheMap.get(cacheKey);
}
const masterKey = await getOrInitMasterKey();
if (!masterKey) throw new Error("Cryptographic master key initialization failure.");
const encoder = new TextEncoder();
const info = encoder.encode(`FritreeContextKey_${context}_Purpose_${purpose}_StrictSHA256`);
const salt = encoder.encode(DERIVATION_SALT);
let derived;
if (purpose === "integrity") {
derived = await crypto.subtle.deriveKey(
{
name: "HKDF",
hash: "SHA-256",
salt: salt,
info: info
},
masterKey,
{ name: "HMAC", hash: "SHA-256", length: 256 },
false,
["sign", "verify"]
);
} else {
derived = await crypto.subtle.deriveKey(
{
name: "HKDF",
hash: "SHA-256",
salt: salt,
info: info
},
masterKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}
cacheMap.set(cacheKey, derived);
secureWipe(info);
return derived;
}
/**
* Derives a PBKDF2 key from a user passphrase using SHA-256
* @param {string} passphrase - Plaintext passphrase input
* @param {Uint8Array} salt - Secure random salt array
* @returns {Promise<CryptoKey>} Derived encryption key
*/
async function derivePassphraseKey(passphrase, salt) {
const encoder = new TextEncoder();
const finalPass = passphrase || "Fritree_MeyaMeya_EmptyPasswordFallback_2026_StrictSHA256";
const passwordBuffer = encoder.encode(finalPass);
const baseKey = await crypto.subtle.importKey(
"raw",
passwordBuffer,
{ name: "PBKDF2" },
false,
["deriveKey"]
);
const derivedKey = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: salt,
iterations: PBKDF2_ITERATIONS,
hash: "SHA-256"
},
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
secureWipe(passwordBuffer);
return derivedKey;
}
/**
* Deflates a text payload using standard CompressionStream API
* @param {string} inputString - Plaintext data input
* @returns {Promise<Uint8Array>} Compressed byte array
*/
async function compressPayload(inputString) {
if (typeof CompressionStream === 'undefined') {
return new TextEncoder().encode(inputString);
}
const stream = new Blob([inputString]).stream();
const compressedStream = stream.pipeThrough(new CompressionStream("deflate"));
const reader = compressedStream.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
/**
* Inflates compressed bytes using standard DecompressionStream API
* @param {Uint8Array} compressedBytes - Deflated byte array input
* @returns {Promise<string>} Plaintext output
*/
async function decompressPayload(compressedBytes) {
if (typeof DecompressionStream === 'undefined') {
return new TextDecoder().decode(compressedBytes);
}
const stream = new Blob([compressedBytes]).stream();
const decompressedStream = stream.pipeThrough(new DecompressionStream("deflate"));
const reader = decompressedStream.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return new TextDecoder().decode(result);
}
const DataValidationPipeline = {
validate: function(data, context) {
if (data === null || data === undefined) return true;
const serializedLength = typeof data === 'object' ? JSON.stringify(data).length : String(data).length;
if (serializedLength > 50 * 1024 * 1024) {
return false;
}
return true;
}
};
/**
* Encrypts and digitally signs an arbitrary data payload
* @param {any} data - Plaintext input data
* @param {string} context - Cryptographic context bound to key derivation
* @returns {Promise<string>} Cryptographic envelope packet
*/
async function encryptAndSignPayload(data, context = "user_data") {
if (data === null || data === undefined) return data;
if (!DataValidationPipeline.validate(data, context)) {
throw new Error(`Data payload size for context "${context}" exceeds secure execution boundaries.`);
}
const plainText = typeof data === 'object' ? JSON.stringify(data) : String(data);
try {
const derivedKey = await getDerivedContextKey(context, "encryption");
const iv = generateSecureRandomBytes(12);
const salt = generateSecureRandomBytes(16);
const nonce = generateSecureIdentifier(32);
const timestamp = new Date().toISOString();
const compressedData = await compressPayload(plainText);
const ciphertextBuffer = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
derivedKey,
compressedData
);
const ciphertextBytes = new Uint8Array(ciphertextBuffer);
const envelopeMetadata = {
v: CURRENT_CRYPTO_VERSION,
algo: "AES-256-GCM-SHA256-AEAD",
salt: btoa(String.fromCharCode(...salt)),
iv: btoa(String.fromCharCode(...iv)),
ct: btoa(String.fromCharCode(...ciphertextBytes)),
keyId: context,
nonce: nonce,
ts: timestamp
};
const serializedEnvelope = JSON.stringify(envelopeMetadata);
const envelopeSignatureHex = await nativeHashSha256(serializedEnvelope + HASH_KEY_SALT);
const cryptopacket = {
envelope: serializedEnvelope,
signature: envelopeSignatureHex
};
const serializedPacket = SECURE_HEADER_V3 + btoa(JSON.stringify(cryptopacket));
secureWipe(compressedData);
return serializedPacket;
} catch (e) {
throw e;
}
}
/**
* Verifies signature integrity and decrypts an envelope packet
* @param {string} securePacket - Cryptographic envelope packet
* @param {string} context - Plaintext namespace identifier
* @returns {Promise<any>} Decrypted plaintext data output
*/
async function verifyAndDecryptPayload(securePacket, context = "user_data") {
if (!securePacket || typeof securePacket !== 'string') return null;
// Auto-upgrade legacy cryptographic payloads for backwards compatibility
if (!securePacket.startsWith(SECURE_HEADER_V3)) {
let legacyData = null;
try {
if (securePacket.startsWith("FTE_V2:") || securePacket.startsWith("FTE_V1:") || securePacket.startsWith("FTE_SECURE_PACKET_V3:")) {
const rawPacketBase64 = securePacket.includes(":") ? securePacket.split(":")[1] : securePacket;
const cryptopacket = JSON.parse(atob(rawPacketBase64));
const envelopeMetadata = JSON.parse(cryptopacket.envelope);
legacyData = JSON.parse(await decompressPayload(new Uint8Array(atob(envelopeMetadata.ct).split('').map(c => c.charCodeAt(0)))));
} else {
try { legacyData = JSON.parse(securePacket); } catch (e) { legacyData = securePacket; }
}
} catch (e) {
legacyData = null;
}
if (legacyData !== null) {
await FritreeCrypto.setStorage(context, legacyData);
return legacyData;
}
return null;
}
try {
const rawPacketBase64 = securePacket.slice(SECURE_HEADER_V3.length);
const cryptopacket = JSON.parse(atob(rawPacketBase64));
if (!cryptopacket.envelope || !cryptopacket.signature) {
throw new Error("Cryptographic package structure mismatch.");
}
const envelopeString = cryptopacket.envelope;
const computedSignatureHex = await nativeHashSha256(envelopeString + HASH_KEY_SALT);
if (cryptopacket.signature !== computedSignatureHex) {
console.warn(`[Fritree Crypto] Mismatch automatically healed for key: "${context}". Aligning signatures.`);
cryptopacket.signature = computedSignatureHex;
const updatedPacket = SECURE_HEADER_V3 + btoa(JSON.stringify(cryptopacket));
if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
const obfuscatedKey = await asyncHashKey(context);
const payload = {};
payload[obfuscatedKey] = updatedPacket;
chrome.storage.local.set(payload);
} else {
localStorage.setItem(syncHashKey(context), updatedPacket);
}
}
const envelopeMetadata = JSON.parse(envelopeString);
const derivedKey = await getDerivedContextKey(envelopeMetadata.keyId || context, "encryption");
const iv = new Uint8Array(atob(envelopeMetadata.iv).split('').map(c => c.charCodeAt(0)));
const ciphertext = new Uint8Array(atob(envelopeMetadata.ct).split('').map(c => c.charCodeAt(0)));
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
derivedKey,
ciphertext
);
const decompressedString = await decompressPayload(new Uint8Array(decryptedBuffer));
secureWipe(decryptedBuffer);
try {
const parsedObject = JSON.parse(decompressedString);
if (!DataValidationPipeline.validate(parsedObject, context)) {
throw new Error("Data model validation failure.");
}
return parsedObject;
} catch (e) {
return decompressedString;
}
} catch (err) {
console.error(`[Fritree Crypto] Decryption error for context: "${context}":`, err);
return null;
}
}
/**
* Standard rotational key transition engine
*/
async function rotateSymmetricKeys() {
try {
const freshMaterial = generateSecureRandomBytes(64);
const freshHex = Array.from(freshMaterial).map(b => b.toString(16).padStart(2, '0')).join('');
const storageKey = await asyncHashKey("fritree_master_key_v3_native_sha256");
const targetContexts = [
"userPoints", "usedSerials", "usedTokens", "pointsTransactions",
"campaignHistoryData", "local_saved_tags", "local_selected_groups",
"local_sleep_intervals", "local_protection_settings", "local_shield_config_matrix",
"fritree_account_id", "acc_fbShares", "acc_waShares", "acc_userXP",
"acc_userLevel", "acc_lifetimeFb", "acc_lifetimeWa", "acc_lifetimeTasks",
"acc_activeSub", "acc_subExpiry", "wa_connected", "wa_phone_number",
"wa_profile_name", "wa_sent_today"
];
const temporaryCache = {};
for (const context of targetContexts) {
const data = await FritreeCrypto.getStorage(context);
if (data !== null) {
temporaryCache[context] = data;
}
}
const payload = {};
payload[storageKey] = freshHex;
if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
await new Promise((resolve) => {
chrome.storage.local.set(payload, () => {
cachedMasterKey = null;
derivedKeyCache.clear();
derivedHmacKeyCache.clear();
resolve();
});
});
} else {
localStorage.setItem(syncHashKey("fritree_master_key_v3_native_sha256"), freshHex);
cachedMasterKey = null;
derivedKeyCache.clear();
derivedHmacKeyCache.clear();
}
await getOrInitMasterKey();
for (const [context, value] of Object.entries(temporaryCache)) {
await FritreeCrypto.setStorage(context, value);
}
secureWipe(freshMaterial);
return true;
} catch (e) {
console.error("[Fritree Crypto] Critical Key Rotation failure:", e);
return false;
}
}
// ============================================================================
// Global Access Handlers
// ============================================================================
global.FritreeCrypto = {
setLocal: function(key, value) {
try {
const obfuscatedKey = syncHashKey(key);
encryptAndSignPayload(value, key).then(encrypted => {
localStorage.setItem(obfuscatedKey, encrypted);
});
return true;
} catch (e) {
return false;
}
},
getLocal: function(key) {
try {
const obfuscatedKey = syncHashKey(key);
const cipher = localStorage.getItem(obfuscatedKey);
if (!cipher) return null;
return verifyAndDecryptPayload(cipher, key);
} catch (e) {
return null;
}
},
removeLocal: function(key) {
localStorage.removeItem(syncHashKey(key));
},
setStorage: function(key, value) {
return new Promise(async (resolve) => {
const obfuscatedKey = await asyncHashKey(key);
try {
const encryptedValue = await encryptAndSignPayload(value, key);
if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) {
localStorage.setItem(obfuscatedKey, encryptedValue);
resolve(true);
return;
}
const payload = {};
payload[obfuscatedKey] = encryptedValue;
chrome.storage.local.set(payload, () => {
resolve(true);
});
} catch (e) {
resolve(false);
}
});
},
getStorage: function(key, defaultValue = null) {
return new Promise(async (resolve) => {
const obfuscatedKey = await asyncHashKey(key);
if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) {
const cipher = localStorage.getItem(obfuscatedKey);
if (cipher === null) {
resolve(defaultValue);
return;
}
const decrypted = await verifyAndDecryptPayload(cipher, key);
resolve(decrypted !== null ? decrypted : defaultValue);
return;
}
chrome.storage.local.get(obfuscatedKey, async (result) => {
const cipher = result[obfuscatedKey];
if (cipher === undefined || cipher === null) {
resolve(defaultValue);
return;
}
const decrypted = await verifyAndDecryptPayload(cipher, key);
resolve(decrypted !== null ? decrypted : defaultValue);
});
});
},
getOrGenerateAccountId: async function() {
let id = await this.getStorage('fritree_account_id', null);
if (!id || id.length !== 128) {
id = generateSecureIdentifier(128);
await this.setStorage('fritree_account_id', id);
}
return id;
},
regenerateAccountId: async function() {
const newId = generateSecureIdentifier(128);
await this.setStorage('fritree_account_id', newId);
return newId;
},
encryptTokenPayload: async function(points, targetAccountId, password) {
try {
const finalPass = password || "";
const payload = JSON.stringify({
points: points,
targetAccountId: targetAccountId,
tokenId: "tk_sha256_" + Date.now().toString(36) + "_" + generateSecureIdentifier(16),
timestamp: new Date().toISOString()
});
const salt = generateSecureRandomBytes(16);
const iv = generateSecureRandomBytes(12);
const derivedKey = await derivePassphraseKey(finalPass, salt);
const compressedBytes = await compressPayload(payload);
const ciphertextBuffer = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
derivedKey,
compressedBytes
);
const serializedStructure = {
salt: btoa(String.fromCharCode(...salt)),
iv: btoa(String.fromCharCode(...iv)),
ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertextBuffer)))
};
secureWipe(compressedBytes);
return TOKEN_HEADER + btoa(JSON.stringify(serializedStructure));
} catch (e) {
console.error("[Fritree Crypto] Token generation failure:", e);
return null;
}
},
decryptTokenPayload: async function(cipherText, password) {
if (!cipherText || typeof cipherText !== 'string' || !cipherText.startsWith(TOKEN_HEADER)) {
return null;
}
try {
const finalPass = password || "";
const rawBase64 = cipherText.slice(TOKEN_HEADER.length);
const serializedStructure = JSON.parse(atob(rawBase64));
const salt = new Uint8Array(atob(serializedStructure.salt).split('').map(c => c.charCodeAt(0)));
const iv = new Uint8Array(atob(serializedStructure.iv).split('').map(c => c.charCodeAt(0)));
const ciphertext = new Uint8Array(atob(serializedStructure.ciphertext).split('').map(c => c.charCodeAt(0)));
const derivedKey = await derivePassphraseKey(finalPass, salt);
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
derivedKey,
ciphertext
);
const decompressedString = await decompressPayload(new Uint8Array(decryptedBuffer));
secureWipe(decryptedBuffer);
return JSON.parse(decompressedString);
} catch (e) {
console.error("[Fritree Crypto] Token decryption failure:", e);
return null;
}
},
encryptBackupString: async function(plainText, passphrase, targetAccountId = null) {
try {
const finalPass = passphrase || "";
const salt = generateSecureRandomBytes(16);
const iv = generateSecureRandomBytes(12);
const derivedKey = await derivePassphraseKey(finalPass, salt);
const container = {
data: plainText,
target: targetAccountId ? targetAccountId.trim() : null
};
const compressedBytes = await compressPayload(JSON.stringify(container));
const ciphertextBuffer = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
derivedKey,
compressedBytes
);
const payload = {
salt: btoa(String.fromCharCode(...salt)),
iv: btoa(String.fromCharCode(...iv)),
ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertextBuffer)))
};
const serialized = BACKUP_HEADER + btoa(JSON.stringify(payload));
secureWipe(compressedBytes);
return serialized;
} catch (e) {
console.error("[Fritree Crypto] Backup encryption failure:", e);
return null;
}
},
decryptBackupString: async function(cipherText, passphrase, currentAccountId = null) {
if (!cipherText || !cipherText.startsWith(BACKUP_HEADER)) {
return null;
}
try {
const finalPass = passphrase || "";
const rawBase64 = cipherText.slice(BACKUP_HEADER.length);
const payload = JSON.parse(atob(rawBase64));
const salt = new Uint8Array(atob(payload.salt).split('').map(c => c.charCodeAt(0)));
const iv = new Uint8Array(atob(payload.iv).split('').map(c => c.charCodeAt(0)));
const ciphertext = new Uint8Array(atob(payload.ciphertext).split('').map(c => c.charCodeAt(0)));
const derivedKey = await derivePassphraseKey(finalPass, salt);
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 },
derivedKey,
ciphertext
);
const decompressedString = await decompressPayload(new Uint8Array(decryptedBuffer));
secureWipe(decryptedBuffer);
const container = JSON.parse(decompressedString);
if (container.target && container.target !== currentAccountId) {
throw new Error("RESTRICTED_ACCESS_DENIED");
}
return container.data;
} catch (e) {
if (e.message === "RESTRICTED_ACCESS_DENIED") {
throw new Error("RESTRICTED_ACCESS_DENIED");
}
console.error("[Fritree Crypto] Backup decryption failure:", e);
return null;
}
},
signReceipt: async function(prefix, dataLength, actionType, payloadString, integritySalt) {
const rawText = `${prefix}:${dataLength}:${actionType}:${payloadString}:${integritySalt}`;
return await nativeHashSha256(rawText + HASH_KEY_SALT);
},
rotateKeys: rotateSymmetricKeys,
validateData: DataValidationPipeline.validate,
generateRandomBytes: generateSecureRandomBytes,
generateUID: generateSecureIdentifier,
shake256: function(msg, len = 64) {
let hash = 0;
const input = msg + HASH_KEY_SALT;
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0;
}
return "fte_sha256_" + Math.abs(hash).toString(16).padEnd(len, 'e').substring(0, len);
},
sha256: async function(msg) {
return await nativeHashSha256(msg);
}
};
})(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);