From 2450c6326ff08bb5af2dbf8a6c544496eab0c0c4 Mon Sep 17 00:00:00 2001 From: AlphaX-Qortal Date: Sat, 25 Jan 2025 14:12:22 +0100 Subject: [PATCH] Fiv decrypt when key is missing --- crypto/api/deps/Base64Message.js | 5 + .../core/components/GroupEncryption.js | 110 ++++++++++++------ .../components/webworkerDecodeMessages.js | 5 + .../group-management/group-management.src.js | 5 + plugins/plugins/core/q-chat/q-chat.src.js | 2 +- 5 files changed, 93 insertions(+), 34 deletions(-) diff --git a/crypto/api/deps/Base64Message.js b/crypto/api/deps/Base64Message.js index d26c352b..09d4a509 100644 --- a/crypto/api/deps/Base64Message.js +++ b/crypto/api/deps/Base64Message.js @@ -33,6 +33,11 @@ Base64Message.decode = function (string, keys, ref) { let hubString = '' const res = decryptSingle(string, keys, false) + + if (res === 'noKey' || res === 'decryptionFailed') { + return '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"This message could not be decrypted"}]}]},"images":[""],"repliedTo":"","version":3}' + } + const decryptToUnit8Array = base64ToUint8Array(res) const responseData = uint8ArrayToObject(decryptToUnit8Array) diff --git a/plugins/plugins/core/components/GroupEncryption.js b/plugins/plugins/core/components/GroupEncryption.js index d18cfe3b..9f09d5ba 100644 --- a/plugins/plugins/core/components/GroupEncryption.js +++ b/plugins/plugins/core/components/GroupEncryption.js @@ -17,7 +17,8 @@ export function base64ToUint8Array(base64) { export function uint8ArrayToBase64(uint8Array) { const length = uint8Array.length let binaryString = '' - const chunkSize = 1024 * 1024 // Process 1MB at a time + // Process 1MB at a time + const chunkSize = 1024 * 1024 for (let i = 0; i < length; i += chunkSize) { const chunkEnd = Math.min(i + chunkSize, length) @@ -104,7 +105,8 @@ export function validateSecretKey(obj) { // Function to create a symmetric key and nonce export const createSymmetricKeyAndNonce = () => { - const messageKey = new Uint8Array(32) // 32 bytes for the symmetric key + // 32 bytes for the symmetric key + const messageKey = new Uint8Array(32) crypto.getRandomValues(messageKey) return { messageKey: uint8ArrayToBase64(messageKey)} @@ -233,11 +235,13 @@ export const encryptSingle = async (data64, secretKeyObject, typeNumber = 2) => encryptedDataBase64 = uint8ArrayToBase64(encryptedData) // Concatenate the highest key, type number, and encrypted data (old format) - const highestKeyStr = highestKey.toString().padStart(10, '0') // Fixed length of 10 digits + // Fixed length of 10 digits + const highestKeyStr = highestKey.toString().padStart(10, '0') finalEncryptedData = btoa(highestKeyStr + encryptedDataBase64) } else { // New format: Generate a random nonce and embed it in the message - nonce = new Uint8Array(24) // 24 bytes for the nonce + // 24 bytes for the nonce + nonce = new Uint8Array(24) crypto.getRandomValues(nonce) // Encrypt the data with the new nonce and message key @@ -248,7 +252,8 @@ export const encryptSingle = async (data64, secretKeyObject, typeNumber = 2) => const nonceBase64 = uint8ArrayToBase64(nonce) // Concatenate the highest key, type number, nonce, and encrypted data (new format) - const highestKeyStr = highestKey.toString().padStart(10, '0') // Fixed length of 10 digits + // Fixed length of 10 digits + const highestKeyStr = highestKey.toString().padStart(10, '0') const highestKeyBytes = new TextEncoder().encode(highestKeyStr.padStart(10, '0')) const typeNumberBytes = new TextEncoder().encode(typeNumberStr.padStart(3, '0')) @@ -306,7 +311,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Check if we have a valid secret key for the extracted highestKey if (!secretKeyObject[highestKey]) { - throw new Error('Cannot find correct secretKey') + return 'noKey' } const secretKeyEntry = secretKeyObject[highestKey] @@ -315,18 +320,24 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits const possibleTypeNumberStr = decodeForNumber.slice(10, 13) - const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr) // Check if next 3 characters are digits + + // Check if next 3 characters are digits + const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr) if (secretKeyEntry.nonce) { // Old format: nonce is present in the secretKeyObject, so no type number exists nonceBase64 = secretKeyEntry.nonce - encryptedDataBase64 = decodeForNumber.slice(10) // The remaining part is the encrypted data + + // The remaining part is the encrypted data + encryptedDataBase64 = decodeForNumber.slice(10) } else { if (hasTypeNumber) { - // const typeNumberStr = new TextDecoder().decode(typeNumberBytes) if(decodeForNumber.slice(10, 13) !== '001'){ const decodedBinary = base64ToUint8Array(decodedData) - const highestKeyBytes = decodedBinary.slice(0, 10) // if ASCII digits only + + // if ASCII digits only + const highestKeyBytes = decodedBinary.slice(0, 10) + const highestKeyStr = new TextDecoder().decode(highestKeyBytes) const nonce = decodedBinary.slice(13, 13 + 24) const encryptedData = decodedBinary.slice(13 + 24) @@ -336,7 +347,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Check if decryption was successful if (!decryptedBytes) { - throw new Error("Decryption failed") + return 'decryptionFailed' } // Convert the decrypted Uint8Array back to a Base64 string @@ -344,13 +355,21 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { } // New format: Extract type number and nonce - typeNumberStr = possibleTypeNumberStr // Extract type number - nonceBase64 = decodeForNumber.slice(13, 45) // Extract nonce (next 32 characters after type number) - encryptedDataBase64 = decodeForNumber.slice(45) // The remaining part is the encrypted data + // Extract type number + typeNumberStr = possibleTypeNumberStr + + // Extract nonce (next 32 characters after type number) + nonceBase64 = decodeForNumber.slice(13, 45) + + // The remaining part is the encrypted data + encryptedDataBase64 = decodeForNumber.slice(45) } else { // Old format without type number (nonce is embedded in the message, first 32 characters after keyStr) - nonceBase64 = decodeForNumber.slice(10, 42) // First 32 characters for the nonce - encryptedDataBase64 = decodeForNumber.slice(42) // The remaining part is the encrypted data + // First 32 characters for the nonce + nonceBase64 = decodeForNumber.slice(10, 42) + + // The remaining part is the encrypted data + encryptedDataBase64 = decodeForNumber.slice(42) } } @@ -360,7 +379,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { const messageKey = base64ToUint8Array(secretKeyEntry.messageKey) if (!(Uint8ArrayData instanceof Uint8Array)) { - throw new Error("The Uint8ArrayData you've submitted is invalid") + return 'decryptionFailed' } // Decrypt the data using the nonce and messageKey @@ -368,7 +387,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Check if decryption was successful if (!decryptedData) { - throw new Error("Decryption failed") + return 'decryptionFailed' } // Convert the decrypted Uint8Array back to a Base64 string @@ -383,25 +402,33 @@ export const decryptGroupEncryptionWithSharingKey = async (data64EncryptedData, // Extract the nonce const nonceStartPosition = strUint8Array.length - const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition - const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition - const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Calculate count first - const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) const count = new Uint32Array(countArray.buffer)[0] // Then use count to calculate encryptedData - const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) const symmetricKey = base64ToUint8Array(key) @@ -426,26 +453,34 @@ export function decryptGroupDataQortalRequest(data64EncryptedData, privateKey) { // Extract the nonce const nonceStartPosition = strUint8Array.length - const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition - const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition - const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition) // Calculate count first - const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) const count = new Uint32Array(countArray.buffer)[0] // Then use count to calculate encryptedData - const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) @@ -474,6 +509,7 @@ export function decryptGroupDataQortalRequest(data64EncryptedData, privateKey) { if (decryptedKey) { // Decrypt the data using the symmetric key. const decryptedData = nacl.secretbox.open(encryptedData, nonce, decryptedKey) + // If decryption was successful, decryptedData will not be null. if (decryptedData) { return decryptedData @@ -493,26 +529,34 @@ export function decryptGroupData(data64EncryptedData, privateKey) { // Extract the nonce const nonceStartPosition = strUint8Array.length - const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition - const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition - const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition) // Calculate count first - const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) const count = new Uint32Array(countArray.buffer)[0] // Then use count to calculate encryptedData - const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) diff --git a/plugins/plugins/core/components/webworkerDecodeMessages.js b/plugins/plugins/core/components/webworkerDecodeMessages.js index dc8c0e09..a9fbef4b 100644 --- a/plugins/plugins/core/components/webworkerDecodeMessages.js +++ b/plugins/plugins/core/components/webworkerDecodeMessages.js @@ -2789,6 +2789,11 @@ const decode = (string, keys, ref) => { let hubString = '' const res = decryptSingle(string, keys, false) + + if (res === 'noKey' || res === 'decryptionFailed') { + return '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"This message could not be decrypted"}]}]},"images":[""],"repliedTo":"","version":3}' + } + const decryptToUnit8Array = base64ToUint8Array(res) const responseData = uint8ArrayToObject(decryptToUnit8Array) diff --git a/plugins/plugins/core/group-management/group-management.src.js b/plugins/plugins/core/group-management/group-management.src.js index 2df62b99..7b1a6df9 100644 --- a/plugins/plugins/core/group-management/group-management.src.js +++ b/plugins/plugins/core/group-management/group-management.src.js @@ -3035,6 +3035,11 @@ class GroupManagement extends LitElement { let hubString = '' const res = decryptSingle(string, this.secretKeys, false) + + if (res === 'noKey' || res === 'decryptionFailed') { + return '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"This message could not be decrypted"}]}]},"images":[""],"repliedTo":"","version":3}' + } + const decryptToUnit8Array = base64ToUint8Array(res) const responseData = uint8ArrayToObject(decryptToUnit8Array) diff --git a/plugins/plugins/core/q-chat/q-chat.src.js b/plugins/plugins/core/q-chat/q-chat.src.js index ba12a089..cd9a11d1 100644 --- a/plugins/plugins/core/q-chat/q-chat.src.js +++ b/plugins/plugins/core/q-chat/q-chat.src.js @@ -515,7 +515,7 @@ class Chat extends LitElement { if (adminExists(gAddress)) { const sleep = (t) => new Promise(r => setTimeout(r, t)) - const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64&rebuild=true&async=true` + const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64` const res = await fetch(dataUrl) do {