This repository has been archived by the owner on Feb 2, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathharbour.go
250 lines (207 loc) · 6.13 KB
/
harbour.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package main
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)
// VerifyResult does the thing
type VerifyResult struct {
returnCode int
}
func check(e error) {
if e != nil {
panic(e)
}
}
// Contains returns true if a contains x.
func contains(a []string, x string) bool {
for _, n := range a {
if x == n {
return true
}
}
return false
}
// Returns the index of string x in a, if it exists, and -1 if not.
func indexOf(a []string, x string) int {
for i, n := range a {
if x == n {
return i
}
}
return -1
}
func getGnupg2Binary() string {
path, err := exec.LookPath("gpg2")
if err != nil {
log.Fatal("GnuPG2 is not installed (or gpg2 isn't in your $PATH)")
}
return path
}
func tryVerifyWithGpg2(
signatureFile string,
messageContents string) VerifyResult {
cmd := exec.Command(
getGnupg2Binary(),
"--status-fd",
"1",
"--keyid-format",
"long",
"--verify",
signatureFile,
"-")
// Use a stdin pipe for writing the message contents into
stdin, err := cmd.StdinPipe()
check(err)
// Send GPG2 the message
go func() {
defer stdin.Close()
io.WriteString(stdin, messageContents)
}()
// Git expects both stderr and stdout, so we may as well
// re-use the pipes Harbour uses
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
// Execute gpg2
gpgErr := cmd.Run()
if gpgErr != nil {
// If there was an error, we report it to the higher ups
return VerifyResult{-1}
}
// Otherwise return like all is well.
return VerifyResult{0}
}
func keybaseCliList() []string {
return []string{"pgp", "list"}
}
func keybaseCliVerify(inputFile string) []string {
return []string{"pgp", "verify", "--detached", inputFile, "--infile", "-"}
}
func keybaseCliSign(key string) []string {
return []string{"pgp", "sign", "--detached", "-k", key}
}
func getKeybaseBinary() (string, error) {
var possibleNames = []string{"keybase", "keybase.exe"}
for _, name := range possibleNames {
path, err := exec.LookPath(name)
if err == nil {
return path, nil
}
}
return "", errors.New("Couldn't find Keybase on your $PATH")
}
func getFingerprintFromID(keyID string) string {
fingerprint := ""
keybaseBin, err := getKeybaseBinary()
if err != nil {
return ""
}
out, err := exec.Command(keybaseBin, keybaseCliList()...).Output()
if err != nil {
log.Fatal(err)
}
outString := string(out)
matchString := "PGP Fingerprint: "
for _, line := range strings.Split(strings.TrimSuffix(outString, "\n"), "\n") {
if strings.HasPrefix(line, matchString) && strings.HasSuffix(line, keyID) {
fingerprint = strings.Split(line, matchString)[1]
break
}
}
return fingerprint
}
func verify(args []string) VerifyResult {
// Use Keybase to verify a given commit and commit signature.
//
// If the environment variable HARBOUR_USE_GNUPG2 is set, then we use
// GnuPG2 if Keybase cannot validate the key.
inputFile := args[len(args)-2]
messageBytes, err := ioutil.ReadAll(os.Stdin)
check(err) // fail if we can't read stdin from git
message := string(messageBytes)
keybaseBin, err := getKeybaseBinary()
check(err) // fail if there's no Keybase binary
// Run the verify command in Keybase and capture standard out
procVerify := exec.Command(keybaseBin, keybaseCliVerify(inputFile)...)
stdin, err := procVerify.StdinPipe()
check(err) // fail if we can't open a pipe to Keybase
// Write the commit message to stdin to be verified by Keybase
go func() {
defer stdin.Close()
io.WriteString(stdin, message)
}()
// We'll need to parse the output before sending it to Git to make sure
// Keybase verified the signature; if not, we may want to try GnuPG2
var stderr bytes.Buffer
procVerify.Stderr = &stderr
err = procVerify.Run()
errString := string(stderr.Bytes())
// Note that on Linux keybase prints signature verification to stderr.
// No idea why.
if err != nil || !strings.Contains(errString, "Signature verified.") {
// Keybase couldn't verify the commit
_, useGnupg2 := os.LookupEnv("HARBOUR_USE_GNUPG2")
if useGnupg2 {
fmt.Println("Trying GnuPG2")
return tryVerifyWithGpg2(inputFile, message)
}
fmt.Println("Keybase was unable to verify the signature: the public key is unknown.\n" + "To verify with your GnuPG2 keychain, set HARBOUR_USE_GNUPG2 in your shell.")
} else {
// Keybase verified the commit
// git expects this string to show up in stderr; that's how it verifies the
// signature was generated.
io.WriteString(os.Stdout, "\n[GNUPG:] GOODSIG \n")
// Prepend Keybase's messsage with a label
io.WriteString(os.Stderr, "Keybase: ")
// Make the output prettier by adding a newline after
// "Signature verified."
output := strings.Split(errString, "Signature verified. ")[1]
// Ouput the verification message to git
io.WriteString(os.Stderr, "Signature verified.\n")
io.WriteString(os.Stderr, output)
}
return VerifyResult{returnCode: procVerify.ProcessState.ExitCode()}
}
func sign(args []string) {
// Sign a given commit message with a specific key using Keybase.
// The key ID follows the -u flag
key := args[indexOf(args, "-bsau")+1]
messageBytes, err := ioutil.ReadAll(os.Stdin)
check(err) // fail if we can't read stdin from git; this is what we sign
message := string(messageBytes)
keybaseBin, err := getKeybaseBinary()
check(err) // fail if there's no Keybase binary
// Run the sign command in Keybase and capture standard out
procSign := exec.Command(keybaseBin, keybaseCliSign(key)...)
stdin, err := procSign.StdinPipe()
check(err) // fail if we can't open a pipe to Keybase
// Write the commit message to stdin to be verified by Keybase
go func() {
defer stdin.Close()
io.WriteString(stdin, message)
}()
// We don't need to process the output, so we'll share our stdout/stderr
// pipes with Keybase
procSign.Stdout = os.Stdout
procSign.Stderr = os.Stderr
err = procSign.Run()
check(err)
io.WriteString(os.Stderr, "\n[GNUPG:] SIG_CREATED \n")
}
func main() {
if contains(os.Args, "--verify") {
result := verify(os.Args)
os.Exit(result.returnCode)
} else if contains(os.Args, "-bsau") {
sign(os.Args)
} else {
fmt.Println("That's not a valid command. Use Harbour by setting your gpg program to it in your .gitconfig.")
os.Exit(1)
}
}