From 02cbb1f33b74db5faf4422fe3ab2e442d4786c35 Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Wed, 25 Jul 2018 08:15:34 -0400 Subject: [PATCH 01/13] Quick implementation --- cmd/lyra/locker.go | 183 +++++++++++++++++++++++++++++++++++++++++++++ cmd/lyra/lyra.go | 1 + 2 files changed, 184 insertions(+) create mode 100644 cmd/lyra/locker.go diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go new file mode 100644 index 0000000..5c0ff69 --- /dev/null +++ b/cmd/lyra/locker.go @@ -0,0 +1,183 @@ +package main + +import ( + "bufio" + "errors" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +const lockerConfig = "./lyralocker" +const lockerpassFilename = "./.lockerpass" + +type lockercmd struct { + doEncrypt bool + fileRecursionDepth int + passphrase string +} + +func (cmd *lockercmd) CName() string { + return "locker" +} + +func (cmd *lockercmd) Help() string { + return "Help TBT" +} + +func (cmd *lockercmd) RegCFlags(fs *flag.FlagSet) { +} + +func (cmd *lockercmd) Run(opt []string) error { + + if len(opt) < 1 { + return errors.New("Bad args...") + } + + switch opt[0] { + case "lock": + cmd.doEncrypt = true + break + case "unlock": + cmd.doEncrypt = false + break + default: + return errors.New("Bad args...") + } + + pass, err := readPassFile() + if err != nil { + return err + } + fmt.Printf("Read password: %s\n", pass) + cmd.passphrase = pass + + // TODO support multiple possible names like Docker... + lockerFilename, err := filepath.Abs(lockerConfig) + if err != nil { + return err + } + + lockerFile, err := os.Open(lockerFilename) + defer lockerFile.Close() + if err != nil { + return err + } + + lockerScanner := bufio.NewScanner(lockerFile) + + for lockerScanner.Scan() { + filename, err := filepath.Abs(lockerScanner.Text()) + if err != nil { + report(err) + continue + } + fmt.Println("Read: " + filename) + + fileInfo, err := os.Stat(filename) + if err != nil { + fmt.Printf("Error because exists? %t\n", os.IsExist(err)) + report(err) + continue + } + + if fileInfo.IsDir() { + processLockerFolder(filename, cmd) + } else { + err := processLockerFile(filename, cmd) + if err != nil { + report(err) + continue + } + + } + } + + return nil +} + +func report(err error) { + // TODO report error +} + +func processLockerFolder(foldername string, cmd *lockercmd) { + filepath.Walk(foldername, func(path string, info os.FileInfo, err error) error { + if err != nil { + report(err) + return err + } + + if !info.IsDir() { + return processLockerFile(path, cmd) + } + return nil + }) +} + +func processLockerFile(filename string, cmd *lockercmd) error { + fmt.Println("Processing: " + filename) + + if cmd.doEncrypt { + fmt.Println("Trying to encrypt: " + filename) + err := encrypt(filename, createLockedFilename(filename), []byte(cmd.passphrase)) + if err != nil { + return err + } + return os.Remove(filename) + } else { + fmt.Println("Trying to decrypt: " + filename) + lockedFilename := createLockedFilename(filename) + + err := decrypt(lockedFilename, filename, false, []byte(cmd.passphrase)) + if err != nil { + return err + } + return os.Remove(lockedFilename) + } + + // if cmd.doEncrypt != isLocked(filename) { + // if cmd.doEncrypt { + // err := encrypt(filename, createFilenameLock(filename, cmd.doEncrypt), []byte(cmd.passphrase)) + // if err != nil { + // return err + // } + // return os.Remove(filename) + // } else { + // err := decrypt(filename, createFilenameLock(filename, cmd.doEncrypt), false, []byte(cmd.passphrase)) + // if err != nil { + // return err + // } + // return os.Remove(filename) + // } + // } + +} + +func createLockedFilename(filename string) string { + return filename + ".locked" +} + +func readPassFile() (string, error) { + contents, err := ioutil.ReadFile(lockerpassFilename) + if err != nil { + return "", err + } + return strings.TrimSpace(string(contents)), nil +} + +func isFileLocked(filename string) (bool, error) { + lockedFilename := createLockedFilename(filename) + + _, infoErr := os.Stat(filename) + _, lockedInfoErr := os.Stat(lockedFilename) + + if infoErr != nil && lockedInfoErr != nil { + return false, infoErr + } + + return os.IsExist(infoErr), nil + +} diff --git a/cmd/lyra/lyra.go b/cmd/lyra/lyra.go index b166cf7..4cbe37c 100644 --- a/cmd/lyra/lyra.go +++ b/cmd/lyra/lyra.go @@ -62,6 +62,7 @@ func main() { &encryptcmd{}, &decryptcmd{}, &gencmd{}, + &lockercmd{}, } versionSet := flag.Bool("version", false, "") From b7153797df81eef05af1b51c86aa10af0202f99e Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Wed, 25 Jul 2018 16:35:06 -0400 Subject: [PATCH 02/13] Moved encryption function calls to pkg, and added base functionality of locker features --- cmd/lyra/decrypt.go | 45 +---------- cmd/lyra/decrypt_test.go | 9 --- cmd/lyra/encrypt.go | 40 +--------- cmd/lyra/encrypt_test.go | 10 --- cmd/lyra/locker.go | 127 +++++------------------------ cmd/lyra/locker/locker.go | 128 ++++++++++++++++++++++++++++++ cmd/lyra/locker/locker_test.go | 35 ++++++++ pkg/encryption/encryption.go | 81 +++++++++++++++++++ pkg/encryption/encryption_test.go | 17 ++++ 9 files changed, 284 insertions(+), 208 deletions(-) delete mode 100644 cmd/lyra/decrypt_test.go delete mode 100644 cmd/lyra/encrypt_test.go create mode 100644 cmd/lyra/locker/locker.go create mode 100644 cmd/lyra/locker/locker_test.go create mode 100644 pkg/encryption/encryption.go create mode 100644 pkg/encryption/encryption_test.go diff --git a/cmd/lyra/decrypt.go b/cmd/lyra/decrypt.go index 577736f..966bd3c 100644 --- a/cmd/lyra/decrypt.go +++ b/cmd/lyra/decrypt.go @@ -4,8 +4,7 @@ import ( "errors" "flag" - "github.com/azohra/lyra/pkg/lcrypt" - "github.com/azohra/lyra/pkg/lfile" + "github.com/fvumbaca/lyra/pkg/encryption" ) const ( @@ -93,7 +92,7 @@ func (cmd *decryptcmd) Run(opt []string) error { cmd.passphrase = string(getPassphrase()) } - err = decrypt(opt[0], cmd.path, cmd.printOnly, []byte(cmd.passphrase)) + err = encryption.Decrypt(opt[0], cmd.path, cmd.printOnly, []byte(cmd.passphrase)) cmd.passphrase = "" return err @@ -106,43 +105,3 @@ func (cmd *decryptcmd) validateInputs() error { return nil } - -//decrypt encrypts file file and overides the content of file with the ciphertext -//of the specified plaintext file. -func decrypt(file, saveTo string, print bool, passphrase []byte) error { - ctFile, err := lfile.NewParsedSLFile(file) - if err != nil { - return err - } - - key, err := lcrypt.NewLKey(passphrase, ctFile.RetrieveSalt()) - if err != nil { - return err - } - - ptFile, err := ctFile.DecipherFile(key) - if err != nil { - return err - } - - if saveTo == "" && !print { - err = ptFile.Write(file) - } else if saveTo != "" && !print { - err = ptFile.Write(saveTo) - } - - if err != nil { - return err - } - - if print { - ptFile.PrintLyraFile() - } - - err = key.DestroyKey() - if err != nil { - handleErr(err) - } - - return nil -} diff --git a/cmd/lyra/decrypt_test.go b/cmd/lyra/decrypt_test.go deleted file mode 100644 index 36265c2..0000000 --- a/cmd/lyra/decrypt_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import "testing" - -func TestDecrypt(t *testing.T) { - file := "../../test/fixture.32.go.txt" - save := "../../test/fixture.322.go.txt" - decrypt(file, save, false, []byte("THIS IS A TEST PASSPHRASE YOU SHOULD NOT USE THIS")) -} diff --git a/cmd/lyra/encrypt.go b/cmd/lyra/encrypt.go index afd950d..32befcc 100644 --- a/cmd/lyra/encrypt.go +++ b/cmd/lyra/encrypt.go @@ -6,9 +6,8 @@ import ( "fmt" "os" - "github.com/azohra/lyra/pkg/lcrypt" - "github.com/azohra/lyra/pkg/lfile" "github.com/brsmsn/gware/pkg/diceware" + "github.com/fvumbaca/lyra/pkg/encryption" ) const ( @@ -138,7 +137,7 @@ func (cmd *encryptcmd) Run(opt []string) error { cmd.passphrase = string(input) } - err = encrypt(opt[0], cmd.path, []byte(cmd.passphrase)) + err = encryption.Encrypt(opt[0], cmd.path, []byte(cmd.passphrase)) if err != nil { return err } @@ -159,41 +158,6 @@ func (cmd *encryptcmd) validateInputs() error { return nil } -//encrypt encrypts file file and overides the content of file with the ciphertext -//of the specified plaintext file. -func encrypt(file, saveTo string, passphrase []byte) error { - ptFile, err := lfile.NewParsedLyraFile(file) - if err != nil { - return err - } - - key, err := lcrypt.NewLKey(passphrase, nil) - if err != nil { - return err - } - - ctFile, err := ptFile.EncipherFile(key) - if err != nil { - return err - } - - switch saveTo { - case "": - err = ctFile.Write(file) - default: - err = ctFile.Write(saveTo) - } - if err != nil { - return err - } - err = key.DestroyKey() - if err != nil { - return err - } - - return nil -} - //gen a diceware passphrase using eff long wordlist func (cmd *encryptcmd) genPass() error { diff --git a/cmd/lyra/encrypt_test.go b/cmd/lyra/encrypt_test.go deleted file mode 100644 index 3ce8514..0000000 --- a/cmd/lyra/encrypt_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import "testing" - -func TestEncrypt(t *testing.T) { - file := "../../test/fixture.31.go.txt" - save := "../../test/fixture.311.go.txt" - encrypt(file, save, []byte("THIS IS A TEST PASSPHRASE YOU SHOULD NOT USE THIS")) - -} diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index 5c0ff69..4952727 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -1,14 +1,13 @@ package main import ( - "bufio" "errors" "flag" "fmt" "io/ioutil" - "os" - "path/filepath" "strings" + + "github.com/fvumbaca/lyra/cmd/lyra/locker" ) const lockerConfig = "./lyralocker" @@ -20,6 +19,12 @@ type lockercmd struct { passphrase string } +type lockerfile struct { + filename string + lockedFilename string + isLocked bool +} + func (cmd *lockercmd) CName() string { return "locker" } @@ -52,112 +57,32 @@ func (cmd *lockercmd) Run(opt []string) error { if err != nil { return err } - fmt.Printf("Read password: %s\n", pass) cmd.passphrase = pass - // TODO support multiple possible names like Docker... - lockerFilename, err := filepath.Abs(lockerConfig) + files, err := locker.ParseLockerFile("./lyralocker") if err != nil { return err } - lockerFile, err := os.Open(lockerFilename) - defer lockerFile.Close() - if err != nil { - return err - } - - lockerScanner := bufio.NewScanner(lockerFile) - - for lockerScanner.Scan() { - filename, err := filepath.Abs(lockerScanner.Text()) - if err != nil { - report(err) - continue - } - fmt.Println("Read: " + filename) - - fileInfo, err := os.Stat(filename) - if err != nil { - fmt.Printf("Error because exists? %t\n", os.IsExist(err)) - report(err) - continue - } - - if fileInfo.IsDir() { - processLockerFolder(filename, cmd) + for _, f := range files { + fmt.Printf("Parsed file: %+v\n", f) + if f.Err != nil { + report(f.Err) } else { - err := processLockerFile(filename, cmd) - if err != nil { - report(err) - continue + if cmd.doEncrypt { + f.Lock([]byte(cmd.passphrase)) + } else { + f.Unlock([]byte(cmd.passphrase)) } - } + } return nil } func report(err error) { - // TODO report error -} - -func processLockerFolder(foldername string, cmd *lockercmd) { - filepath.Walk(foldername, func(path string, info os.FileInfo, err error) error { - if err != nil { - report(err) - return err - } - - if !info.IsDir() { - return processLockerFile(path, cmd) - } - return nil - }) -} - -func processLockerFile(filename string, cmd *lockercmd) error { - fmt.Println("Processing: " + filename) - - if cmd.doEncrypt { - fmt.Println("Trying to encrypt: " + filename) - err := encrypt(filename, createLockedFilename(filename), []byte(cmd.passphrase)) - if err != nil { - return err - } - return os.Remove(filename) - } else { - fmt.Println("Trying to decrypt: " + filename) - lockedFilename := createLockedFilename(filename) - - err := decrypt(lockedFilename, filename, false, []byte(cmd.passphrase)) - if err != nil { - return err - } - return os.Remove(lockedFilename) - } - - // if cmd.doEncrypt != isLocked(filename) { - // if cmd.doEncrypt { - // err := encrypt(filename, createFilenameLock(filename, cmd.doEncrypt), []byte(cmd.passphrase)) - // if err != nil { - // return err - // } - // return os.Remove(filename) - // } else { - // err := decrypt(filename, createFilenameLock(filename, cmd.doEncrypt), false, []byte(cmd.passphrase)) - // if err != nil { - // return err - // } - // return os.Remove(filename) - // } - // } - -} - -func createLockedFilename(filename string) string { - return filename + ".locked" + fmt.Println("An error occurred: " + err.Error()) } func readPassFile() (string, error) { @@ -167,17 +92,3 @@ func readPassFile() (string, error) { } return strings.TrimSpace(string(contents)), nil } - -func isFileLocked(filename string) (bool, error) { - lockedFilename := createLockedFilename(filename) - - _, infoErr := os.Stat(filename) - _, lockedInfoErr := os.Stat(lockedFilename) - - if infoErr != nil && lockedInfoErr != nil { - return false, infoErr - } - - return os.IsExist(infoErr), nil - -} diff --git a/cmd/lyra/locker/locker.go b/cmd/lyra/locker/locker.go new file mode 100644 index 0000000..236e912 --- /dev/null +++ b/cmd/lyra/locker/locker.go @@ -0,0 +1,128 @@ +package locker + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/fvumbaca/lyra/pkg/encryption" +) + +const ( + lockedFileExtension = ".locked" // What the extension is for locked files + commentSequence = "#" // What signals a comment in a locker file +) + +// Asset represents a Locker file asset. +type Asset struct { + Filename string + LockedFilename string + IsLocked bool + Err error +} + +// ParseLockerFile parses a locker file and returns a list of locker assets +func ParseLockerFile(filename string) ([]Asset, error) { + jobs := []Asset{} + + lockerFile, err := os.Open(filename) + defer lockerFile.Close() + if err != nil { + return nil, err + } + scanner := bufio.NewScanner(lockerFile) + for scanner.Scan() { + entry := strings.TrimSpace(scanner.Text()) + + // ignore empty lines and comments from locker file + if entry == "" || strings.HasPrefix(entry, commentSequence) { + continue + } + + fileInfo, err := os.Stat(entry) + // cant be a dir if it is not found but maybe .locked? + if err == nil && fileInfo.IsDir() { + fmt.Println("Not handling folders yet") + + filepath.Walk(entry, func(path string, info os.FileInfo, err error) error { + // newLockerAsset will propagate errors to the top cmd level so we dont need to + // handle them here + jobs = append(jobs, newLockerAsset(path)) + return nil + }) + + } else { + // Creating a locker asset from a missing file is ok. Error will + // propagate to the cmd level for reporting + jobs = append(jobs, newLockerAsset(entry)) + } + + } + return jobs, nil +} + +func newLockerAsset(filename string) (l Asset) { + l.Filename = filename + l.LockedFilename = filename + l.IsLocked = true + l.Err = nil + + if isFilenameLocked(filename) { + l.Filename = createLockedFilename(filename, false) + } else { + l.LockedFilename = createLockedFilename(filename, true) + } + + _, baseErr := os.Stat(l.Filename) + _, lockedErr := os.Stat(l.LockedFilename) + + if baseErr == nil && lockedErr != nil && os.IsNotExist(lockedErr) { + l.IsLocked = false + } else if baseErr != nil && os.IsNotExist(baseErr) && lockedErr == nil { + l.IsLocked = true + } else { + l.Err = baseErr + } + + return +} + +// Lock encrypts a locker Asset. +func (a Asset) Lock(passphrase []byte) error { + if !a.IsLocked { + err := encryption.Encrypt(a.Filename, a.LockedFilename, passphrase) + if err != nil { + return err + } + return os.Remove(a.Filename) + } + return nil +} + +// Unlock decrypts a locker Asset. +func (a Asset) Unlock(passphrase []byte) error { + if a.IsLocked { + err := encryption.Decrypt(a.LockedFilename, a.Filename, false, passphrase) + if err != nil { + return err + } + return os.Remove(a.LockedFilename) + } + return nil +} + +func createLockedFilename(filename string, lock bool) string { + if lock && !isFilenameLocked(filename) { + return filename + lockedFileExtension + } else if !lock && isFilenameLocked(filename) { + return strings.TrimSuffix(filename, lockedFileExtension) + } else { + return filename + } +} + +func isFilenameLocked(filename string) bool { + return strings.HasSuffix(filename, lockedFileExtension) +} diff --git a/cmd/lyra/locker/locker_test.go b/cmd/lyra/locker/locker_test.go new file mode 100644 index 0000000..3a4d8d5 --- /dev/null +++ b/cmd/lyra/locker/locker_test.go @@ -0,0 +1,35 @@ +package locker + +import "testing" + +func TestIsFilenameLocked(t *testing.T) { + if isFilenameLocked("somefile.txt") { + t.Errorf("'somefile.txt' is not a locked name") + } + + if !isFilenameLocked("someOtherFile.exs.locked") { + t.Errorf("'someOtherFile.exs.locked' is a locked name") + } +} + +func TestCreateLockedFilename(t *testing.T) { + test1 := [...]string{"someFile.txt", "someFile.txt.locked"} + if createLockedFilename(test1[0], true) != test1[1] { + t.Errorf("'%s' locked filename not correct! Got: %s", test1[0], test1[1]) + } + + test2 := [...]string{"someFile.txt.locked", "someFile.txt"} + if createLockedFilename(test2[0], false) != test2[1] { + t.Errorf("'%s' unlocked filename not correct! Got: %s", test2[0], test2[1]) + } + + test3 := [...]string{"someFile.txt", "someFile.txt"} + if createLockedFilename(test3[0], false) != test3[1] { + t.Errorf("'%s' unlocked filename not correct! Got: %s", test3[0], test3[1]) + } + + test4 := [...]string{"someFile.txt.locked", "someFile.txt.locked"} + if createLockedFilename(test4[0], true) != test4[1] { + t.Errorf("'%s' locked filename not correct! Got: %s", test4[0], test4[1]) + } +} diff --git a/pkg/encryption/encryption.go b/pkg/encryption/encryption.go new file mode 100644 index 0000000..72329c9 --- /dev/null +++ b/pkg/encryption/encryption.go @@ -0,0 +1,81 @@ +package encryption + +import ( + "github.com/azohra/lyra/pkg/lcrypt" + "github.com/azohra/lyra/pkg/lfile" +) + +// Encrypt encrypts file file and overides the content of file with the ciphertext +// of the specified plaintext file. +func Encrypt(file, saveTo string, passphrase []byte) error { + ptFile, err := lfile.NewParsedLyraFile(file) + if err != nil { + return err + } + + key, err := lcrypt.NewLKey(passphrase, nil) + if err != nil { + return err + } + + ctFile, err := ptFile.EncipherFile(key) + if err != nil { + return err + } + + switch saveTo { + case "": + err = ctFile.Write(file) + default: + err = ctFile.Write(saveTo) + } + if err != nil { + return err + } + err = key.DestroyKey() + if err != nil { + return err + } + + return nil +} + +// Decrypt encrypts file file and overides the content of file with the ciphertext +// of the specified plaintext file. +func Decrypt(file, saveTo string, print bool, passphrase []byte) error { + ctFile, err := lfile.NewParsedSLFile(file) + if err != nil { + return err + } + + key, err := lcrypt.NewLKey(passphrase, ctFile.RetrieveSalt()) + if err != nil { + return err + } + + ptFile, err := ctFile.DecipherFile(key) + if err != nil { + return err + } + + if saveTo == "" && !print { + err = ptFile.Write(file) + } else if saveTo != "" && !print { + err = ptFile.Write(saveTo) + } + + if err != nil { + return err + } + + if print { + ptFile.PrintLyraFile() + } + + err = key.DestroyKey() + if err != nil { + return err + } + + return nil +} diff --git a/pkg/encryption/encryption_test.go b/pkg/encryption/encryption_test.go new file mode 100644 index 0000000..6d046e1 --- /dev/null +++ b/pkg/encryption/encryption_test.go @@ -0,0 +1,17 @@ +package encryption + +import ( + "testing" +) + +func TestEncrypt(t *testing.T) { + file := "../../test/fixture.31.go.txt" + save := "../../test/fixture.311.go.txt" + Encrypt(file, save, []byte("THIS IS A TEST PASSPHRASE YOU SHOULD NOT USE THIS")) +} + +func TestDecrypt(t *testing.T) { + file := "../../test/fixture.32.go.txt" + save := "../../test/fixture.322.go.txt" + Decrypt(file, save, false, []byte("THIS IS A TEST PASSPHRASE YOU SHOULD NOT USE THIS")) +} From cc9b0445c5a49b092ee333380e82be70fd50563b Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Wed, 25 Jul 2018 16:45:01 -0400 Subject: [PATCH 03/13] Merged encrypted pkg with lcrypt --- cmd/lyra/decrypt.go | 4 ++-- cmd/lyra/encrypt.go | 4 ++-- cmd/lyra/locker/locker.go | 6 +++--- pkg/{encryption => lcrypt}/encryption.go | 2 +- pkg/{encryption => lcrypt}/encryption_test.go | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename pkg/{encryption => lcrypt}/encryption.go (98%) rename pkg/{encryption => lcrypt}/encryption_test.go (95%) diff --git a/cmd/lyra/decrypt.go b/cmd/lyra/decrypt.go index 966bd3c..51fe518 100644 --- a/cmd/lyra/decrypt.go +++ b/cmd/lyra/decrypt.go @@ -4,7 +4,7 @@ import ( "errors" "flag" - "github.com/fvumbaca/lyra/pkg/encryption" + "github.com/fvumbaca/lyra/pkg/lcrypt" ) const ( @@ -92,7 +92,7 @@ func (cmd *decryptcmd) Run(opt []string) error { cmd.passphrase = string(getPassphrase()) } - err = encryption.Decrypt(opt[0], cmd.path, cmd.printOnly, []byte(cmd.passphrase)) + err = lcrypt.Decrypt(opt[0], cmd.path, cmd.printOnly, []byte(cmd.passphrase)) cmd.passphrase = "" return err diff --git a/cmd/lyra/encrypt.go b/cmd/lyra/encrypt.go index 32befcc..3d2151c 100644 --- a/cmd/lyra/encrypt.go +++ b/cmd/lyra/encrypt.go @@ -7,7 +7,7 @@ import ( "os" "github.com/brsmsn/gware/pkg/diceware" - "github.com/fvumbaca/lyra/pkg/encryption" + "github.com/fvumbaca/lyra/pkg/lcrypt" ) const ( @@ -137,7 +137,7 @@ func (cmd *encryptcmd) Run(opt []string) error { cmd.passphrase = string(input) } - err = encryption.Encrypt(opt[0], cmd.path, []byte(cmd.passphrase)) + err = lcrypt.Encrypt(opt[0], cmd.path, []byte(cmd.passphrase)) if err != nil { return err } diff --git a/cmd/lyra/locker/locker.go b/cmd/lyra/locker/locker.go index 236e912..cca00b3 100644 --- a/cmd/lyra/locker/locker.go +++ b/cmd/lyra/locker/locker.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - "github.com/fvumbaca/lyra/pkg/encryption" + "github.com/fvumbaca/lyra/pkg/lcrypt" ) const ( @@ -92,7 +92,7 @@ func newLockerAsset(filename string) (l Asset) { // Lock encrypts a locker Asset. func (a Asset) Lock(passphrase []byte) error { if !a.IsLocked { - err := encryption.Encrypt(a.Filename, a.LockedFilename, passphrase) + err := lcrypt.Encrypt(a.Filename, a.LockedFilename, passphrase) if err != nil { return err } @@ -104,7 +104,7 @@ func (a Asset) Lock(passphrase []byte) error { // Unlock decrypts a locker Asset. func (a Asset) Unlock(passphrase []byte) error { if a.IsLocked { - err := encryption.Decrypt(a.LockedFilename, a.Filename, false, passphrase) + err := lcrypt.Decrypt(a.LockedFilename, a.Filename, false, passphrase) if err != nil { return err } diff --git a/pkg/encryption/encryption.go b/pkg/lcrypt/encryption.go similarity index 98% rename from pkg/encryption/encryption.go rename to pkg/lcrypt/encryption.go index 72329c9..cfcdd69 100644 --- a/pkg/encryption/encryption.go +++ b/pkg/lcrypt/encryption.go @@ -1,4 +1,4 @@ -package encryption +package lcrypt import ( "github.com/azohra/lyra/pkg/lcrypt" diff --git a/pkg/encryption/encryption_test.go b/pkg/lcrypt/encryption_test.go similarity index 95% rename from pkg/encryption/encryption_test.go rename to pkg/lcrypt/encryption_test.go index 6d046e1..41ce511 100644 --- a/pkg/encryption/encryption_test.go +++ b/pkg/lcrypt/encryption_test.go @@ -1,4 +1,4 @@ -package encryption +package lcrypt import ( "testing" From bb8ddb65fc547f3eff6fa5458fd0cbbb3fd1fa51 Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Wed, 25 Jul 2018 22:39:36 -0400 Subject: [PATCH 04/13] Updated command messages --- cmd/lyra/locker.go | 44 ++++++++++++++++++++++++++++++++------- cmd/lyra/locker/locker.go | 10 +++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index 4952727..e0811c1 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io/ioutil" + "os" "strings" "github.com/fvumbaca/lyra/cmd/lyra/locker" @@ -64,25 +65,54 @@ func (cmd *lockercmd) Run(opt []string) error { return err } + successCount := 0 + failCount := 0 + for _, f := range files { - fmt.Printf("Parsed file: %+v\n", f) if f.Err != nil { - report(f.Err) + if os.IsNotExist(f.Err) { + fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", f.Filename)) + } + failCount++ } else { if cmd.doEncrypt { + if !f.IsLocked { + fmt.Printf("Locking: %s\n", f.Filename) + } else { + fmt.Println(f.Filename + " is already locked.") + } f.Lock([]byte(cmd.passphrase)) } else { - f.Unlock([]byte(cmd.passphrase)) + if f.IsLocked { + fmt.Printf("Unlocking: %s\n", f.Filename) + } else { + fmt.Println(f.Filename + " is already unlocked.") + } + err := f.Unlock([]byte(cmd.passphrase)) + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + } else { + + successCount++ + } } } } - return nil -} + action := "encripted" + if !cmd.doEncrypt { + action = "decrypted" + } -func report(err error) { - fmt.Println("An error occurred: " + err.Error()) + if failCount > 0 { + fmt.Fprintf(os.Stderr, "%d assets were unable to be encrypted", failCount) + os.Exit(1) + } else { + fmt.Printf("%d assets %s successfully!\n", successCount, action) + return nil + } + return nil } func readPassFile() (string, error) { diff --git a/cmd/lyra/locker/locker.go b/cmd/lyra/locker/locker.go index cca00b3..78c8c00 100644 --- a/cmd/lyra/locker/locker.go +++ b/cmd/lyra/locker/locker.go @@ -44,12 +44,14 @@ func ParseLockerFile(filename string) ([]Asset, error) { fileInfo, err := os.Stat(entry) // cant be a dir if it is not found but maybe .locked? if err == nil && fileInfo.IsDir() { - fmt.Println("Not handling folders yet") filepath.Walk(entry, func(path string, info os.FileInfo, err error) error { - // newLockerAsset will propagate errors to the top cmd level so we dont need to - // handle them here - jobs = append(jobs, newLockerAsset(path)) + if info != nil && !info.IsDir() { + // newLockerAsset will propagate errors to the top cmd level so we dont need to + // handle them here + fmt.Println("Walking on: " + path) + jobs = append(jobs, newLockerAsset(path)) + } return nil }) From b3a97817b0e966f5ebea8b293a6e3e9201ece18a Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Thu, 26 Jul 2018 10:53:18 -0400 Subject: [PATCH 05/13] Cleaned up command output --- cmd/lyra/locker.go | 22 +++++++++++----------- cmd/lyra/locker/locker.go | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index e0811c1..cf087c2 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -13,6 +13,8 @@ import ( const lockerConfig = "./lyralocker" const lockerpassFilename = "./.lockerpass" +const checkmark = "✓" +const chironKey = string(0x26B7) type lockercmd struct { doEncrypt bool @@ -76,23 +78,21 @@ func (cmd *lockercmd) Run(opt []string) error { failCount++ } else { if cmd.doEncrypt { - if !f.IsLocked { - fmt.Printf("Locking: %s\n", f.Filename) + err := f.Lock([]byte(cmd.passphrase)) + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + failCount++ } else { - fmt.Println(f.Filename + " is already locked.") + fmt.Printf("%s %s\n", checkmark, f.Filename) + successCount++ } - f.Lock([]byte(cmd.passphrase)) } else { - if f.IsLocked { - fmt.Printf("Unlocking: %s\n", f.Filename) - } else { - fmt.Println(f.Filename + " is already unlocked.") - } err := f.Unlock([]byte(cmd.passphrase)) if err != nil { fmt.Fprint(os.Stderr, err.Error()) + failCount++ } else { - + fmt.Printf("%s %s\n", checkmark, f.Filename) successCount++ } } @@ -109,7 +109,7 @@ func (cmd *lockercmd) Run(opt []string) error { fmt.Fprintf(os.Stderr, "%d assets were unable to be encrypted", failCount) os.Exit(1) } else { - fmt.Printf("%d assets %s successfully!\n", successCount, action) + fmt.Printf("%d assets %s\n", successCount, action) return nil } return nil diff --git a/cmd/lyra/locker/locker.go b/cmd/lyra/locker/locker.go index 78c8c00..e927cc1 100644 --- a/cmd/lyra/locker/locker.go +++ b/cmd/lyra/locker/locker.go @@ -2,7 +2,6 @@ package locker import ( "bufio" - "fmt" "os" "path/filepath" "strings" @@ -49,7 +48,6 @@ func ParseLockerFile(filename string) ([]Asset, error) { if info != nil && !info.IsDir() { // newLockerAsset will propagate errors to the top cmd level so we dont need to // handle them here - fmt.Println("Walking on: " + path) jobs = append(jobs, newLockerAsset(path)) } return nil From e7002ebff29fd9a36f912440e6ecf1f04f4b8d4a Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Thu, 26 Jul 2018 16:00:12 -0400 Subject: [PATCH 06/13] Added usage, shake command, and changed import paths --- cmd/lyra/encrypt.go | 2 +- cmd/lyra/locker.go | 193 ++++++++++++++++++++++++++++---------- cmd/lyra/locker/locker.go | 34 ++++++- cmd/lyra/lyra.go | 1 + 4 files changed, 177 insertions(+), 53 deletions(-) diff --git a/cmd/lyra/encrypt.go b/cmd/lyra/encrypt.go index 3d2151c..b5d819c 100644 --- a/cmd/lyra/encrypt.go +++ b/cmd/lyra/encrypt.go @@ -6,8 +6,8 @@ import ( "fmt" "os" + "github.com/azohra/lyra/pkg/lcrypt" "github.com/brsmsn/gware/pkg/diceware" - "github.com/fvumbaca/lyra/pkg/lcrypt" ) const ( diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index cf087c2..5f9fca7 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -1,31 +1,42 @@ package main import ( - "errors" "flag" "fmt" "io/ioutil" "os" "strings" - "github.com/fvumbaca/lyra/cmd/lyra/locker" + "github.com/azohra/lyra/cmd/lyra/locker" ) -const lockerConfig = "./lyralocker" -const lockerpassFilename = "./.lockerpass" -const checkmark = "✓" -const chironKey = string(0x26B7) +const ( + lockerUsage = `Locker is a tool for project secret management using Lyra's encryption.unlock + + $ lyra locker [Flags] + +Commands: + + lock Lock all assets listed in lyralocker file + unlock Unlock all assets listed in lyralocker file + shake Shakes the locker to test for unencrypted files + +Flags: + + -q Quiet mode. Only prints necessary info to the screen + -p Enter password +` + + lockerConfig = "./lyralocker" + lockerpassFilename = "./.lockerpass" + checkmark = "✓" + cross = "✕" +) type lockercmd struct { - doEncrypt bool fileRecursionDepth int passphrase string -} - -type lockerfile struct { - filename string - lockedFilename string - isLocked bool + quiet bool } func (cmd *lockercmd) CName() string { @@ -33,43 +44,68 @@ func (cmd *lockercmd) CName() string { } func (cmd *lockercmd) Help() string { - return "Help TBT" + return lockerUsage } func (cmd *lockercmd) RegCFlags(fs *flag.FlagSet) { + fs.BoolVar(&cmd.quiet, "q", false, "Run command in quiet mode") + fs.StringVar(&cmd.passphrase, "p", "", "Use provided passphrase") } func (cmd *lockercmd) Run(opt []string) error { if len(opt) < 1 { - return errors.New("Bad args...") + fmt.Println(lockerUsage) + os.Exit(0) } + // If no passphrase provided to command, pull + // from project password file + if cmd.passphrase == "" { + pass, err := readPassFile() + if err != nil { + return err + } + cmd.passphrase = pass + } + + files, err := locker.ParseLockerFile("./lyralocker") + if err != nil { + return err + } + + response := "" + code := 0 + switch opt[0] { case "lock": - cmd.doEncrypt = true + response, code = lock(files, cmd) break case "unlock": - cmd.doEncrypt = false + response, code = unlock(files, cmd) + break + case "shake": + response, code = shake(files, cmd) break default: - return errors.New("Bad args...") + fmt.Println(lockerUsage) + os.Exit(1) } - pass, err := readPassFile() - if err != nil { - return err + outputTo := os.Stdout + if code > 0 { + outputTo = os.Stderr } - cmd.passphrase = pass - - files, err := locker.ParseLockerFile("./lyralocker") - if err != nil { - return err + if !cmd.quiet { + fmt.Fprintf(outputTo, response) } + os.Exit(code) + return nil // will never be reached since we handle things locally +} +func lock(files []locker.Asset, cmd *lockercmd) (string, int) { successCount := 0 failCount := 0 - for _, f := range files { if f.Err != nil { if os.IsNotExist(f.Err) { @@ -77,42 +113,101 @@ func (cmd *lockercmd) Run(opt []string) error { } failCount++ } else { - if cmd.doEncrypt { - err := f.Lock([]byte(cmd.passphrase)) - if err != nil { - fmt.Fprint(os.Stderr, err.Error()) - failCount++ - } else { + err := f.Lock([]byte(cmd.passphrase)) + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + failCount++ + } else { + if !cmd.quiet { fmt.Printf("%s %s\n", checkmark, f.Filename) - successCount++ } + successCount++ + } + } + } + + reply := fmt.Sprintf("%d assets encrypted\n", successCount) + replyCode := 0 + + if failCount > 0 { + replyCode = 1 + reply = fmt.Sprintf("%d could not be encrypted\n", failCount) + } + + return reply, replyCode +} + +func unlock(files []locker.Asset, cmd *lockercmd) (string, int) { + successCount := 0 + failCount := 0 + for _, f := range files { + if f.Err != nil { + if os.IsNotExist(f.Err) { + fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", f.Filename)) + } + failCount++ + } else { + err := f.Unlock([]byte(cmd.passphrase)) + + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + failCount++ } else { - err := f.Unlock([]byte(cmd.passphrase)) - if err != nil { - fmt.Fprint(os.Stderr, err.Error()) - failCount++ - } else { + if !cmd.quiet { fmt.Printf("%s %s\n", checkmark, f.Filename) - successCount++ } + successCount++ } } + } + reply := fmt.Sprintf("%d assets decrypted\n", successCount) + replyCode := 0 + + if failCount > 0 { + replyCode = 1 + reply = fmt.Sprintf("%d could not be decrypted\n", failCount) } - action := "encripted" - if !cmd.doEncrypt { - action = "decrypted" + return reply, replyCode +} + +func shake(files []locker.Asset, cmd *lockercmd) (string, int) { + successCount := 0 + failCount := 0 + for _, f := range files { + if f.Err != nil { + if os.IsNotExist(f.Err) { + fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", f.Filename)) + } + failCount++ + } else { + validEncryption, err := f.ValidateLocked() + + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + failCount++ + } else if validEncryption { + if !cmd.quiet { + fmt.Printf("%s %s\n", checkmark, f.Filename) + } + successCount++ + } else { + fmt.Fprintf(os.Stderr, "%s %s\n", cross, f.Filename) + failCount++ + } + } } + reply := fmt.Sprintf("%d assets secure\n", successCount) + replyCode := 0 + if failCount > 0 { - fmt.Fprintf(os.Stderr, "%d assets were unable to be encrypted", failCount) - os.Exit(1) - } else { - fmt.Printf("%d assets %s\n", successCount, action) - return nil + replyCode = 1 + reply = fmt.Sprintf("%d assets are insecure\n", failCount) } - return nil + + return reply, replyCode } func readPassFile() (string, error) { diff --git a/cmd/lyra/locker/locker.go b/cmd/lyra/locker/locker.go index e927cc1..e3ace5a 100644 --- a/cmd/lyra/locker/locker.go +++ b/cmd/lyra/locker/locker.go @@ -4,14 +4,16 @@ import ( "bufio" "os" "path/filepath" + "regexp" "strings" - "github.com/fvumbaca/lyra/pkg/lcrypt" + "github.com/azohra/lyra/pkg/lcrypt" ) const ( - lockedFileExtension = ".locked" // What the extension is for locked files - commentSequence = "#" // What signals a comment in a locker file + lockedFileExtension = ".locked" // What the extension is for locked files + commentSequence = "#" // What signals a comment in a locker file + encryptionValidatorRegex = `^\@\![a-f0-9]{32}\@\![a-f0-9]{24}$` ) // Asset represents a Locker file asset. @@ -63,6 +65,9 @@ func ParseLockerFile(filename string) ([]Asset, error) { return jobs, nil } +// newLockerAsset creates a new asset record from a filename. +// This includes determining if the file is locked and/or +// even exists func newLockerAsset(filename string) (l Asset) { l.Filename = filename l.LockedFilename = filename @@ -113,6 +118,29 @@ func (a Asset) Unlock(passphrase []byte) error { return nil } +// ValidateLocked validates an asset is locked +// and the file is encrypted - not just +// the extension changed. +func (a Asset) ValidateLocked() (bool, error) { + validationRegex := regexp.MustCompile(encryptionValidatorRegex) + if !a.IsLocked { + return false, nil + } else { + file, err := os.Open(a.LockedFilename) + defer file.Close() + if err != nil { + return false, err + } + + scanner := bufio.NewScanner(file) + if scanner.Scan() { + firstLine := scanner.Text() + return validationRegex.MatchString(firstLine), nil + } + } + return false, nil +} + func createLockedFilename(filename string, lock bool) string { if lock && !isFilenameLocked(filename) { return filename + lockedFileExtension diff --git a/cmd/lyra/lyra.go b/cmd/lyra/lyra.go index 4cbe37c..4f0a2de 100644 --- a/cmd/lyra/lyra.go +++ b/cmd/lyra/lyra.go @@ -29,6 +29,7 @@ Commands: encrypt Encipher a specified file with inputed passphrase decrypt Decipher a specified file with inputed passphrase generate Generate passphrase(s) + locker A tool for locking project secrets To get more info on commands do: lyra [Command] --help ` From 5d408f523709b29109e7aeb4b7fb11f21050e287 Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Fri, 27 Jul 2018 10:33:15 -0400 Subject: [PATCH 07/13] Migrated to azohra org and changed imports to match. Also reworked project structure a little --- cmd/lyra/decrypt.go | 4 ++-- cmd/lyra/encrypt.go | 6 ++--- cmd/lyra/locker.go | 22 +++++++------------ .../pkg/encryption}/encryption.go | 2 +- .../pkg/encryption}/encryption_test.go | 2 +- {cmd/lyra => internal/pkg}/locker/locker.go | 14 ++++++------ .../pkg}/locker/locker_test.go | 0 7 files changed, 22 insertions(+), 28 deletions(-) rename {pkg/lcrypt => internal/pkg/encryption}/encryption.go (98%) rename {pkg/lcrypt => internal/pkg/encryption}/encryption_test.go (95%) rename {cmd/lyra => internal/pkg}/locker/locker.go (89%) rename {cmd/lyra => internal/pkg}/locker/locker_test.go (100%) diff --git a/cmd/lyra/decrypt.go b/cmd/lyra/decrypt.go index 51fe518..04ddf36 100644 --- a/cmd/lyra/decrypt.go +++ b/cmd/lyra/decrypt.go @@ -4,7 +4,7 @@ import ( "errors" "flag" - "github.com/fvumbaca/lyra/pkg/lcrypt" + "github.com/azohra/lyra/internal/pkg/encryption" ) const ( @@ -92,7 +92,7 @@ func (cmd *decryptcmd) Run(opt []string) error { cmd.passphrase = string(getPassphrase()) } - err = lcrypt.Decrypt(opt[0], cmd.path, cmd.printOnly, []byte(cmd.passphrase)) + err = encryption.Decrypt(opt[0], cmd.path, cmd.printOnly, []byte(cmd.passphrase)) cmd.passphrase = "" return err diff --git a/cmd/lyra/encrypt.go b/cmd/lyra/encrypt.go index b5d819c..af80f04 100644 --- a/cmd/lyra/encrypt.go +++ b/cmd/lyra/encrypt.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "github.com/azohra/lyra/pkg/lcrypt" + "github.com/azohra/lyra/internal/pkg/encryption" "github.com/brsmsn/gware/pkg/diceware" ) @@ -37,7 +37,7 @@ lyra encrypt --gen-str file Encrypts and overides file (user specified file) with an auto generated passphrase and outputs the auto generated passphrase to stdout. - Auto generaates a 7 word passphrase in kebab case (no spaces). + Auto generates a 7 word passphrase in kebab case (no spaces). lyra encrypt -p "mypassphrase" file @@ -137,7 +137,7 @@ func (cmd *encryptcmd) Run(opt []string) error { cmd.passphrase = string(input) } - err = lcrypt.Encrypt(opt[0], cmd.path, []byte(cmd.passphrase)) + err = encryption.Encrypt(opt[0], cmd.path, []byte(cmd.passphrase)) if err != nil { return err } diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index 5f9fca7..6764869 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -7,24 +7,18 @@ import ( "os" "strings" - "github.com/azohra/lyra/cmd/lyra/locker" + "github.com/azohra/lyra/internal/pkg/locker" ) const ( - lockerUsage = `Locker is a tool for project secret management using Lyra's encryption.unlock - - $ lyra locker [Flags] - -Commands: + lockerUsage = ` + +Sub commands: lock Lock all assets listed in lyralocker file unlock Unlock all assets listed in lyralocker file - shake Shakes the locker to test for unencrypted files - -Flags: + check Checks the locker to test for any unencrypted files - -q Quiet mode. Only prints necessary info to the screen - -p Enter password ` lockerConfig = "./lyralocker" @@ -84,8 +78,8 @@ func (cmd *lockercmd) Run(opt []string) error { case "unlock": response, code = unlock(files, cmd) break - case "shake": - response, code = shake(files, cmd) + case "check": + response, code = check(files, cmd) break default: fmt.Println(lockerUsage) @@ -172,7 +166,7 @@ func unlock(files []locker.Asset, cmd *lockercmd) (string, int) { return reply, replyCode } -func shake(files []locker.Asset, cmd *lockercmd) (string, int) { +func check(files []locker.Asset, cmd *lockercmd) (string, int) { successCount := 0 failCount := 0 for _, f := range files { diff --git a/pkg/lcrypt/encryption.go b/internal/pkg/encryption/encryption.go similarity index 98% rename from pkg/lcrypt/encryption.go rename to internal/pkg/encryption/encryption.go index cfcdd69..72329c9 100644 --- a/pkg/lcrypt/encryption.go +++ b/internal/pkg/encryption/encryption.go @@ -1,4 +1,4 @@ -package lcrypt +package encryption import ( "github.com/azohra/lyra/pkg/lcrypt" diff --git a/pkg/lcrypt/encryption_test.go b/internal/pkg/encryption/encryption_test.go similarity index 95% rename from pkg/lcrypt/encryption_test.go rename to internal/pkg/encryption/encryption_test.go index 41ce511..6d046e1 100644 --- a/pkg/lcrypt/encryption_test.go +++ b/internal/pkg/encryption/encryption_test.go @@ -1,4 +1,4 @@ -package lcrypt +package encryption import ( "testing" diff --git a/cmd/lyra/locker/locker.go b/internal/pkg/locker/locker.go similarity index 89% rename from cmd/lyra/locker/locker.go rename to internal/pkg/locker/locker.go index e3ace5a..ef0fcc7 100644 --- a/cmd/lyra/locker/locker.go +++ b/internal/pkg/locker/locker.go @@ -7,7 +7,7 @@ import ( "regexp" "strings" - "github.com/azohra/lyra/pkg/lcrypt" + "github.com/azohra/lyra/internal/pkg/encryption" ) const ( @@ -50,7 +50,7 @@ func ParseLockerFile(filename string) ([]Asset, error) { if info != nil && !info.IsDir() { // newLockerAsset will propagate errors to the top cmd level so we dont need to // handle them here - jobs = append(jobs, newLockerAsset(path)) + jobs = append(jobs, NewLockerAsset(path)) } return nil }) @@ -58,17 +58,17 @@ func ParseLockerFile(filename string) ([]Asset, error) { } else { // Creating a locker asset from a missing file is ok. Error will // propagate to the cmd level for reporting - jobs = append(jobs, newLockerAsset(entry)) + jobs = append(jobs, NewLockerAsset(entry)) } } return jobs, nil } -// newLockerAsset creates a new asset record from a filename. +// NewLockerAsset creates a new asset record from a filename. // This includes determining if the file is locked and/or // even exists -func newLockerAsset(filename string) (l Asset) { +func NewLockerAsset(filename string) (l Asset) { l.Filename = filename l.LockedFilename = filename l.IsLocked = true @@ -97,7 +97,7 @@ func newLockerAsset(filename string) (l Asset) { // Lock encrypts a locker Asset. func (a Asset) Lock(passphrase []byte) error { if !a.IsLocked { - err := lcrypt.Encrypt(a.Filename, a.LockedFilename, passphrase) + err := encryption.Encrypt(a.Filename, a.LockedFilename, passphrase) if err != nil { return err } @@ -109,7 +109,7 @@ func (a Asset) Lock(passphrase []byte) error { // Unlock decrypts a locker Asset. func (a Asset) Unlock(passphrase []byte) error { if a.IsLocked { - err := lcrypt.Decrypt(a.LockedFilename, a.Filename, false, passphrase) + err := encryption.Decrypt(a.LockedFilename, a.Filename, false, passphrase) if err != nil { return err } diff --git a/cmd/lyra/locker/locker_test.go b/internal/pkg/locker/locker_test.go similarity index 100% rename from cmd/lyra/locker/locker_test.go rename to internal/pkg/locker/locker_test.go From dc1538188e1dff0b78998e3578c2b815915e3f6a Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Mon, 30 Jul 2018 10:56:42 -0400 Subject: [PATCH 08/13] Added some tweeks to output and docs --- cmd/lyra/locker.go | 230 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 177 insertions(+), 53 deletions(-) diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index 6764869..e6808bb 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/azohra/lyra/internal/pkg/locker" + "github.com/brsmsn/gware/pkg/diceware" ) const ( @@ -21,10 +22,26 @@ Sub commands: ` - lockerConfig = "./lyralocker" - lockerpassFilename = "./.lockerpass" - checkmark = "✓" - cross = "✕" + // Used to make cool reports + checkmark = "✓" + cross = "✕" + + // LockerConfigFilename defines the filename of the projects lyra locker config file + LockerConfigFilename = "lyralocker" + + // LockerPassphraseFilename defines the filename of the file that stores the projects passphrase + LockerPassphraseFilename = ".lockerpass" + + // Might need to add flags to configure these in the future + lockerPassGenNumWords = 7 + lockerPassGenNumPhrases = 1 + lockerPassGenRemoveSpaces = false + + lockerFileTemplate = `# This file is for listing assets to be encrypted + +# Uncomment the following line to lock the file +# super-secret-key.txt +` ) type lockercmd struct { @@ -53,37 +70,45 @@ func (cmd *lockercmd) Run(opt []string) error { os.Exit(0) } - // If no passphrase provided to command, pull - // from project password file - if cmd.passphrase == "" { - pass, err := readPassFile() + response := "" + code := 0 + + // Init is a special command that does not error + // when the lyrafile does not exist + if opt[0] == "init" { + response, code = initializeLocker() + } else { + + // If no passphrase provided to command, pull + // from project password file + if cmd.passphrase == "" { + pass, err := readPassFile() + if err != nil { + return err + } + cmd.passphrase = pass + } + + files, err := locker.ParseLockerFile(LockerConfigFilename) if err != nil { return err } - cmd.passphrase = pass - } - files, err := locker.ParseLockerFile("./lyralocker") - if err != nil { - return err - } - - response := "" - code := 0 + switch opt[0] { + case "lock": + response, code = lock(files, cmd) + break + case "unlock": + response, code = unlock(files, cmd) + break + case "check": + response, code = check(files, cmd) + break + default: + fmt.Println(lockerUsage) + os.Exit(1) + } - switch opt[0] { - case "lock": - response, code = lock(files, cmd) - break - case "unlock": - response, code = unlock(files, cmd) - break - case "check": - response, code = check(files, cmd) - break - default: - fmt.Println(lockerUsage) - os.Exit(1) } outputTo := os.Stdout @@ -97,23 +122,103 @@ func (cmd *lockercmd) Run(opt []string) error { return nil // will never be reached since we handle things locally } -func lock(files []locker.Asset, cmd *lockercmd) (string, int) { +// Initializes the cwd with a password file and a config file +func initializeLocker() (string, int) { + createdPassFile := true + passFileExists, err := isFileExists(LockerPassphraseFilename) + if err != nil { + return err.Error(), BadExit + } + + if !passFileExists { + + passFile, err := os.Create(LockerPassphraseFilename) + defer passFile.Close() + if err != nil { + return err.Error(), BadExit + } else { + words, err := diceware.GeneratePassphrases(lockerPassGenNumPhrases, lockerPassGenNumWords, diceware.EffWorldList) + if err != nil { + return "Could not generate password", BadExit + } + + passphrase := strings.Join(words, " ") + if lockerPassGenRemoveSpaces { + passphrase = strings.Join(words, "") + } + + // Keep the formatting clean. Will be stripped out when read + passphrase += "\n" + + _, err = fmt.Fprint(passFile, passphrase) + if err != nil { + return "Could not write password to " + LockerPassphraseFilename, BadExit + } + + } + + } + + configFileCreated := false + + configFileExists, err := isFileExists(LockerConfigFilename) + if err != nil { + return err.Error(), BadExit + } + + if !configFileExists { + + configFile, err := os.Create(LockerConfigFilename) + defer configFile.Close() + if err != nil { + if !os.IsExist(err) { + configFileCreated = false + } + } else { + + _, err := fmt.Fprintln(configFile, lockerFileTemplate) + if err != nil { + configFileCreated = true + } + + } + } + + reply := "" + + if createdPassFile { + reply += fmt.Sprintf("Generated passphrase file at %s\n", LockerPassphraseFilename) + } else { + reply += fmt.Sprintf("Passphrase file already exists\n") + } + + if configFileCreated { + reply += fmt.Sprintf("Generated %s file\n", LockerConfigFilename) + } else { + reply += fmt.Sprintf("%s file already exists\n", LockerConfigFilename) + } + + return reply, GoodExit +} + +// Locks all provided assets +func lock(assets []locker.Asset, cmd *lockercmd) (string, int) { successCount := 0 failCount := 0 - for _, f := range files { - if f.Err != nil { - if os.IsNotExist(f.Err) { - fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", f.Filename)) + for _, asset := range assets { + if asset.Err != nil { + if os.IsNotExist(asset.Err) { + fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", asset.Filename)) } failCount++ } else { - err := f.Lock([]byte(cmd.passphrase)) + err := asset.Lock([]byte(cmd.passphrase)) if err != nil { fmt.Fprint(os.Stderr, err.Error()) failCount++ } else { if !cmd.quiet { - fmt.Printf("%s %s\n", checkmark, f.Filename) + fmt.Printf("Locked %s\n", asset.Filename) } successCount++ } @@ -121,34 +226,35 @@ func lock(files []locker.Asset, cmd *lockercmd) (string, int) { } reply := fmt.Sprintf("%d assets encrypted\n", successCount) - replyCode := 0 + replyCode := GoodExit if failCount > 0 { - replyCode = 1 + replyCode = BadExit reply = fmt.Sprintf("%d could not be encrypted\n", failCount) } return reply, replyCode } -func unlock(files []locker.Asset, cmd *lockercmd) (string, int) { +// Unlocks locked assets provided +func unlock(assets []locker.Asset, cmd *lockercmd) (string, int) { successCount := 0 failCount := 0 - for _, f := range files { - if f.Err != nil { - if os.IsNotExist(f.Err) { - fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", f.Filename)) + for _, asset := range assets { + if asset.Err != nil { + if os.IsNotExist(asset.Err) { + fmt.Fprint(os.Stderr, fmt.Sprintf("File %s does not exist\n", asset.Filename)) } failCount++ } else { - err := f.Unlock([]byte(cmd.passphrase)) + err := asset.Unlock([]byte(cmd.passphrase)) if err != nil { fmt.Fprint(os.Stderr, err.Error()) failCount++ } else { if !cmd.quiet { - fmt.Printf("%s %s\n", checkmark, f.Filename) + fmt.Printf("Unlocked %s\n", asset.Filename) } successCount++ } @@ -156,16 +262,18 @@ func unlock(files []locker.Asset, cmd *lockercmd) (string, int) { } reply := fmt.Sprintf("%d assets decrypted\n", successCount) - replyCode := 0 + replyCode := GoodExit if failCount > 0 { - replyCode = 1 + replyCode = BadExit reply = fmt.Sprintf("%d could not be decrypted\n", failCount) } return reply, replyCode } +// Checks the current project for any assets that are in an +// unencrypted state func check(files []locker.Asset, cmd *lockercmd) (string, int) { successCount := 0 failCount := 0 @@ -193,21 +301,37 @@ func check(files []locker.Asset, cmd *lockercmd) (string, int) { } } - reply := fmt.Sprintf("%d assets secure\n", successCount) - replyCode := 0 + reply := fmt.Sprintf("%d/%d assets secured.\n", successCount, len(files)) + replyCode := GoodExit if failCount > 0 { - replyCode = 1 - reply = fmt.Sprintf("%d assets are insecure\n", failCount) + replyCode = BadExit + reply = fmt.Sprintf("%d/%d assets secured. %d files could not be encrypted.\n", successCount, len(files), failCount) } return reply, replyCode } +// Reads a password file func readPassFile() (string, error) { - contents, err := ioutil.ReadFile(lockerpassFilename) + contents, err := ioutil.ReadFile(LockerPassphraseFilename) if err != nil { return "", err } return strings.TrimSpace(string(contents)), nil } + +// Checks if a file exists and not a directory +func isFileExists(filename string) (bool, error) { + fileInfo, err := os.Stat(filename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + if os.IsExist(err) { + return true, nil + } + return false, err + } + return !fileInfo.IsDir(), nil +} From 7fa5b4050db47d251cd3209caf18eff4df6ce192 Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Mon, 30 Jul 2018 11:54:39 -0400 Subject: [PATCH 09/13] Fixed bugs in result output --- cmd/lyra/locker.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/lyra/locker.go b/cmd/lyra/locker.go index e6808bb..3da66e8 100644 --- a/cmd/lyra/locker.go +++ b/cmd/lyra/locker.go @@ -124,7 +124,7 @@ func (cmd *lockercmd) Run(opt []string) error { // Initializes the cwd with a password file and a config file func initializeLocker() (string, int) { - createdPassFile := true + createdPassFile := false passFileExists, err := isFileExists(LockerPassphraseFilename) if err != nil { return err.Error(), BadExit @@ -154,7 +154,7 @@ func initializeLocker() (string, int) { if err != nil { return "Could not write password to " + LockerPassphraseFilename, BadExit } - + createdPassFile = true } } @@ -178,6 +178,8 @@ func initializeLocker() (string, int) { _, err := fmt.Fprintln(configFile, lockerFileTemplate) if err != nil { + configFileCreated = false + } else { configFileCreated = true } @@ -214,7 +216,7 @@ func lock(assets []locker.Asset, cmd *lockercmd) (string, int) { } else { err := asset.Lock([]byte(cmd.passphrase)) if err != nil { - fmt.Fprint(os.Stderr, err.Error()) + fmt.Fprint(os.Stderr, err.Error()+"\n") failCount++ } else { if !cmd.quiet { @@ -306,7 +308,7 @@ func check(files []locker.Asset, cmd *lockercmd) (string, int) { if failCount > 0 { replyCode = BadExit - reply = fmt.Sprintf("%d/%d assets secured. %d files could not be encrypted.\n", successCount, len(files), failCount) + reply = fmt.Sprintf("%d/%d assets secured. %d files have not be encrypted.\n", successCount, len(files), failCount) } return reply, replyCode @@ -328,9 +330,6 @@ func isFileExists(filename string) (bool, error) { if os.IsNotExist(err) { return false, nil } - if os.IsExist(err) { - return true, nil - } return false, err } return !fileInfo.IsDir(), nil From ee6737f59d62e41dd1413a43803ea297e42f01e9 Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Mon, 30 Jul 2018 13:49:17 -0400 Subject: [PATCH 10/13] Added tests for verifying locked states --- internal/pkg/locker/locker.go | 8 +++++++ internal/pkg/locker/locker_test.go | 37 +++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/internal/pkg/locker/locker.go b/internal/pkg/locker/locker.go index ef0fcc7..93c0b3c 100644 --- a/internal/pkg/locker/locker.go +++ b/internal/pkg/locker/locker.go @@ -126,6 +126,14 @@ func (a Asset) ValidateLocked() (bool, error) { if !a.IsLocked { return false, nil } else { + + // Make sure the file does not exist in plain text first + _, err := os.Stat(a.Filename) + if err == nil || os.IsExist(err) { + return false, nil + } + + // Check the headder of the locked file file, err := os.Open(a.LockedFilename) defer file.Close() if err != nil { diff --git a/internal/pkg/locker/locker_test.go b/internal/pkg/locker/locker_test.go index 3a4d8d5..7eb7bfa 100644 --- a/internal/pkg/locker/locker_test.go +++ b/internal/pkg/locker/locker_test.go @@ -1,6 +1,41 @@ package locker -import "testing" +import ( + "testing" +) + +func TestValidateLocked(t *testing.T) { + asset1 := NewLockerAsset("../../../test/locker/test-file1.txt") // just the txt + asset2 := NewLockerAsset("../../../test/locker/test-file2.txt") // normal txt and locked files + asset3 := NewLockerAsset("../../../test/locker/test-file3.txt") // plain txt marked as locked but not encrypted + asset4 := NewLockerAsset("../../../test/locker/test-file4.txt") // properly locked + asset5 := NewLockerAsset("../../../test/locker/test-file-404.txt") // does not exist + + isLocked, err := asset1.ValidateLocked() + if err != nil || isLocked { + t.Errorf("%s is plan text and should not be validated as locked. Got: (%v, %v)", asset1.Filename, isLocked, err) + } + + isLocked, err = asset2.ValidateLocked() + if err != nil || isLocked { + t.Errorf("%s is in both plain text and encrypted. It should not be validated as locked. Got: (%v, %v)", asset2.Filename, isLocked, err) + } + + isLocked, err = asset3.ValidateLocked() + if err != nil || isLocked { + t.Errorf("%s has the extension .locked but is still in plan text. It should not be validated as locked. Got: (%v, %v)", asset3.Filename, isLocked, err) + } + + isLocked, err = asset4.ValidateLocked() + if err != nil || !isLocked { + t.Errorf("%s should be validated as locked. Got: (%v, %v)", asset4.Filename, isLocked, err) + } + + isLocked, err = asset5.ValidateLocked() + if err == nil { + t.Errorf("%s does not exist and should error out. Got: (%v, %v)", asset5.Filename, isLocked, err) + } +} func TestIsFilenameLocked(t *testing.T) { if isFilenameLocked("somefile.txt") { From 44fcd4b0df3f08320d85d71efb769750a3704776 Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Mon, 30 Jul 2018 16:06:14 -0400 Subject: [PATCH 11/13] Added lock and unlock tests to the locker subcommand --- internal/pkg/locker/locker.go | 18 ++++++++---- internal/pkg/locker/locker_test.go | 45 +++++++++++++++++++++++++++++ test/locker/.lockerpass | 1 + test/locker/lyralocker | 8 +++++ test/locker/test-file1.txt | 1 + test/locker/test-file2.txt | 1 + test/locker/test-file2.txt.locked | 2 ++ test/locker/test-file3.txt.locked | 1 + test/locker/test-file4.txt.locked | Bin 0 -> 108 bytes 9 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 test/locker/.lockerpass create mode 100644 test/locker/lyralocker create mode 100644 test/locker/test-file1.txt create mode 100644 test/locker/test-file2.txt create mode 100644 test/locker/test-file2.txt.locked create mode 100644 test/locker/test-file3.txt.locked create mode 100644 test/locker/test-file4.txt.locked diff --git a/internal/pkg/locker/locker.go b/internal/pkg/locker/locker.go index 93c0b3c..1ee17db 100644 --- a/internal/pkg/locker/locker.go +++ b/internal/pkg/locker/locker.go @@ -95,25 +95,33 @@ func NewLockerAsset(filename string) (l Asset) { } // Lock encrypts a locker Asset. -func (a Asset) Lock(passphrase []byte) error { +func (a *Asset) Lock(passphrase []byte) error { if !a.IsLocked { err := encryption.Encrypt(a.Filename, a.LockedFilename, passphrase) if err != nil { return err } - return os.Remove(a.Filename) + err = os.Remove(a.Filename) + if err == nil { + a.IsLocked = true + } + return err } return nil } // Unlock decrypts a locker Asset. -func (a Asset) Unlock(passphrase []byte) error { +func (a *Asset) Unlock(passphrase []byte) error { if a.IsLocked { err := encryption.Decrypt(a.LockedFilename, a.Filename, false, passphrase) if err != nil { return err } - return os.Remove(a.LockedFilename) + err = os.Remove(a.LockedFilename) + if err == nil { + a.IsLocked = false + } + return err } return nil } @@ -121,7 +129,7 @@ func (a Asset) Unlock(passphrase []byte) error { // ValidateLocked validates an asset is locked // and the file is encrypted - not just // the extension changed. -func (a Asset) ValidateLocked() (bool, error) { +func (a *Asset) ValidateLocked() (bool, error) { validationRegex := regexp.MustCompile(encryptionValidatorRegex) if !a.IsLocked { return false, nil diff --git a/internal/pkg/locker/locker_test.go b/internal/pkg/locker/locker_test.go index 7eb7bfa..b8a45d4 100644 --- a/internal/pkg/locker/locker_test.go +++ b/internal/pkg/locker/locker_test.go @@ -1,9 +1,54 @@ package locker import ( + "io/ioutil" + "os" "testing" ) +func TestValidateLockAndUnlock(t *testing.T) { + testFile := "../../../test/locker/lockme.txt" + fileText := "This data is to be locked and unlocked by tests\n" + passphrase := "forthegoodoftesting" + + // clean anything that may be remaining from previous tests + os.Remove(testFile) + os.Remove(testFile + ".locked") + + // overwrite the file if already exists, and schedule it to be cleaned up + ioutil.WriteFile(testFile, []byte(fileText), 0666) + defer os.Remove(testFile) + defer os.Remove(testFile + ".locked") // just incase a premature exit + + asset := NewLockerAsset(testFile) + asset.Lock([]byte(passphrase)) + + isLocked, err := asset.ValidateLocked() + if err != nil || !isLocked { + t.Errorf("Failed to lock file. Got: (%v, %v)", isLocked, err) + } + + contents, err := ioutil.ReadFile(testFile + ".locked") + if err != nil { + t.Error(err) + } + if string(contents) == fileText { + t.Errorf("File was not encrypted.") + return + } + + asset.Unlock([]byte(passphrase)) + + contents, err = ioutil.ReadFile(testFile) + if err != nil { + t.Error(err) + } + if string(contents) != fileText { + t.Errorf("File was not decrypted properly.") + } + +} + func TestValidateLocked(t *testing.T) { asset1 := NewLockerAsset("../../../test/locker/test-file1.txt") // just the txt asset2 := NewLockerAsset("../../../test/locker/test-file2.txt") // normal txt and locked files diff --git a/test/locker/.lockerpass b/test/locker/.lockerpass new file mode 100644 index 0000000..f29df65 --- /dev/null +++ b/test/locker/.lockerpass @@ -0,0 +1 @@ +polyester speech luxurious rocklike untidy reproduce daytime diff --git a/test/locker/lyralocker b/test/locker/lyralocker new file mode 100644 index 0000000..9be01de --- /dev/null +++ b/test/locker/lyralocker @@ -0,0 +1,8 @@ +# This file is for listing assets to be encrypted + +# Uncomment the following line to lock the file +# super-secret-key.txt + +test-item1 +test-item2 +# test-item3 diff --git a/test/locker/test-file1.txt b/test/locker/test-file1.txt new file mode 100644 index 0000000..ba7c6c7 --- /dev/null +++ b/test/locker/test-file1.txt @@ -0,0 +1 @@ +I the first test file. diff --git a/test/locker/test-file2.txt b/test/locker/test-file2.txt new file mode 100644 index 0000000..ba7c6c7 --- /dev/null +++ b/test/locker/test-file2.txt @@ -0,0 +1 @@ +I the first test file. diff --git a/test/locker/test-file2.txt.locked b/test/locker/test-file2.txt.locked new file mode 100644 index 0000000..116b45c --- /dev/null +++ b/test/locker/test-file2.txt.locked @@ -0,0 +1,2 @@ +@!350af03a671a0145058b362abf0701d5@!2dd1f2e3cab997ab940c15ae +3 dDhصbF6xk0sUqަv \ No newline at end of file diff --git a/test/locker/test-file3.txt.locked b/test/locker/test-file3.txt.locked new file mode 100644 index 0000000..2be6b4e --- /dev/null +++ b/test/locker/test-file3.txt.locked @@ -0,0 +1 @@ +I am an invalid locked file diff --git a/test/locker/test-file4.txt.locked b/test/locker/test-file4.txt.locked new file mode 100644 index 0000000000000000000000000000000000000000..0adccd1797298bdccc35f66b2154d5b7c29cdaa7 GIT binary patch literal 108 zcmV-y0F(bfAu}~)WiVnjW;bOtHaRshVKg^3F*apmWMO47IAb+8I6xsaHZf*3VK_8o zG%z?aH#aadVmUZ7W?^Oum3#-;>5wWG#8KZV1jUvw-`&$snE=|m-Tt&_2Fy7Z`*B$C Op;G(em1S@iB(XgyS14ux literal 0 HcmV?d00001 From 67500b0d3f2f9e80a5b49cb17689aedb128b7f8d Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Mon, 30 Jul 2018 16:10:44 -0400 Subject: [PATCH 12/13] added tests for the lyralocker file parsing --- internal/pkg/locker/locker_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/pkg/locker/locker_test.go b/internal/pkg/locker/locker_test.go index b8a45d4..b54f2c2 100644 --- a/internal/pkg/locker/locker_test.go +++ b/internal/pkg/locker/locker_test.go @@ -6,6 +6,18 @@ import ( "testing" ) +func TestParseLockerFile(t *testing.T) { + assets, err := ParseLockerFile("../../../lyralocker") + if err != nil { + t.Error(err) + return + } + if len(assets) != 1 { + t.Errorf("Expecting 1 entry in test lyralocker file") + return + } +} + func TestValidateLockAndUnlock(t *testing.T) { testFile := "../../../test/locker/lockme.txt" fileText := "This data is to be locked and unlocked by tests\n" From 94ca6d50f5cd737e909f568f286e2b843fd3567a Mon Sep 17 00:00:00 2001 From: Frank Vumbaca Date: Tue, 31 Jul 2018 09:16:09 -0400 Subject: [PATCH 13/13] Fixed path to test lyralocker file --- internal/pkg/locker/locker_test.go | 4 ++-- test/locker/lyralocker | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/pkg/locker/locker_test.go b/internal/pkg/locker/locker_test.go index b54f2c2..46bd267 100644 --- a/internal/pkg/locker/locker_test.go +++ b/internal/pkg/locker/locker_test.go @@ -7,12 +7,12 @@ import ( ) func TestParseLockerFile(t *testing.T) { - assets, err := ParseLockerFile("../../../lyralocker") + assets, err := ParseLockerFile("../../../test/locker/lyralocker") if err != nil { t.Error(err) return } - if len(assets) != 1 { + if len(assets) != 2 { t.Errorf("Expecting 1 entry in test lyralocker file") return } diff --git a/test/locker/lyralocker b/test/locker/lyralocker index 9be01de..637c085 100644 --- a/test/locker/lyralocker +++ b/test/locker/lyralocker @@ -6,3 +6,4 @@ test-item1 test-item2 # test-item3 +# test-item4