diff --git a/cmd/commands.go b/cmd/commands.go index e0c62f2..960f07e 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -2,6 +2,8 @@ package cmd import ( + "io" + "github.com/urfave/cli/v2" ) @@ -25,6 +27,7 @@ var All = []*cli.Command{ Usage: "List profiles for subcommands", UsageText: "gosop list-profiles SUBCOMMAND", Flags: []cli.Flag{}, + Before: BeforeListProfiles, Action: func(c *cli.Context) error { return ListProfiles(c.Args().Slice()...) }, @@ -39,6 +42,7 @@ var All = []*cli.Command{ keyPasswordFlag, signingOnlyFlag, }, + Before: BeforeGenerateKey, Action: func(c *cli.Context) error { return GenerateKey(c.Args().Slice()...) }, @@ -63,6 +67,7 @@ var All = []*cli.Command{ asFlag, keyPasswordFlag, }, + Before: BeforeSign, Action: func(c *cli.Context) error { return Sign(c.Args().Slice()...) }, @@ -75,6 +80,7 @@ var All = []*cli.Command{ notBeforeFlag, notAfterFlag, }, + Before: BeforeVerify, Action: func(c *cli.Context) error { return Verify(c.Args().Slice()...) }, @@ -88,6 +94,7 @@ var All = []*cli.Command{ asSignedFlag, keyPasswordFlag, }, + Before: BeforeInlineSign, Action: func(c *cli.Context) error { return InlineSign(c.Args().Slice()...) }, @@ -101,6 +108,7 @@ var All = []*cli.Command{ notAfterFlag, verificationsOutFlag, }, + Before: BeforeInlineVerify, Action: func(c *cli.Context) error { return InlineVerify(c.Args().Slice()...) }, @@ -129,6 +137,7 @@ var All = []*cli.Command{ signWithFlag, keyPasswordFlag, }, + Before: BeforeEncrypt, Action: func(c *cli.Context) error { return Encrypt(c.Args().Slice()...) }, @@ -147,6 +156,7 @@ var All = []*cli.Command{ verifyNotAfterFlag, keyPasswordFlag, }, + Before: BeforeDecrypt, Action: func(c *cli.Context) error { return Decrypt(c.Args().Slice()...) }, @@ -167,4 +177,20 @@ var All = []*cli.Command{ return DearmorComm() }, }, + { + Name: "supports", + Usage: "Check whether gosop supports the given subcommand and options", + UsageText: "gosop supports COMMAND", + Action: func(c *cli.Context) error { + for _, c := range c.App.Commands { + c.Action = func(ctx *cli.Context) error { + return nil // Don't run the actual action. + } + } + c.App.Writer = io.Discard // Discard help text. + args := []string{"gosop"} + args = append(args, c.Args().Slice()...) + return c.App.Run(args) + }, + }, } diff --git a/cmd/decrypt.go b/cmd/decrypt.go index 83352bf..918e3bc 100644 --- a/cmd/decrypt.go +++ b/cmd/decrypt.go @@ -3,11 +3,14 @@ package cmd import ( "encoding/hex" "errors" + "fmt" "io" "os" "strconv" "strings" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/constants" @@ -24,17 +27,27 @@ var symKeyAlgos = map[packet.CipherFunction]string{ packet.CipherAES256: constants.AES256, } +func BeforeDecrypt(c *cli.Context) error { + if !c.Args().Present() && password == "" && sessionKey == "" { + fmt.Fprintln(os.Stderr, "Please provide decryption keys, passphrase, or session key.") + return Err19 + } + if c.Args().Present() && password != "" || + c.Args().Present() && sessionKey != "" || + password != "" && sessionKey != "" { + fmt.Fprintln(os.Stderr, "Can't decrypt with more than one of keys, passphrase and session key.") + return Err37 + } + return nil +} + // Decrypt takes the data from stdin and decrypts it with the key file passed as // argument, or a passphrase in a file passed with the --with-password flag. -// Note: Can't encrypt both symmetrically (passphrase) and keys. +// Note: Can't decrypt with more than one of keys, passphrase and session key. // TODO: Multiple signers? // // --session-key-out=file flag: Outputs session key byte stream to given file. func Decrypt(keyFilenames ...string) error { - if len(keyFilenames) == 0 && password == "" && sessionKey == "" { - println("Please provide decryption keys, session key, or passphrase") - return Err69 - } var err error pgp := crypto.PGP() diff --git a/cmd/encrypt.go b/cmd/encrypt.go index defbf3b..c11cd91 100644 --- a/cmd/encrypt.go +++ b/cmd/encrypt.go @@ -1,8 +1,9 @@ package cmd import ( - "io" "bufio" + "fmt" + "io" "os" "unicode" @@ -17,15 +18,27 @@ const ( textOpt = "text" ) +func BeforeEncrypt(c *cli.Context) error { + if !c.Args().Present() && password == "" { + fmt.Fprintln(os.Stderr, "Please provide encryption keys or passphrase (--with-password).") + return Err19 + } + if c.Args().Present() && password != "" { + fmt.Fprintln(os.Stderr, "Can't encrypt with both keys and passphrase.") + return Err37 + } + profile := utils.SelectEncryptionProfile(selectedProfile) + if profile == nil { + return Err89 + } + return nil +} + // Encrypt takes the data from stdin and encrypts it with the keys passed as // argument, or a passphrase passed with the --with-password flag. It signs // with the given private keys. // Note: Can't encrypt both symmetrically (passphrase) and keys. func Encrypt(keyFilenames ...string) error { - if len(keyFilenames) == 0 && password == "" { - println("Please provide recipients and/or passphrase (--with-password)") - return Err19 - } profile := utils.SelectEncryptionProfile(selectedProfile) if profile == nil { return Err89 diff --git a/cmd/generate_key.go b/cmd/generate_key.go index ac3890e..6c3533a 100644 --- a/cmd/generate_key.go +++ b/cmd/generate_key.go @@ -1,9 +1,12 @@ package cmd import ( + "fmt" "os" "strings" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/crypto" @@ -11,6 +14,28 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/v2" ) +func BeforeGenerateKey(c *cli.Context) error { + profile := utils.SelectKeyGenerationProfile(selectedProfile) + if profile == nil { + return Err89 + } + if !profile.PgpProfile.V6 && !c.Args().Present() { + fmt.Fprintln(os.Stderr, "Non-v6 key requires a User ID.") + return Err19 + } + for _, userID := range c.Args().Slice() { + _, comment, _, err := utils.ParseUserID(userID) + if err != nil { + return kgErr(err) + } + if comment != "" { + fmt.Fprintln(os.Stderr, "Comments in User IDs are not supported.") + return Err37 + } + } + return nil +} + // GenerateKey creates a single default OpenPGP certificate with zero or more // User IDs. Given that go-crypto expects name, comment, email parameters, we // force the USERID of this implementation to be of the form "name (comment) diff --git a/cmd/inline_sign.go b/cmd/inline_sign.go index 489ff1f..d03d128 100644 --- a/cmd/inline_sign.go +++ b/cmd/inline_sign.go @@ -1,10 +1,13 @@ package cmd import ( + "fmt" "io/ioutil" "os" "unicode/utf8" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/crypto" @@ -14,14 +17,20 @@ const ( clearsignedOpt = "clearsigned" ) -// InlineSign takes the data from stdin and signs it with the key passed as argument. -// TODO: Exactly one signature should be made by each supplied "KEY". -func InlineSign(keyFilenames ...string) error { - if len(keyFilenames) == 0 { - println("Please provide keys to create detached signature") +func BeforeInlineSign(c *cli.Context) error { + if !c.Args().Present() { + fmt.Fprintln(os.Stderr, "Please provide keys to create detached signature") return Err19 } + if noArmor && asType == clearsignedOpt { + return Err83 + } + return nil +} +// InlineSign takes the data from stdin and signs it with the key passed as argument. +// TODO: Exactly one signature should be made by each supplied "KEY". +func InlineSign(keyFilenames ...string) error { // Signer keyring var keyRing *crypto.KeyRing var err error @@ -56,10 +65,6 @@ func InlineSign(keyFilenames ...string) error { return Err53 } - if noArmor && asType == clearsignedOpt { - return Err83 - } - encoding := crypto.Armor if noArmor { encoding = crypto.Bytes diff --git a/cmd/inline_verify.go b/cmd/inline_verify.go index d613493..be01102 100644 --- a/cmd/inline_verify.go +++ b/cmd/inline_verify.go @@ -1,22 +1,28 @@ package cmd import ( + "fmt" "io/ioutil" "os" "strings" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/crypto" ) -// InlineVerify checks the validity of a signed message against a set of certificates. -func InlineVerify(input ...string) error { - if len(input) == 0 { - println("Please provide a certificate (public key)") +func BeforeInlineVerify(c *cli.Context) error { + if !c.Args().Present() { + fmt.Fprintln(os.Stderr, "Please provide a certificate (public key)") return Err19 } + return nil +} +// InlineVerify checks the validity of a signed message against a set of certificates. +func InlineVerify(input ...string) error { timeFrom, timeTo, err := utils.ParseDates(notBefore, notAfter) if err != nil { return inlineVerErr(err) diff --git a/cmd/list_profiles.go b/cmd/list_profiles.go index bba88a5..d1283be 100644 --- a/cmd/list_profiles.go +++ b/cmd/list_profiles.go @@ -4,16 +4,27 @@ import ( "fmt" "strings" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/gosop/utils" ) const encryptCommand = "encrypt" const keyGenCommand = "generate-key" -func ListProfiles(commands ...string) error { - if len(commands) < 1 { +func BeforeListProfiles(c *cli.Context) error { + if !c.Args().Present() { + return Err89 + } + switch c.Args().First() { + case keyGenCommand, encryptCommand: + return nil + default: return Err89 } +} + +func ListProfiles(commands ...string) error { command := commands[0] switch command { case keyGenCommand: diff --git a/cmd/sign.go b/cmd/sign.go index 17e35fb..c52cbce 100644 --- a/cmd/sign.go +++ b/cmd/sign.go @@ -1,21 +1,28 @@ package cmd import ( + "fmt" "io" "os" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/crypto" ) +func BeforeSign(c *cli.Context) error { + if !c.Args().Present() { + fmt.Fprintln(os.Stderr, "Please provide keys to create detached signature") + return Err19 + } + return nil +} + // Sign takes the data from stdin and signs it with the key passed as argument. // TODO: Exactly one signature will be made by each supplied "KEY". func Sign(keyFilenames ...string) error { - if len(keyFilenames) == 0 { - println("Please provide keys to create detached signature") - return Err19 - } pgp := crypto.PGP() // Signer keyring diff --git a/cmd/verify.go b/cmd/verify.go index 03c539f..54ccfc6 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -2,8 +2,11 @@ package cmd import ( "bytes" + "fmt" "os" + "github.com/urfave/cli/v2" + "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/ProtonMail/gosop/utils" @@ -11,15 +14,23 @@ import ( "github.com/ProtonMail/gopenpgp/v3/crypto" ) -// Verify checks the validity of a signature against a set of certificates. -func Verify(input ...string) error { - switch len(input) { +func BeforeVerify(c *cli.Context) error { + switch c.Args().Len() { case 0: return Err3 case 1: - println("Please provide a certificate (public key)") + fmt.Fprintln(os.Stderr, "Please provide a certificate (public key)") return Err19 } + _, _, err := utils.ParseDates(notBefore, notAfter) + if err != nil { + return verErr(err) + } + return nil +} + +// Verify checks the validity of a signature against a set of certificates. +func Verify(input ...string) error { timeFrom, timeTo, err := utils.ParseDates(notBefore, notAfter) if err != nil { return verErr(err)