Browse Source

Merge branch 'master' of https://github.com/Qortal/qortal-ui

qortal-ui-dev^2
AlphaX-Projects 1 year ago
parent
commit
fa50e01981
  1. 214
      plugins/plugins/core/components/qdn-action-encryption.js
  2. 3
      plugins/plugins/core/components/qdn-action-types.js
  3. 112
      plugins/plugins/core/qdn/browser/browser.src.js

214
plugins/plugins/core/components/qdn-action-encryption.js

@ -107,7 +107,217 @@ export const encryptData = ({ data64, recipientPublicKey }) => {
recipientPublicKey
}
} catch (error) {
console.log({ error })
throw new Error("Error in encrypting data")
}
}
}
export const encryptDataGroup = ({ data64, publicKeys }) => {
const userPublicKey = window.parent.reduxStore.getState().app.selectedAddress.base58PublicKey
let combinedPublicKeys = publicKeys
if (userPublicKey) {
combinedPublicKeys = [...publicKeys, userPublicKey]
}
const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)];
const Uint8ArrayData = base64ToUint8Array(data64)
if (!(Uint8ArrayData instanceof Uint8Array)) {
throw new Error("The Uint8ArrayData you've submitted is invalid")
}
try {
const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey
if (!privateKey) {
throw new Error("Unable to retrieve keys")
}
// Generate a random symmetric key for the message.
const messageKey = new Uint8Array(32);
window.crypto.getRandomValues(messageKey);
const nonce = new Uint8Array(24);
window.crypto.getRandomValues(nonce);
// Encrypt the data with the symmetric key.
const encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey);
// Generate a keyNonce outside of the loop.
const keyNonce = new Uint8Array(24);
window.crypto.getRandomValues(keyNonce);
// Encrypt the symmetric key for each recipient.
let encryptedKeys = [];
publicKeysDuplicateFree.forEach((recipientPublicKey) => {
const publicKeyUnit8Array = window.parent.Base58.decode(recipientPublicKey)
const convertedPrivateKey = ed2curve.convertSecretKey(privateKey)
const convertedPublicKey = ed2curve.convertPublicKey(publicKeyUnit8Array)
const sharedSecret = new Uint8Array(32)
// the length of the sharedSecret will be 32 + 16
// When you're encrypting data using nacl.secretbox, it's adding an authentication tag to the result, which is 16 bytes long. This tag is used for verifying the integrity and authenticity of the data when it is decrypted
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey)
// Encrypt the symmetric key with the shared secret.
const encryptedKey = nacl.secretbox(messageKey, keyNonce, sharedSecret);
encryptedKeys.push(encryptedKey);
});
const str = "qortalGroupEncryptedData";
const strEncoder = new TextEncoder();
const strUint8Array = strEncoder.encode(str);
// Convert sender's public key to Uint8Array and add to the message
const senderPublicKeyUint8Array = window.parent.Base58.decode(userPublicKey);
// Combine all data into a single Uint8Array.
// Calculate size of combinedData
let combinedDataSize = strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length + encryptedData.length + 4;
let encryptedKeysSize = 0;
encryptedKeys.forEach((key) => {
encryptedKeysSize += key.length;
});
combinedDataSize += encryptedKeysSize;
let combinedData = new Uint8Array(combinedDataSize);
combinedData.set(strUint8Array);
combinedData.set(nonce, strUint8Array.length);
combinedData.set(keyNonce, strUint8Array.length + nonce.length);
combinedData.set(senderPublicKeyUint8Array, strUint8Array.length + nonce.length + keyNonce.length);
combinedData.set(encryptedData, strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length);
// Initialize offset for encryptedKeys
let encryptedKeysOffset = strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length + encryptedData.length;
encryptedKeys.forEach((key) => {
combinedData.set(key, encryptedKeysOffset);
encryptedKeysOffset += key.length;
});
const countArray = new Uint8Array(new Uint32Array([publicKeysDuplicateFree.length]).buffer);
combinedData.set(countArray, combinedData.length - 4);
const uint8arrayToData64 = uint8ArrayToBase64(combinedData)
return uint8arrayToData64;
} catch (error) {
throw new Error("Error in encrypting data")
}
}
export function uint8ArrayStartsWith(uint8Array, string) {
const stringEncoder = new TextEncoder();
const stringUint8Array = stringEncoder.encode(string);
if (uint8Array.length < stringUint8Array.length) {
return false;
}
for (let i = 0; i < stringUint8Array.length; i++) {
if (uint8Array[i] !== stringUint8Array[i]) {
return false;
}
}
return true;
}
export function decryptDeprecatedSingle(uint8Array, publicKey) {
const combinedData = uint8Array
const str = "qortalEncryptedData";
const strEncoder = new TextEncoder();
const strUint8Array = strEncoder.encode(str);
const strData = combinedData.slice(0, strUint8Array.length);
const nonce = combinedData.slice(strUint8Array.length, strUint8Array.length + 24);
const _encryptedData = combinedData.slice(strUint8Array.length + 24);
const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey
const _publicKey = window.parent.Base58.decode(publicKey)
if (!privateKey || !_publicKey) {
throw new Error("Unable to retrieve keys")
}
const convertedPrivateKey = ed2curve.convertSecretKey(privateKey)
const convertedPublicKey = ed2curve.convertPublicKey(_publicKey)
const sharedSecret = new Uint8Array(32);
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey)
const _chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result
const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed)
if (!_decryptedData) {
throw new Error("Unable to decrypt")
}
const decryptedDataToBase64 = uint8ArrayToBase64(_decryptedData)
return decryptedDataToBase64
}
export function decryptGroupData(data64EncryptedData) {
const allCombined = base64ToUint8Array(data64EncryptedData);
const str = "qortalGroupEncryptedData";
const strEncoder = new TextEncoder();
const strUint8Array = strEncoder.encode(str);
// Extract the nonce
const nonceStartPosition = strUint8Array.length;
const nonceEndPosition = nonceStartPosition + 24; // Nonce is 24 bytes
const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition);
// Extract the shared keyNonce
const keyNonceStartPosition = nonceEndPosition;
const keyNonceEndPosition = keyNonceStartPosition + 24; // Nonce is 24 bytes
const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition);
// Extract the sender's public key
const senderPublicKeyStartPosition = keyNonceEndPosition;
const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32; // Public keys are 32 bytes
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)
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
const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4);
const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition);
// Extract the encrypted keys
// 32+16 = 48
const combinedKeys = allCombined.slice(encryptedDataEndPosition, encryptedDataEndPosition + (count * 48));
const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey
if (!privateKey) {
throw new Error("Unable to retrieve keys")
}
const convertedPrivateKey = ed2curve.convertSecretKey(privateKey)
const convertedSenderPublicKey = ed2curve.convertPublicKey(senderPublicKey)
const sharedSecret = new Uint8Array(32)
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedSenderPublicKey)
for (let i = 0; i < count; i++) {
const encryptedKey = combinedKeys.slice(i * 48, (i + 1) * 48);
// Decrypt the symmetric key.
const decryptedKey = nacl.secretbox.open(encryptedKey, keyNonce, sharedSecret);
// If decryption was successful, decryptedKey will not be null.
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
}
}
}
throw new Error("Unable to decrypt data")
}

3
plugins/plugins/core/components/qdn-action-types.js

@ -43,6 +43,9 @@ export const ENCRYPT_DATA = 'ENCRYPT_DATA'
// DECRYPT_DATA
export const DECRYPT_DATA = 'DECRYPT_DATA'
// DECRYPT_DATA_GROUP
export const DECRYPT_DATA_GROUP = 'DECRYPT_DATA_GROUP'
// SAVE_FILE
export const SAVE_FILE = 'SAVE_FILE'

112
plugins/plugins/core/qdn/browser/browser.src.js

@ -20,10 +20,9 @@ import { Loader } from '../../../utils/loader.js';
import { QORT_DECIMALS } from '../../../../../crypto/api/constants'
import nacl from '../../../../../crypto/api/deps/nacl-fast.js'
import ed2curve from '../../../../../crypto/api/deps/ed2curve.js'
import { mimeToExtensionMap } from '../../components/qdn-action-constants'
import { base64ToUint8Array, encryptData, fileToBase64, uint8ArrayToBase64 } from '../../components/qdn-action-encryption'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
import { mimeToExtensionMap } from '../../components/qdn-action-constants';
import { base64ToUint8Array, decryptDeprecatedSingle, decryptGroupData, encryptDataGroup, fileToBase64, uint8ArrayStartsWith, uint8ArrayToBase64 } from '../../components/qdn-action-encryption';
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
class WebBrowser extends LitElement {
static get properties() {
@ -618,57 +617,52 @@ class WebBrowser extends LitElement {
}
case actions.DECRYPT_DATA: {
const requiredFields = ['encryptedData', 'publicKey'];
const missingFields = [];
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
}
});
const { encryptedData, publicKey } = data
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`
try {
let data = {};
data['error'] = errorMsg;
response = JSON.stringify(data);
break
}
const { encryptedData, publicKey } = data
if (!encryptedData) {
const errorMsg = `Missing fields: encryptedData`
data['error'] = errorMsg;
response = JSON.stringify(data);
break
try {
}
const uint8Array = base64ToUint8Array(encryptedData)
const combinedData = uint8Array
const str = "qortalEncryptedData";
const strEncoder = new TextEncoder();
const strUint8Array = strEncoder.encode(str);
const startsWithQortalEncryptedData = uint8ArrayStartsWith(uint8Array, "qortalEncryptedData");
if (startsWithQortalEncryptedData) {
const strData = combinedData.slice(0, strUint8Array.length);
const nonce = combinedData.slice(strUint8Array.length, strUint8Array.length + 24);
const _encryptedData = combinedData.slice(strUint8Array.length + 24);
if (!publicKey) {
const errorMsg = `Missing fields: publicKey`
data['error'] = errorMsg;
response = JSON.stringify(data);
break
}
const decryptedDataToBase64 = decryptDeprecatedSingle(uint8Array, publicKey)
response = JSON.stringify(decryptedDataToBase64);
break;
const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey
const _publicKey = window.parent.Base58.decode(publicKey)
if (!privateKey || !_publicKey) {
data['error'] = "Unable to retrieve keys"
response = JSON.stringify(data);
break
}
const startsWithQortalGroupEncryptedData = uint8ArrayStartsWith(uint8Array, "qortalGroupEncryptedData");
if (startsWithQortalGroupEncryptedData) {
const convertedPrivateKey = ed2curve.convertSecretKey(privateKey)
const convertedPublicKey = ed2curve.convertPublicKey(_publicKey)
const sharedSecret = new Uint8Array(32);
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey)
const decryptedData = decryptGroupData(encryptedData)
const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData)
response = JSON.stringify(decryptedDataToBase64);
break;
const _chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result
const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed)
const decryptedDataToBase64 = uint8ArrayToBase64(_decryptedData)
response = JSON.stringify(decryptedDataToBase64);
}
break;
const errorMsg = "Unable to decrypt"
data['error'] = errorMsg;
response = JSON.stringify(data);
break
} catch (error) {
const data = {};
@ -678,6 +672,7 @@ class WebBrowser extends LitElement {
break
}
}
case actions.GET_LIST_ITEMS: {
const requiredFields = ['list_name'];
const missingFields = [];
@ -972,9 +967,9 @@ class WebBrowser extends LitElement {
identifier = 'default';
}
if (data.encrypt && !data.recipientPublicKey) {
if (data.encrypt && (!data.publicKeys || (Array.isArray(data.publicKeys) && data.publicKeys.length === 0))) {
let data = {};
data['error'] = "Encrypting data requires the recipient's public key";
data['error'] = "Encrypting data requires public keys";
response = JSON.stringify(data);
break
}
@ -984,14 +979,17 @@ class WebBrowser extends LitElement {
response = JSON.stringify(data);
break
}
if (data.file) {
data64 = await fileToBase64(data.file)
}
if (data.encrypt) {
try {
const encryptDataResponse = encryptData({
data64, recipientPublicKey: data.recipientPublicKey
const encryptDataResponse = encryptDataGroup({
data64, publicKeys: data.publicKeys
})
if (encryptDataResponse.encryptedData) {
data64 = encryptDataResponse.encryptedData
if (encryptDataResponse) {
data64 = encryptDataResponse
}
} catch (error) {
@ -1005,6 +1003,7 @@ class WebBrowser extends LitElement {
}
const res2 = await showModalAndWait(
actions.PUBLISH_QDN_RESOURCE,
{
@ -1097,9 +1096,9 @@ class WebBrowser extends LitElement {
response = JSON.stringify(data);
break
}
if (data.encrypt && !data.recipientPublicKey) {
if (data.encrypt && (!data.publicKeys || (Array.isArray(data.publicKeys) && data.publicKeys.length === 0))) {
let data = {};
data['error'] = "Encrypting data requires the recipient's public key";
data['error'] = "Encrypting data requires public keys";
response = JSON.stringify(data);
break
}
@ -1157,14 +1156,19 @@ class WebBrowser extends LitElement {
if (!data.encrypt && service.endsWith("_PRIVATE")) {
throw new Error("Only encrypted data can go into private services")
}
if (data.file) {
data64 = await fileToBase64(data.file)
}
if (data.encrypt) {
try {
const encryptDataResponse = encryptData({
data64, recipientPublicKey: data.recipientPublicKey
const encryptDataResponse = encryptDataGroup({
data64, publicKeys: data.publicKeys
})
if (encryptDataResponse.encryptedData) {
data64 = encryptDataResponse.encryptedData
if (encryptDataResponse) {
data64 = encryptDataResponse
}
} catch (error) {

Loading…
Cancel
Save