encrypt and upload asset slices

This commit is contained in:
Roland Osborne 2023-04-26 13:05:55 -07:00
parent e388d9ba42
commit 7782ace9c4
3 changed files with 96 additions and 34 deletions

View File

@ -56,6 +56,27 @@ export function updateChannelSubject(subject, contentKey) {
return { subjectEncrypted, subjectIv };
}
export function encryptBlock(block, contentKey) {
const key = CryptoJS.enc.Hex.parse(contentKey);
const iv = CryptoJS.lib.WordArray.random(128 / 8);
const encrypted = CryptoJS.AES.encrypt(block, key, { iv: iv });
const blockEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64)
const blockIv = iv.toString();
return { blockEncrypted, blockIv };
}
export function decryptBlock(blockEncrypted, blockIv, contentKey) {
const iv = CryptoJS.enc.Hex.parse(blockIv);
const key = CryptoJS.enc.Hex.parse(contentKey);
const enc = CryptoJS.enc.Base64.parse(blockEncrypted);
const cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv });
const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv });
const block = dec.toString(CryptoJS.enc.Utf8);
return block;
}
export function decryptChannelSubject(subject, contentKey) {
const { subjectEncrypted, subjectIv } = JSON.parse(subject);
const iv = CryptoJS.enc.Hex.parse(subjectIv);

View File

@ -1,6 +1,8 @@
import { useState, useRef } from 'react';
import axios from 'axios';
const ENCRYPTED_BLOCK_SIZE = (128 * 1024); //110k
export function useUploadContext() {
const [state, setState] = useState({
@ -69,7 +71,8 @@ export function useUploadContext() {
const controller = new AbortController();
const entry = {
index: index.current,
url: `${host}/content/channels/${channelId}/topics/${topicId}/assets?contact=${token}`,
baseUrl: `${host}/content/channels/${channelId}/topics/${topicId}/`,
urlParams: `?contact=${token}`,
files,
assets: [],
current: null,
@ -91,7 +94,8 @@ export function useUploadContext() {
const controller = new AbortController();
const entry = {
index: index.current,
url: `/content/channels/${channelId}/topics/${topicId}/assets?agent=${token}`,
baseUrl: `/content/channels/${channelId}/topics/${topicId}/`,
urlParams: `?agent=${token}`,
files,
assets: [],
current: null,
@ -154,11 +158,33 @@ async function upload(entry, update, complete) {
const file = entry.files.shift();
entry.active = {};
try {
if (file.image) {
if (file.encrypted) {
const { size, getThumb, getEncryptedBlock, position } = file;
const type = file.image ? 'image' : file.video ? 'video' : file.audio ? 'audio' : '';
const thumb = getThumb(position);
const parts = [];
for (let pos = 0; pos < size; pos += ENCRYPTED_BLOCK_SIZE) {
const { blockEncrypted, blockIv } = await getEncryptedBlock(pos, ENCRYPTED_BLOCK_SIZE);
const partId = await axios.post(`${entry.baseUrl}block${entry.urlParams}`, blockEncrypted, {
signal: entry.cancel.signal,
onUploadProgress: (ev) => {
const { loaded, total } = ev;
const partLoaded = pos + Math.floor(blockEncrypted.length * loaded / total);
entry.active = { partLoaded, size }
update();
}
});
parts.push({ blockIv, partId });
}
entry.assets.push({
encrypted: { type, thumb, parts }
});
}
else if (file.image) {
const formData = new FormData();
formData.append('asset', file.image);
let transform = encodeURIComponent(JSON.stringify(["ithumb;photo", "ilg;photo"]));
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
let asset = await axios.post(`${entry.baseUrl}assets${entry.urlParams}&transforms=${transform}`, formData, {
signal: entry.cancel.signal,
onUploadProgress: (ev) => {
const { loaded, total } = ev;
@ -178,7 +204,7 @@ async function upload(entry, update, complete) {
formData.append('asset', file.video);
let thumb = 'vthumb;video;' + file.position;
let transform = encodeURIComponent(JSON.stringify(["vlq;video", "vhd;video", thumb]));
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
let asset = await axios.post(`${entry.baseUrl}assets${entry.urlParams}&transforms=${transform}`, formData, {
signal: entry.cancel.signal,
onUploadProgress: (ev) => {
const { loaded, total } = ev;
@ -198,7 +224,7 @@ async function upload(entry, update, complete) {
const formData = new FormData();
formData.append('asset', file.audio);
let transform = encodeURIComponent(JSON.stringify(["acopy;audio"]));
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
let asset = await axios.post(`${entry.baseUrl}assets${entry.urlParams}&transforms=${transform}`, formData, {
signal: entry.cancel.signal,
onUploadProgress: (ev) => {
const { loaded, total } = ev;

View File

@ -1,6 +1,6 @@
import { useContext, useState, useRef, useEffect } from 'react';
import { ConversationContext } from 'context/ConversationContext';
import { encryptTopicSubject } from 'context/sealUtil';
import { encryptBlock, encryptTopicSubject } from 'context/sealUtil';
import Resizer from "react-image-file-resizer";
export function useAddTopic(contentKey) {
@ -64,20 +64,48 @@ export function useAddTopic(contentKey) {
updateState({ enableImage, enableAudio, enableVideo });
}, [conversation.state.channel?.data?.channelDetail]);
const setUrl = async (url, getThumb) => {
if (contentKey) {
const buffer = await url.arrayBuffer();
const getEncryptedBlock = (pos, len) => {
if (pos + len > buffer.byteLen) {
return null;
}
const block = btoa(String.fromCharCode.apply(null, buffer.slice(pos, len)));
return getEncryptedBlock(block, contentKey);
}
return { url, position: 0, label: '', encrypted: true, size: buffer.byteLength, getEncryptedBlock, getThumb };
}
else {
return { url, position: 0, label: '', encrypted: false };
}
}
const actions = {
addImage: async (image) => {
let url = URL.createObjectURL(image);
addAsset({ image, url });
const url = URL.createObjectURL(image);
objects.current.push(url);
const getThumb = async () => {
return await getImageThumb(url);
}
const asset = setUrl(url, getThumb);
addAsset({ image, ...asset });
},
addVideo: async (video) => {
let url = URL.createObjectURL(video);
addAsset({ video, url, position: 0 })
const url = URL.createObjectURL(video);
objects.current.push(url);
const getThumb = async (position) => {
return await getVideoThumb(url, position);
}
const asset = setUrl(url, getThumb);
addAsset({ video, ...asset });
},
addAudio: (audio) => {
let url = URL.createObjectURL(audio);
addAsset({ audio, url, label: '' })
addAudio: async (audio) => {
const url = URL.createObjectURL(audio);
objects.current.push(url);
const getThumb = async () => { return null };
const asset = setUrl(url, getThumb);
addAsset({ audio, ...asset });
},
setLabel: (index, label) => {
updateAsset(index, { label });
@ -103,32 +131,19 @@ export function useAddTopic(contentKey) {
updateState({ busy: true });
const type = contentKey ? 'sealedtopic' : 'superbasictopic';
const message = (assets) => {
if (contentKey) {
if (assets?.length) {
console.log('assets not yet supported on sealed channels');
}
const message = {
if (assets?.length) {
return {
assets,
text: state.messageText,
textColor: state.textColorSet ? state.textColor : null,
textSize: state.textSizeSet ? state.textSize : null,
}
return encryptTopicSubject({ message }, contentKey);
}
else {
if (assets?.length) {
return {
assets,
text: state.messageText,
textColor: state.textColorSet ? state.textColor : null,
textSize: state.textSizeSet ? state.textSize : null,
}
}
else {
return {
text: state.messageText,
textColor: state.textColorSet ? state.textColor : null,
textSize: state.textSizeSet ? state.textSize : null,
}
return {
text: state.messageText,
textColor: state.textColorSet ? state.textColor : null,
textSize: state.textSizeSet ? state.textSize : null,
}
}
};