2016-07-25 18:18:56 +00:00
// Package crypt provides wrappers for Fs and Object which implement encryption
package crypt
import (
"fmt"
"io"
2016-09-10 10:29:57 +00:00
"io/ioutil"
2016-07-25 18:18:56 +00:00
"path"
"sync"
"github.com/ncw/rclone/fs"
"github.com/pkg/errors"
)
// Register with Fs
func init ( ) {
fs . Register ( & fs . RegInfo {
Name : "crypt" ,
Description : "Encrypt/Decrypt a remote" ,
NewFs : NewFs ,
Options : [ ] fs . Option { {
Name : "remote" ,
2016-10-08 09:34:59 +00:00
Help : "Remote to encrypt/decrypt.\nNormally should contain a ':' and a path, eg \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended)." ,
2016-07-25 18:18:56 +00:00
} , {
2016-08-20 17:46:10 +00:00
Name : "filename_encryption" ,
Help : "How to encrypt the filenames." ,
2016-07-25 18:18:56 +00:00
Examples : [ ] fs . OptionExample {
{
2016-08-20 17:46:10 +00:00
Value : "off" ,
Help : "Don't encrypt the file names. Adds a \".bin\" extension only." ,
2016-07-25 18:18:56 +00:00
} , {
2016-08-20 17:46:10 +00:00
Value : "standard" ,
Help : "Encrypt the filenames see the docs for the details." ,
2016-07-25 18:18:56 +00:00
} ,
} ,
} , {
Name : "password" ,
Help : "Password or pass phrase for encryption." ,
IsPassword : true ,
2016-08-19 19:02:02 +00:00
} , {
Name : "password2" ,
Help : "Password or pass phrase for salt. Optional but recommended.\nShould be different to the previous password." ,
IsPassword : true ,
Optional : true ,
2016-07-25 18:18:56 +00:00
} } ,
} )
}
// NewFs contstructs an Fs from the path, container:path
func NewFs ( name , rpath string ) ( fs . Fs , error ) {
2016-08-20 17:46:10 +00:00
mode , err := NewNameEncryptionMode ( fs . ConfigFile . MustValue ( name , "filename_encryption" , "standard" ) )
if err != nil {
return nil , err
}
2016-07-25 18:18:56 +00:00
password := fs . ConfigFile . MustValue ( name , "password" , "" )
if password == "" {
return nil , errors . New ( "password not set in config file" )
}
2016-08-20 17:46:10 +00:00
password , err = fs . Reveal ( password )
2016-07-25 18:18:56 +00:00
if err != nil {
return nil , errors . Wrap ( err , "failed to decrypt password" )
}
2016-08-19 19:02:02 +00:00
salt := fs . ConfigFile . MustValue ( name , "password2" , "" )
if salt != "" {
salt , err = fs . Reveal ( salt )
if err != nil {
return nil , errors . Wrap ( err , "failed to decrypt password2" )
}
}
2016-08-20 17:46:10 +00:00
cipher , err := newCipher ( mode , password , salt )
2016-07-25 18:18:56 +00:00
if err != nil {
return nil , errors . Wrap ( err , "failed to make cipher" )
}
remote := fs . ConfigFile . MustValue ( name , "remote" )
2016-08-20 17:46:10 +00:00
// Look for a file first
remotePath := path . Join ( remote , cipher . EncryptFileName ( rpath ) )
2016-07-25 18:18:56 +00:00
wrappedFs , err := fs . NewFs ( remotePath )
2016-08-20 17:46:10 +00:00
// if that didn't produce a file, look for a directory
if err != fs . ErrorIsFile {
remotePath = path . Join ( remote , cipher . EncryptDirName ( rpath ) )
wrappedFs , err = fs . NewFs ( remotePath )
}
2016-07-25 18:18:56 +00:00
if err != fs . ErrorIsFile && err != nil {
return nil , errors . Wrapf ( err , "failed to make remote %q to wrap" , remotePath )
}
f := & Fs {
2016-08-20 17:46:10 +00:00
Fs : wrappedFs ,
cipher : cipher ,
mode : mode ,
2016-09-09 07:38:18 +00:00
name : name ,
root : rpath ,
2016-07-25 18:18:56 +00:00
}
return f , err
}
// Fs represents a wrapped fs.Fs
type Fs struct {
fs . Fs
2016-08-20 17:46:10 +00:00
cipher Cipher
mode NameEncryptionMode
2016-09-09 07:38:18 +00:00
name string
root string
}
// Name of the remote (as passed into NewFs)
func ( f * Fs ) Name ( ) string {
return f . name
}
// Root of the remote (as passed into NewFs)
func ( f * Fs ) Root ( ) string {
return f . root
2016-07-25 18:18:56 +00:00
}
// String returns a description of the FS
func ( f * Fs ) String ( ) string {
2016-08-23 16:43:43 +00:00
return fmt . Sprintf ( "Encrypted %s" , f . Fs . String ( ) )
2016-07-25 18:18:56 +00:00
}
// List the Fs into a channel
func ( f * Fs ) List ( opts fs . ListOpts , dir string ) {
2016-08-20 17:46:10 +00:00
f . Fs . List ( f . newListOpts ( opts , dir ) , f . cipher . EncryptDirName ( dir ) )
2016-07-25 18:18:56 +00:00
}
// NewObject finds the Object at remote.
func ( f * Fs ) NewObject ( remote string ) ( fs . Object , error ) {
2016-08-20 17:46:10 +00:00
o , err := f . Fs . NewObject ( f . cipher . EncryptFileName ( remote ) )
2016-07-25 18:18:56 +00:00
if err != nil {
return nil , err
}
return f . newObject ( o ) , nil
}
// Put in to the remote path with the modTime given of the given size
//
// May create the object even if it returns an error - if so
// will return the object and the error, otherwise will return
// nil and the error
func ( f * Fs ) Put ( in io . Reader , src fs . ObjectInfo ) ( fs . Object , error ) {
wrappedIn , err := f . cipher . EncryptData ( in )
if err != nil {
return nil , err
}
o , err := f . Fs . Put ( wrappedIn , f . newObjectInfo ( src ) )
if err != nil {
return nil , err
}
return f . newObject ( o ) , nil
}
// Hashes returns the supported hash sets.
func ( f * Fs ) Hashes ( ) fs . HashSet {
return fs . HashSet ( fs . HashNone )
}
// Purge all files in the root and the root directory
//
// Implement this if you have a way of deleting all the files
// quicker than just running Remove() on the result of List()
//
// Return an error if it doesn't exist
func ( f * Fs ) Purge ( ) error {
do , ok := f . Fs . ( fs . Purger )
if ! ok {
return fs . ErrorCantPurge
}
return do . Purge ( )
}
// Copy src to this remote using server side copy operations.
//
// This is stored with the remote path given
//
// It returns the destination Object and a possible error
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantCopy
func ( f * Fs ) Copy ( src fs . Object , remote string ) ( fs . Object , error ) {
do , ok := f . Fs . ( fs . Copier )
if ! ok {
return nil , fs . ErrorCantCopy
}
o , ok := src . ( * Object )
if ! ok {
return nil , fs . ErrorCantCopy
}
2016-08-20 17:46:10 +00:00
oResult , err := do . Copy ( o . Object , f . cipher . EncryptFileName ( remote ) )
2016-07-25 18:18:56 +00:00
if err != nil {
return nil , err
}
return f . newObject ( oResult ) , nil
}
// Move src to this remote using server side move operations.
//
// This is stored with the remote path given
//
// It returns the destination Object and a possible error
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantMove
func ( f * Fs ) Move ( src fs . Object , remote string ) ( fs . Object , error ) {
do , ok := f . Fs . ( fs . Mover )
if ! ok {
return nil , fs . ErrorCantMove
}
o , ok := src . ( * Object )
if ! ok {
2016-08-23 16:43:43 +00:00
return nil , fs . ErrorCantMove
2016-07-25 18:18:56 +00:00
}
2016-08-20 17:46:10 +00:00
oResult , err := do . Move ( o . Object , f . cipher . EncryptFileName ( remote ) )
2016-07-25 18:18:56 +00:00
if err != nil {
return nil , err
}
return f . newObject ( oResult ) , nil
}
2016-08-23 16:43:43 +00:00
// DirMove moves src to this remote using server side move
// operations.
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantDirMove
//
// If destination exists then return fs.ErrorDirExists
func ( f * Fs ) DirMove ( src fs . Fs ) error {
do , ok := f . Fs . ( fs . DirMover )
if ! ok {
return fs . ErrorCantDirMove
}
srcFs , ok := src . ( * Fs )
if ! ok {
fs . Debug ( srcFs , "Can't move directory - not same remote type" )
return fs . ErrorCantDirMove
}
return do . DirMove ( srcFs . Fs )
}
2016-07-25 18:18:56 +00:00
// UnWrap returns the Fs that this Fs is wrapping
func ( f * Fs ) UnWrap ( ) fs . Fs {
return f . Fs
}
// Object describes a wrapped for being read from the Fs
//
// This decrypts the remote name and decrypts the data
type Object struct {
fs . Object
f * Fs
}
func ( f * Fs ) newObject ( o fs . Object ) * Object {
return & Object {
Object : o ,
f : f ,
}
}
// Fs returns read only access to the Fs that this object is part of
func ( o * Object ) Fs ( ) fs . Info {
return o . f
}
// Return a string version
func ( o * Object ) String ( ) string {
if o == nil {
return "<nil>"
}
return o . Remote ( )
}
// Remote returns the remote path
func ( o * Object ) Remote ( ) string {
remote := o . Object . Remote ( )
2016-08-20 17:46:10 +00:00
decryptedName , err := o . f . cipher . DecryptFileName ( remote )
2016-07-25 18:18:56 +00:00
if err != nil {
fs . Debug ( remote , "Undecryptable file name: %v" , err )
return remote
}
return decryptedName
}
// Size returns the size of the file
func ( o * Object ) Size ( ) int64 {
size , err := o . f . cipher . DecryptedSize ( o . Object . Size ( ) )
if err != nil {
fs . Debug ( o , "Bad size for decrypt: %v" , err )
}
return size
}
// Hash returns the selected checksum of the file
// If no checksum is available it returns ""
func ( o * Object ) Hash ( hash fs . HashType ) ( string , error ) {
return "" , nil
}
// Open opens the file for read. Call Close() on the returned io.ReadCloser
2016-09-10 10:29:57 +00:00
func ( o * Object ) Open ( options ... fs . OpenOption ) ( io . ReadCloser , error ) {
var offset int64
for _ , option := range options {
switch x := option . ( type ) {
case * fs . SeekOption :
offset = x . Offset
default :
if option . Mandatory ( ) {
fs . Log ( o , "Unsupported mandatory option: %v" , option )
}
}
}
2016-07-25 18:18:56 +00:00
in , err := o . Object . Open ( )
if err != nil {
2016-09-10 10:29:57 +00:00
return nil , err
}
// This reads the header and checks it is OK
rc , err := o . f . cipher . DecryptData ( in )
if err != nil {
return nil , err
}
// If seeking required, then...
if offset != 0 {
// FIXME could cache the unseeked decrypter as we re-read the header on every seek
decrypter := rc . ( * decrypter )
// Seek the decrypter and work out where to seek the
// underlying file and how many bytes to discard
underlyingOffset , discard := decrypter . seek ( offset )
// Re-open stream with a seek of underlyingOffset
err = in . Close ( )
if err != nil {
return nil , err
}
in , err := o . Object . Open ( & fs . SeekOption { Offset : underlyingOffset } )
if err != nil {
return nil , err
}
// Update the stream
decrypter . rc = in
// Discard the bytes
_ , err = io . CopyN ( ioutil . Discard , decrypter , discard )
if err != nil {
return nil , err
}
2016-07-25 18:18:56 +00:00
}
2016-09-10 10:29:57 +00:00
return rc , err
2016-07-25 18:18:56 +00:00
}
// Update in to the object with the modTime given of the given size
func ( o * Object ) Update ( in io . Reader , src fs . ObjectInfo ) error {
wrappedIn , err := o . f . cipher . EncryptData ( in )
if err != nil {
return err
}
return o . Object . Update ( wrappedIn , o . f . newObjectInfo ( src ) )
}
// newDir returns a dir with the Name decrypted
func ( f * Fs ) newDir ( dir * fs . Dir ) * fs . Dir {
new := * dir
remote := dir . Name
2016-08-20 17:46:10 +00:00
decryptedRemote , err := f . cipher . DecryptDirName ( remote )
2016-07-25 18:18:56 +00:00
if err != nil {
fs . Debug ( remote , "Undecryptable dir name: %v" , err )
} else {
new . Name = decryptedRemote
}
return & new
}
// ObjectInfo describes a wrapped fs.ObjectInfo for being the source
//
// This encrypts the remote name and adjusts the size
type ObjectInfo struct {
fs . ObjectInfo
f * Fs
}
func ( f * Fs ) newObjectInfo ( src fs . ObjectInfo ) * ObjectInfo {
return & ObjectInfo {
ObjectInfo : src ,
f : f ,
}
}
// Fs returns read only access to the Fs that this object is part of
func ( o * ObjectInfo ) Fs ( ) fs . Info {
return o . f
}
// Remote returns the remote path
func ( o * ObjectInfo ) Remote ( ) string {
2016-08-20 17:46:10 +00:00
return o . f . cipher . EncryptFileName ( o . ObjectInfo . Remote ( ) )
2016-07-25 18:18:56 +00:00
}
// Size returns the size of the file
func ( o * ObjectInfo ) Size ( ) int64 {
return o . f . cipher . EncryptedSize ( o . ObjectInfo . Size ( ) )
}
2016-08-25 20:26:55 +00:00
// Hash returns the selected checksum of the file
// If no checksum is available it returns ""
func ( o * ObjectInfo ) Hash ( hash fs . HashType ) ( string , error ) {
return "" , nil
}
2016-07-25 18:18:56 +00:00
// ListOpts wraps a listopts decrypting the directory listing and
// replacing the Objects
type ListOpts struct {
fs . ListOpts
f * Fs
dir string // dir we are listing
mu sync . Mutex // to protect dirs
dirs map [ string ] struct { } // keep track of synthetic directory objects added
}
// Make a ListOpts wrapper
func ( f * Fs ) newListOpts ( lo fs . ListOpts , dir string ) * ListOpts {
if dir != "" {
dir += "/"
}
return & ListOpts {
ListOpts : lo ,
f : f ,
dir : dir ,
dirs : make ( map [ string ] struct { } ) ,
}
}
// Level gets the recursion level for this listing.
//
// Fses may ignore this, but should implement it for improved efficiency if possible.
//
// Level 1 means list just the contents of the directory
//
// Each returned item must have less than level `/`s in.
func ( lo * ListOpts ) Level ( ) int {
return lo . ListOpts . Level ( )
}
// Add an object to the output.
// If the function returns true, the operation has been aborted.
// Multiple goroutines can safely add objects concurrently.
func ( lo * ListOpts ) Add ( obj fs . Object ) ( abort bool ) {
remote := obj . Remote ( )
2016-08-20 17:46:10 +00:00
_ , err := lo . f . cipher . DecryptFileName ( remote )
2016-07-25 18:18:56 +00:00
if err != nil {
fs . Debug ( remote , "Skipping undecryptable file name: %v" , err )
return lo . ListOpts . IsFinished ( )
}
return lo . ListOpts . Add ( lo . f . newObject ( obj ) )
}
// AddDir adds a directory to the output.
// If the function returns true, the operation has been aborted.
// Multiple goroutines can safely add objects concurrently.
func ( lo * ListOpts ) AddDir ( dir * fs . Dir ) ( abort bool ) {
remote := dir . Name
2016-08-20 17:46:10 +00:00
_ , err := lo . f . cipher . DecryptDirName ( remote )
2016-07-25 18:18:56 +00:00
if err != nil {
fs . Debug ( remote , "Skipping undecryptable dir name: %v" , err )
return lo . ListOpts . IsFinished ( )
}
return lo . ListOpts . AddDir ( lo . f . newDir ( dir ) )
}
// IncludeDirectory returns whether this directory should be
// included in the listing (and recursed into or not).
func ( lo * ListOpts ) IncludeDirectory ( remote string ) bool {
2016-08-20 17:46:10 +00:00
decryptedRemote , err := lo . f . cipher . DecryptDirName ( remote )
2016-07-25 18:18:56 +00:00
if err != nil {
fs . Debug ( remote , "Not including undecryptable directory name: %v" , err )
return false
}
return lo . ListOpts . IncludeDirectory ( decryptedRemote )
}
// Check the interfaces are satisfied
var (
2016-08-23 16:43:43 +00:00
_ fs . Fs = ( * Fs ) ( nil )
_ fs . Purger = ( * Fs ) ( nil )
_ fs . Copier = ( * Fs ) ( nil )
_ fs . Mover = ( * Fs ) ( nil )
_ fs . DirMover = ( * Fs ) ( nil )
2016-07-25 18:18:56 +00:00
// _ fs.PutUncheckeder = (*Fs)(nil)
_ fs . UnWrapper = ( * Fs ) ( nil )
_ fs . ObjectInfo = ( * ObjectInfo ) ( nil )
_ fs . Object = ( * Object ) ( nil )
_ fs . ListOpts = ( * ListOpts ) ( nil )
)