1080 lines · 30.9 KB · Go
RawDownload
// Package crypt provides wrappers for Fs and Object which implement encryptionpackage crypt import (	"context"	"fmt"	"io"	"path"	"strings"	"time" 	"github.com/pkg/errors"	"github.com/rclone/rclone/fs"	"github.com/rclone/rclone/fs/accounting"	"github.com/rclone/rclone/fs/cache"	"github.com/rclone/rclone/fs/config/configmap"	"github.com/rclone/rclone/fs/config/configstruct"	"github.com/rclone/rclone/fs/config/obscure"	"github.com/rclone/rclone/fs/fspath"	"github.com/rclone/rclone/fs/hash") // Globals// Register with Fsfunc init() {	fs.Register(&fs.RegInfo{		Name:        "crypt",		Description: "Encrypt/Decrypt a remote",		NewFs:       NewFs,		CommandHelp: commandHelp,		Options: []fs.Option{{			Name:     "remote",			Help:     "Remote to encrypt/decrypt.\nNormally should contain a ':' and a path, e.g. \"myremote:path/to/dir\",\n\"myremote:bucket\" or maybe \"myremote:\" (not recommended).",			Required: true,		}, {			Name:    "filename_encryption",			Help:    "How to encrypt the filenames.",			Default: "standard",			Examples: []fs.OptionExample{				{					Value: "standard",					Help:  "Encrypt the filenames see the docs for the details.",				}, {					Value: "obfuscate",					Help:  "Very simple filename obfuscation.",				}, {					Value: "off",					Help:  "Don't encrypt the file names.  Adds a \".bin\" extension only.",				},			},		}, {			Name: "directory_name_encryption",			Help: `Option to either encrypt directory names or leave them intact. NB If filename_encryption is "off" then this option will do nothing.`,			Default: true,			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,			Required:   true,		}, {			Name:       "password2",			Help:       "Password or pass phrase for salt. Optional but recommended.\nShould be different to the previous password.",			IsPassword: true,		}, {			Name:    "server_side_across_configs",			Default: false,			Help: `Allow server-side operations (e.g. copy) to work across different crypt configs. Normally this option is not what you want, but if you have two cryptspointing to the same backend you can use it. This can be used, for example, to change file name encryption typewithout re-uploading all the data. Just make two crypt backendspointing to two different directories with the single changedparameter and use rclone move to move the files between the cryptremotes.`,			Advanced: true,		}, {			Name: "show_mapping",			Help: `For all files listed show how the names encrypt. If this flag is set then for each file that the remote is asked tolist, it will log (at level INFO) a line stating the decrypted filename and the encrypted file name. This is so you can work out which encrypted names are which decryptednames just in case you need to do something with the encrypted filenames, or for debugging purposes.`,			Default:  false,			Hide:     fs.OptionHideConfigurator,			Advanced: true,		}, {			Name:     "no_data_encryption",			Help:     "Option to either encrypt file data or leave it unencrypted.",			Default:  false,			Advanced: true,			Examples: []fs.OptionExample{				{					Value: "true",					Help:  "Don't encrypt file data, leave it unencrypted.",				},				{					Value: "false",					Help:  "Encrypt file data.",				},			},		}},	})} // newCipherForConfig constructs a Cipher for the given config namefunc newCipherForConfig(opt *Options) (*Cipher, error) {	mode, err := NewNameEncryptionMode(opt.FilenameEncryption)	if err != nil {		return nil, err	}	if opt.Password == "" {		return nil, errors.New("password not set in config file")	}	password, err := obscure.Reveal(opt.Password)	if err != nil {		return nil, errors.Wrap(err, "failed to decrypt password")	}	var salt string	if opt.Password2 != "" {		salt, err = obscure.Reveal(opt.Password2)		if err != nil {			return nil, errors.Wrap(err, "failed to decrypt password2")		}	}	cipher, err := newCipher(mode, password, salt, opt.DirectoryNameEncryption)	if err != nil {		return nil, errors.Wrap(err, "failed to make cipher")	}	return cipher, nil} // NewCipher constructs a Cipher for the given configfunc NewCipher(m configmap.Mapper) (*Cipher, error) {	// Parse config into Options struct	opt := new(Options)	err := configstruct.Set(m, opt)	if err != nil {		return nil, err	}	return newCipherForConfig(opt)} // NewFs constructs an Fs from the path, container:pathfunc NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs, error) {	// Parse config into Options struct	opt := new(Options)	err := configstruct.Set(m, opt)	if err != nil {		return nil, err	}	cipher, err := newCipherForConfig(opt)	if err != nil {		return nil, err	}	remote := opt.Remote	if strings.HasPrefix(remote, name+":") {		return nil, errors.New("can't point crypt remote at itself - check the value of the remote setting")	}	// Make sure to remove trailing . referring to the current dir	if path.Base(rpath) == "." {		rpath = strings.TrimSuffix(rpath, ".")	}	// Look for a file first	var wrappedFs fs.Fs	if rpath == "" {		wrappedFs, err = cache.Get(ctx, remote)	} else {		remotePath := fspath.JoinRootPath(remote, cipher.EncryptFileName(rpath))		wrappedFs, err = cache.Get(ctx, remotePath)		// if that didn't produce a file, look for a directory		if err != fs.ErrorIsFile {			remotePath = fspath.JoinRootPath(remote, cipher.EncryptDirName(rpath))			wrappedFs, err = cache.Get(ctx, remotePath)		}	}	if err != fs.ErrorIsFile && err != nil {		return nil, errors.Wrapf(err, "failed to make remote %q to wrap", remote)	}	f := &Fs{		Fs:     wrappedFs,		name:   name,		root:   rpath,		opt:    *opt,		cipher: cipher,	}	cache.PinUntilFinalized(f.Fs, f)	// 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,		SetTier:                 true,		GetTier:                 true,		ServerSideAcrossConfigs: opt.ServerSideAcrossConfigs,	}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs) 	return f, err} // Options defines the configuration for this backendtype Options struct {	Remote                  string `config:"remote"`	FilenameEncryption      string `config:"filename_encryption"`	DirectoryNameEncryption bool   `config:"directory_name_encryption"`	NoDataEncryption        bool   `config:"no_data_encryption"`	Password                string `config:"password"`	Password2               string `config:"password2"`	ServerSideAcrossConfigs bool   `config:"server_side_across_configs"`	ShowMapping             bool   `config:"show_mapping"`} // Fs represents a wrapped fs.Fstype Fs struct {	fs.Fs	wrapper  fs.Fs	name     string	root     string	opt      Options	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 f.opt.ShowMapping {		fs.Logf(decryptedRemote, "Encrypts to %q", remote)	}	*entries = append(*entries, f.newObject(obj))} // Encrypt a directory file name to entries.func (f *Fs) addDir(ctx context.Context, 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 f.opt.ShowMapping {		fs.Logf(decryptedRemote, "Encrypts to %q", remote)	}	*entries = append(*entries, f.newDir(ctx, dir))} // Encrypt some directory entries.  This alters entries returning it as newEntries.func (f *Fs) encryptEntries(ctx context.Context, 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(ctx, &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(ctx context.Context, dir string) (entries fs.DirEntries, err error) {	entries, err = f.Fs.List(ctx, f.cipher.EncryptDirName(dir))	if err != nil {		return nil, err	}	return f.encryptEntries(ctx, 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(ctx context.Context, dir string, callback fs.ListRCallback) (err error) {	return f.Fs.Features().ListR(ctx, f.cipher.EncryptDirName(dir), func(entries fs.DirEntries) error {		newEntries, err := f.encryptEntries(ctx, entries)		if err != nil {			return err		}		return callback(newEntries)	})} // NewObject finds the Object at remote.func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {	o, err := f.Fs.NewObject(ctx, f.cipher.EncryptFileName(remote))	if err != nil {		return nil, err	}	return f.newObject(o), nil} type putFn func(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) // put implements Put or PutStreamfunc (f *Fs) put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put putFn) (fs.Object, error) {	if f.opt.NoDataEncryption {		return put(ctx, in, f.newObjectInfo(src, nonce{}), options...)	} 	// Encrypt the data into wrappedIn	wrappedIn, encrypter, 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		}		// unwrap the accounting		var wrap accounting.WrapFn		wrappedIn, wrap = accounting.UnWrap(wrappedIn)		// add the hasher		wrappedIn = io.TeeReader(wrappedIn, hasher)		// wrap the accounting back on		wrappedIn = wrap(wrappedIn)	} 	// Transfer the data	o, err := put(ctx, wrappedIn, f.newObjectInfo(src, encrypter.nonce), 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(ctx, ht)		if err != nil {			return nil, errors.Wrap(err, "failed to read destination hash")		}		if srcHash != "" && dstHash != "" {			if srcHash != dstHash {				// remove object				err = o.Remove(ctx)				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)			}			fs.Debugf(src, "%v = %s OK", ht, srcHash)		}	} 	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(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {	return f.put(ctx, in, src, options, f.Fs.Put)} // PutStream uploads to the remote path with the modTime given of indeterminate sizefunc (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {	return f.put(ctx, 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(ctx context.Context, dir string) error {	return f.Fs.Mkdir(ctx, 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(ctx context.Context, dir string) error {	return f.Fs.Rmdir(ctx, f.cipher.EncryptDirName(dir))} // Purge all files in the directory specified//// 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(ctx context.Context, dir string) error {	do := f.Fs.Features().Purge	if do == nil {		return fs.ErrorCantPurge	}	return do(ctx, f.cipher.EncryptDirName(dir))} // 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(ctx context.Context, 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(ctx, 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//

Lines 1–500 of 1080.