748 lines · 20.4 KB · Go
RawDownload
// Package crypt provides wrappers for Fs and Object which implement encryptionpackage crypt import (	"fmt"	"io"	"path"	"strconv"	"strings"	"time" 	"github.com/ncw/rclone/fs"	"github.com/ncw/rclone/fs/config"	"github.com/ncw/rclone/fs/config/flags"	"github.com/ncw/rclone/fs/config/obscure"	"github.com/ncw/rclone/fs/hash"	"github.com/pkg/errors") // Globalsvar (	// Flags	cryptShowMapping = flags.BoolP("crypt-show-mapping", "", false, "For all files listed show how the names encrypt.")) // Register with Fsfunc init() {	fs.Register(&fs.RegInfo{		Name:        "crypt",		Description: "Encrypt/Decrypt a remote",		NewFs:       NewFs,		Options: []fs.Option{{			Name: "remote",			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).",		}, {			Name: "filename_encryption",			Help: "How to encrypt the filenames.",			Examples: []fs.OptionExample{				{					Value: "off",					Help:  "Don't encrypt the file names.  Adds a \".bin\" extension only.",				}, {					Value: "standard",					Help:  "Encrypt the filenames see the docs for the details.",				}, {					Value: "obfuscate",					Help:  "Very simple filename obfuscation.",				},			},		}, {			Name: "directory_name_encryption",			Help: "Option to either encrypt directory names or leave them intact.",			Examples: []fs.OptionExample{				{					Value: "true",					Help:  "Encrypt directory names.",				},				{					Value: "false",					Help:  "Don't encrypt directory names, leave them intact.",				},			},		}, {			Name:       "password",			Help:       "Password or pass phrase for encryption.",			IsPassword: true,		}, {			Name:       "password2",			Help:       "Password or pass phrase for salt. Optional but recommended.\nShould be different to the previous password.",			IsPassword: true,			Optional:   true,		}},	})} // NewCipher constructs a Cipher for the given config namefunc NewCipher(name string) (Cipher, error) {	mode, err := NewNameEncryptionMode(config.FileGet(name, "filename_encryption", "standard"))	if err != nil {		return nil, err	}	dirNameEncrypt, err := strconv.ParseBool(config.FileGet(name, "directory_name_encryption", "true"))	if err != nil {		return nil, err	}	password := config.FileGet(name, "password", "")	if password == "" {		return nil, errors.New("password not set in config file")	}	password, err = obscure.Reveal(password)	if err != nil {		return nil, errors.Wrap(err, "failed to decrypt password")	}	salt := config.FileGet(name, "password2", "")	if salt != "" {		salt, err = obscure.Reveal(salt)		if err != nil {			return nil, errors.Wrap(err, "failed to decrypt password2")		}	}	cipher, err := newCipher(mode, password, salt, dirNameEncrypt)	if err != nil {		return nil, errors.Wrap(err, "failed to make cipher")	}	return cipher, nil} // NewFs contstructs an Fs from the path, container:pathfunc NewFs(name, rpath string) (fs.Fs, error) {	cipher, err := NewCipher(name)	if err != nil {		return nil, err	}	remote := config.FileGet(name, "remote")	if strings.HasPrefix(remote, name+":") {		return nil, errors.New("can't point crypt remote at itself - check the value of the remote setting")	}	// Look for a file first	remotePath := path.Join(remote, cipher.EncryptFileName(rpath))	wrappedFs, err := fs.NewFs(remotePath)	// 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)	}	if err != fs.ErrorIsFile && err != nil {		return nil, errors.Wrapf(err, "failed to make remote %q to wrap", remotePath)	}	f := &Fs{		Fs:     wrappedFs,		name:   name,		root:   rpath,		cipher: cipher,	}	// the features here are ones we could support, and they are	// ANDed with the ones from wrappedFs	f.features = (&fs.Features{		CaseInsensitive:         cipher.NameEncryptionMode() == NameEncryptionOff,		DuplicateFiles:          true,		ReadMimeType:            false, // MimeTypes not supported with crypt		WriteMimeType:           false,		BucketBased:             true,		CanHaveEmptyDirectories: true,	}).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs) 	doChangeNotify := wrappedFs.Features().ChangeNotify	if doChangeNotify != nil {		f.features.ChangeNotify = func(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool {			wrappedNotifyFunc := func(path string, entryType fs.EntryType) {				decrypted, err := f.DecryptFileName(path)				if err != nil {					fs.Logf(f, "ChangeNotify was unable to decrypt %q: %s", path, err)					return				}				notifyFunc(decrypted, entryType)			}			return doChangeNotify(wrappedNotifyFunc, pollInterval)		}	} 	return f, err} // Fs represents a wrapped fs.Fstype Fs struct {	fs.Fs	name     string	root     string	features *fs.Features // optional features	cipher   Cipher} // 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} // Features returns the optional features of this Fsfunc (f *Fs) Features() *fs.Features {	return f.features} // String returns a description of the FSfunc (f *Fs) String() string {	return fmt.Sprintf("Encrypted drive '%s:%s'", f.name, f.root)} // Encrypt an object file name to entries.func (f *Fs) add(entries *fs.DirEntries, obj fs.Object) {	remote := obj.Remote()	decryptedRemote, err := f.cipher.DecryptFileName(remote)	if err != nil {		fs.Debugf(remote, "Skipping undecryptable file name: %v", err)		return	}	if *cryptShowMapping {		fs.Logf(decryptedRemote, "Encrypts to %q", remote)	}	*entries = append(*entries, f.newObject(obj))} // Encrypt an directory file name to entries.func (f *Fs) addDir(entries *fs.DirEntries, dir fs.Directory) {	remote := dir.Remote()	decryptedRemote, err := f.cipher.DecryptDirName(remote)	if err != nil {		fs.Debugf(remote, "Skipping undecryptable dir name: %v", err)		return	}	if *cryptShowMapping {		fs.Logf(decryptedRemote, "Encrypts to %q", remote)	}	*entries = append(*entries, f.newDir(dir))} // Encrypt some directory entries.  This alters entries returning it as newEntries.func (f *Fs) encryptEntries(entries fs.DirEntries) (newEntries fs.DirEntries, err error) {	newEntries = entries[:0] // in place filter	for _, entry := range entries {		switch x := entry.(type) {		case fs.Object:			f.add(&newEntries, x)		case fs.Directory:			f.addDir(&newEntries, x)		default:			return nil, errors.Errorf("Unknown object type %T", entry)		}	}	return newEntries, nil} // List the objects and directories in dir into entries.  The// entries can be returned in any order but should be for a// complete directory.//// dir should be "" to list the root, and should not have// trailing slashes.//// This should return ErrDirNotFound if the directory isn't// found.func (f *Fs) List(dir string) (entries fs.DirEntries, err error) {	entries, err = f.Fs.List(f.cipher.EncryptDirName(dir))	if err != nil {		return nil, err	}	return f.encryptEntries(entries)} // ListR lists the objects and directories of the Fs starting// from dir recursively into out.//// dir should be "" to start from the root, and should not// have trailing slashes.//// This should return ErrDirNotFound if the directory isn't// found.//// It should call callback for each tranche of entries read.// These need not be returned in any particular order.  If// callback returns an error then the listing will stop// immediately.//// Don't implement this unless you have a more efficient way// of listing recursively that doing a directory traversal.func (f *Fs) ListR(dir string, callback fs.ListRCallback) (err error) {	return f.Fs.Features().ListR(f.cipher.EncryptDirName(dir), func(entries fs.DirEntries) error {		newEntries, err := f.encryptEntries(entries)		if err != nil {			return err		}		return callback(newEntries)	})} // NewObject finds the Object at remote.func (f *Fs) NewObject(remote string) (fs.Object, error) {	o, err := f.Fs.NewObject(f.cipher.EncryptFileName(remote))	if err != nil {		return nil, err	}	return f.newObject(o), nil} type putFn func(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) // put implements Put or PutStreamfunc (f *Fs) put(in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (fs.Object, error) {	// Encrypt the data into wrappedIn	wrappedIn, err := f.cipher.EncryptData(in)	if err != nil {		return nil, err	} 	// Find a hash the destination supports to compute a hash of	// the encrypted data	ht := f.Fs.Hashes().GetOne()	var hasher *hash.MultiHasher	if ht != hash.None {		hasher, err = hash.NewMultiHasherTypes(hash.NewHashSet(ht))		if err != nil {			return nil, err		}		wrappedIn = io.TeeReader(wrappedIn, hasher)	} 	// Transfer the data	o, err := put(wrappedIn, f.newObjectInfo(src), options...)	if err != nil {		return nil, err	} 	// Check the hashes of the encrypted data if we were comparing them	if ht != hash.None && hasher != nil {		srcHash := hasher.Sums()[ht]		var dstHash string		dstHash, err = o.Hash(ht)		if err != nil {			return nil, errors.Wrap(err, "failed to read destination hash")		}		if srcHash != "" && dstHash != "" && srcHash != dstHash {			// remove object			err = o.Remove()			if err != nil {				fs.Errorf(o, "Failed to remove corrupted object: %v", err)			}			return nil, errors.Errorf("corrupted on transfer: %v crypted hash differ %q vs %q", ht, srcHash, dstHash)		}	} 	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 errorfunc (f *Fs) Put(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {	return f.put(in, src, options, f.Fs.Put)} // PutStream uploads to the remote path with the modTime given of indeterminate sizefunc (f *Fs) PutStream(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {	return f.put(in, src, options, f.Fs.Features().PutStream)} // Hashes returns the supported hash sets.func (f *Fs) Hashes() hash.Set {	return hash.Set(hash.None)} // Mkdir makes the directory (container, bucket)//// Shouldn't return an error if it already existsfunc (f *Fs) Mkdir(dir string) error {	return f.Fs.Mkdir(f.cipher.EncryptDirName(dir))} // Rmdir removes the directory (container, bucket) if empty//// Return an error if it doesn't exist or isn't emptyfunc (f *Fs) Rmdir(dir string) error {	return f.Fs.Rmdir(f.cipher.EncryptDirName(dir))} // 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 existfunc (f *Fs) Purge() error {	do := f.Fs.Features().Purge	if do == nil {		return fs.ErrorCantPurge	}	return do()} // 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.ErrorCantCopyfunc (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {	do := f.Fs.Features().Copy	if do == nil {		return nil, fs.ErrorCantCopy	}	o, ok := src.(*Object)	if !ok {		return nil, fs.ErrorCantCopy	}	oResult, err := do(o.Object, f.cipher.EncryptFileName(remote))	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.ErrorCantMovefunc (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {	do := f.Fs.Features().Move	if do == nil {		return nil, fs.ErrorCantMove	}	o, ok := src.(*Object)	if !ok {		return nil, fs.ErrorCantMove	}	oResult, err := do(o.Object, f.cipher.EncryptFileName(remote))	if err != nil {		return nil, err	}	return f.newObject(oResult), nil} // DirMove moves src, srcRemote to this remote at dstRemote// 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.ErrorDirExistsfunc (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error {	do := f.Fs.Features().DirMove	if do == nil {		return fs.ErrorCantDirMove	}	srcFs, ok := src.(*Fs)	if !ok {		fs.Debugf(srcFs, "Can't move directory - not same remote type")		return fs.ErrorCantDirMove	}	return do(srcFs.Fs, f.cipher.EncryptDirName(srcRemote), f.cipher.EncryptDirName(dstRemote))} // PutUnchecked uploads the object//// This will create a duplicate if we upload a new file without// checking to see if there is one already - use Put() for that.func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {	do := f.Fs.Features().PutUnchecked	if do == nil {		return nil, errors.New("can't PutUnchecked")	}	wrappedIn, err := f.cipher.EncryptData(in)	if err != nil {		return nil, err	}	o, err := do(wrappedIn, f.newObjectInfo(src))	if err != nil {		return nil, err	}	return f.newObject(o), nil} // CleanUp the trash in the Fs//// Implement this if you have a way of emptying the trash or// otherwise cleaning up old versions of files.func (f *Fs) CleanUp() error {	do := f.Fs.Features().CleanUp	if do == nil {		return errors.New("can't CleanUp")	}	return do()} // About gets quota information from the Fsfunc (f *Fs) About() (*fs.Usage, error) {	do := f.Fs.Features().About	if do == nil {		return nil, errors.New("About not supported")	}	return do()} // UnWrap returns the Fs that this Fs is wrappingfunc (f *Fs) UnWrap() fs.Fs {	return f.Fs}

Lines 1–500 of 748.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t