diff --git a/cmd/strongbox/strongbox.go b/cmd/strongbox/strongbox.go new file mode 100644 index 0000000..aa84fa0 --- /dev/null +++ b/cmd/strongbox/strongbox.go @@ -0,0 +1,140 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/uw-labs/strongbox/internal/strongbox" +) + +var ( + version = "dev" + commit = "none" + date = "unknown" + builtBy = "unknown" + + keyLoader = strongbox.Key + kr strongbox.KeyRing + prefix = []byte("# STRONGBOX ENCRYPTED RESOURCE ;") + defaultPrefix = []byte("# STRONGBOX ENCRYPTED RESOURCE ; See https://github.com/uw-labs/strongbox\n") + + errKeyNotFound = errors.New("key not found") + + // flags + flagGitConfig = flag.Bool("git-config", false, "Configure git for strongbox use") + flagGenKey = flag.String("gen-key", "", "Generate a new key and add it to your strongbox keyring") + flagDecrypt = flag.Bool("decrypt", false, "Decrypt single resource") + flagKey = flag.String("key", "", "Private key to use to decrypt") + flagKeyRing = flag.String("keyring", "", "strongbox keyring file path, if not set default '$HOME/.strongbox_keyring' will be used") + flagRecursive = flag.Bool("recursive", false, "Recursively decrypt all files under given folder, must be used with -decrypt flag") + + flagClean = flag.String("clean", "", "intended to be called internally by git") + flagSmudge = flag.String("smudge", "", "intended to be called internally by git") + flagDiff = flag.String("diff", "", "intended to be called internally by git") + + flagVersion = flag.Bool("version", false, "Strongbox version") +) + +func usage() { + fmt.Fprintf(os.Stderr, "Usage:\n\n") + fmt.Fprintf(os.Stderr, "\tstrongbox -git-config\n") + fmt.Fprintf(os.Stderr, "\tstrongbox [-keyring ] -gen-key key-name\n") + fmt.Fprintf(os.Stderr, "\tstrongbox [-keyring ] -decrypt -recursive \n") + fmt.Fprintf(os.Stderr, "\tstrongbox -decrypt -recursive -key \n") + fmt.Fprintf(os.Stderr, "\tstrongbox -decrypt -key \n") + fmt.Fprintf(os.Stderr, "\tstrongbox -version\n") + fmt.Fprintf(os.Stderr, "\nif -keyring flag is not set default file '$HOME/.strongbox_keyring' or '$STRONGBOX_HOME/.strongbox_keyring' will be used as keyring\n") + os.Exit(2) +} + +func main() { + log.SetPrefix("strongbox: ") + log.SetFlags(log.LstdFlags | log.Lshortfile) + + flag.Usage = usage + flag.Parse() + + if *flagVersion || (flag.NArg() == 1 && flag.Arg(0) == "version") { + fmt.Printf("version=%s commit=%s date=%s builtBy=%s\n", version, commit, date, builtBy) + return + } + + if *flagGitConfig { + strongbox.GitConfig() + return + } + + if *flagDiff != "" { + strongbox.Diff(*flagDiff) + return + } + + // Set up keyring file name + home := strongbox.DeriveHome() + kr = &strongbox.FileKeyRing{FileName: filepath.Join(home, ".strongbox_keyring")} + + // if keyring flag is set replace default keyRing + if *flagKeyRing != "" { + kr = &strongbox.FileKeyRing{FileName: *flagKeyRing} + // verify keyring is valid + if err := kr.Load(); err != nil { + log.Fatalf("unable to load keyring file:%s err:%s", *flagKeyRing, err) + } + } + + if *flagGenKey != "" { + strongbox.GenKey(*flagGenKey) + return + } + + if *flagDecrypt { + // handle recursive + if *flagRecursive { + var err error + + target := flag.Arg(0) + if target == "" { + target, err = os.Getwd() + if err != nil { + log.Fatalf("target path not provided and unable to get cwd err:%s", err) + } + } + // for recursive decryption 'key' flag is optional but if provided + // it should be valid and all encrypted file will be decrypted using it + dk, err := strongbox.Decode([]byte(*flagKey)) + if err != nil && *flagKey != "" { + log.Fatalf("Unable to decode given private key %v", err) + } + + if err = strongbox.RecursiveDecrypt(target, dk); err != nil { + log.Fatalln(err) + } + return + } + + if *flagKey == "" { + log.Fatalf("Must provide a `-key` when using -decrypt") + } + strongbox.DecryptCLI(*flagKey) + return + } + + if *flagRecursive { + log.Println("-recursive flag is only supported with -decrypt") + usage() + } + + if *flagClean != "" { + strongbox.Clean(os.Stdin, os.Stdout, *flagClean) + return + } + if *flagSmudge != "" { + strongbox.Smudge(os.Stdin, os.Stdout, *flagSmudge) + return + } +} + diff --git a/example/go.mod b/example/go.mod new file mode 100644 index 0000000..b6ae97d --- /dev/null +++ b/example/go.mod @@ -0,0 +1,10 @@ +module main.go + +go 1.21 + +require github.com/uw-labs/strongbox/internal/strongbox v1.1.0 + +require ( + github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/example/go.sum b/example/go.sum new file mode 100644 index 0000000..64d4324 --- /dev/null +++ b/example/go.sum @@ -0,0 +1,26 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 h1:YuDUUFNM21CAbyPOpOP8BicaTD/0klJEKt5p8yuw+uY= +github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU= +github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= +github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= +github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= +github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff/go.mod h1:gJWba/XXGl0UoOmBQKRWCJdHrr3nE0T65t6ioaj3mLI= +github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 h1:BMb8s3ENQLt5ulwVIHVDWFHp8eIXmbfSExkvdn9qMXI= +github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11/go.mod h1:+DBdDyfoO2McrOyDemRBq0q9CMEByef7sYl7JH5Q3BI= +github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb h1:uSWBjJdMf47kQlXMwWEfmc864bA1wAC+Kl3ApryuG9Y= +github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb/go.mod h1:ivcmUvxXWjb27NsPEaiYK7AidlZXS7oQ5PowUS9z3I4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/uw-labs/strongbox v1.1.0 h1:gIFhB+YFkY4wbD6ZU4/nZI26d1O6/TnSPg2ADJTV8Z4= +github.com/uw-labs/strongbox v1.1.0/go.mod h1:MeDTE5Nj3SAPmhZXuqju0KcZWJW3D1HPmU14buyWgqU= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..bb8a6b7 --- /dev/null +++ b/example/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "os" + + "github.com/uw-labs/strongbox/internal/strongbox" +) + +var ( + path = "" // Path to encrypted file or directory containing encrypted files + keyPath = "" // Path to strongbox key +) + +func main() { + key, err := os.ReadFile(keyPath) + if err != nil { + log.Printf("Error reading file: %v\n", err) + return + } + + keyBytes := []byte(key) // Convert key string into byte slice + + // Decode the key + dk, err := strongbox.Decode([]byte(keyBytes)) + if err != nil { + log.Fatalf("Unable to decode given private key %v", err) + } + + // Decrypt file(s) at the path provided + if err := strongbox.RecursiveDecrypt(path, dk); err != nil { + log.Fatalln(err) + } +} diff --git a/go.mod b/go.mod index 88215af..9530ee9 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,19 @@ go 1.21 require ( github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 - github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd // indirect - github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff // indirect - github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 // indirect - github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb // indirect github.com/stretchr/testify v1.7.0 - golang.org/x/net v0.7.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd // indirect + github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff // indirect + github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 // indirect + github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect + golang.org/x/net v0.15.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7ba9094..63171fa 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 h1:YuDUUFNM21CAbyPOpOP8BicaTD/0klJEKt5p8yuw+uY= github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= @@ -10,17 +12,25 @@ github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 h1:BMb8s3ENQLt5ul github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11/go.mod h1:+DBdDyfoO2McrOyDemRBq0q9CMEByef7sYl7JH5Q3BI= github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb h1:uSWBjJdMf47kQlXMwWEfmc864bA1wAC+Kl3ApryuG9Y= github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb/go.mod h1:ivcmUvxXWjb27NsPEaiYK7AidlZXS7oQ5PowUS9z3I4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integration_tests/run.sh b/integration_tests/run.sh index bc8a2db..0caa3b1 100755 --- a/integration_tests/run.sh +++ b/integration_tests/run.sh @@ -2,8 +2,8 @@ PATH=$PATH:$GOPATH/bin -go get -t . -go install +go get -t ./cmd/strongbox/ +go install ./cmd/strongbox/ go get -t ./integration_tests/ go test -v -tags=integration ./integration_tests/ diff --git a/keyring.go b/internal/strongbox/keyring.go similarity index 64% rename from keyring.go rename to internal/strongbox/keyring.go index a1df914..2272d7e 100644 --- a/keyring.go +++ b/internal/strongbox/keyring.go @@ -1,4 +1,4 @@ -package main +package strongbox import ( "fmt" @@ -9,15 +9,15 @@ import ( "gopkg.in/yaml.v2" ) -type keyRing interface { +type KeyRing interface { Load() error Save() error AddKey(name string, keyID []byte, key []byte) Key(keyID []byte) ([]byte, error) } -type fileKeyRing struct { - fileName string +type FileKeyRing struct { + FileName string KeyEntries []keyEntry } @@ -27,20 +27,20 @@ type keyEntry struct { Key string `yaml:"key"` } -func (kr *fileKeyRing) AddKey(desc string, keyID []byte, key []byte) { +func (kr *FileKeyRing) AddKey(desc string, keyID []byte, key []byte) { kr.KeyEntries = append(kr.KeyEntries, keyEntry{ Description: desc, - KeyID: string(encode(keyID[:])), - Key: string(encode(key[:])), + KeyID: string(Encode(keyID[:])), + Key: string(Encode(key[:])), }) } -func (kr *fileKeyRing) Key(keyID []byte) ([]byte, error) { - b64 := string(encode(keyID[:])) +func (kr *FileKeyRing) Key(keyID []byte) ([]byte, error) { + b64 := string(Encode(keyID[:])) for _, ke := range kr.KeyEntries { if ke.KeyID == b64 { - dec, err := decode([]byte(ke.Key)) + dec, err := Decode([]byte(ke.Key)) if err != nil { return []byte{}, err } @@ -54,9 +54,9 @@ func (kr *fileKeyRing) Key(keyID []byte) ([]byte, error) { return []byte{}, errKeyNotFound } -func (kr *fileKeyRing) Load() error { +func (kr *FileKeyRing) Load() error { - bytes, err := os.ReadFile(kr.fileName) + bytes, err := os.ReadFile(kr.FileName) if err != nil { return err } @@ -65,13 +65,13 @@ func (kr *fileKeyRing) Load() error { return err } -func (kr *fileKeyRing) Save() error { +func (kr *FileKeyRing) Save() error { ser, err := yaml.Marshal(kr) if err != nil { log.Fatal(err) } - path := filepath.Dir(kr.fileName) + path := filepath.Dir(kr.FileName) _, err = os.Stat(path) if os.IsNotExist(err) { err := os.MkdirAll(path, 0700) @@ -80,5 +80,5 @@ func (kr *fileKeyRing) Save() error { } } - return os.WriteFile(kr.fileName, ser, 0600) + return os.WriteFile(kr.FileName, ser, 0600) } diff --git a/strongbox.go b/internal/strongbox/main.go similarity index 67% rename from strongbox.go rename to internal/strongbox/main.go index dfbdc1a..0e72c50 100644 --- a/strongbox.go +++ b/internal/strongbox/main.go @@ -1,4 +1,4 @@ -package main +package strongbox import ( "bytes" @@ -22,133 +22,15 @@ import ( ) var ( - version = "dev" - commit = "none" - date = "unknown" - builtBy = "unknown" - - keyLoader = key - kr keyRing + keyLoader = Key + kr KeyRing prefix = []byte("# STRONGBOX ENCRYPTED RESOURCE ;") defaultPrefix = []byte("# STRONGBOX ENCRYPTED RESOURCE ; See https://github.com/uw-labs/strongbox\n") errKeyNotFound = errors.New("key not found") - - // flags - flagGitConfig = flag.Bool("git-config", false, "Configure git for strongbox use") - flagGenKey = flag.String("gen-key", "", "Generate a new key and add it to your strongbox keyring") - flagDecrypt = flag.Bool("decrypt", false, "Decrypt single resource") - flagKey = flag.String("key", "", "Private key to use to decrypt") - flagKeyRing = flag.String("keyring", "", "strongbox keyring file path, if not set default '$HOME/.strongbox_keyring' will be used") - flagRecursive = flag.Bool("recursive", false, "Recursively decrypt all files under given folder, must be used with -decrypt flag") - - flagClean = flag.String("clean", "", "intended to be called internally by git") - flagSmudge = flag.String("smudge", "", "intended to be called internally by git") - flagDiff = flag.String("diff", "", "intended to be called internally by git") - - flagVersion = flag.Bool("version", false, "Strongbox version") ) -func usage() { - fmt.Fprintf(os.Stderr, "Usage:\n\n") - fmt.Fprintf(os.Stderr, "\tstrongbox -git-config\n") - fmt.Fprintf(os.Stderr, "\tstrongbox [-keyring ] -gen-key key-name\n") - fmt.Fprintf(os.Stderr, "\tstrongbox [-keyring ] -decrypt -recursive \n") - fmt.Fprintf(os.Stderr, "\tstrongbox -decrypt -recursive -key \n") - fmt.Fprintf(os.Stderr, "\tstrongbox -decrypt -key \n") - fmt.Fprintf(os.Stderr, "\tstrongbox -version\n") - fmt.Fprintf(os.Stderr, "\nif -keyring flag is not set default file '$HOME/.strongbox_keyring' or '$STRONGBOX_HOME/.strongbox_keyring' will be used as keyring\n") - os.Exit(2) -} - -func main() { - log.SetPrefix("strongbox: ") - log.SetFlags(log.LstdFlags | log.Lshortfile) - - flag.Usage = usage - flag.Parse() - - if *flagVersion || (flag.NArg() == 1 && flag.Arg(0) == "version") { - fmt.Printf("version=%s commit=%s date=%s builtBy=%s\n", version, commit, date, builtBy) - return - } - - if *flagGitConfig { - gitConfig() - return - } - - if *flagDiff != "" { - diff(*flagDiff) - return - } - - // Set up keyring file name - home := deriveHome() - kr = &fileKeyRing{fileName: filepath.Join(home, ".strongbox_keyring")} - - // if keyring flag is set replace default keyRing - if *flagKeyRing != "" { - kr = &fileKeyRing{fileName: *flagKeyRing} - // verify keyring is valid - if err := kr.Load(); err != nil { - log.Fatalf("unable to load keyring file:%s err:%s", *flagKeyRing, err) - } - } - - if *flagGenKey != "" { - genKey(*flagGenKey) - return - } - - if *flagDecrypt { - // handle recursive - if *flagRecursive { - var err error - - target := flag.Arg(0) - if target == "" { - target, err = os.Getwd() - if err != nil { - log.Fatalf("target path not provided and unable to get cwd err:%s", err) - } - } - // for recursive decryption 'key' flag is optional but if provided - // it should be valid and all encrypted file will be decrypted using it - dk, err := decode([]byte(*flagKey)) - if err != nil && *flagKey != "" { - log.Fatalf("Unable to decode given private key %v", err) - } - - if err = recursiveDecrypt(target, dk); err != nil { - log.Fatalln(err) - } - return - } - - if *flagKey == "" { - log.Fatalf("Must provide a `-key` when using -decrypt") - } - decryptCLI() - return - } - - if *flagRecursive { - log.Println("-recursive flag is only supported with -decrypt") - usage() - } - - if *flagClean != "" { - clean(os.Stdin, os.Stdout, *flagClean) - return - } - if *flagSmudge != "" { - smudge(os.Stdin, os.Stdout, *flagSmudge) - return - } -} - -func deriveHome() string { +func DeriveHome() string { // try explicitly set STRONGBOX_HOME if home := os.Getenv("STRONGBOX_HOME"); home != "" { return home @@ -168,7 +50,7 @@ func deriveHome() string { return "" } -func decryptCLI() { +func DecryptCLI(flagKey string) { var fn string if flag.Arg(0) == "" { // no file passed, try to read stdin @@ -180,7 +62,7 @@ func decryptCLI() { if err != nil { log.Fatalf("Unable to read file to decrypt %v", err) } - dk, err := decode([]byte(*flagKey)) + dk, err := Decode([]byte(flagKey)) if err != nil { log.Fatalf("Unable to decode private key %v", err) } @@ -191,7 +73,7 @@ func decryptCLI() { fmt.Printf("%s", out) } -func gitConfig() { +func GitConfig() { args := [][]string{ {"config", "--global", "--replace-all", "filter.strongbox.clean", "strongbox -clean %f"}, {"config", "--global", "--replace-all", "filter.strongbox.smudge", "strongbox -smudge %f"}, @@ -208,7 +90,7 @@ func gitConfig() { log.Println("git global configuration updated successfully") } -func genKey(desc string) { +func GenKey(desc string) { err := kr.Load() if err != nil && !os.IsNotExist(err) { log.Fatal(err) @@ -230,7 +112,7 @@ func genKey(desc string) { } } -func diff(filename string) { +func Diff(filename string) { f, err := os.Open(filename) if err != nil { log.Fatal(err) @@ -246,12 +128,13 @@ func diff(filename string) { } } -func clean(r io.Reader, w io.Writer, filename string) { +func Clean(r io.Reader, w io.Writer, filename string) { // Read the file, fail on error in, err := io.ReadAll(r) if err != nil { log.Fatal(err) } + // Check the file is plaintext, if its an encrypted strongbox file, copy as is, and exit 0 if bytes.HasPrefix(in, prefix) { _, err = io.Copy(w, bytes.NewReader(in)) @@ -278,7 +161,7 @@ func clean(r io.Reader, w io.Writer, filename string) { } // Called by git on `git checkout` -func smudge(r io.Reader, w io.Writer, filename string) { +func Smudge(r io.Reader, w io.Writer, filename string) { in, err := io.ReadAll(r) if err != nil { log.Fatal(err) @@ -323,7 +206,7 @@ func smudge(r io.Reader, w io.Writer, filename string) { // otherwise it will find key based on file location // if error is generated in finding key or in decryption then it will continue with next file // function will only return early if it failed to read/write files -func recursiveDecrypt(target string, givenKey []byte) error { +func RecursiveDecrypt(target string, givenKey []byte) error { var decErrors []string err := filepath.WalkDir(target, func(path string, entry fs.DirEntry, err error) error { // always return on error @@ -413,7 +296,7 @@ func encrypt(b, key []byte) ([]byte, error) { } var buf []byte buf = append(buf, defaultPrefix...) - b64 := encode(out) + b64 := Encode(out) for len(b64) > 0 { l := 76 if len(b64) < 76 { @@ -454,13 +337,13 @@ func decompress(b []byte) []byte { return b } -func encode(decoded []byte) []byte { +func Encode(decoded []byte) []byte { b64 := make([]byte, base64.StdEncoding.EncodedLen(len(decoded))) base64.StdEncoding.Encode(b64, decoded) return b64 } -func decode(encoded []byte) ([]byte, error) { +func Decode(encoded []byte) ([]byte, error) { decoded := make([]byte, len(encoded)) i, err := base64.StdEncoding.Decode(decoded, encoded) if err != nil { @@ -476,7 +359,7 @@ func decrypt(enc []byte, priv []byte) ([]byte, error) { return nil, errors.New("couldn't split on end of line") } b64encoded := spl[1] - b64decoded, err := decode(b64encoded) + b64decoded, err := Decode(b64encoded) if err != nil { return nil, err } @@ -489,7 +372,7 @@ func decrypt(enc []byte, priv []byte) ([]byte, error) { } // key returns private key and error -func key(filename string) ([]byte, error) { +func Key(filename string) ([]byte, error) { keyID, err := findKey(filename) if err != nil { return []byte{}, err @@ -532,7 +415,7 @@ func readKey(filename string) ([]byte, error) { } b64 := strings.TrimSpace(string(fp)) - b, err := decode([]byte(b64)) + b, err := Decode([]byte(b64)) if err != nil { return []byte{}, err } diff --git a/strongbox_test.go b/internal/strongbox/strongbox_test.go similarity index 76% rename from strongbox_test.go rename to internal/strongbox/strongbox_test.go index 12bd854..3945476 100644 --- a/strongbox_test.go +++ b/internal/strongbox/strongbox_test.go @@ -1,10 +1,9 @@ -package main +package strongbox import ( "bytes" "crypto/rand" "crypto/sha256" - "fmt" "os" "testing" @@ -12,6 +11,7 @@ import ( ) var ( + // keyLoader = TestKeyLoader priv, pub []byte plain = []byte("hello world. this is some plain text for testing") ) @@ -59,10 +59,10 @@ func TestMultipleClean(t *testing.T) { assert := assert.New(t) var cleaned bytes.Buffer - clean(bytes.NewReader(plain), &cleaned, "") + Clean(bytes.NewReader(plain), &cleaned, "") var doubleCleaned bytes.Buffer - clean(bytes.NewReader(cleaned.Bytes()), &doubleCleaned, "") + Clean(bytes.NewReader(cleaned.Bytes()), &doubleCleaned, "") assert.Equal(cleaned.String(), doubleCleaned.String()) } @@ -71,7 +71,7 @@ func TestSmudgeAlreadyPlaintext(t *testing.T) { assert := assert.New(t) var smudged bytes.Buffer - smudge(bytes.NewReader(plain), &smudged, "") + Smudge(bytes.NewReader(plain), &smudged, "") assert.Equal(string(plain), smudged.String()) } @@ -80,14 +80,12 @@ func TestRoundTrip(t *testing.T) { assert := assert.New(t) var cleaned bytes.Buffer - clean(bytes.NewReader(plain), &cleaned, "") - - fmt.Printf("%s", string(cleaned.String())) + Clean(bytes.NewReader(plain), &cleaned, "") assert.NotEqual(plain, cleaned.Bytes()) var smudged bytes.Buffer - smudge(bytes.NewReader(cleaned.Bytes()), &smudged, "") + Smudge(bytes.NewReader(cleaned.Bytes()), &smudged, "") assert.Equal(string(plain), smudged.String()) } @@ -96,10 +94,10 @@ func TestDeterministic(t *testing.T) { assert := assert.New(t) var cleaned1 bytes.Buffer - clean(bytes.NewReader(plain), &cleaned1, "") + Clean(bytes.NewReader(plain), &cleaned1, "") var cleaned2 bytes.Buffer - clean(bytes.NewReader(plain), &cleaned2, "") + Clean(bytes.NewReader(plain), &cleaned2, "") assert.Equal(cleaned1.String(), cleaned2.String()) } @@ -107,9 +105,9 @@ func TestDeterministic(t *testing.T) { func BenchmarkRoundTripPlain(b *testing.B) { for n := 0; n < b.N; n++ { var cleaned bytes.Buffer - clean(bytes.NewReader(plain), &cleaned, "") + Clean(bytes.NewReader(plain), &cleaned, "") var smudged bytes.Buffer - smudge(bytes.NewReader(cleaned.Bytes()), &smudged, "") + Smudge(bytes.NewReader(cleaned.Bytes()), &smudged, "") } }