pax_global_header00006660000000000000000000000064147201372520014515gustar00rootroot0000000000000052 comment=4a2cda0874f3b3a04b42c87a3548500bbac8c847 gosop-1.1.0/000077500000000000000000000000001472013725200126435ustar00rootroot00000000000000gosop-1.1.0/.github/000077500000000000000000000000001472013725200142035ustar00rootroot00000000000000gosop-1.1.0/.github/workflows/000077500000000000000000000000001472013725200162405ustar00rootroot00000000000000gosop-1.1.0/.github/workflows/release.yaml000066400000000000000000000011111472013725200205360ustar00rootroot00000000000000on: release: types: [created] permissions: contents: write packages: write jobs: releases-matrix: name: Release Binaries runs-on: ubuntu-latest strategy: matrix: goos: [linux, windows, darwin, freebsd, openbsd, netbsd] goarch: [amd64, arm64] exclude: - goarch: arm64 goos: windows steps: - uses: actions/checkout@v4 - uses: wangyoucao577/go-release-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} gosop-1.1.0/.gitignore000066400000000000000000000000061472013725200146270ustar00rootroot00000000000000gosop gosop-1.1.0/.golangci.yml000066400000000000000000000011471472013725200152320ustar00rootroot00000000000000linters: 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-1.1.0/LICENSE000066400000000000000000000020671472013725200136550ustar00rootroot00000000000000MIT 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-1.1.0/Makefile000066400000000000000000000010531472013725200143020ustar00rootroot00000000000000verbosity := 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) rfc4880 rfc4880 bash scripts/tests.sh gosop -v $(verbosity) draft-koch-eddsa-for-openpgp-00 rfc4880 bash scripts/tests.sh gosop -v $(verbosity) draft-ietf-openpgp-crypto-refresh-10 draft-ietf-openpgp-crypto-refresh-10 gosop-1.1.0/README.md000066400000000000000000000033561472013725200141310ustar00rootroot00000000000000# 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-1.1.0/cmd/000077500000000000000000000000001472013725200134065ustar00rootroot00000000000000gosop-1.1.0/cmd/README.md000066400000000000000000000032111472013725200146620ustar00rootroot00000000000000## 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-1.1.0/cmd/armor.go000066400000000000000000000050471472013725200150630ustar00rootroot00000000000000package cmd import ( "bytes" "io" "io/ioutil" "os" openpgp "github.com/ProtonMail/go-crypto/openpgp/v2" "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/ProtonMail/gopenpgp/v3/armor" "github.com/ProtonMail/gopenpgp/v3/constants" ) // ArmorComm takes unarmored OpenPGP material from Std input and outputs the // same material with ASCII-armoring added. func ArmorComm(keyFilenames ...string) error { inputReader, isArmored := armor.IsPGPArmored(os.Stdin) if isArmored { // If already armored, output directly and return _, err := io.Copy(os.Stdout, inputReader) if err != nil { return armErr(err) } return nil } input, err := ioutil.ReadAll(inputReader) if err != nil { return armErr(err) } armored, err := armorDecidingType(input) 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 { armored, err = armor.ArmorPGPMessage(input) if err != nil { return armored, err } } if _, ok := p.(*packet.PublicKey); ok { return armorKeys(input, constants.PublicKeyHeader) } if _, ok := p.(*packet.PrivateKey); ok { return armorKeys(input, constants.PrivateKeyHeader) } if _, ok := p.(*packet.Signature); ok { // If every packet is a signature packet, armor the input as a // signature; otherwise, armor it as a message. for { if p, err = packets.Next(); err == io.EOF { return armor.ArmorPGPSignature(input) } if err != nil { break } if _, ok := p.(*packet.Signature); !ok { break } } } return armor.ArmorPGPMessage(input) } func armorKeys(input []byte, armorType string) (armored string, err error) { entities, err := openpgp.ReadKeyRing(bytes.NewReader(input)) if err != nil { return armored, err } var output bytes.Buffer v6 := true for _, entity := range entities { if entity.PrimaryKey.Version != 6 { v6 = false } } w, err := armor.ArmorWriterWithTypeChecksum(&output, armorType, !v6) if err != nil { return armored, err } for _, entity := range entities { if entity.PrivateKey != nil { err = entity.SerializePrivateWithoutSigning(w, &packet.Config{}) } else { err = entity.Serialize(w) } if err != nil { return armored, err } } err = w.Close() if err != nil { return armored, err } armored = output.String() return armored, err } gosop-1.1.0/cmd/commands.go000066400000000000000000000073731472013725200155500ustar00rootroot00000000000000// 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, sopSpecFlag, sopvFlag, }, Action: func(c *cli.Context) error { return Version() }, }, { Name: "list-profiles", Usage: "List profiles for subcommands", UsageText: "gosop list-profiles SUBCOMMAND", Flags: []cli.Flag{}, Action: func(c *cli.Context) error { return ListProfiles(c.Args().Slice()...) }, }, { Name: "generate-key", Usage: "Generate a Secret Key", UsageText: "gosop generate-key [command options] [USERID...]", Flags: []cli.Flag{ noArmorFlag, selectedProfileFlag, keyPasswordFlag, }, 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, keyPasswordFlag, }, 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, keyPasswordFlag, }, 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: "inline-detach", Usage: "Split Signatures from an Inline-Signed Message", UsageText: "gosop inline-verify < INLINESIGNED", Flags: []cli.Flag{ noArmorFlag, signaturesOutFlag, }, Action: func(c *cli.Context) error { return InlineDetach() }, }, { Name: "encrypt", Usage: "Encrypt a Message", UsageText: "gosop encrypt [command options] [CERTS...] < DATA", Flags: []cli.Flag{ selectedProfileFlag, asFlag, noArmorFlag, passwordFlag, signWithFlag, keyPasswordFlag, }, 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, keyPasswordFlag, }, Action: func(c *cli.Context) error { return Decrypt(c.Args().Slice()...) }, }, { Name: "armor", Usage: "Add ASCII Armor", UsageText: "gosop armor [command options] < DATA", 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-1.1.0/cmd/dearmor.go000066400000000000000000000013251472013725200153670ustar00rootroot00000000000000package cmd import ( "io" "os" "github.com/ProtonMail/gopenpgp/v3/armor" ) // DearmorComm takes armored OpenPGP material from Std input and outputs the // same material with ASCII-armoring removed. func DearmorComm() error { inputReader, isArmored := armor.IsPGPArmored(os.Stdin) if !isArmored { // If already dearmored, output directly and return _, err := io.Copy(os.Stdout, inputReader) if err != nil { return dearmErr(err) } return nil } armorReader, err := armor.ArmorReader(inputReader) if err != nil { return dearmErr(err) } _, err = io.Copy(os.Stdout, armorReader) if err != nil { return dearmErr(err) } return nil } func dearmErr(err error) error { return Err99("dearmor", err) } gosop-1.1.0/cmd/decrypt.go000066400000000000000000000103471472013725200154140ustar00rootroot00000000000000package cmd import ( "encoding/hex" "errors" "io" "os" "strconv" "strings" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/constants" "github.com/ProtonMail/gopenpgp/v3/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. 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() builder := pgp.Decryption() var pubKeyRing *crypto.KeyRing if verifyWith.Value() != nil { verifyKeys := utils.CollectFilesFromCliSlice(verifyWith.Value()) pubKeyRing, err = utils.CollectKeys(verifyKeys...) if err != nil { return decErr(err) } builder.VerificationKeys(pubKeyRing) } if (verificationsOut == "" && pubKeyRing.CountEntities() != 0) || (verificationsOut != "" && pubKeyRing.CountEntities() == 0) { return Err23 } timeFrom, timeTo, err := utils.ParseDates(notBefore, notAfter) if err != nil { return decErr(err) } var sk *crypto.SessionKey if sessionKey != "" { sk, err = parseSessionKey() if err != nil { return decErr(err) } builder.SessionKey(sk) } else if password != "" { pw, err := utils.ReadFileOrEnv(password) if err != nil { return decErr(err) } pw = []byte(strings.TrimSpace(string(pw))) builder.Password(pw) } else { var pw []byte if keyPassword != "" { pw, err = utils.ReadSanitizedPassword(keyPassword) if err != nil { return decErr(err) } } privKeyRing, failUnlock, err := utils.CollectKeysPassword(pw, keyFilenames...) if failUnlock { return Err67 } if err != nil { return decErr(err) } defer privKeyRing.ClearPrivateParams() builder.DecryptionKeys(privKeyRing) } if sessionKeyOut != "" { builder.RetrieveSessionKey() } decryptor, _ := builder.New() ptReader, err := decryptor.DecryptingReader(os.Stdin, crypto.Auto) if err != nil { return decErr(err) } _, err = io.Copy(os.Stdout, ptReader) if err != nil { return decErr(err) } if sessionKeyOut != "" { err = writeSessionKeyToFile(ptReader.SessionKey()) if err != nil { return decErr(err) } } if verificationsOut != "" { result, err := ptReader.VerifySignature() if err != nil { return decErr(err) } result.ConstrainToTimeRange(timeFrom.Unix(), timeTo.Unix()) if err := writeVerificationToFileFromResult(result); err != nil { return decErr(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 writeSessionKeyToFile(sk *crypto.SessionKey) error { sessionKeyFile, err := utils.OpenOutFile(sessionKeyOut) if err != nil { return err } defer sessionKeyFile.Close() 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 } gosop-1.1.0/cmd/encrypt.go000066400000000000000000000046601472013725200154270ustar00rootroot00000000000000package cmd import ( "bytes" "io" "os" "unicode/utf8" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/crypto" ) 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 } profile := utils.SelectEncryptionProfile(selectedProfile) if profile == nil { return Err89 } pgp := crypto.PGPWithProfile(profile.PgpProfile) builder := pgp.Encryption() var err error var input io.Reader = os.Stdin if signWith.Value() != nil { // GopenPGP signs automatically if an unlocked private key is passed. var privKeyRing *crypto.KeyRing var pw []byte if keyPassword != "" { pw, err = utils.ReadSanitizedPassword(keyPassword) if err != nil { return encErr(err) } } keys := utils.CollectFilesFromCliSlice(signWith.Value()) privKeyRing, failUnlock, err := utils.CollectKeysPassword(pw, keys...) if failUnlock { return Err67 } if err != nil { return encErr(err) } defer privKeyRing.ClearPrivateParams() builder.SigningKeys(privKeyRing) } if asType == textOpt { builder.Utf8() // Expensive check var plaintextBytes []byte if plaintextBytes, err = io.ReadAll(input); err != nil { return encErr(err) } if !utf8.Valid(plaintextBytes) { return Err53 } input = bytes.NewReader(plaintextBytes) } // Password encrypt if password != "" { pw, err := utils.ReadSanitizedPassword(password) if err != nil { return encErr(err) } builder.Password(pw) } else { pubKeyRing, err := utils.CollectKeys(keyFilenames...) if err != nil { return encErr(err) } builder.Recipients(pubKeyRing) } encoding := crypto.Armor if noArmor { encoding = crypto.Bytes } encryption, _ := builder.New() ptWriter, err := encryption.EncryptingWriter(os.Stdout, encoding) if err != nil { return encErr(err) } _, err = io.Copy(ptWriter, input) if err != nil { return encErr(err) } err = ptWriter.Close() if err != nil { return encErr(err) } if !noArmor { _, err = os.Stdout.WriteString("\n") } return err } func encErr(err error) error { return Err99("encrypt", err) } gosop-1.1.0/cmd/exit_codes.go000066400000000000000000000025361472013725200160710ustar00rootroot00000000000000package 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) Err67 = cli.Exit("A KEYS input is password-protected (locked), and sop cannot unlock it with any of the --with-key-password options", 67) Err69 = cli.Exit("Unsupported subcommand", 69) Err83 = cli.Exit("Options were supplied that are incompatible with each other", 83) Err89 = cli.Exit("The requested profile is unsupported or the indicated subcommand does not accept profiles", 89) ) // 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-1.1.0/cmd/extract_cert.go000066400000000000000000000022231472013725200164230ustar00rootroot00000000000000package cmd import ( "io" "os" openpgp "github.com/ProtonMail/go-crypto/openpgp/v2" "github.com/ProtonMail/gopenpgp/v3/armor" "github.com/ProtonMail/gopenpgp/v3/constants" ) // ExtractCert - Extract a Certificate from a Secret Key func ExtractCert() error { var entities openpgp.EntityList var err error r, armored := armor.IsPGPArmored(os.Stdin) if armored { entities, err = openpgp.ReadArmoredKeyRing(r) } else { entities, err = openpgp.ReadKeyRing(r) } if err != nil { return certErr(err) } var w io.WriteCloser w = os.Stdout if !noArmor { v6 := true for _, entity := range entities { if entity.PrimaryKey.Version != 6 { v6 = false } } w, err = armor.ArmorWriterWithTypeChecksum(w, constants.PublicKeyHeader, !v6) if err != nil { return certErr(err) } } for _, entity := range entities { err = entity.Serialize(w) if err != nil { return certErr(err) } } if !noArmor { err = w.Close() if err != nil { return certErr(err) } _, err = os.Stdout.WriteString("\n") if err != nil { return certErr(err) } } return nil } func certErr(err error) error { return Err99("extract-cert", err) } gosop-1.1.0/cmd/flags.go000066400000000000000000000066461472013725200150450ustar00rootroot00000000000000package cmd import ( "github.com/ProtonMail/gosop/utils" "github.com/urfave/cli/v2" ) // Variables defined by flags var ( backend bool extended bool noArmor bool sopSpec bool sopv bool asType string notBefore string notAfter string password string signWith cli.StringSlice sessionKey string sessionKeyOut string verificationsOut string signaturesOut string verifyWith cli.StringSlice selectedProfile string keyPassword 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, } sopSpecFlag = &cli.BoolFlag{ Name: "sop-spec", Value: false, Destination: &sopSpec, } sopvFlag = &cli.BoolFlag{ Name: "sopv", Value: false, Destination: &sopv, } 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.StringSliceFlag{ 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: "--verifications-out=VERIFICATIONS", Destination: &verificationsOut, } signaturesOutFlag = &cli.StringFlag{ Name: "signatures-out", Usage: "--signatures-out=SIGNATURES", Required: true, Destination: &signaturesOut, } verifyWithFlag = &cli.StringSliceFlag{ Name: "verify-with", Usage: "[--verify-with=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, } selectedProfileFlag = &cli.StringFlag{ Name: "profile", Value: utils.DefaultProfileName, Usage: "--profile=PROFILE", Destination: &selectedProfile, } keyPasswordFlag = &cli.StringFlag{ Name: "with-key-password", Usage: "--with-key-password=PASSWORD", Destination: &keyPassword, } ) gosop-1.1.0/cmd/generate_key.go000066400000000000000000000031201472013725200163730ustar00rootroot00000000000000package cmd import ( "os" "strings" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/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) // ". func GenerateKey(userIDs ...string) error { profile := utils.SelectKeyGenerationProfile(selectedProfile) if profile == nil { return Err89 } pgp := crypto.PGPWithProfile(profile.PgpProfile) // Generate key gen := pgp.KeyGeneration() for _, userID := range userIDs { name, _, email, err := utils.ParseUserID(userID) if err != nil { return kgErr(err) } gen.AddUserId(name, email) } key, err := gen.New().GenerateKeyWithSecurity(profile.SecurityLevel) if err != nil { return kgErr(err) } defer key.ClearPrivateParams() // Lock key if required if keyPassword != "" { pw, err := utils.ReadFileOrEnv(keyPassword) if err != nil { return err } pw = []byte(strings.TrimSpace(string(pw))) key, err = pgp.LockKey(key, pw) if err != nil { return kgErr(err) } } // Output if noArmor { keyBytes, err := key.Serialize() if err != nil { return kgErr(err) } if _, err := os.Stdout.Write(keyBytes); err != nil { return kgErr(err) } } else { armored, err := key.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-1.1.0/cmd/inline_detach.go000066400000000000000000000043061472013725200165260ustar00rootroot00000000000000package cmd import ( "bytes" "io/ioutil" "os" "strings" "github.com/ProtonMail/gopenpgp/v3/armor" "github.com/ProtonMail/gopenpgp/v3/crypto" ) // InlineDetach splits signatures from an inline-signed message. func InlineDetach() error { pgp := crypto.PGP() // Create empty keyring keyRing, err := crypto.NewKeyRing(nil) if err != nil { return inlineDetachErr(err) } builder := pgp.Verify(). VerificationKeys(keyRing) signatureBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return inlineDetachErr(err) } signature := string(signatureBytes) if strings.HasPrefix(signature, "-----BEGIN PGP SIGNED MESSAGE-----") { // handle cleartext verifier, _ := builder.New() result, err := verifier.VerifyCleartext(signatureBytes) if err != nil { return inlineDetachErr(err) } _, err = os.Stdout.Write(result.Cleartext()) if err != nil { return inlineDetachErr(err) } if err := writeSignaturesToFileFromResult(&result.VerifyResult); err != nil { return inlineDetachErr(err) } } else { verifier, _ := builder.New() result, err := verifier.VerifyInline(signatureBytes, crypto.Auto) if err != nil { return inlineDetachErr(err) } _, err = os.Stdout.Write(result.Bytes()) if err != nil { return inlineDetachErr(err) } if err := writeSignaturesToFileFromResult(&result.VerifyResult); err != nil { return inlineDetachErr(err) } } return err } func writeSignaturesToFileFromResult(result *crypto.VerifyResult) error { outputSigsFile, err := os.Create(signaturesOut) if err != nil { return err } defer outputSigsFile.Close() var buf bytes.Buffer for _, sig := range result.Signatures { if err = sig.Signature.Serialize(&buf); err != nil { return inlineDetachErr(err) } } if noArmor { if _, err = outputSigsFile.Write(buf.Bytes()); err != nil { return inlineDetachErr(err) } } else { armored, err := armor.ArmorPGPSignature(buf.Bytes()) if err != nil { return inlineDetachErr(err) } if _, err = outputSigsFile.WriteString(armored); err != nil { return inlineDetachErr(err) } } if err = outputSigsFile.Close(); err != nil { return err } return nil } func inlineDetachErr(err error) error { return Err99("inline-detach", err) } gosop-1.1.0/cmd/inline_sign.go000066400000000000000000000044211472013725200162340ustar00rootroot00000000000000package cmd import ( "io/ioutil" "os" "unicode/utf8" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/crypto" ) 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 var pw []byte if keyPassword != "" { pw, err = utils.ReadSanitizedPassword(keyPassword) if err != nil { return inlineSignErr(err) } } keyRing, failUnlock, err := utils.CollectKeysPassword(pw, keyFilenames...) if failUnlock { return Err67 } if err != nil { return inlineSignErr(err) } if keyRing.CountEntities() == 0 { return Err41 } defer keyRing.ClearPrivateParams() pgp := crypto.PGP() builder := pgp.Sign().SigningKeys(keyRing) // Message var messageBytes []byte if messageBytes, err = ioutil.ReadAll(os.Stdin); err != nil { return inlineSignErr(err) } if (asType == clearsignedOpt || asType == textOpt) && !utf8.Valid(messageBytes) { return Err53 } if noArmor && asType == clearsignedOpt { return Err83 } encoding := crypto.Armor if noArmor { encoding = crypto.Bytes } if asType == clearsignedOpt { signer, _ := builder.Utf8().New() signedMessage, err := signer.SignCleartext(messageBytes) if err != nil { return inlineSignErr(err) } if _, err = os.Stdout.Write(append(signedMessage, byte('\n'))); err != nil { return inlineSignErr(err) } } else if asType == textOpt { signer, _ := builder.Utf8().New() signedMessage, err := signer.Sign(messageBytes, encoding) if err != nil { return inlineSignErr(err) } if _, err = os.Stdout.Write(append(signedMessage, byte('\n'))); err != nil { return inlineSignErr(err) } } else { signer, _ := builder.New() signedMessage, err := signer.Sign(messageBytes, encoding) if err != nil { return inlineSignErr(err) } if _, err = os.Stdout.Write(append(signedMessage, byte('\n'))); err != nil { return inlineSignErr(err) } } return nil } func inlineSignErr(err error) error { return Err99("inline-sign", err) } gosop-1.1.0/cmd/inline_verify.go000066400000000000000000000045111472013725200166000ustar00rootroot00000000000000package cmd import ( "io/ioutil" "os" "strings" "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)") return Err19 } timeFrom, timeTo, err := utils.ParseDates(notBefore, notAfter) if err != nil { return inlineVerErr(err) } pgp := crypto.PGP() // Collect keyring keyRing, err := utils.CollectKeys(input...) if err != nil { return inlineVerErr(err) } builder := pgp.Verify(). VerificationKeys(keyRing) signatureBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return inlineVerErr(err) } signature := string(signatureBytes) if strings.HasPrefix(signature, "-----BEGIN PGP SIGNED MESSAGE-----") { // handle cleartext verifier, _ := builder.New() result, err := verifier.VerifyCleartext(signatureBytes) if err != nil { return inlineVerErr(err) } result.ConstrainToTimeRange(timeFrom.Unix(), timeTo.Unix()) if result.SignatureError() != nil { return Err3 } _, err = os.Stdout.Write(result.Cleartext()) if err != nil { return inlineVerErr(err) } if verificationsOut != "" { if err := writeVerificationToFileFromResult(&result.VerifyResult); err != nil { return inlineVerErr(err) } } } else { verifier, _ := builder.New() result, err := verifier.VerifyInline(signatureBytes, crypto.Auto) if err != nil { return inlineVerErr(err) } result.ConstrainToTimeRange(timeFrom.Unix(), timeTo.Unix()) if result.SignatureError() != nil { return Err3 } _, err = os.Stdout.Write(result.Bytes()) if err != nil { return inlineVerErr(err) } if verificationsOut != "" { if err := writeVerificationToFileFromResult(&result.VerifyResult); err != nil { return inlineVerErr(err) } } } return err } func writeVerificationToFileFromResult(result *crypto.VerifyResult) error { outputVerFile, err := utils.OpenOutFile(verificationsOut) if err != nil { return err } defer outputVerFile.Close() if err = writeVerificationToOutput(outputVerFile, result); err != nil { return err } if err = outputVerFile.Close(); err != nil { return err } return nil } func inlineVerErr(err error) error { return Err99("inline-verify", err) } gosop-1.1.0/cmd/list_profiles.go000066400000000000000000000021621472013725200166140ustar00rootroot00000000000000package cmd import ( "fmt" "strings" "github.com/ProtonMail/gosop/utils" ) const encryptCommand = "encrypt" const keyGenCommand = "generate-key" func ListProfiles(commands ...string) error { if len(commands) < 1 { return Err89 } command := commands[0] switch command { case keyGenCommand: if err := printProfiles(utils.KeyGenerationProfiles); err != nil { listProfileErr(err) } case encryptCommand: if err := printProfiles(utils.EncryptionProfiles); err != nil { listProfileErr(err) } default: return Err89 } return nil } func printProfiles(profiles []*utils.SopProfile) error { for id, profile := range profiles { aliases := "" if len(profile.Names) > 2 { aliases = fmt.Sprintf(" (aliases: %s)", strings.Join(profile.Names[1:], ", ")) } else if len(profile.Names) > 1 { aliases = fmt.Sprintf(" (alias: %s)", profile.Names[1]) } _, err := fmt.Printf("%s: %s%s\n", profile.Names[0], profile.Description, aliases) if err != nil { return listProfileErr(err) } if id > 2 { break } } return nil } func listProfileErr(err error) error { return Err99("list_profiles", err) } gosop-1.1.0/cmd/sign.go000066400000000000000000000030001472013725200146660ustar00rootroot00000000000000package cmd import ( "io" "os" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/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 } pgp := crypto.PGP() // Signer keyring var keyRing *crypto.KeyRing var err error var pw []byte if keyPassword != "" { pw, err = utils.ReadSanitizedPassword(keyPassword) if err != nil { return signErr(err) } } keyRing, failUnlock, err := utils.CollectKeysPassword(pw, keyFilenames...) if failUnlock { return Err67 } if err != nil { return signErr(err) } if keyRing.CountEntities() == 0 { return Err41 } defer keyRing.ClearPrivateParams() builder := pgp.Sign().SigningKeys(keyRing).Detached() // Prepare sign if asType == textOpt { builder.Utf8() } encoding := crypto.Armor if noArmor { encoding = crypto.Bytes } // Sign signer, _ := builder.New() ptWriter, err := signer.SigningWriter(os.Stdout, encoding) if err != nil { return signErr(err) } _, err = io.Copy(ptWriter, os.Stdin) if err != nil { return signErr(err) } err = ptWriter.Close() if err != nil { return signErr(err) } if !noArmor { if _, err = os.Stdout.WriteString("\n"); err != nil { return signErr(err) } } return nil } func signErr(err error) error { return Err99("sign", err) } gosop-1.1.0/cmd/verify.go000066400000000000000000000044351472013725200152470ustar00rootroot00000000000000package cmd import ( "bytes" "os" "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/ProtonMail/gosop/utils" "github.com/ProtonMail/gopenpgp/v3/armor" "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) { case 0: return Err3 case 1: println("Please provide a certificate (public key)") return Err19 } timeFrom, timeTo, err := utils.ParseDates(notBefore, notAfter) if err != nil { return verErr(err) } pgp := crypto.PGP() // Collect keyring keyRing, err := utils.CollectKeys(input[1:]...) if err != nil { return verErr(err) } verifier, _ := pgp.Verify(). VerificationKeys(keyRing). New() // Collect signature sigBytes, err := utils.ReadFileOrEnv(input[0]) if err != nil { return verErr(err) } var signature []byte signature, err = armor.UnarmorBytes(sigBytes) if err != nil { signature = sigBytes } dataReader, err := verifier.VerifyingReader(os.Stdin, bytes.NewReader(signature), crypto.Auto) if err != nil { return verErr(err) } result, err := dataReader.DiscardAllAndVerifySignature() if err != nil { return verErr(err) } result.ConstrainToTimeRange(timeFrom.Unix(), timeTo.Unix()) if result.SignatureError() != nil { return Err3 } if err = writeVerificationToOutput(os.Stdout, result); err != nil { return verErr(err) } return err } func writeVerificationToOutput(out *os.File, result *crypto.VerifyResult) error { var ver string if result.SignatureError() != nil { return nil } for _, signature := range result.Signatures { if signature.SignatureError != nil || signature.Signature == nil { continue } var mode string signType := signature.Signature.SigType if signType == packet.SigTypeText { mode = "mode:text" } else { mode = "mode:binary" } creationTime := signature.Signature.CreationTime fingerprintSign := signature.SignedBy.GetFingerprintBytes() fingerprintPrimarySign := signature.SignedBy.GetFingerprintBytes() ver = utils.VerificationString( creationTime, fingerprintSign, fingerprintPrimarySign, mode, ) if _, err := out.WriteString(ver + "\n"); err != nil { return err } } return nil } func verErr(err error) error { return Err99("verify", err) } gosop-1.1.0/cmd/version.go000066400000000000000000000032501472013725200154220ustar00rootroot00000000000000package cmd import ( "errors" "os" "runtime/debug" "strings" "github.com/ProtonMail/gopenpgp/v3/constants" ) const VERSION = "1.1.0" const SOP_VERSION = "~draft-dkg-openpgp-stateless-cli-06" const SOPV_VERSION = "1.0" // Version prints version information about gosop, and/or the // underlying OpenPGP library/libraries. func Version() error { if sopSpec { _, err := os.Stdout.WriteString(SOP_VERSION + "\n") if err != nil { return versionErr(err) } return nil } if sopv { _, err := os.Stdout.WriteString(SOPV_VERSION + "\n") if err != nil { return versionErr(err) } return nil } 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-1.1.0/go.mod000066400000000000000000000004201472013725200137450ustar00rootroot00000000000000module github.com/ProtonMail/gosop require ( github.com/ProtonMail/go-crypto v1.1.0 github.com/ProtonMail/gopenpgp/v3 v3.0.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/urfave/cli/v2 v2.2.0 ) go 1.14 gosop-1.1.0/go.sum000066400000000000000000000164061472013725200140050ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ProtonMail/go-crypto v1.1.0 h1:OnlSGxXflfrWJESDsGQOmACNQRM9IflG3q8XTrOqvbE= github.com/ProtonMail/go-crypto v1.1.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/gopenpgp/v3 v3.0.0 h1:lqsrNKFv0U4tRYRdaMA8qzh3TACaDTg3iJiv7MFFmuM= github.com/ProtonMail/gopenpgp/v3 v3.0.0/go.mod h1:XXZYIzOSEtEhKCyDcq/xepg3zuANcL5amIjwF4XZbNg= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/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/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 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-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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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/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.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-1.1.0/main.go000066400000000000000000000012211472013725200141120ustar00rootroot00000000000000package main import ( "log" "os" "sort" "github.com/ProtonMail/gosop/cmd" "github.com/urfave/cli/v2" ) func main() { app := &cli.App{ Name: "gosop", Usage: "Stateless OpenPGP implementation for GopenPGP", Version: cmd.VERSION, Authors: []*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-1.1.0/man/000077500000000000000000000000001472013725200134165ustar00rootroot00000000000000gosop-1.1.0/man/gosop.1000066400000000000000000000072411472013725200146330ustar00rootroot00000000000000.\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 .TH "GOSOP" "1" "August 2024" "" .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-1.1.0/man/gosop.1.ronn000066400000000000000000000063261472013725200156110ustar00rootroot00000000000000gosop(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-1.1.0/scripts/000077500000000000000000000000001472013725200143325ustar00rootroot00000000000000gosop-1.1.0/scripts/tests.sh000077500000000000000000000206521472013725200160400ustar00rootroot00000000000000#!/bin/bash profileKeyGen="default" profileEnc="default" if [ $# -le 2 ] || [ $2 != '-v' ]; then echo "Usage: test.sh sop.binary -v [profile generate-key] [profile encryption]" exit 1 fi if [ ! -z "$4" ]; then profileKeyGen=$4 fi if [ ! -z "$4" ]; then profileEnc=$5 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 signatures=$data/signatures.pgp 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 "version" $sop version check_exit_code $? 0 comm "list-profiles generate-key" $sop list-profiles generate-key check_exit_code $? 0 comm "list-profiles encrypt" $sop list-profiles encrypt check_exit_code $? 0 comm "generate-key --no-armor" $sop generate-key --no-armor --profile=$profileKeyGen --with-key-password=$password 'Bob Lovelace ' > $bob_secret check_exit_code $? 0 comm "generate-key --no-armor" $sop generate-key --no-armor --profile=$profileKeyGen --with-key-password=$password 'Bob Lovelace ' > $bob_secret check_exit_code $? 0 comm "generate-key" $sop generate-key --profile=$profileKeyGen --with-key-password=$password '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 comm "sign" $sop sign --as=text --with-key-password=$password $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 sleep 1 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 --as=clearsigned" $sop inline-sign --as=clearsigned --with-key-password=$password $alice_secret < $message > $signed check_exit_code $? 0 my_cat $signed comm "inline-verify clearsigned" $sop inline-verify --verifications-out=$verification $alice_public < $signed > $verified check_exit_code $? 0 my_cat $verification my_cat $verified diff $message $verified check_exit_code $? 0 comm "inline-detach clearsigned" $sop inline-detach --signatures-out=$signatures < $signed > $verified check_exit_code $? 0 diff $message $verified check_exit_code $? 0 my_cat $signatures $sop verify $signatures $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "inline-detach --no-armor clearsigned" $sop inline-detach --no-armor --signatures-out=$signatures < $signed > $verified check_exit_code $? 0 diff $message $verified check_exit_code $? 0 my_cat $signatures $sop verify $signatures $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "inline-sign --as=text" $sop inline-sign --as=text --with-key-password=$password $alice_secret < $message > $signed check_exit_code $? 0 my_cat $signed | my_cat comm "inline-verify text" $sop inline-verify --verifications-out=$verification $alice_public < $signed > $verified check_exit_code $? 0 my_cat $verification my_cat $verified diff $message <( tr -d '\r' < $verified ) check_exit_code $? 0 comm "inline-detach text" $sop inline-detach --signatures-out=$signatures < $signed > $verified check_exit_code $? 0 diff $message <( tr -d '\r' < $verified ) check_exit_code $? 0 my_cat $signatures $sop verify $signatures $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "inline-detach --no-armor text" $sop inline-detach --no-armor --signatures-out=$signatures < $signed > $verified check_exit_code $? 0 diff $message <( tr -d '\r' < $verified ) check_exit_code $? 0 my_cat $signatures $sop verify $signatures $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "inline-sign --as=binary" $sop inline-sign --as=binary --with-key-password=$password $alice_secret < $message > $signed check_exit_code $? 0 my_cat $signed | my_cat comm "inline-verify binary" $sop inline-verify --verifications-out=$verification $alice_public < $signed > $verified check_exit_code $? 0 my_cat $verification my_cat $verified diff $message $verified check_exit_code $? 0 comm "inline-detach binary" $sop inline-detach --signatures-out=$signatures < $signed > $verified check_exit_code $? 0 diff $message $verified check_exit_code $? 0 my_cat $signatures $sop verify $signatures $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "inline-detach --no-armor binary" $sop inline-detach --no-armor --signatures-out=$signatures < $signed > $verified check_exit_code $? 0 diff $message $verified check_exit_code $? 0 my_cat $signatures $sop verify $signatures $alice_public < $message > $verification check_exit_code $? 0 my_cat $verification comm "encrypt --with-password" $sop encrypt --with-password=$password --profile=$profileEnc < $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 --profile=$profileEnc $alice_public < $message > $encrypted check_exit_code $? 0 my_cat $encrypted comm "decrypt" $sop decrypt --with-key-password=$password $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 --with-key-password=$password $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 --with-key-password=$password $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-1.1.0/utils/000077500000000000000000000000001472013725200140035ustar00rootroot00000000000000gosop-1.1.0/utils/profile.go000066400000000000000000000045151472013725200157770ustar00rootroot00000000000000package utils import ( "strings" "github.com/ProtonMail/gopenpgp/v3/constants" "github.com/ProtonMail/gopenpgp/v3/profile" ) const DefaultProfileName string = "default" var EncryptionProfiles = createEncryptionProfiles() var KeyGenerationProfiles = createKeyGenerationProfiles() type SopProfile struct { Names []string Description string PgpProfile *profile.Custom SecurityLevel int8 // Only applies to key generation. } func SelectKeyGenerationProfile(name string) (selectedProfile *SopProfile) { lowercase := strings.ToLower(name) for _, keyGenProfile := range KeyGenerationProfiles { for _, name := range keyGenProfile.Names { if name == lowercase { selectedProfile = keyGenProfile } } } return } func SelectEncryptionProfile(name string) (selectedProfile *SopProfile) { lowercase := strings.ToLower(name) for _, encProfile := range EncryptionProfiles { for _, name := range encProfile.Names { if name == lowercase { selectedProfile = encProfile } } } return } func createEncryptionProfiles() []*SopProfile { return []*SopProfile{ { Names: []string{"default", "compatibility", "rfc4880"}, Description: "Use CFB encryption (SEIPDv1)", PgpProfile: profile.Default(), SecurityLevel: constants.StandardSecurity, }, { Names: []string{"performance", "security", "rfc9580"}, Description: "Use AEAD encryption (SEIPDv2)", PgpProfile: profile.RFC9580(), SecurityLevel: constants.StandardSecurity, }, } } func createKeyGenerationProfiles() []*SopProfile { return []*SopProfile{ { Names: []string{"default"}, Description: "Generate v4 keys using Curve25519", PgpProfile: profile.Default(), SecurityLevel: constants.StandardSecurity, }, { Names: []string{"compatibility", "rfc4880"}, Description: "Generate v4 keys using 3072-bit RSA", PgpProfile: profile.RFC4880(), SecurityLevel: constants.StandardSecurity, }, { Names: []string{"performance", "rfc9580"}, Description: "Generate v6 keys using Ed25519/X25519", PgpProfile: profile.RFC9580(), SecurityLevel: constants.StandardSecurity, }, { Names: []string{"security"}, Description: "Generate v6 keys using Ed448/X448", PgpProfile: profile.RFC9580(), SecurityLevel: constants.HighSecurity, }, } } gosop-1.1.0/utils/utils.go000066400000000000000000000117461472013725200155030ustar00rootroot00000000000000// Package utils contains helper functions related to the sop implementation of // gopenpgp. package utils import ( "bytes" "errors" "fmt" "io/ioutil" "os" "strconv" "strings" "time" openpgp "github.com/ProtonMail/go-crypto/openpgp/v2" "github.com/ProtonMail/gopenpgp/v3/armor" "github.com/ProtonMail/gopenpgp/v3/crypto" ) // Time format layouts const ( layout = time.RFC3339 // See RFC3339 or ISO-8601 layoutSecondary = "20060102T150405Z0700" // If the above fails ) const ArmorPrefix = "" // 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, mode string) string { formattedTime := timestamp.UTC().Format(layout) return fmt.Sprintf("%v %X %X %s", formattedTime, fgp, primFgp, mode) } // 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 := CollectKeysPassword([]byte{}, keyFilenames...) return keyRing, err } // CollectKeysPassword forms a crypto.KeyRing with all the keys provided in the input // files and tries to unlock them with password if locked. It returns the keyring, // a bool indicating an unlock issue, and an error. func CollectKeysPassword(password []byte, keyFilenames ...string) (*crypto.KeyRing, bool, error) { keyRing, err := crypto.NewKeyRing(nil) if err != nil { return keyRing, false, err } for _, filename := range keyFilenames { keyData, err := ReadFileOrEnv(filename) if err != nil { return keyRing, false, err } var entities openpgp.EntityList r, armored := armor.IsPGPArmored(bytes.NewReader(keyData)) if armored { entities, err = openpgp.ReadArmoredKeyRing(r) } else { entities, err = openpgp.ReadKeyRing(r) } if err != nil { return keyRing, false, err } for _, entity := range entities { key, err := crypto.NewKeyFromEntity(entity) if err != nil { return keyRing, false, err } locked, err := key.IsLocked() if err == nil && locked { unlockedKey, err := key.Unlock(password) if err != nil { // unlock failed return nil, true, err } key = unlockedKey } if err = keyRing.AddKey(key); err != nil { return nil, false, err } } } return keyRing, false, err } func ReadFileOrEnv(filename string) ([]byte, error) { if len(filename) > 4 && filename[0:5] == "@ENV:" { return []byte(os.Getenv(filename[5:])), nil } if len(filename) > 3 && 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) } func ReadSanitizedPassword(filename string) ([]byte, error) { pw, err := ReadFileOrEnv(filename) if err != nil { return nil, err } pw = []byte(strings.TrimSpace(string(pw))) return pw, nil } func CollectFilesFromCliSlice(data []string) []string { result := []string{} for _, value := range data { result = append(result, strings.Split(value, " ")...) } return result } func OpenOutFile(filename string) (*os.File, error) { if len(filename) > 3 && filename[0:4] == "@FD:" { fd, err := strconv.ParseUint(filename[4:], 10, strconv.IntSize) if err != nil { return nil, err } return os.NewFile(uintptr(fd), filename), nil } return os.Create(filename) }