diff --git a/src/crypto.coffee b/src/crypto.coffee index 605a3ae..c8b7922 100644 --- a/src/crypto.coffee +++ b/src/crypto.coffee @@ -17,9 +17,34 @@ HMAC_SHA256 = (key, str) -> SHA256 = (str) -> crypto.subtle.digest "SHA-256", utf8Bytes str +# For client-side encryption of files, +# always use AES-128-GCM +# Encrypt a File object +# Returns hexed key, iv, encrypted file name and mime type, and the encrypted ArrayBuffer +encryptFile = (file) -> + # Generate a key to use + keyParams = + name: 'AES-GCM' + length: 128 + keyUsage = ['encrypt', 'decrypt'] + key = await crypto.subtle.generateKey keyParams, true, keyUsage + # Generate IV and configure the cipher + iv = crypto.getRandomValues new Uint8Array 16 + algoParams = + name: 'AES-GCM' + iv: iv + tagLength: 128 + # Encrypt + encrypted = await crypto.subtle.encrypt algoParams, key, await file.arrayBuffer() + name = hex await crypto.subtle.encrypt algoParams, key, utf8Bytes file.name + mime = 'binary/' + hex await crypto.subtle.encrypt algoParams, key, utf8Bytes file.type + exportedKey = hex await crypto.subtle.exportKey 'raw', key + [exportedKey, hex(iv), name, mime, encrypted] + export { utf8Bytes, hex, HMAC_SHA256, - SHA256 + SHA256, + encryptFile } \ No newline at end of file diff --git a/src/web/binaryUpload.coffee b/src/web/binaryUpload.coffee index fa3bee8..063ee7c 100644 --- a/src/web/binaryUpload.coffee +++ b/src/web/binaryUpload.coffee @@ -1,6 +1,7 @@ import React from "react" import { Redirect } from "react-router-dom" import Dropzone from "react-dropzone" +import * as crypto from "../crypto" class BinaryUpload extends React.Component constructor: (props) -> @@ -10,14 +11,19 @@ class BinaryUpload extends React.Component uploading: false progress: 0 switchToText: false + encrypt: false + encrypting: false onDrop: (files) => @setState file: files[0] doUpload: => + key = null + iv = null @setState uploading: true + encrypting: @state.encrypt progress: 0 # Due to the lack of progress feature in current Fetch API # We have to use XHR for now. Dang. @@ -30,16 +36,30 @@ class BinaryUpload extends React.Component if xhr.readyState == XMLHttpRequest.DONE @setState uploading: false + encrypting: false file: null - @props.openDialog do -> + @props.openDialog do => if xhr.status == 200 - - https://{window.location.hostname}{xhr.responseText} + url = if not @state.encrypt + xhr.responseText + else + xhr.responseText + "?crypt#" + key + "+" + iv + + https://{window.location.hostname}{url} else xhr.responseText - xhr.open 'PUT', '/paste/' + @state.file.name - xhr.send @state.file + + if not @state.encrypt + xhr.open 'PUT', '/paste/' + @state.file.name + xhr.send @state.file + else + [key, iv, name, mime, encrypted] = await crypto.encryptFile @state.file + xhr.open 'PUT', '/paste/' + name + xhr.setRequestHeader 'content-type', mime + xhr.send encrypted + @setState + encrypting: false progressText: -> txt = (@state.progress * 100).toFixed(2) + "%" @@ -48,6 +68,10 @@ class BinaryUpload extends React.Component else txt + toggleEncrypt: => + @setState (state, props) -> + { encrypt: not state.encrypt } + render: -> if @state.switchToText return @@ -72,6 +96,13 @@ class BinaryUpload extends React.Component }
+