Skip to content

Commit

Permalink
E2E: encrypt on upload(at rest remotely), decrypt on download
Browse files Browse the repository at this point in the history
Fixes #543.

When E2E encryption is enabled, data should only be unencrypted
on the user's local disk but should always be encrypted remotely
on Google Drive, if the password was provided.

- When a password is provided, encrypt the data before it is received
by Google Drive. Encrypt for upload:
```shell
$ drive push --encryption-password foolsGold issueFilingDrive.mp4
$ cat issueFiling.txt | drive push --encryption-password "$1N4Lax"
ok.enc
```

- When a password is provided, decrypt the data before writing
it to disk:
```shell
$ drive pull --piped --decryption-password foolsGold issueFilingDrive.mp4 > txt.v
$ drive pull --decryption-password "t51L3ntNA6T" few.mp4
```
  • Loading branch information
odeke-em committed May 23, 2016
1 parent 87197b6 commit 10e285c
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 6 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Usage](#usage)
- [Initializing](#initializing)
- [De Initializing](#de-initializing)
- [End to End Encryption](#end-to-end-encryption)
- [Traversal Depth](#traversal-depth)
- [Pulling](#pulling)
- [Exporting Docs](#exporting-docs)
Expand Down Expand Up @@ -221,6 +222,14 @@ Run it without any arguments to pull all of the files from the current path:
$ drive pull
```
To pull and decrypt your data that is stored encrypted at rest on Google Drive, use flag `--decryption-password`:
See [Issue #543](https://github.com/odeke-em/issues/543)
```shell
$ drive pull --decryption-password "$JiME5Umf" influx.txt
```
Pulling by matches is also supported
```shell
Expand Down Expand Up @@ -359,6 +368,13 @@ Note: To ignore checksum verification during a push:
$ drive push -ignore-checksum
```
To keep your data encrypted at rest remotely on Google Drive:
```shell
$ drive push --encryption-password "$JiME5Umf" influx.txt
```
For E2E discussions, see [issue #543](https://github.com/odeke-em/issues/543):
drive also supports pushing content piped from stdin which can be accomplished by:
```shell
Expand Down Expand Up @@ -470,6 +486,39 @@ default count of 20:
$ drive push --retry-count 4 a/bc/def terms
```
### End to End Encryption
See [Issue #543](https://github.com/odeke-em/issues/543)
This can be toggled when you supply a non-empty password ie
- `--encryption-password` for a push.
- `--decryption-password` for a pull.
When you supply argument `--encryption-password` during a push, drive will encrypt your data
and store it remotely encrypted(stored encrypted at rest), it can only be decrypted by you when you
perform a pull with the respective arg `--decryption-password`.
```shell
$ drive push --encryption-password "$400lsGO1Di3" few-ones.mp4 newest.mkv
```
```shell
$ drive pull --decryption-password "$400lsGO1Di3" few-ones.mp4 newest.mkv
```
If you supply the wrong password, you'll be warned if it cannot be decrypted
```shell
$ drive pull --decryption-password "4nG5troM" few-ones.mp4 newest.mkv
message corrupt or incorrect password
```
To pull normally push or pull your content, without attempting any *cryption attempts, skip
passing in a password and no attempts will be made.
### Publishing
The `pub` command publishes a file or directory globally so that anyone can view it on the web using the link returned.
Expand Down
42 changes: 36 additions & 6 deletions cmd/drive/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/odeke-em/drive/config"
"github.com/odeke-em/drive/gen"
"github.com/odeke-em/drive/src"
"github.com/odeke-em/drive/src/dcrypto"
)

var context *config.Context
Expand Down Expand Up @@ -627,12 +629,13 @@ type pullCmd struct {
ExplicitlyExport *bool `json:"explicitly-export"`
FixClashes *bool `json:"fix-clashes"`

Verbose *bool `json:"verbose"`
Depth *int `json:"depth"`
Starred *bool `json:"starred"`
AllStarred *bool `json:"all-starred"`
InTrash *bool `json:"trashed"`
ExponentialBackoffRetryCount *int `json:"retry-count"`
Verbose *bool `json:"verbose"`
Depth *int `json:"depth"`
Starred *bool `json:"starred"`
AllStarred *bool `json:"all-starred"`
InTrash *bool `json:"trashed"`
ExponentialBackoffRetryCount *int `json:"retry-count"`
DecryptionPassword *string `json:"decryption-password"`
}

func (cmd *pullCmd) Flags(fs *flag.FlagSet) *flag.FlagSet {
Expand Down Expand Up @@ -661,6 +664,7 @@ func (cmd *pullCmd) Flags(fs *flag.FlagSet) *flag.FlagSet {
cmd.Starred = fs.Bool(drive.CLIOptionStarred, false, drive.DescStarred)
cmd.InTrash = fs.Bool(drive.TrashedKey, false, "pull content in the trash")
cmd.ExponentialBackoffRetryCount = fs.Int(drive.CLIOptionRetryCount, drive.MaxFailedRetryCount, drive.DescExponentialBackoffRetryCount)
cmd.DecryptionPassword = fs.String(drive.CLIDecryptionPassword, "", drive.DescDecryptionPassword)

return fs
}
Expand Down Expand Up @@ -696,6 +700,17 @@ func (pCmd *pullCmd) Run(args []string, definedFlags map[string]*flag.Flag) {
retryCount = *(cmd.ExponentialBackoffRetryCount)
}

var decryptFn func(io.Reader) (io.ReadCloser, error)
if cmd.DecryptionPassword != nil {
passStr := *(cmd.DecryptionPassword)
if passStr != "" {
passwordAsBytes := []byte(passStr)
decryptFn = func(r io.Reader) (io.ReadCloser, error) {
return dcrypto.NewDecrypter(r, passwordAsBytes)
}
}
}

options := &drive.Options{
Path: path,
Sources: sources,
Expand All @@ -721,6 +736,7 @@ func (pCmd *pullCmd) Run(args []string, definedFlags map[string]*flag.Flag) {
Match: *cmd.Matches,
InTrash: *cmd.InTrash,
ExponentialBackoffRetryCount: retryCount,
Decrypter: decryptFn,
}

if *cmd.Matches || *cmd.Starred {
Expand Down Expand Up @@ -764,6 +780,7 @@ type pushCmd struct {
FixClashes *bool `json:"fix-clashes"`
Destination *string `json:"dest"`
ExponentialBackoffRetryCount *int `json:"retry-count"`
EncryptionPassword *string `json:"encryption-password"`
}

func (cmd *pushCmd) Flags(fs *flag.FlagSet) *flag.FlagSet {
Expand All @@ -788,6 +805,7 @@ func (cmd *pushCmd) Flags(fs *flag.FlagSet) *flag.FlagSet {
cmd.FixClashes = fs.Bool(drive.CLIOptionFixClashesKey, false, drive.DescFixClashes)
cmd.Destination = fs.String(drive.CLIOptionPushDestination, "", drive.DescPushDestination)
cmd.ExponentialBackoffRetryCount = fs.Int(drive.CLIOptionRetryCount, drive.MaxFailedRetryCount, drive.DescExponentialBackoffRetryCount)
cmd.EncryptionPassword = fs.String(drive.CLIEncryptionPassword, "", drive.DescEncryptionPassword)

return fs
}
Expand Down Expand Up @@ -939,6 +957,17 @@ func (pCmd *pushCmd) createPushOptions(absEntryPath string, definedFlags map[str
retryCount = *(cmd.ExponentialBackoffRetryCount)
}

var encryptFn func(io.Reader) (io.Reader, error)
if cmd.EncryptionPassword != nil {
passStr := *(cmd.EncryptionPassword)
if passStr != "" {
passwordAsBytes := []byte(passStr)
encryptFn = func(r io.Reader) (io.Reader, error) {
return dcrypto.NewEncrypter(r, passwordAsBytes)
}
}
}

opts := &drive.Options{
Force: *cmd.Force,
Hidden: *cmd.Hidden,
Expand All @@ -957,6 +986,7 @@ func (pCmd *pushCmd) createPushOptions(absEntryPath string, definedFlags map[str
Depth: *cmd.Depth,
FixClashes: *cmd.FixClashes,
Destination: *cmd.Destination,
Encrypter: encryptFn,
ExponentialBackoffRetryCount: retryCount,
}

Expand Down
4 changes: 4 additions & 0 deletions src/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package drive

import (
"errors"
"io"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -92,6 +93,9 @@ type Options struct {
Destination string
RenameMode RenameMode
ExponentialBackoffRetryCount int

Encrypter func(io.Reader) (io.Reader, error)
Decrypter func(io.Reader) (io.ReadCloser, error)
}

type Commands struct {
Expand Down
4 changes: 4 additions & 0 deletions src/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ const (
DescSkipContentCheck = "skip diffing actual body content, show only name, time, type changes"
DescPushDestination = "specify the final destination of the contents of an operation"
DescExponentialBackoffRetryCount = "max number of retries for exponential backoff"
DescEncryptionPassword = "encryption password"
DescDecryptionPassword = "decryption password"
)

const (
Expand Down Expand Up @@ -234,6 +236,8 @@ const (
CLIOptionRenameLocal = "local"
CLIOptionRenameRemote = "remote"
CLIOptionRetryCount = "retry-count"
CLIEncryptionPassword = "encryption-password"
CLIDecryptionPassword = "decryption-password"
)

const (
Expand Down
6 changes: 6 additions & 0 deletions src/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ func (g *Commands) PullMatchLike() error {
}

func pull(g *Commands, pt pullType) error {
g.rem.encrypter = g.opts.Encrypter
g.rem.decrypter = g.opts.Decrypter

cl, clashes, err := pullLikeResolve(g, pt)

if len(clashes) >= 1 {
Expand Down Expand Up @@ -273,6 +276,9 @@ func (g *Commands) pullLikeMatchesResolver(pt pullType) (cl, clashes []*Change,
}

func (g *Commands) PullPiped(byId bool) (err error) {
g.rem.encrypter = g.opts.Encrypter
g.rem.decrypter = g.opts.Decrypter

resolver := g.rem.FindByPathM
if byId {
resolver = g.rem.FindByIdM
Expand Down
6 changes: 6 additions & 0 deletions src/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ var mkdirAllMu = sync.Mutex{}
// directory, it recursively pushes to the remote if there are local changes.
// It doesn't check if there are local changes if isForce is set.
func (g *Commands) Push() (err error) {
g.rem.encrypter = g.opts.Encrypter
g.rem.decrypter = g.opts.Decrypter

defer g.clearMountPoints()

var cl []*Change
Expand Down Expand Up @@ -181,6 +184,9 @@ func (g *Commands) resolveConflicts(cl []*Change, push bool) (*[]*Change, *[]*Ch
}

func (g *Commands) PushPiped() (err error) {
g.rem.encrypter = g.opts.Encrypter
g.rem.decrypter = g.opts.Decrypter

// Cannot push asynchronously because the push order must be maintained
for _, relToRootPath := range g.opts.Sources {
rem, resErr := g.rem.FindByPath(relToRootPath)
Expand Down
20 changes: 20 additions & 0 deletions src/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ func errCannotMkdirAll(p string) error {
type Remote struct {
client *http.Client
service *drive.Service
encrypter func(io.Reader) (io.Reader, error)
decrypter func(io.Reader) (io.ReadCloser, error)
progressChan chan int
}

Expand Down Expand Up @@ -569,6 +571,15 @@ func (r *Remote) Download(id string, exportURL string) (io.ReadCloser, error) {
}
}

if r.decrypter != nil && body != nil {
decR, err := r.decrypter(body)
_ = body.Close()
if err != nil {
return nil, err
}
body = decR
}

return body, err
}

Expand Down Expand Up @@ -666,6 +677,15 @@ func (r *Remote) upsertByComparison(body io.Reader, args *upsertOpt) (f *File, m
uploaded.MimeType = DriveFolderMimeType
}

if r.encrypter != nil && body != nil {
encR, encErr := r.encrypter(body)
if encErr != nil {
err = encErr
return
}
body = encR
}

if args.src.MimeType != "" {
uploaded.MimeType = args.src.MimeType
}
Expand Down

0 comments on commit 10e285c

Please sign in to comment.