├── .gitignore ├── Gopkg.toml ├── .travis.yml ├── fixtures └── signed.attached ├── Gopkg.lock ├── Makefile ├── lookup ├── localpgp_test.go ├── lookup_test.go ├── localpgp.go ├── keybase.go └── lookup.go ├── signature_test.go ├── signature.go ├── script_test.go ├── main.go ├── script.go ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | files/ 2 | vendor/ 3 | 4 | pipethis* 5 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | [[constraint]] 2 | version = "^1.1" 3 | name = "github.com/stretchr/testify" 4 | 5 | [[constraint]] 6 | branch = "master" 7 | name = "golang.org/x/crypto" 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | os: 4 | - linux 5 | - osx 6 | env: 7 | global: 8 | - PATH="$HOME/gopath/bin:$HOME/bin:$PATH" 9 | 10 | # need to fix the linter before enabling tip again 11 | matrix: 12 | include: 13 | - os: linux 14 | go: tip 15 | 16 | notifications: 17 | email: 18 | on_success: never 19 | 20 | go: 21 | - 1.7.6 22 | - 1.8.3 23 | 24 | before_install: 25 | - go get github.com/mattn/goveralls 26 | 27 | script: 28 | - make test 29 | - goveralls -service=travis-ci 30 | -------------------------------------------------------------------------------- /fixtures/signed.attached: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA256 3 | 4 | # PIPETHIS_AUTHOR 5AA6F296 5 | 6 | echo this is my file and there will be one match 7 | -----BEGIN PGP SIGNATURE----- 8 | 9 | wsFcBAEBCAAQBQJW7YbQCRALxruWWqbylgAAAwcQACrkjS8FVy6T5+pdrcYQToo3 10 | 41ppFNARFLor94lgeDzzuR4XkfNR4Cms8saceKTLfkan5nxnS/w6c/koFPskDHyS 11 | 2mZL0dDia7KUNMPt2rZGC6CLkKXs83eWAFZr4S2Y4gUYQGatAzvhTc9kgFtPRyBn 12 | DA6FT4zj5JK4CcmxHITHeKokCwOdje80kGYlX2u3e5bqQpTbKLQ2oLMMZFfEXrDJ 13 | Y3QOOKmBBKrS1TXVBReauojXVNNjADPZHYQzGoxgZ0GjTESkEjzrCjQnnTMcIN4C 14 | nlskH9q28xyeDWBj+H7gNOpQZ2B3fs0cUs05Ucce/xBZeHaXqaW3GFmfdCbv1J9A 15 | CjgVMGojAjTZf47y1mmHL9yh9gkXaLTYyO37MNku+cR0ntKIi3VyIHopiljYPGOG 16 | r3EFxZOHg40QalMezFIfUG0S2MLpJ9+d5cvgdzHHHZYeL49L17U6eePnbt++xmGy 17 | RrnWb1C/OXxYfCveB42v1/gg9novYYZ8/n/OLCsOL37v+b8rwEjNufmv+7G7DqOU 18 | ejljGRd27WQSBaYQMovWGpgLmMyCiW6wnUbYFivlOTcnMvOnRsBXqCJt0jdpFEBp 19 | hfZr8sPYSgWIDnkt7DWIwd8/eap5mgMkC5j+Q81Lcv01OfDmppRlMWRf+a4BpHFd 20 | FiV3SWOrHc2hIbLugeNf 21 | =tnov 22 | -----END PGP SIGNATURE----- 23 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/davecgh/go-spew" 6 | packages = ["spew"] 7 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 8 | version = "v1.1.0" 9 | 10 | [[projects]] 11 | name = "github.com/pmezard/go-difflib" 12 | packages = ["difflib"] 13 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 14 | version = "v1.0.0" 15 | 16 | [[projects]] 17 | name = "github.com/stretchr/testify" 18 | packages = ["assert","require","suite"] 19 | revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" 20 | version = "v1.1.4" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "golang.org/x/crypto" 25 | packages = ["cast5","openpgp","openpgp/armor","openpgp/clearsign","openpgp/elgamal","openpgp/errors","openpgp/packet","openpgp/s2k"] 26 | revision = "e1a4589e7d3ea14a3352255d04b6f1a418845e5e" 27 | 28 | [solve-meta] 29 | analyzer-name = "dep" 30 | analyzer-version = 1 31 | inputs-digest = "4fe170334603e2ee2092c4dcb62076416836204f8755e1b295d6da91ae171ce3" 32 | solver-name = "gps-cdcl" 33 | solver-version = 1 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | bin = pipethis 2 | build = $(shell git describe --tags)-$(shell go env GOOS)-$(shell go env GOARCH) 3 | goversion = $(word 3, $(shell go version)) 4 | dist = $(bin)-$(build).tar.bz2 5 | files = $(shell go list ./... | grep -v vendor) 6 | 7 | all: test build 8 | 9 | .PHONY: test 10 | test: deps lint 11 | go test -cover -race $(files) 12 | 13 | .PHONY: build 14 | build: deps 15 | go build -o $(bin) -ldflags "-w -s -X main.bin=$(bin) -X main.build=$(build) -X main.builder=$(goversion)" 16 | 17 | .PHONY: clean 18 | clean: dist-clean 19 | # go clean -r hits a bunch of the stdlib, which isn't ideal 20 | go clean -i ./... 21 | rm -f $(bin)* 22 | 23 | .PHONY: dist 24 | dist: build 25 | tar -jcf $(dist) $(bin) 26 | gpg --detach-sign --armor --output $(dist).sig $(dist) 27 | 28 | .PHONY: dist-clean 29 | dist-clean: 30 | rm -f $(dist)* 31 | 32 | .PHONY: lint 33 | lint: 34 | @go get github.com/golang/lint/golint 35 | @go get honnef.co/go/tools/cmd/gosimple 36 | @go get honnef.co/go/tools/cmd/staticcheck 37 | @if gofmt -l -s . |grep -v vendor/; then \ 38 | echo "found formatting errors. run gofmt -s -d ." && exit 1; \ 39 | fi 40 | go vet $(files) 41 | staticcheck $(files) 42 | golint $(files) 43 | gosimple $(files) 44 | 45 | .PHONY: deps 46 | deps: 47 | @go get github.com/golang/dep/cmd/dep 48 | dep ensure 49 | 50 | .PHONY: watch 51 | watch: 52 | @while true; do \ 53 | go test -cover -race $(files); \ 54 | echo watching for changes...; \ 55 | inotifywait -qqre modify,create,delete,move --exclude "\.git" .; \ 56 | done 57 | -------------------------------------------------------------------------------- /lookup/localpgp_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | pipethis: Stop piping the internet into your shell 3 | Copyright 2016 Ellotheth 4 | 5 | Use of this source code is governed by the GNU Public License version 2 6 | (GPLv2). You should have received a copy of the GPLv2 along with your copy of 7 | the source. If not, see http://www.gnu.org/licenses/gpl-2.0.html. 8 | */ 9 | 10 | package lookup 11 | 12 | import ( 13 | "os" 14 | "testing" 15 | 16 | "github.com/stretchr/testify/suite" 17 | ) 18 | 19 | type LocalPGPTest struct { 20 | suite.Suite 21 | } 22 | 23 | func (s *LocalPGPTest) TestIsMatchMatchesOnFingerprint() { 24 | local := LocalPGPService{} 25 | user := User{Fingerprint: "foobar"} 26 | 27 | s.True(local.isMatch("oba", user)) 28 | s.True(local.isMatch("foo", user)) 29 | s.True(local.isMatch("bar", user)) 30 | s.True(local.isMatch("FOOBAR", user)) 31 | } 32 | 33 | func (s *LocalPGPTest) TestIsMatchMatchesOnEmails() { 34 | local := LocalPGPService{} 35 | user := User{Emails: []string{"foobar", "bizbaz", "THINGS"}} 36 | 37 | s.True(local.isMatch("oba", user)) 38 | s.True(local.isMatch("foo", user)) 39 | s.True(local.isMatch("bar", user)) 40 | s.True(local.isMatch("FOOBAR", user)) 41 | s.True(local.isMatch("zba", user)) 42 | s.True(local.isMatch("thin", user)) 43 | } 44 | 45 | func (s *LocalPGPTest) TestIsMatchFailsWithoutMatches() { 46 | local := LocalPGPService{} 47 | user := User{} 48 | 49 | s.False(local.isMatch("foo", user)) 50 | } 51 | 52 | func (s *LocalPGPTest) TestGnupgHomeOverride() { 53 | os.Setenv("GNUPGHOME", "/foo") 54 | _, err := NewLocalPGPService() 55 | s.EqualError(err, "stat /foo/pubring.gpg: no such file or directory") 56 | os.Unsetenv("GNUPGHOME") 57 | } 58 | 59 | func (s *LocalPGPTest) TestBuildRingfileName() { 60 | cases := []struct { 61 | home string 62 | gnupghome string 63 | expected string 64 | }{ 65 | {"/foo/", "", "/foo/.gnupg/pubring.gpg"}, 66 | {"/foo", "", "/foo/.gnupg/pubring.gpg"}, 67 | {"foo", "", "foo/.gnupg/pubring.gpg"}, 68 | {"foo", "/things", "/things/pubring.gpg"}, 69 | {"foo", "/things/", "/things/pubring.gpg"}, 70 | {"foo", "things/", "things/pubring.gpg"}, 71 | {"", "/things/", "/things/pubring.gpg"}, 72 | {"", "", ".gnupg/pubring.gpg"}, 73 | } 74 | 75 | for _, c := range cases { 76 | os.Setenv("HOME", c.home) 77 | os.Setenv("GNUPGHOME", c.gnupghome) 78 | local := LocalPGPService{} 79 | local.buildRingfileName() 80 | s.Equal(c.expected, local.ringfile) 81 | } 82 | } 83 | 84 | func TestLocalPGPTest(t *testing.T) { 85 | suite.Run(t, new(LocalPGPTest)) 86 | } 87 | -------------------------------------------------------------------------------- /signature_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | pipethis: Stop piping the internet into your shell 3 | Copyright 2016 Ellotheth 4 | 5 | Use of this source code is governed by the GNU Public License version 2 6 | (GPLv2). You should have received a copy of the GPLv2 along with your copy of 7 | the source. If not, see http://www.gnu.org/licenses/gpl-2.0.html. 8 | */ 9 | 10 | package main 11 | 12 | import ( 13 | "io/ioutil" 14 | "os" 15 | "testing" 16 | 17 | "github.com/stretchr/testify/suite" 18 | ) 19 | 20 | type SigTest struct { 21 | suite.Suite 22 | } 23 | 24 | func (s *SigTest) TestNewCreatesFilename() { 25 | sig := NewSignature(nil, &Script{filename: "foo.sh"}, "") 26 | s.Equal("foo.sh.sig", sig.Name()) 27 | } 28 | 29 | func (s *SigTest) TestSourceUsesSaved() { 30 | sig := Signature{source: "foosig"} 31 | s.Equal("foosig", sig.Source()) 32 | } 33 | 34 | func (s *SigTest) TestSourceBuildsFromScript() { 35 | sig := Signature{script: &Script{source: "scriptsource"}} 36 | s.Equal("scriptsource.sig", sig.Source()) 37 | } 38 | 39 | func (s *SigTest) TestSourceEmptyWithClearsignedScript() { 40 | sig := Signature{script: &Script{clearsigned: true}} 41 | s.Equal("", sig.Source()) 42 | } 43 | 44 | func (s *SigTest) TestDownloadIsNoopWithClearsignedScript() { 45 | sig := Signature{script: &Script{clearsigned: true}} 46 | s.NoError(sig.Download()) 47 | } 48 | 49 | func (s *SigTest) TestDownloadFailsWithoutSource() { 50 | sig := Signature{} 51 | s.Error(sig.Download()) 52 | } 53 | 54 | func (s *SigTest) TestDownloadFailsWithNonexistentSource() { 55 | sig := Signature{source: "not-a-real-file"} 56 | s.Error(sig.Download()) 57 | } 58 | 59 | func (s *SigTest) TestDownloadFailsWithoutDestinationName() { 60 | f, err := ioutil.TempFile("", "pipethis-test-") 61 | if err != nil { 62 | s.Fail("Failed creating the test file") 63 | } 64 | 65 | sig := Signature{source: f.Name()} 66 | defer os.Remove(sig.Source()) 67 | 68 | s.Error(sig.Download()) 69 | } 70 | 71 | func (s *SigTest) TestDownloadCopiesFile() { 72 | f, err := ioutil.TempFile("", "pipethis-test-") 73 | if err != nil { 74 | s.Fail("Failed creating the test file") 75 | } 76 | 77 | sig := Signature{source: f.Name(), filename: "destination"} 78 | defer os.Remove(sig.Source()) 79 | defer os.Remove(sig.Name()) 80 | 81 | expected := []byte("file contents, wooo") 82 | ioutil.WriteFile(sig.Source(), expected, os.ModePerm) 83 | 84 | s.NoError(sig.Download()) 85 | 86 | actual, _ := ioutil.ReadFile(sig.Name()) 87 | s.Equal(expected, actual) 88 | } 89 | 90 | func (s *SigTest) TestBodyFailsWithoutFiles() { 91 | sig := Signature{} 92 | _, err := sig.Body() 93 | s.Error(err) 94 | } 95 | 96 | func TestSignatureTest(t *testing.T) { 97 | suite.Run(t, new(SigTest)) 98 | } 99 | -------------------------------------------------------------------------------- /lookup/lookup_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | pipethis: Stop piping the internet into your shell 3 | Copyright 2016 Ellotheth 4 | 5 | Use of this source code is governed by the GNU Public License version 2 6 | (GPLv2). You should have received a copy of the GPLv2 along with your copy of 7 | the source. If not, see http://www.gnu.org/licenses/gpl-2.0.html. 8 | */ 9 | 10 | package lookup 11 | 12 | import ( 13 | "os" 14 | "strings" 15 | "testing" 16 | 17 | "github.com/stretchr/testify/suite" 18 | ) 19 | 20 | type LookupTest struct { 21 | home string 22 | suite.Suite 23 | } 24 | 25 | func (s *LookupTest) SetupSuite() { 26 | s.home = os.Getenv("HOME") 27 | os.Setenv("HOME", strings.TrimRight(os.TempDir(), "/")) 28 | } 29 | 30 | func (s *LookupTest) TeardownSuite() { 31 | os.Setenv("HOME", s.home) 32 | } 33 | 34 | func (s *LookupTest) TestUserString() { 35 | expected := ` Identifier: me 36 | Twitter: 37 | Github: me 38 | Hacker News: 39 | Reddit: 40 | Fingerprint: foobitybar 41 | Site: google.com 42 | Site: yahoo.com 43 | Email: me@myself 44 | Email: you@yourself 45 | ` 46 | user := User{ 47 | Username: "me", 48 | Fingerprint: "foobitybar", 49 | GitHub: "me", 50 | Sites: []string{"google.com", "yahoo.com"}, 51 | Emails: []string{"me@myself", "you@yourself"}, 52 | } 53 | 54 | s.Equal(expected, user.String()) 55 | } 56 | 57 | func (s *LookupTest) TestNewKeyServiceBailsOnUnrecognizedType() { 58 | service, err := NewKeyService("foo", false) 59 | s.Nil(service) 60 | s.EqualError(err, "Unrecognized key service") 61 | } 62 | 63 | func (s *LookupTest) TestNewKeyServiceAcceptsKeybaseWithoutPipe() { 64 | service, err := NewKeyService("keybase", false) 65 | 66 | s.NoError(err) 67 | s.IsType(&KeybaseService{}, service) 68 | } 69 | 70 | func (s *LookupTest) TestNewKeyServiceForcesLocalWithPipe() { 71 | _, err := NewKeyService("keybase", true) 72 | s.Error(err) 73 | 74 | perr, ok := err.(*os.PathError) 75 | s.True(ok) 76 | s.Equal(os.Getenv("HOME")+"/.gnupg/pubring.gpg", perr.Path) 77 | } 78 | 79 | func (s *LookupTest) TestChooseSingleMatchBailsWithoutMatches() { 80 | user, err := chooseSingleMatch([]User{}) 81 | 82 | s.Error(err) 83 | s.Equal(User{}, user) 84 | } 85 | 86 | func (s *LookupTest) TestChooseSingleMatchBailsWithMoreThanOneMatch() { 87 | user, err := chooseSingleMatch([]User{{}, {}}) 88 | 89 | s.Error(err) 90 | s.Equal(User{}, user) 91 | } 92 | 93 | func (s *LookupTest) TestChooseSingleMatchReturnsSingleMatch() { 94 | user, err := chooseSingleMatch([]User{{Username: "foo"}}) 95 | 96 | s.NoError(err) 97 | s.Equal("foo", user.Username) 98 | } 99 | 100 | func TestLookupTest(t *testing.T) { 101 | suite.Run(t, new(LookupTest)) 102 | } 103 | -------------------------------------------------------------------------------- /signature.go: -------------------------------------------------------------------------------- 1 | /* 2 | pipethis: Stop piping the internet into your shell 3 | Copyright 2016 Ellotheth 4 | 5 | Use of this source code is governed by the GNU Public License version 2 6 | (GPLv2). You should have received a copy of the GPLv2 along with your copy of 7 | the source. If not, see http://www.gnu.org/licenses/gpl-2.0.html. 8 | */ 9 | 10 | package main 11 | 12 | import ( 13 | "errors" 14 | "io" 15 | "os" 16 | 17 | "golang.org/x/crypto/openpgp" 18 | ) 19 | 20 | // Signature represents the PGP signature to be verified against a key and 21 | // Script. 22 | type Signature struct { 23 | key openpgp.KeyRing 24 | script *Script 25 | filename string 26 | source string 27 | } 28 | 29 | // NewSignature loads a key ring and Script into a new Signature. 30 | func NewSignature(key openpgp.KeyRing, script *Script, source string) *Signature { 31 | sig := &Signature{key: key, script: script, source: source} 32 | sig.filename = script.Name() + ".sig" 33 | 34 | return sig 35 | } 36 | 37 | // Name is the name of the temporary file holding the signature. 38 | func (s Signature) Name() string { 39 | return s.filename 40 | } 41 | 42 | // Source is the original location of the signature file. It defaults to 43 | //