pax_global_header00006660000000000000000000000064144314512640014516gustar00rootroot0000000000000052 comment=57d00b37f7729593d2d67979c0ae3b778059a372 gosop-0.1.0/000077500000000000000000000000001443145126400126435ustar00rootroot00000000000000gosop-0.1.0/.gitignore000066400000000000000000000000061443145126400146270ustar00rootroot00000000000000gosop gosop-0.1.0/.golangci.yml000066400000000000000000000011471443145126400152320ustar00rootroot00000000000000linters: enable-all: true disable: # Have good common sense before disabling more linters - gochecknoglobals - gosec - godox - nestif - wsl fast: false issues: exclude-use-default: false # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-issues-per-linter: 4 # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 4 # options for analysis running # all available settings of specific linters linters-settings: gofmt: # simplify code: gofmt with `-s` option, true by default simplify: false gosop-0.1.0/LICENSE000066400000000000000000000020671443145126400136550ustar00rootroot00000000000000MIT License Copyright (c) 2020 Proton Technologies AG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gosop-0.1.0/Makefile000066400000000000000000000005171443145126400143060ustar00rootroot00000000000000verbosity := 0 clean: ## Remove previous builds @rm -rf data bin/gosop install-linters: go install -u golang.org/x/lint/golint go install github.com/golangci/golangci-lint/cmd/golangci-lint lint: golint -set_exit_status ./... && golangci-lint run ./... test: ## Run example script bash scripts/tests.sh gosop -v $(verbosity) gosop-0.1.0/README.md000066400000000000000000000033561443145126400141310ustar00rootroot00000000000000# gosop Stateless Command-Line Interface implementation for [GopenPGP](https://gopenpgp.org). ### What is SOP? The [Stateless OpenPGP CLI](https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01), known as SOP, is an RFC specification that aims to provide a minimal API for any implementation of the OpenPGP protocol, in the form of a command-line Interface. SOP can be used to test interoperability between different implementations of OpenPGP; the [OpenPGP interoperability Test Suite](https://tests.sequoia-pgp.org/) reports results using several SOP implementations. For more information, please refer to the [specification](https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01). ### Install ``` mkdir -p $GOPATH/src/github.com/ProtonMail/ cd $GOPATH/src/github.com/ProtonMail/ git clone git@github.com:ProtonMail/gosop.git && cd gosop go install ``` ### Usage You can now invoke `gosop` from the command line: ``` echo "Hello PGP" | gosop encrypt --with-password=PASSWORD_FILE ``` or: ``` echo "Hello PGP" | PWD="password" gosop encrypt --with-password="@ENV:PWD" ``` See [commands directory](https://github.com/ProtonMail/gosop/tree/master/cmd) for all currently supported subcommands and flags, or run `gosop help`. ### Test your installation Given the CLI nature of `gosop`, tests are run with `bash` scripts outside the Go testing framework. ``` cd $GOPATH/src/github.com/ProtonMail/gosop make test ``` ### Contribute If you are providing new commands or flags according to the [specification](https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01), please add the appropriate tests and lint before submitting: ``` go install -u golang.org/x/lint/golint go install github.com/golangci/golangci-lint/cmd/golangci-lint make lint ``` gosop-0.1.0/cmd/000077500000000000000000000000001443145126400134065ustar00rootroot00000000000000gosop-0.1.0/cmd/README.md000066400000000000000000000032111443145126400146620ustar00rootroot00000000000000## Usage examples and supported subcommands ### Generate a key-pair The following command generates a **private key**: ``` gosop generate-key "Alice " > private-key.sec ``` Note that, as per the OpenPGP specification, this private key also contains public key material. You can extract the **public key** (also referred to as _certificate_) safely as follows: ``` gosop extract-cert < private-key.sec > public-key.pgp ``` This outputs PGP armored strings by default ``` $ head -n1 private-key.sec -----BEGIN PGP PRIVATE KEY BLOCK----- $ head -n1 public-key.pgp -----BEGIN PGP PUBLIC KEY BLOCK----- ``` but you may use binary format providing the `--no-armor` flag to the above commands. ### Encrypt/decrypt ##### Using a passphrase ``` gosop encrypt --with-password="strong_passphrase" < input_file > encrypted_file gosop decrypt --with-password="strong_passphrase" < encrypted_file > decrypted_file ``` ##### Using PGP keys ``` gosop encrypt public-key.pgp < file_to_encrypt > encrypted_file gosop decrypt private-key.sec < encrypted_file > decrypted_file ``` For advanced modes and available flags, run `gosop help encrypt`, `gosop help decrypt`. ### Sign/Verify ``` gosop sign private-key.sec < file_to_sign > signature.asc gosop verify signature public-key.asc < signature.asc ``` You need to inspect the exit status and output of `verify` to check if the signature is valid. ### Armor/Dearmor Any PGP armored string, you can convert it to/from binary. `gosop` will automatically detect the type from the underlying packets, and set the correct headers (`SIGNATURE`, `MESSAGE`, etc). ``` gosop dearmor < public-key.asc > public-key-binary ``` gosop-0.1.0/cmd/armor.go000066400000000000000000000034271443145126400150630ustar00rootroot00000000000000package cmd import ( "bytes" "io" "io/ioutil" "os" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/go-crypto/openpgp/packet" ) // ArmorComm takes unarmored OpenPGP material from Std input and outputs the // same material with ASCII-armoring added. func ArmorComm(keyFilenames ...string) error { input, err := ioutil.ReadAll(os.Stdin) if err != nil { return armErr(err) } // If already armored, output directly and return if string(input[:14]) == "-----BEGIN PGP" { if _, err := os.Stdout.Write(input); err != nil { return armErr(err) } return nil } armored, err := armorDecidingType(input) if err != nil { return armErr(err) } if armored == "" { message := crypto.NewPGPMessage(input) armored, err = message.GetArmored() if err != nil { return armErr(err) } } _, err = os.Stdout.WriteString(armored + "\n") return err } func armErr(err error) error { return Err99("armor", err) } func armorDecidingType(input []byte) (armored string, err error) { packets := packet.NewReader(bytes.NewReader(input)) var p packet.Packet if p, err = packets.Next(); err != io.EOF && err != nil { message := crypto.NewPGPMessage(input) armored, err = message.GetArmored() if err != nil { return armored, err } } if _, ok := p.(*packet.PublicKey); ok { key, err := crypto.NewKey(input) if err != nil { return armored, err } armored, err = key.Armor() if err != nil { return armored, err } } if _, ok := p.(*packet.PrivateKey); ok { key, err := crypto.NewKey(input) if err != nil { return armored, err } armored, err = key.Armor() if err != nil { return armored, err } } if _, ok := p.(*packet.Signature); ok { sig := crypto.NewPGPSignature(input) armored, err = sig.GetArmored() } return armored, err } gosop-0.1.0/cmd/commands.go000066400000000000000000000061411443145126400155400ustar00rootroot00000000000000// Package cmd defines all commands for the gosop implementation. package cmd import ( "github.com/urfave/cli/v2" ) // All commands defined by the CLI. var All = []*cli.Command{ { Name: "version", Usage: "Version Information", Flags: []cli.Flag{ backendFlag, extendedFlag, }, Action: func(c *cli.Context) error { return Version() }, }, { Name: "generate-key", Usage: "Generate a Secret Key", UsageText: "gosop generate-key [command options] [USERID...]", Flags: []cli.Flag{ noArmorFlag, }, Action: func(c *cli.Context) error { return GenerateKey(c.Args().Slice()...) }, }, { Name: "extract-cert", Usage: "Extract a Certificate from a Secret Key", UsageText: "gosop extract-cert [command options]", Flags: []cli.Flag{ noArmorFlag, }, Action: func(c *cli.Context) error { return ExtractCert() }, }, { Name: "sign", Usage: "Create a Detached Signature", UsageText: "gosop sign [command options] KEY [KEY...] < DATA", Flags: []cli.Flag{ noArmorFlag, asFlag, }, Action: func(c *cli.Context) error { return Sign(c.Args().Slice()...) }, }, { Name: "verify", Usage: "Verify a Detached Signature", UsageText: "gosop verify SIGNATURE CERTS [CERTS...] < DATA", Flags: []cli.Flag{ notBeforeFlag, notAfterFlag, }, Action: func(c *cli.Context) error { return Verify(c.Args().Slice()...) }, }, { Name: "inline-sign", Usage: "Create an Inline-Signed Message", UsageText: "gosop inline-sign [command options] KEY [KEY...] < DATA", Flags: []cli.Flag{ noArmorFlag, asSignedFlag, }, Action: func(c *cli.Context) error { return InlineSign(c.Args().Slice()...) }, }, { Name: "inline-verify", Usage: "Verify an Inline-Signed Message", UsageText: "gosop inline-verify CERTS [CERTS...] < INLINESIGNED", Flags: []cli.Flag{ notBeforeFlag, notAfterFlag, verificationsOutFlag, }, Action: func(c *cli.Context) error { return InlineVerify(c.Args().Slice()...) }, }, { Name: "encrypt", Usage: "Encrypt a Message", UsageText: "gosop encrypt [command options] [CERTS...] < DATA", Flags: []cli.Flag{ asFlag, noArmorFlag, passwordFlag, signWithFlag, }, Action: func(c *cli.Context) error { return Encrypt(c.Args().Slice()...) }, }, { Name: "decrypt", Usage: "Decrypt a Message", UsageText: "gosop decrypt [command options] [KEY...] < CIPHERTEXT", Flags: []cli.Flag{ sessionKeyOutFlag, sessionKeyFlag, passwordFlag, verificationsOutFlag, verifyWithFlag, verifyNotBeforeFlag, verifyNotAfterFlag, }, Action: func(c *cli.Context) error { return Decrypt(c.Args().Slice()...) }, }, { Name: "armor", Usage: "Add ASCII Armor", UsageText: "gosop armor [command options] < DATA", Flags: []cli.Flag{ labelFlag, }, Action: func(c *cli.Context) error { return ArmorComm(c.Args().Slice()...) }, }, { Name: "dearmor", Usage: "Remove ASCII Armor", UsageText: "gosop dearmor < DATA", Action: func(c *cli.Context) error { return DearmorComm() }, }, } gosop-0.1.0/cmd/dearmor.go000066400000000000000000000007431443145126400153720ustar00rootroot00000000000000package cmd import ( "io" "os" "github.com/ProtonMail/go-crypto/openpgp/armor" ) // DearmorComm takes armored OpenPGP material from Std input and outputs the // same material with ASCII-armoring removed. func DearmorComm() error { block, err := armor.Decode(os.Stdin) if err != nil { return dearmErr(err) } _, err = io.Copy(os.Stdout, block.Body) if err != nil { return dearmErr(err) } return nil } func dearmErr(err error) error { return Err99("dearmor", err) } gosop-0.1.0/cmd/decrypt.go000066400000000000000000000127501443145126400154140ustar00rootroot00000000000000package cmd import ( "bytes" "encoding/hex" "errors" "io/ioutil" "os" "strconv" "strings" "time" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/go-crypto/openpgp/packet" ) var symKeyAlgos = map[packet.CipherFunction]string{ packet.Cipher3DES: constants.ThreeDES, packet.CipherCAST5: constants.CAST5, packet.CipherAES128: constants.AES128, packet.CipherAES192: constants.AES192, packet.CipherAES256: constants.AES256, } // 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. // TODO: Multiple signers? // // --session-key-out=file flag: Outputs session key byte stream to given file. // About --with-session-key flag: This is not currently supported and could be // achieved with openpgp.packet, taking the first packet.EncryptedDataPacket // (be it Sym. Encrypted or AEAD Encrypted) and then decrypt directly. func Decrypt(keyFilenames ...string) error { if len(keyFilenames) == 0 && password == "" && sessionKey == "" { println("Please provide decryption keys, session key, or passphrase") return Err69 } ciphertextBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return decErr(err) } ciphertext, err := crypto.NewPGPMessageFromArmored(string(ciphertextBytes)) if err != nil { // If that fails, try binary ciphertext = crypto.NewPGPMessage(ciphertextBytes) } var pubKeyRing *crypto.KeyRing if verifyWith != "" { pubKeyRing, err = utils.CollectKeys([]string{verifyWith}...) if err != nil { return decErr(err) } } var sk *crypto.SessionKey if sessionKey != "" { sk, err = parseSessionKey() } else if password != "" { sk, err = passwordDecrypt(ciphertext) } else { sk, err = publicKeyDecrypt(ciphertext, keyFilenames) } if err != nil { return decErr(err) } if sessionKeyOut != "" { err := writeSessionKeyToFile(sk) if err != nil { return decErr(err) } } plaintext, err := sk.DecryptAndVerify(getEncryptedDataPacket(ciphertext), pubKeyRing, crypto.GetUnixTime()) if err != nil { return decErr(err) } _, err = os.Stdout.WriteString(plaintext.GetString()) if err != nil { return decErr(err) } if verificationsOut != "" { // TODO: This is fake if err := writeVerificationToFile(pubKeyRing); err != nil { return err } } return nil } func decErr(err error) error { return Err99("decrypt", err) } func parseSessionKey() (*crypto.SessionKey, error) { formattedSessionKey, err := utils.ReadFileOrEnv(sessionKey) if err != nil { return nil, err } parts := strings.Split(strings.TrimSpace(string(formattedSessionKey)), ":") skAlgo, err := strconv.ParseUint(parts[0], 10, 8) if err != nil { return nil, err } skAlgoName, ok := symKeyAlgos[packet.CipherFunction(skAlgo)] if !ok { return nil, errors.New("unsupported session key algorithm") } skBytes, err := hex.DecodeString(parts[1]) if err != nil { return nil, err } sk := crypto.NewSessionKeyFromToken(skBytes, skAlgoName) return sk, nil } func passwordDecrypt(message *crypto.PGPMessage) (*crypto.SessionKey, error) { pw, err := utils.ReadFileOrEnv(password) if err != nil { return nil, err } pw = []byte(strings.TrimSpace(string(pw))) sk, err := crypto.DecryptSessionKeyWithPassword(message.GetBinary(), pw) if err != nil { return nil, err } return sk, err } func publicKeyDecrypt(message *crypto.PGPMessage, keyFilenames []string) (*crypto.SessionKey, error) { privKeyRing, err := utils.CollectKeys(keyFilenames...) if err != nil { return nil, err } sk, err := privKeyRing.DecryptSessionKey(message.GetBinary()) if err != nil { return nil, err } return sk, nil } func getEncryptedDataPacket(message *crypto.PGPMessage) []byte { bytesReader := bytes.NewReader(message.Data) packets := packet.NewReader(bytesReader) start := int64(0) for { p, err := packets.Next() if err != nil { break } switch p.(type) { case *packet.SymmetricKeyEncrypted, *packet.EncryptedKey: start = bytesReader.Size() - int64(bytesReader.Len()) case *packet.SymmetricallyEncrypted, *packet.AEADEncrypted: break } } return message.Data[start:] } func writeSessionKeyToFile(sk *crypto.SessionKey) error { var sessionKeyFile *os.File if sessionKeyOut[0:4] == "@FD:" { fd, err := strconv.ParseUint(sessionKeyOut[4:], 10, strconv.IntSize) if err != nil { return err } sessionKeyFile = os.NewFile(uintptr(fd), sessionKeyOut) } else { var err error sessionKeyFile, err = os.Create(sessionKeyOut) if err != nil { return err } } cipherFunc, err := sk.GetCipherFunc() if err != nil { return decErr(err) } formattedSessionKey := strconv.FormatUint(uint64(cipherFunc), 10) + ":" + strings.ToUpper(hex.EncodeToString(sk.Key)) if _, err = sessionKeyFile.Write([]byte(formattedSessionKey)); err != nil { return decErr(err) } if err = sessionKeyFile.Close(); err != nil { return decErr(err) } return nil } func writeVerificationToFile(pubKeyRing *crypto.KeyRing) error { fgp, err := hex.DecodeString(pubKeyRing.GetKeys()[0].GetFingerprint()) if err != nil { return decErr(err) } ver := utils.VerificationString(time.Now(), fgp, fgp) outputVerFile, err := os.Create(verificationsOut) if err != nil { return decErr(err) } if _, err = outputVerFile.WriteString(ver + "\n"); err != nil { return decErr(err) } if err = outputVerFile.Close(); err != nil { return decErr(err) } return nil } gosop-0.1.0/cmd/encrypt.go000066400000000000000000000041661443145126400154300ustar00rootroot00000000000000package cmd import ( "io/ioutil" "os" "strings" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/helper" ) const ( textOpt = "text" ) // 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 } var err error var plaintextBytes []byte if plaintextBytes, err = ioutil.ReadAll(os.Stdin); err != nil { return encErr(err) } // Password encrypt var pgpMessage *crypto.PGPMessage if password != "" { pw, err := utils.ReadFileOrEnv(password) if err != nil { return err } pw = []byte(strings.TrimSpace(string(pw))) ciphertext, err := helper.EncryptMessageWithPassword(pw, string(plaintextBytes)) if err != nil { return encErr(err) } _, err = os.Stdout.WriteString(ciphertext + "\n") return err } message := &crypto.PlainMessage{ Data: plaintextBytes, TextType: asType == textOpt} pubKeyRing, err := utils.CollectKeys(keyFilenames...) if err != nil { return encErr(err) } if signWith != "" { // GopenPGP signs automatically if an unlocked private key is passed. var privKeyRing *crypto.KeyRing privKeyRing, err = utils.CollectKeys(strings.Split(signWith, " ")...) if err != nil { return encErr(err) } defer privKeyRing.ClearPrivateParams() pgpMessage, err = pubKeyRing.Encrypt(message, privKeyRing) if err != nil { return encErr(err) } } else { pgpMessage, err = pubKeyRing.Encrypt(message, nil) if err != nil { return encErr(err) } } if noArmor { _, err = os.Stdout.Write(pgpMessage.GetBinary()) } else { armored, errArm := pgpMessage.GetArmored() if errArm != nil { return encErr(errArm) } _, err = os.Stdout.WriteString(armored + "\n") } return err } func encErr(err error) error { return Err99("encrypt", err) } gosop-0.1.0/cmd/exit_codes.go000066400000000000000000000020131443145126400160570ustar00rootroot00000000000000package cmd import ( "github.com/urfave/cli/v2" ) // Error codes as defined in the draft, section 6. var ( Err3 = cli.Exit("Code 3: No acceptable signatures found (\"gosop verify\")", 3) Err13 = cli.Exit("Code 13: Asymmetric algorithm unsupported (\"gosop encrypt\")", 13) Err17 = cli.Exit("Code 17: Certificate not encryption-capable (\"gosop encrypt\")", 17) Err19 = cli.Exit("Missing required argument", 19) Err23 = cli.Exit("Incomplete verification instructions (\"gosop decrypt\")", 23) Err29 = cli.Exit("Unable to decrypt (\"gosop decrypt\")", 29) Err31 = cli.Exit("Non-\"UTF-8\" password (\"gosop encrypt\")", 31) Err37 = cli.Exit("Unsupported option", 37) Err41 = cli.Exit("Invalid data type (no secret key where \"KEY\" expected, etc)", 41) Err53 = cli.Exit("Non-text input where text expected", 53) Err69 = cli.Exit("Unsupported subcommand", 69) ) // Err99 returns the error message of any error not defined by the draft. func Err99(cmd string, err error) error { return cli.Exit(cmd+": "+err.Error(), 99) } gosop-0.1.0/cmd/extract_cert.go000066400000000000000000000017631443145126400164330ustar00rootroot00000000000000package cmd import ( "os" "github.com/ProtonMail/gopenpgp/v2/crypto" ) // ExtractCert - Extract a Certificate from a Secret Key // Note that the resultant "CERTS" object will only ever contain one OpenPGP // certificate. func ExtractCert() error { // Get private key from standard input var key *crypto.Key var err error if noArmor { // Unarmored I/O key, err = crypto.NewKeyFromReader(os.Stdin) if err != nil { return certErr(err) } pubKey, err := key.GetPublicKey() if err != nil { return certErr(err) } if _, err := os.Stdout.Write(pubKey); err != nil { return certErr(err) } } else { // Armored I/O key, err = crypto.NewKeyFromArmoredReader(os.Stdin) if err != nil { return certErr(err) } pubKey, err := key.GetArmoredPublicKey() if err != nil { return certErr(err) } if _, err := os.Stdout.WriteString(pubKey + "\n"); err != nil { return certErr(err) } } return nil } func certErr(err error) error { return Err99("extract-cert", err) } gosop-0.1.0/cmd/flags.go000066400000000000000000000053121443145126400150320ustar00rootroot00000000000000package cmd import "github.com/urfave/cli/v2" // Variables defined by flags var ( backend bool extended bool noArmor bool asType string notBefore string notAfter string password string signWith string sessionKey string sessionKeyOut string verificationsOut string verifyWith string label string ) // All possible flags for commands var ( backendFlag = &cli.BoolFlag{ Name: "backend", Value: false, Destination: &backend, } extendedFlag = &cli.BoolFlag{ Name: "extended", Value: false, Destination: &extended, } noArmorFlag = &cli.BoolFlag{ Name: "no-armor", Value: false, Destination: &noArmor, } asFlag = &cli.StringFlag{ Name: "as", Value: "binary", Usage: "--as={binary|text}", Destination: &asType, } asSignedFlag = &cli.StringFlag{ Name: "as", Value: "binary", Usage: "--as={binary|text|clearsigned}", Destination: &asType, } notBeforeFlag = &cli.StringFlag{ Name: "not-before", Value: "-", Usage: "--not-before={-|DATE}", Destination: ¬Before, } notAfterFlag = &cli.StringFlag{ Name: "not-after", Value: "now", Usage: "--not-after={-|DATE}", Destination: ¬After, } passwordFlag = &cli.StringFlag{ Name: "with-password", Usage: "--with-password=PASSWORD", Destination: &password, } signWithFlag = &cli.StringFlag{ Name: "sign-with", Usage: "--sign-with=KEY", Destination: &signWith, } sessionKeyFlag = &cli.StringFlag{ Name: "with-session-key", Usage: "--with-session-key=SESSIONKEY", Destination: &sessionKey, } sessionKeyOutFlag = &cli.StringFlag{ Name: "session-key-out", Usage: "--session-key-out=SESSIONKEY", Destination: &sessionKeyOut, } verificationsOutFlag = &cli.StringFlag{ Name: "verifications-out", Aliases: []string{"verify-out"}, Usage: "--verify-out=VERIFICATIONS", Destination: &verificationsOut, } verifyWithFlag = &cli.StringFlag{ Name: "verify-with", Usage: "--verify-out=CERTS", Destination: &verifyWith, } verifyNotBeforeFlag = &cli.StringFlag{ Name: "verify-not-before", Value: "-", Usage: "--verify-not-before={-|DATE}", Destination: ¬Before, } verifyNotAfterFlag = &cli.StringFlag{ Name: "verify-not-after", Value: "now", Usage: "--verify-not-after={-|DATE}", Destination: ¬After, } labelFlag = &cli.StringFlag{ Name: "label", Value: "auto", Usage: "--label={auto|sig|key|cert|message}", Destination: &label, } ) gosop-0.1.0/cmd/generate_key.go000066400000000000000000000022741443145126400164040ustar00rootroot00000000000000package cmd import ( "os" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/crypto" ) // 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) // ", and we use strictly 1 USERID per generated key. func GenerateKey(userIDs ...string) error { // Parse first userID var name, email string if len(userIDs) > 0 { var err error name, _, email, err = utils.ParseUserID(userIDs[0]) if err != nil { return kgErr(err) } } // Generate RSA key rsaKey, err := crypto.GenerateKey(name, email, "rsa", 2048) if err != nil { return kgErr(err) } // Output if noArmor { keyBytes, err := rsaKey.Serialize() if err != nil { return kgErr(err) } if _, err := os.Stdout.Write(keyBytes); err != nil { return kgErr(err) } } else { armored, err := rsaKey.Armor() if err != nil { return kgErr(err) } _, err = os.Stdout.WriteString(armored + "\n") if err != nil { return kgErr(err) } } return nil } func kgErr(err error) error { return Err99("generate-key", err) } gosop-0.1.0/cmd/inline_sign.go000066400000000000000000000026131443145126400162350ustar00rootroot00000000000000package cmd import ( "io/ioutil" "os" "unicode/utf8" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/helper" ) 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") return Err19 } // Signer keyring var keyRing *crypto.KeyRing var err error keyRing, err = utils.CollectKeys(keyFilenames...) if err != nil { return inlineSignErr(err) } if keyRing.CountEntities() == 0 { return Err41 } // Message var messageBytes []byte if messageBytes, err = ioutil.ReadAll(os.Stdin); err != nil { return inlineSignErr(err) } if !utf8.Valid(messageBytes) { return Err53 } message := string(messageBytes) if asType == clearsignedOpt { signedMessage, err := helper.SignCleartextMessage(keyRing, message) if err != nil { return inlineSignErr(err) } if _, err = os.Stdout.WriteString(signedMessage + "\n"); err != nil { return inlineSignErr(err) } } else { // TODO: Support --as={binary|text}, and not just --as=clearsigned. return Err37 } return nil } func inlineSignErr(err error) error { return Err99("inline-sign", err) } gosop-0.1.0/cmd/inline_verify.go000066400000000000000000000025421443145126400166020ustar00rootroot00000000000000package cmd import ( "io/ioutil" "os" "strings" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/helper" ) // 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)") return Err19 } if notBefore != "-" || notAfter != "now" { println("--not-after and --not-before are not implemented.") return Err37 } // Collect keyring keyRing, err := utils.CollectKeys(input...) if err != nil { return inlineVerErr(err) } signatureBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return inlineVerErr(err) } signature := string(signatureBytes) if !strings.HasPrefix(signature, "-----BEGIN PGP SIGNED MESSAGE-----") { // Only clearsigned messages are supported for now. return Err37 } message, err := helper.VerifyCleartextMessage(keyRing, signature, crypto.GetUnixTime()) if err != nil { return inlineVerErr(err) } _, err = os.Stdout.WriteString(message) if err != nil { return inlineVerErr(err) } if verificationsOut != "" { // TODO: This is fake if err := writeVerificationToFile(keyRing); err != nil { return err } } return err } func inlineVerErr(err error) error { return Err99("inline-verify", err) } gosop-0.1.0/cmd/sign.go000066400000000000000000000026121443145126400146760ustar00rootroot00000000000000package cmd import ( "io/ioutil" "os" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/crypto" ) // 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 } // Signer keyring var keyRing *crypto.KeyRing var err error keyRing, err = utils.CollectKeys(keyFilenames...) if err != nil { return signErr(err) } if keyRing.CountEntities() == 0 { return Err41 } // Message var plaintextBytes []byte if plaintextBytes, err = ioutil.ReadAll(os.Stdin); err != nil { return signErr(err) } var text bool if asType == textOpt { text = true } message := &crypto.PlainMessage{Data: plaintextBytes, TextType: text} // Sign var signature *crypto.PGPSignature if signature, err = keyRing.SignDetached(message); err != nil { return signErr(err) } if noArmor { if _, err = os.Stdout.Write(signature.Data); err != nil { return signErr(err) } } else { var armored string if armored, err = signature.GetArmored(); err != nil { return signErr(err) } if _, err = os.Stdout.WriteString(armored + "\n"); err != nil { return signErr(err) } } return nil } func signErr(err error) error { return Err99("sign", err) } gosop-0.1.0/cmd/verify.go000066400000000000000000000030721443145126400152430ustar00rootroot00000000000000package cmd import ( "encoding/hex" "io/ioutil" "os" "time" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v2/crypto" ) // Verify checks the validity of a signature against a set of certificates. func Verify(input ...string) error { switch len(input) { case 0: return Err3 case 1: println("Please provide a certificate (public key)") return Err19 } if notBefore != "-" || notAfter != "now" { println("--not-after and --not-before are not implemented.") return Err37 } // Collect keyring keyRing, err := utils.CollectKeys(input[1:]...) if err != nil { return verErr(err) } plaintextBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return verErr(err) } var text bool if asType == textOpt { text = true } message := &crypto.PlainMessage{Data: plaintextBytes, TextType: text} // Collect signature sigBytes, err := utils.ReadFileOrEnv(input[0]) if err != nil { return verErr(err) } var signature *crypto.PGPSignature signature, err = crypto.NewPGPSignatureFromArmored(string(sigBytes)) if err != nil { signature = crypto.NewPGPSignature(sigBytes) } creationTime, err := keyRing.GetVerifiedSignatureTimestamp(message, signature, crypto.GetUnixTime()) if err != nil { return Err3 } // TODO: This is fake fgp, err := hex.DecodeString(keyRing.GetKeys()[0].GetFingerprint()) if err != nil { return verErr(err) } ver := utils.VerificationString(time.Unix(creationTime, 0), fgp, fgp) _, err = os.Stdout.WriteString(ver + "\n") return err } func verErr(err error) error { return Err99("verify", err) } gosop-0.1.0/cmd/version.go000066400000000000000000000025151443145126400154250ustar00rootroot00000000000000package cmd import ( "errors" "os" "runtime/debug" "strings" "github.com/ProtonMail/gopenpgp/v2/constants" ) const VERSION = "0.1.0" // Version prints version information about gosop, and/or the // underlying OpenPGP library/libraries. func Version() error { if !backend || extended { _, err := os.Stdout.WriteString("gosop " + VERSION + "\n") if err != nil { return versionErr(err) } } if backend || extended { _, err := os.Stdout.WriteString("GopenPGP " + constants.Version + "\n") if err != nil { return versionErr(err) } } if extended { info, ok := debug.ReadBuildInfo() if !ok { return versionErr(errors.New("couldn't read debug information")) } for i, module := range info.Deps { if module.Path == "github.com/ProtonMail/go-crypto" || module.Path == "golang.org/x/crypto" { for module.Replace != nil { module = module.Replace } version := module.Version versionParts := strings.Split(version, "-") _, err := os.Stdout.WriteString(info.Deps[i].Path[11:] + " " + versionParts[len(versionParts)-1] + "\n") if err != nil { return versionErr(err) } } } _, err := os.Stdout.WriteString("Compiled using " + info.GoVersion + "\n") if err != nil { return versionErr(err) } } return nil } func versionErr(err error) error { return Err99("version", err) } gosop-0.1.0/go.mod000066400000000000000000000003101443145126400137430ustar00rootroot00000000000000module github.com/ProtonMail/gosop require ( github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310 github.com/ProtonMail/gopenpgp/v2 v2.7.0 github.com/urfave/cli/v2 v2.2.0 ) go 1.14 gosop-0.1.0/go.sum000066400000000000000000000152241443145126400140020ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310 h1:dGAdTcqheKrQ/TW76sAcmO2IorwXplUw2inPkOzykbw= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/gopenpgp/v2 v2.7.0 h1:TUH6csU8OpRtA5EKU8yoAXn8M5Inh2T4Y086lKFFyS8= github.com/ProtonMail/gopenpgp/v2 v2.7.0/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= gosop-0.1.0/main.go000066400000000000000000000012271443145126400141200ustar00rootroot00000000000000package main import ( "github.com/ProtonMail/gosop/cmd" "log" "os" "sort" "github.com/urfave/cli/v2" ) func main() { app := &cli.App{ Name: "gosop", Usage: "Stateless OpenPGP implementation for GopenPGP", Version: cmd.VERSION, Authors: []*cli.Author{ &cli.Author{ Name: "Proton Technologies AG", }, }, Commands: cmd.All, Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cmd.Err69 } else { cli.ShowAppHelpAndExit(c, 1) return nil } }, } sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.CommandsByName(app.Commands)) err := app.Run(os.Args) if err != nil { log.Fatal(err) } } gosop-0.1.0/man/000077500000000000000000000000001443145126400134165ustar00rootroot00000000000000gosop-0.1.0/man/gosop.1000066400000000000000000000072331443145126400146340ustar00rootroot00000000000000.\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 .TH "GOSOP" "1" "May 2023" "" .SH "NAME" \fBgosop\fR \- Stateless OpenPGP implementation for GopenPGP .SH "USAGE:" gosop [global options] command [command options] [arguments\|\.\|\.\|\.] .SH "AUTHOR:" Proton Technologies AG .SH "COMMANDS" .TP \fBarmor\fR Add ASCII Armor .IP USAGE: .br gosop armor [command options] < DATA .IP OPTIONS: .br \fB\-\-label\fR \fIvalue\fR .br \fB\-\-label\fR={\fIauto\fR|\fIsig\fR|\fIkey\fR|\fIcert\fR|\fImessage\fR} (default: \fIauto\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBdearmor\fR Remove ASCII Armor .IP USAGE: .br gosop dearmor < DATA .IP OPTIONS: .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBdecrypt\fR Decrypt a Message .IP USAGE: .IP gosop decrypt [command options] [KEY\|\.\|\.\|\.] < CIPHERTEXT .IP OPTIONS: .br \fB\-\-session\-key\-out\fR \fIvalue\fR .br \fB\-\-session\-key\-out\fR=\fISESSIONKEY\fR .br \fB\-\-with\-session\-key\fR \fIvalue\fR .br \fB\-\-with\-session\-key\fR=\fISESSIONKEY\fR .br \fB\-\-with\-password\fR \fIvalue\fR .br \fB\-\-with\-password\fR=\fIPASSWORD\fR .br \fB\-\-verify\-out\fR \fIvalue\fR .br \fB\-\-verify\-out\fR=\fIVERIFICATIONS\fR .br \fB\-\-verify\-with\fR \fIvalue\fR .br \fB\-\-verify\-out\fR=\fICERTS\fR .br \fB\-\-verify\-not\-before\fR \fIvalue\fR .br \fB\-\-verify\-not\-before\fR={\fI\-\fR|\fIDATE\fR} (default: \fI\-\fR) .br \fB\-\-verify\-not\-after\fR \fIvalue\fR .br \fB\-\-verify\-not\-after\fR={\fI\-\fR|\fIDATE\fR} (default: \fInow\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBencrypt\fR Encrypt a Message .IP USAGE: .br gosop encrypt [command options] [CERTS\|\.\|\.\|\.] < DATA .IP OPTIONS: .br \fB\-\-as\fR \fIvalue\fR .br \fB\-\-as\fR={\fIbinary\fR|\fItext\fR} (default: \fIbinary\fR) .br \fB\-\-no\-armor\fR (default: \fIfalse\fR) .br \fB\-\-with\-password\fR \fIvalue\fR .br \fB\-\-with\-password\fR=\fIPASSWORD\fR .br \fB\-\-sign\-with\fR \fIvalue\fR .br \fB\-\-sign\-with\fR=\fIKEY\fR .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBextract\-cert\fR Extract a Certificate from a Secret Key .IP USAGE: .br gosop extract\-cert [command options] .IP OPTIONS: .br \fB\-\-no\-armor\fR (default: \fIfalse\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBgenerate\-key\fR Generate a Secret Key .IP USAGE: .br gosop generate\-key [command options] [USERID\|\.\|\.\|\. .IP OPTIONS: .br \fB\-\-no\-armor\fR (default: \fIfalse\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBsign\fR Create a Detached Signature .IP USAGE: .br gosop extract\-cert [command options] .IP OPTIONS: .br \fB\-\-no\-armor\fR (default: \fIfalse\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBverify\fR Verify a Detached Signature .IP USAGE: .br gosop verify SIGNATURE CERTS [CERTS\|\.\|\.\|\.] < DATA .IP OPTIONS: .br \fB\-\-not\-before\fR \fIvalue\fR .br \fB\-\-not\-before\fR={\fI\-\fR|\fIDATE\fR} (default: \fI\-\fR) .br \fB\-\-not\-after\fR \fIvalue\fR .br \fB\-\-not\-after\fR={\fI\-\fR|\fIDATE\fR} (default: \fInow\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBversion\fR Version Information .IP USAGE: .br gosop version [command options] [arguments\|\.\|\.\|\.] .IP OPTIONS: .br \fB\-\-backend\fR (default: \fIfalse\fR) .br \fB\-\-extended\fR (default: \fIfalse\fR) .br \fB\-\-help\fR, \fB\-h\fR: show help (default: \fIfalse\fR) .TP \fBhelp\fR, \fBh\fR Shows a list of commands or help for one command .SH "GLOBAL OPTIONS:" .TP \fB\-\-help\fR, \fB\-h\fR show help (default: \fIfalse\fR) .TP \fB\-\-version\fR, \fB\-v\fR print the version (default: \fIfalse\fR) gosop-0.1.0/man/gosop.1.ronn000066400000000000000000000063251443145126400156100ustar00rootroot00000000000000gosop(1) -- Stateless OpenPGP implementation for GopenPGP === ## USAGE: gosop \[global options\] command [command options] [arguments...] ## AUTHOR: Proton Technologies AG ## COMMANDS * `armor`: Add ASCII Armor USAGE: gosop armor [command options] < DATA OPTIONS: **--label** **--label**={||||} (default: ) **--help**, **-h**: show help (default: ) * `dearmor`: Remove ASCII Armor USAGE: gosop dearmor < DATA OPTIONS: **--help**, **-h**: show help (default: ) * `decrypt`: Decrypt a Message USAGE: gosop decrypt [command options] [KEY...] < CIPHERTEXT OPTIONS: **--session-key-out** **--session-key-out**= **--with-session-key** **--with-session-key**= **--with-password** **--with-password**= **--verify-out** **--verify-out**= **--verify-with** **--verify-out**= **--verify-not-before** **--verify-not-before**={<->|} (default: <->) **--verify-not-after** **--verify-not-after**={<->|} (default: ) **--help**, **-h**: show help (default: ) * `encrypt`: Encrypt a Message USAGE: gosop encrypt [command options] [CERTS...] < DATA OPTIONS: **--as** **--as**={|} (default: ) **--no-armor** (default: ) **--with-password** **--with-password**= **--sign-with** **--sign-with**= **--help**, **-h**: show help (default: ) * `extract-cert`: Extract a Certificate from a Secret Key USAGE: gosop extract-cert [command options] OPTIONS: **--no-armor** (default: ) **--help**, **-h**: show help (default: ) * `generate-key`: Generate a Secret Key USAGE: gosop generate-key [command options] [USERID... OPTIONS: **--no-armor** (default: ) **--help**, **-h**: show help (default: ) * `sign`: Create a Detached Signature USAGE: gosop extract-cert [command options] OPTIONS: **--no-armor** (default: ) **--help**, **-h**: show help (default: ) * `verify`: Verify a Detached Signature USAGE: gosop verify SIGNATURE CERTS [CERTS...] < DATA OPTIONS: **--not-before** **--not-before**={<->|} (default: <->) **--not-after** **--not-after**={<->|} (default: ) **--help**, **-h**: show help (default: ) * `version`: Version Information USAGE: gosop version [command options] [arguments...] OPTIONS: **--backend** (default: ) **--extended** (default: ) **--help**, **-h**: show help (default: ) * `help`, `h`: Shows a list of commands or help for one command ## GLOBAL OPTIONS: * `--help`, `-h`: show help (default: ) * `--version`, `-v`: print the version (default: ) gosop-0.1.0/scripts/000077500000000000000000000000001443145126400143325ustar00rootroot00000000000000gosop-0.1.0/scripts/tests.sh000077500000000000000000000120601443145126400160320ustar00rootroot00000000000000#!/bin/bash if [ $# != 3 ] || [ $2 != '-v' ]; then echo "Usage: test.sh sop.binary -v " exit 1 fi sop=$1 which $sop if [ $? != 0 ]; then echo "gosop not found."; echo echo "Did you run go install? Is \$GOPATH/bin in your \$PATH?" echo "... else, you can set sop=/path/to/gosop/binary in scripts/tests.sh"; echo exit 1 fi verbosity=$3 # tmp directory, erased on exit create_tmp_dir() { eval $1="$(mktemp -d)" if [ $? != 0 ]; then echo "Failed to create temporary directory" exit $? fi } erase_tmp_dir() { rm -rf $1 if [ $? != 0 ]; then echo "Failed to delete temporary directory: $1" exit $? fi } check_exit_code() { if [ $1 == 69 ] || [ $1 == 37 ]; then printf " ... SKIPPED \n" return 0 fi if [ $1 != $2 ]; then echo "Failed: Exit code $1, expected $2" exit $1 fi echo " ... OK" } comm() { printf "$ $1\n" } my_cat() { if [ $verbosity == 1 ]; then head -n1 $1 fi if [ $verbosity == 2 ]; then cat $1 fi } create_tmp_dir data trap "erase_tmp_dir $data" EXIT # Test files message=$data/message.txt password=$data/password.txt alice_secret=$data/alice.sec bob_secret=$data/bob.sec alice_public=$data/alice.asc bob_public=$data/bob.asc bob_public_unarmored=$data/bob.bin session_key=$data/session.bin signed=$data/signed.asc verified=$data/verified.asc encrypted=$data/encrypted.asc encrypted_with_password=$data/encrypted_with_password.asc verification=$data/verification.txt bad_verification=$data/bad_verification.txt verification_too_old=$data/verification_too_old.txt verification_too_young=$data/verification_too_young.txt decrypted_with_password=$data/decrypted_password.txt decrypted_with_key=$data/decrypted.txt decrypted_with_session_key=$data/decrypted.txt unarmored=$data/unarmored.bin comm "version" $sop version check_exit_code $? 0 comm "generate-key --no-armor" $sop generate-key --no-armor 'Bob Lovelace ' > $bob_secret check_exit_code $? 0 comm "generate-key --no-armor" $sop generate-key --no-armor 'Bob Lovelace ' > $bob_secret check_exit_code $? 0 comm "generate-key" $sop generate-key 'Alice Lovelace ' > $alice_secret check_exit_code $? 0 my_cat $alice_secret comm "extract-cert" $sop extract-cert < $alice_secret > $alice_public check_exit_code $? 0 my_cat $alice_public comm "extract-cert --no-armor" $sop extract-cert --no-armor < $bob_secret > $bob_public_unarmored check_exit_code $? 0 printf "\nOír la noche inmensa, más inmensa sin ella.\nY el verso cae al alma como al pasto el rocío.\n" > $message printf "test.123" > $password comm "sign" $sop sign --as=text $alice_secret < $message > $encrypted check_exit_code $? 0 my_cat $encrypted comm "verify" $sop verify $encrypted $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "verify corrupt" tr a-z A-Z < $message | $sop verify $encrypted $alice_public > $bad_verification check_exit_code $? 3 my_cat $bad_verification comm "verify --not-after" $sop verify --not-after=20060102T150405Z $encrypted $alice_public < $message > $verification_too_old check_exit_code $? 3 my_cat $verification_too_old comm "verify --not-before" $sop verify --not-before=now $encrypted $alice_public < $message > $verification_too_young check_exit_code $? 3 my_cat $verification_too_young comm "inline-sign" $sop inline-sign --as=clearsigned $alice_secret < $message > $signed check_exit_code $? 0 my_cat $signed comm "inline-verify" $sop inline-verify --verifications-out=$verification $alice_public < $signed > $verified check_exit_code $? 0 my_cat $verification my_cat $verified diff $message $verified comm "encrypt --with-password" $sop encrypt --with-password=$password < $message > $encrypted_with_password check_exit_code $? 0 my_cat $encrypted_with_password comm "decrypt --with-password" $sop decrypt --with-password=$password < $encrypted_with_password > $decrypted_with_password check_exit_code $? 0 my_cat $decrypted_with_password comm "encrypt" $sop encrypt $alice_public < $message > $encrypted check_exit_code $? 0 my_cat $encrypted comm "decrypt" $sop decrypt $alice_secret < $encrypted > $decrypted_with_key check_exit_code $? 0 my_cat $decrypted_with_key comm "decrypt --as=text --sign-with" $sop encrypt --as=mime --sign-with=$alice_secret $alice_public < $message > $encrypted check_exit_code $? 0 my_cat $encrypted comm "decrypt --session-key-out --verify-with --verifications-out" $sop decrypt --session-key-out=$session_key --verify-with=$alice_public --verifications-out=$verification $alice_secret < $encrypted > $decrypted_with_key check_exit_code $? 0 my_cat $decrypted_with_key my_cat $verification comm "decrypt --with-session-key" $sop decrypt --with-session-key=$session_key < $encrypted > $decrypted_with_session_key check_exit_code $? 0 my_cat $decrypted_with_session_key comm "armor" $sop armor < $bob_public_unarmored > $bob_public check_exit_code $? 0 my_cat $bob_public comm "dearmor" $sop dearmor < $encrypted > $unarmored check_exit_code $? 0 gosop-0.1.0/utils/000077500000000000000000000000001443145126400140035ustar00rootroot00000000000000gosop-0.1.0/utils/utils.go000066400000000000000000000065101443145126400154740ustar00rootroot00000000000000// Package utils contains helper functions related to the sop implementation of // gopenpgp. package utils import ( "errors" "fmt" "io/ioutil" "os" "strconv" "strings" "time" "github.com/ProtonMail/gopenpgp/v2/crypto" ) // Time format layouts const ( layout = time.RFC3339 // See RFC3339 or ISO-8601 layoutSecondary = "20060102T150405Z0700" // If the above fails ) // ParseUserID takes a string of the form "x (y) " and outputs x, y, z and // an error. Note that x, y may contain whitespaces. func ParseUserID(id string) (x, y, z string, err error) { if !strings.Contains(id, "<") { x = id return } var slice []string if strings.Contains(id, "(") { slice = strings.Split(id, "(") x = slice[0] slice = strings.Split(slice[1], ")") y = slice[0] slice = strings.Split(slice[1], "<") } else { slice = strings.Split(id, "<") x = slice[0] } x = strings.TrimSpace(x) z = strings.Split(slice[1], ">")[0] all := x + y + z if strings.ContainsAny(all, "<>()") { err = errors.New("unsupported USERID") } return } // ParseDates reads --not-before and --not-after flags, and parses them // according to the layout. func ParseDates(notBefore, notAfter string) (nb, na time.Time, err error) { layouts := []string{layout, layoutSecondary} // Not before switch notBefore { case "now": // Of no use, but only for compliance with the spec nb = time.Now() case "-": BegOfTime := time.Date(1970, time.January, 0, 0, 0, 0, 0, time.UTC) nb = BegOfTime default: for _, layout := range layouts { nb, err = time.Parse(layout, notBefore) if err == nil { break } } if err != nil { return } } // Not after switch notAfter { case "now", "-": na = time.Now() default: for _, layout := range layouts { na, err = time.Parse(layout, notAfter) if err == nil { break } } } return nb, na, err } // VerificationString gives the line containing the result of a verification. func VerificationString(timestamp time.Time, fgp, primFgp []byte) string { formattedTime := timestamp.UTC().Format(layout) return fmt.Sprintf("%v %X %X", formattedTime, fgp, primFgp) } // Linebreak prints "\n" to os.Stdout. func Linebreak() { if _, err := os.Stdout.WriteString("\n"); err != nil { panic(err) } } // CollectKeys forms a crypto.KeyRing with all the keys provided in the input // files. It returns the keyring and an error. func CollectKeys(keyFilenames ...string) (*crypto.KeyRing, error) { keyRing, err := crypto.NewKeyRing(nil) if err != nil { return keyRing, err } for _, filename := range keyFilenames { keyData, err := ReadFileOrEnv(filename) if err != nil { return keyRing, err } var key *crypto.Key key, err = crypto.NewKeyFromArmored(string(keyData)) if err != nil { // Try unarmored key, err = crypto.NewKey(keyData) if err != nil { return nil, err } } if err != nil { return nil, err } if err = keyRing.AddKey(key); err != nil { return nil, err } } return keyRing, err } func ReadFileOrEnv(filename string) ([]byte, error) { if filename[0:5] == "@ENV:" { return []byte(os.Getenv(filename[5:])), nil } if filename[0:4] == "@FD:" { fd, err := strconv.ParseUint(filename[4:], 10, strconv.IntSize) if err != nil { return nil, err } return ioutil.ReadAll(os.NewFile(uintptr(fd), filename)) } return ioutil.ReadFile(filename) }