mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-04-12 12:09:03 +02:00
Client side encryption (WIP)
This commit is contained in:
parent
4f76c81bb7
commit
d94f7aeb06
@ -18,7 +18,8 @@
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-scripts": "5.0.0",
|
||||
"recharts": "^2.1.10"
|
||||
"recharts": "^2.1.10",
|
||||
"sjcl": "^1.0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -2,6 +2,8 @@ import "./FileUpload.css";
|
||||
import {useEffect, useState} from "react";
|
||||
import * as CryptoJS from 'crypto-js';
|
||||
import {useSelector} from "react-redux";
|
||||
import sjcl from "sjcl";
|
||||
import {sjclcodec} from "../../codecBytes";
|
||||
|
||||
import {ConstName, FormatBytes} from "../Shared/Util";
|
||||
import {RateCalculator} from "../Shared/RateCalculator";
|
||||
@ -18,6 +20,7 @@ const UploadState = {
|
||||
};
|
||||
|
||||
export const DigestAlgo = "SHA-256";
|
||||
const BlockSize = 16;
|
||||
|
||||
export function FileUpload(props) {
|
||||
const auth = useSelector(state => state.login.jwt);
|
||||
@ -27,6 +30,7 @@ export function FileUpload(props) {
|
||||
const [result, setResult] = useState();
|
||||
const [uState, setUState] = useState(UploadState.NotStarted);
|
||||
const [challenge, setChallenge] = useState();
|
||||
const [encryptionKey, setEncryptionKey] = useState();
|
||||
const calc = new RateCalculator();
|
||||
|
||||
function handleProgress(e) {
|
||||
@ -39,7 +43,19 @@ export function FileUpload(props) {
|
||||
}
|
||||
}
|
||||
|
||||
function generateEncryptionKey() {
|
||||
let key = {
|
||||
key: sjclcodec.toBits(window.crypto.getRandomValues(new Uint8Array(16))),
|
||||
iv: sjclcodec.toBits(window.crypto.getRandomValues(new Uint8Array(12)))
|
||||
};
|
||||
setEncryptionKey(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
async function doStreamUpload() {
|
||||
let key = generateEncryptionKey();
|
||||
let aes = new sjcl.cipher.aes(key.key);
|
||||
|
||||
setUState(UploadState.Hashing);
|
||||
let hash = await digest(props.file);
|
||||
calc.Reset();
|
||||
@ -56,13 +72,27 @@ export function FileUpload(props) {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
async function readEncryptedChunk(size) {
|
||||
if (offset >= props.file.size) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
size -= size % BlockSize;
|
||||
|
||||
let end = Math.min(offset + size, props.file.size);
|
||||
let blob = props.file.slice(offset, end, props.file.type);
|
||||
let data = new Uint8Array(await blob.arrayBuffer());
|
||||
offset += data.byteLength;
|
||||
let encryptedData = sjcl.mode.gcm.encrypt(aes, sjclcodec.toBits(data), key.iv);
|
||||
return new Uint8Array(sjclcodec.fromBits(encryptedData));
|
||||
}
|
||||
|
||||
let rs = new ReadableStream({
|
||||
start: (controller) => {
|
||||
console.log(controller);
|
||||
start: () => {
|
||||
setUState(UploadState.Uploading);
|
||||
},
|
||||
pull: async (controller) => {
|
||||
let chunk = await readChunk(controller.desiredSize);
|
||||
let chunkSize = controller.desiredSize;
|
||||
let chunk = key ? await readEncryptedChunk(chunkSize) : await readChunk(chunkSize);
|
||||
if (chunk.byteLength === 0) {
|
||||
controller.close();
|
||||
return;
|
||||
@ -71,7 +101,6 @@ export function FileUpload(props) {
|
||||
calc.ReportProgress(chunk.byteLength);
|
||||
setSpeed(calc.RateWindow(5));
|
||||
setProgress(offset / props.file.size);
|
||||
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
cancel: (reason) => {
|
||||
@ -194,7 +223,7 @@ export function FileUpload(props) {
|
||||
}
|
||||
}
|
||||
|
||||
function getChromeVersion () {
|
||||
function getChromeVersion() {
|
||||
let raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
return raw ? parseInt(raw[2], 10) : false;
|
||||
}
|
||||
@ -217,10 +246,11 @@ export function FileUpload(props) {
|
||||
|
||||
function renderStatus() {
|
||||
if (result) {
|
||||
let link = encryptionKey ? `/${result.id}#${sjcl.codec.hex.fromBits(encryptionKey.key)}:${sjcl.codec.hex.fromBits(encryptionKey.iv)}` : `/${result.id}`;
|
||||
return uState === UploadState.Done ?
|
||||
<dl>
|
||||
<dt>Link:</dt>
|
||||
<dd><a target="_blank" href={`/${result.id}`}>{result.id}</a></dd>
|
||||
<dd><a target="_blank" href={link}>{result.id}</a></dd>
|
||||
</dl>
|
||||
: <b>{result}</b>;
|
||||
} else {
|
||||
@ -245,13 +275,13 @@ export function FileUpload(props) {
|
||||
|
||||
useEffect(() => {
|
||||
console.log(props.file);
|
||||
|
||||
|
||||
let chromeVersion = getChromeVersion();
|
||||
if(chromeVersion >= 105) {
|
||||
if (chromeVersion >= 105) {
|
||||
doStreamUpload().catch(console.error);
|
||||
} else {
|
||||
doXHRUpload().catch(console.error);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -10,6 +10,8 @@ import {Helmet} from "react-helmet";
|
||||
import {FormatBytes} from "../Components/Shared/Util";
|
||||
import {ApiHost} from "../Components/Shared/Const";
|
||||
import {InlineProfile} from "../Components/Shared/InlineProfile";
|
||||
import sjcl from "sjcl";
|
||||
import {sjclcodec} from "../codecBytes";
|
||||
|
||||
export function FilePreview() {
|
||||
const {Api} = useApi();
|
||||
@ -143,6 +145,41 @@ export function FilePreview() {
|
||||
useEffect(() => {
|
||||
if (info) {
|
||||
let fileLink = info.metadata?.url ?? `${ApiHost}/d/${info.id}`;
|
||||
|
||||
// detect encrypted file link
|
||||
let hashKey = window.location.hash.match(/#([0-9a-z]{32}):([0-9a-z]{24})/);
|
||||
if (hashKey.length === 3) {
|
||||
let [key, iv] = [sjcl.codec.hex.toBits(hashKey[1]), sjcl.codec.hex.toBits(hashKey[2])];
|
||||
console.log(key, iv);
|
||||
let aes = new sjcl.cipher.aes(key);
|
||||
|
||||
async function load() {
|
||||
let decryptStream = new window.TransformStream({
|
||||
transform: async (chunk, controller) => {
|
||||
chunk = await chunk;
|
||||
console.log("Transforming chunk:", chunk);
|
||||
|
||||
let buff = sjclcodec.toBits(chunk);
|
||||
let decryptedBuff = sjclcodec.fromBits(sjcl.mode.gcm.decrypt(aes, buff, iv));
|
||||
console.log("Decrypted data:", decryptedBuff);
|
||||
controller.enqueue(new Uint8Array(decryptedBuff));
|
||||
}
|
||||
});
|
||||
let rsp = await fetch(fileLink);
|
||||
if (rsp.ok) {
|
||||
let reader = rsp.body
|
||||
.pipeThrough(decryptStream);
|
||||
|
||||
console.log("Pipe reader", reader);
|
||||
let newResponse = new Response(reader);
|
||||
setLink(window.URL.createObjectURL(await newResponse.blob(), {type: info.metadata.mimeType}));
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
return;
|
||||
}
|
||||
|
||||
let order = window.localStorage.getItem(`payment-${info.id}`);
|
||||
if (order) {
|
||||
let orderObj = JSON.parse(order);
|
||||
|
42
VoidCat/spa/src/codecBytes.js
Normal file
42
VoidCat/spa/src/codecBytes.js
Normal file
@ -0,0 +1,42 @@
|
||||
import sjcl from "sjcl";
|
||||
/** @fileOverview Bit array codec implementations.
|
||||
*
|
||||
* @author Emily Stark
|
||||
* @author Mike Hamburg
|
||||
* @author Dan Boneh
|
||||
*/
|
||||
|
||||
/**
|
||||
* Arrays of bytes
|
||||
* @namespace
|
||||
*/
|
||||
export const sjclcodec = {
|
||||
/** Convert from a bitArray to an array of bytes. */
|
||||
fromBits: function (arr) {
|
||||
var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
|
||||
for (i=0; i<bl/8; i++) {
|
||||
if ((i&3) === 0) {
|
||||
tmp = arr[i/4];
|
||||
}
|
||||
out.push(tmp >>> 24);
|
||||
tmp <<= 8;
|
||||
}
|
||||
return out;
|
||||
},
|
||||
/** Convert from an array of bytes to a bitArray. */
|
||||
/** @return {bitArray} */
|
||||
toBits: function (bytes) {
|
||||
var out = [], i, tmp=0;
|
||||
for (i=0; i<bytes.length; i++) {
|
||||
tmp = tmp << 8 | bytes[i];
|
||||
if ((i&3) === 3) {
|
||||
out.push(tmp);
|
||||
tmp = 0;
|
||||
}
|
||||
}
|
||||
if (i&3) {
|
||||
out.push(sjcl.bitArray.partial(8*(i&3), tmp));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
@ -7726,6 +7726,11 @@ sisteransi@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
|
||||
|
||||
sjcl@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.8.tgz#f2ec8d7dc1f0f21b069b8914a41a8f236b0e252a"
|
||||
integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==
|
||||
|
||||
slash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
|
Loading…
x
Reference in New Issue
Block a user