binaryUpload: implement file encryption
This commit is contained in:
parent
ed625596df
commit
ec540f9718
|
@ -17,9 +17,34 @@ HMAC_SHA256 = (key, str) ->
|
||||||
SHA256 = (str) ->
|
SHA256 = (str) ->
|
||||||
crypto.subtle.digest "SHA-256", utf8Bytes 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 {
|
export {
|
||||||
utf8Bytes,
|
utf8Bytes,
|
||||||
hex,
|
hex,
|
||||||
HMAC_SHA256,
|
HMAC_SHA256,
|
||||||
SHA256
|
SHA256,
|
||||||
|
encryptFile
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { Redirect } from "react-router-dom"
|
import { Redirect } from "react-router-dom"
|
||||||
import Dropzone from "react-dropzone"
|
import Dropzone from "react-dropzone"
|
||||||
|
import * as crypto from "../crypto"
|
||||||
|
|
||||||
class BinaryUpload extends React.Component
|
class BinaryUpload extends React.Component
|
||||||
constructor: (props) ->
|
constructor: (props) ->
|
||||||
|
@ -10,14 +11,19 @@ class BinaryUpload extends React.Component
|
||||||
uploading: false
|
uploading: false
|
||||||
progress: 0
|
progress: 0
|
||||||
switchToText: false
|
switchToText: false
|
||||||
|
encrypt: false
|
||||||
|
encrypting: false
|
||||||
|
|
||||||
onDrop: (files) =>
|
onDrop: (files) =>
|
||||||
@setState
|
@setState
|
||||||
file: files[0]
|
file: files[0]
|
||||||
|
|
||||||
doUpload: =>
|
doUpload: =>
|
||||||
|
key = null
|
||||||
|
iv = null
|
||||||
@setState
|
@setState
|
||||||
uploading: true
|
uploading: true
|
||||||
|
encrypting: @state.encrypt
|
||||||
progress: 0
|
progress: 0
|
||||||
# Due to the lack of progress feature in current Fetch API
|
# Due to the lack of progress feature in current Fetch API
|
||||||
# We have to use XHR for now. Dang.
|
# We have to use XHR for now. Dang.
|
||||||
|
@ -30,16 +36,30 @@ class BinaryUpload extends React.Component
|
||||||
if xhr.readyState == XMLHttpRequest.DONE
|
if xhr.readyState == XMLHttpRequest.DONE
|
||||||
@setState
|
@setState
|
||||||
uploading: false
|
uploading: false
|
||||||
|
encrypting: false
|
||||||
file: null
|
file: null
|
||||||
@props.openDialog do ->
|
@props.openDialog do =>
|
||||||
if xhr.status == 200
|
if xhr.status == 200
|
||||||
<a href={xhr.responseText} target="_blank">
|
url = if not @state.encrypt
|
||||||
https://{window.location.hostname}{xhr.responseText}
|
xhr.responseText
|
||||||
|
else
|
||||||
|
xhr.responseText + "?crypt#" + key + "+" + iv
|
||||||
|
<a href={url} target="_blank">
|
||||||
|
https://{window.location.hostname}{url}
|
||||||
</a>
|
</a>
|
||||||
else
|
else
|
||||||
xhr.responseText
|
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: ->
|
progressText: ->
|
||||||
txt = (@state.progress * 100).toFixed(2) + "%"
|
txt = (@state.progress * 100).toFixed(2) + "%"
|
||||||
|
@ -48,6 +68,10 @@ class BinaryUpload extends React.Component
|
||||||
else
|
else
|
||||||
txt
|
txt
|
||||||
|
|
||||||
|
toggleEncrypt: =>
|
||||||
|
@setState (state, props) ->
|
||||||
|
{ encrypt: not state.encrypt }
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
if @state.switchToText
|
if @state.switchToText
|
||||||
return <Redirect to="/paste/text" />
|
return <Redirect to="/paste/text" />
|
||||||
|
@ -72,6 +96,13 @@ class BinaryUpload extends React.Component
|
||||||
}
|
}
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
<div className="content-buttons">
|
<div className="content-buttons">
|
||||||
|
<button
|
||||||
|
className="button-blue"
|
||||||
|
disabled={@state.uploading}
|
||||||
|
onClick={@toggleEncrypt}
|
||||||
|
>
|
||||||
|
{ "Encrypt: " + if @state.encrypt then "ON" else "OFF" }
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="button-blue"
|
className="button-blue"
|
||||||
disabled={@state.uploading}
|
disabled={@state.uploading}
|
||||||
|
@ -87,6 +118,8 @@ class BinaryUpload extends React.Component
|
||||||
{
|
{
|
||||||
if not @state.uploading
|
if not @state.uploading
|
||||||
"Upload"
|
"Upload"
|
||||||
|
else if @state.encrypting
|
||||||
|
"Encrypting"
|
||||||
else
|
else
|
||||||
@progressText()
|
@progressText()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue