229 lines
6.6 KiB
Go
229 lines
6.6 KiB
Go
/*
|
|
** Copyright (c) 2014 Arnaud Ysmal. All Rights Reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
|
** OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
** DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
** SUCH DAMAGE.
|
|
*/
|
|
|
|
package dropbox
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
// GenerateKey generates a key by reading length bytes from /dev/random
|
|
func GenerateKey(length int) ([]byte, error) {
|
|
var err error
|
|
var fd io.Reader
|
|
var rv []byte
|
|
|
|
if fd, err = os.Open("/dev/random"); err != nil {
|
|
return nil, err
|
|
}
|
|
rv = make([]byte, length)
|
|
_, err = io.ReadFull(fd, rv)
|
|
return rv, err
|
|
}
|
|
|
|
func newCrypter(key []byte, in io.Reader, size int, newCipher func(key []byte) (cipher.Block, error)) (io.ReadCloser, int, error) {
|
|
var block cipher.Block
|
|
var err error
|
|
|
|
if block, err = newCipher(key); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
outsize := size - size%block.BlockSize() + 2*block.BlockSize()
|
|
|
|
rd, wr := io.Pipe()
|
|
go encrypt(block, in, size, wr)
|
|
return rd, outsize, nil
|
|
}
|
|
|
|
func newDecrypter(key []byte, in io.Reader, size int, newCipher func(key []byte) (cipher.Block, error)) (io.ReadCloser, error) {
|
|
var block cipher.Block
|
|
var err error
|
|
|
|
if block, err = newCipher(key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rd, wr := io.Pipe()
|
|
go decrypt(block, in, size, wr)
|
|
return rd, nil
|
|
}
|
|
|
|
// NewAESDecrypterReader creates and returns a new io.ReadCloser to decrypt the given io.Reader containing size bytes with the given AES key.
|
|
// The AES key should be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
func NewAESDecrypterReader(key []byte, input io.Reader, size int) (io.ReadCloser, error) {
|
|
return newDecrypter(key, input, size, aes.NewCipher)
|
|
}
|
|
|
|
// NewAESCrypterReader creates and returns a new io.ReadCloser to encrypt the given io.Reader containing size bytes with the given AES key.
|
|
// The AES key should be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
func NewAESCrypterReader(key []byte, input io.Reader, size int) (io.ReadCloser, int, error) {
|
|
return newCrypter(key, input, size, aes.NewCipher)
|
|
}
|
|
|
|
func encrypt(block cipher.Block, in io.Reader, size int, out io.WriteCloser) error {
|
|
var err error
|
|
var rd int
|
|
var buf []byte
|
|
var last bool
|
|
var encrypter cipher.BlockMode
|
|
|
|
defer out.Close()
|
|
|
|
buf = make([]byte, block.BlockSize())
|
|
|
|
if _, err = io.ReadFull(rand.Reader, buf); err != nil {
|
|
return err
|
|
}
|
|
encrypter = cipher.NewCBCEncrypter(block, buf)
|
|
|
|
if _, err = out.Write(buf); err != nil {
|
|
return err
|
|
}
|
|
for !last {
|
|
if rd, err = io.ReadFull(in, buf); err != nil {
|
|
if err == io.ErrUnexpectedEOF || err == io.EOF {
|
|
buf = buf[:rd]
|
|
buf = append(buf, 0x80)
|
|
for len(buf) < block.BlockSize() {
|
|
buf = append(buf, 0x00)
|
|
}
|
|
last = true
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
encrypter.CryptBlocks(buf, buf)
|
|
if _, err = out.Write(buf); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func decrypt(block cipher.Block, in io.Reader, size int, out io.WriteCloser) error {
|
|
var err error
|
|
var buf []byte
|
|
var count int
|
|
var decrypter cipher.BlockMode
|
|
|
|
defer out.Close()
|
|
|
|
buf = make([]byte, block.BlockSize())
|
|
if _, err = io.ReadFull(in, buf); err != nil {
|
|
return err
|
|
}
|
|
decrypter = cipher.NewCBCDecrypter(block, buf)
|
|
|
|
count = (size - block.BlockSize()) / block.BlockSize()
|
|
for count > 0 && err == nil {
|
|
if _, err = io.ReadFull(in, buf); err == nil {
|
|
decrypter.CryptBlocks(buf, buf)
|
|
if count == 1 {
|
|
for count = block.BlockSize() - 1; buf[count] == 0x00; count-- {
|
|
continue
|
|
}
|
|
if buf[count] == 0x80 {
|
|
buf = buf[:count]
|
|
}
|
|
}
|
|
_, err = out.Write(buf)
|
|
}
|
|
count--
|
|
}
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// FilesPutAES uploads and encrypts size bytes from the input reader to the dst path on Dropbox.
|
|
func (db *Dropbox) FilesPutAES(key []byte, input io.ReadCloser, size int64, dst string, overwrite bool, parentRev string) (*Entry, error) {
|
|
var encreader io.ReadCloser
|
|
var outsize int
|
|
var err error
|
|
|
|
if encreader, outsize, err = NewAESCrypterReader(key, input, int(size)); err != nil {
|
|
return nil, err
|
|
}
|
|
return db.FilesPut(encreader, int64(outsize), dst, overwrite, parentRev)
|
|
}
|
|
|
|
// UploadFileAES uploads and encrypts the file located in the src path on the local disk to the dst path on Dropbox.
|
|
func (db *Dropbox) UploadFileAES(key []byte, src, dst string, overwrite bool, parentRev string) (*Entry, error) {
|
|
var err error
|
|
var fd *os.File
|
|
var fsize int64
|
|
|
|
if fd, err = os.Open(src); err != nil {
|
|
return nil, err
|
|
}
|
|
defer fd.Close()
|
|
|
|
if fi, err := fd.Stat(); err == nil {
|
|
fsize = fi.Size()
|
|
} else {
|
|
return nil, err
|
|
}
|
|
return db.FilesPutAES(key, fd, fsize, dst, overwrite, parentRev)
|
|
}
|
|
|
|
// DownloadAES downloads and decrypts the file located in the src path on Dropbox and return a io.ReadCloser.
|
|
func (db *Dropbox) DownloadAES(key []byte, src, rev string, offset int) (io.ReadCloser, error) {
|
|
var in io.ReadCloser
|
|
var size int64
|
|
var err error
|
|
|
|
if in, size, err = db.Download(src, rev, offset); err != nil {
|
|
return nil, err
|
|
}
|
|
return NewAESDecrypterReader(key, in, int(size))
|
|
}
|
|
|
|
// DownloadToFileAES downloads and decrypts the file located in the src path on Dropbox to the dst file on the local disk.
|
|
func (db *Dropbox) DownloadToFileAES(key []byte, src, dst, rev string) error {
|
|
var input io.ReadCloser
|
|
var fd *os.File
|
|
var err error
|
|
|
|
if fd, err = os.Create(dst); err != nil {
|
|
return err
|
|
}
|
|
defer fd.Close()
|
|
|
|
if input, err = db.DownloadAES(key, src, rev, 0); err != nil {
|
|
os.Remove(dst)
|
|
return err
|
|
}
|
|
defer input.Close()
|
|
if _, err = io.Copy(fd, input); err != nil {
|
|
os.Remove(dst)
|
|
}
|
|
return err
|
|
}
|