/
gpgme_test.go
462 lines (399 loc) · 11.6 KB
/
gpgme_test.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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
package gpgme
import (
"bytes"
"flag"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
const (
testGPGHome = "./testdata/gpghome"
testData = "data\n"
testCipherText = `-----BEGIN PGP MESSAGE-----
Version: GnuPG v1
hQEMAw4698hF4WUhAQf/SdkbF/zUE6YjBxscDurrUZunSnt87kipLXypSxTDIdgj
O9huAaQwBz4uAJf2DuEN/7iAFGhi/v45NTujrG+7ocfjM3m/A2T80g4RVF5kKXBr
pFFgH7bMRY6VdZt1GKI9izSO/uFkoKXG8M31tCX3hWntQUJ9p+n1avGpu3wo4Ru3
CJhpL+ChDzXuZv4IK81ahrixEz4fJH0vd0TbsHpTXx4WPkTGXelM0R9PwiY7TovZ
onGZUIplrfA1HUUbQfzExyFw3oAo1/almzD5CBIfq5EnU8Siy5BNulDXm0/44h8A
lOUy6xqx7ITnWMYYf4a1cFoW80Yk+x6SYQqbcqHFQdJIAVr00V4pPV4ppFcXgdw/
BxKERzDupyqS0tcfVFCYLRmvtQp7ceOS6jRW3geXPPIz1U/VYBvKlvFu4XTMCS6z
4qY4SzZlFEsU
=IQOA
-----END PGP MESSAGE-----`
textSignedCipherText = `-----BEGIN PGP MESSAGE-----
Version: GnuPG v1
hQEMAw4698hF4WUhAQf9FaI8zSltIMcDxqGtpcTfcBtC30rcKqE/oa1bacxuZQKW
uT671N7J2TjU8tLIsAO9lDSRHCsf6DbB+O6qOJsu6eJownyMUEmJ2oBON9Z2x+Ci
aS0KeRqcRxNJYbIth9/ffa2I4sSBA0GS93yCfGKHNL4JvNo/dgVjJwOPWZ5TKu49
n9h81LMwTQ8qpLGPeo7nsgBtSKNTi5s7pC0bd9HTii7gQcLUEeeqk5U7oG5CRN48
zKPrVud8pIDvSoYuymZmjWI1MmF4xQ+6IHK7hWKMyA2RkS4CpyBPERMifc+y+K2+
LnKLt2ul9jxgqv3fd/dU7UhyqdvJdj83tUvhN4Iru9LAuQHfVsP23keH8CHpUzCj
PkuUYvpgmRdMSC+l3yBCqnaaAI51rRdyJ+HX4DrrinOFst2JfXEICiQAg5UkO4i/
YYHsfD3xfk7MKMOY/DfV25OyAU6Yeq/5SMisONhtlzcosVU7HULudbIKSG6q/tCx
jRGu+oMIBdiQ+eipnaLVetyGxSEDzxHH367NbxC9ffxK7I+Srmwh+oS/yMNuZA8r
bUp6NbykK8Si7VNgVG1+yOIULLuR0VBO5+e2PKQU8FP0wABTn7h3zE8z4rCrcaZl
43bc/TBUyTdWY79e2P1otaCbhQGAjfF8ClPbAdsFppykf5I2ZDDf8c67d0Nqui74
Yry4/fF/2sAzyoyzdP6ktBw6pCA1MbwDqTGg7aEI8Es3X+jfh3qIaLnGmZ7jIyUl
D4jgGAEpFU+mpPH8eukKuT+OJ3P6gdmSiAJH7+C96tlcEg9BNxjYNkCTil/3yygQ
EV/5zFTEpzm4CtYHHdmY5uCaEJq/4hhE8BY8
=8mxI
-----END PGP MESSAGE-----`
testSignedText = `-----BEGIN PGP MESSAGE-----
Version: GnuPG v1
owEBQwG8/pANAwACAQMn/7Ain2E2AcsTYgBW47+PVGVzdCBtZXNzYWdlCokBHAQA
AQIABgUCVuO/jwAKCRADJ/+wIp9hNh9lCACPiDkY0CqI9ss4EBcpToqnF/8NmV99
2wi6FmbQnUmY98OMM2VJXrX6PvfD/X+FsiLog0CZU4heMEAI3Dd3qELgTfaTFqNc
bbDkenzA0kO734WLsEU/z1F9iWAcfeF3crKqd3fBw5kZ1PkhuJFdcqQEOUQALvXY
8VAtQmQWzf3sn2KIQ7R1wyAJVoUZaN5Xwc9Y0F1l4Xxifax8nkFBl35X6gmHRxZ7
jlfmWMcAkXASNXl9/Yso2XGJMs85JPhZPJ3KJRuuurnhZSxAbDJMNBFJ+HbQv3y6
pupeR7ut6pWJxr6MND793yoFGoRYwKklQdfP4xzFCatYRU4RkPBp95KJ
=RMUj
-----END PGP MESSAGE-----
`
)
func TestMain(m *testing.M) {
flag.Parse()
os.Setenv("GNUPGHOME", absTestGPGHome())
unsetenvGPGAgentInfo()
os.Exit(m.Run())
}
func compareEngineInfo(t *testing.T, info *EngineInfo, proto Protocol, fileName, homeDir string) {
for info != nil && info.Protocol() != proto {
info = info.Next()
}
if info == nil {
t.Errorf("Expected engine info %d not found", proto)
return
}
if info.FileName() != fileName {
t.Errorf("Testing file name %s does not match %s", info.FileName(), fileName)
}
if info.HomeDir() != homeDir {
t.Errorf("Testing home directory %s does not match %s", info.HomeDir(), homeDir)
}
}
func TestEngineInfo(t *testing.T) {
testProto := ProtocolOpenPGP // Careful, this is global state!
defer func() {
_ = SetEngineInfo(testProto, "", "") // Try to reset to defaults after we are done.
}()
testFN := "testFN"
testHomeDir := "testHomeDir"
checkError(t, SetEngineInfo(testProto, testFN, testHomeDir))
info, err := GetEngineInfo()
checkError(t, err)
compareEngineInfo(t, info, testProto, testFN, testHomeDir)
// SetEngineInfo with empty strings works, using defaults which we don't know,
// so just test that it doesn't fail.
checkError(t, SetEngineInfo(testProto, testFN, ""))
checkError(t, SetEngineInfo(testProto, "", testHomeDir))
}
func ctxWithCallback(t *testing.T) *Context {
ensureVersion(t, "1.", "can only set password callback for GPG v1.x")
ctx, err := New()
checkError(t, err)
checkError(t, ctx.SetCallback(func(uid_hint string, prev_was_bad bool, f *os.File) error {
if prev_was_bad {
t.Fatal("Bad passphrase")
}
_, err := io.WriteString(f, "password\n")
return err
}))
return ctx
}
func TestContext_Armor(t *testing.T) {
ctx, err := New()
checkError(t, err)
ctx.SetArmor(true)
if !ctx.Armor() {
t.Error("expected armor set")
}
ctx.SetArmor(false)
if ctx.Armor() {
t.Error("expected armor not set")
}
}
func TestContext_TextMode(t *testing.T) {
ctx, err := New()
checkError(t, err)
ctx.SetTextMode(true)
if !ctx.TextMode() {
t.Error("expected textmode set")
}
ctx.SetTextMode(false)
if ctx.TextMode() {
t.Error("expected textmode not set")
}
}
func TestContext_EngineInfo(t *testing.T) {
ctx, err := New()
checkError(t, err)
testProto := ProtocolOpenPGP
testFN := "testFN"
testHomeDir := "testHomeDir"
checkError(t, ctx.SetEngineInfo(testProto, testFN, testHomeDir))
info := ctx.EngineInfo()
compareEngineInfo(t, info, testProto, testFN, testHomeDir)
// SetEngineInfo with empty strings works, using defaults which we don't know,
// so just test that it doesn't fail.
checkError(t, ctx.SetEngineInfo(testProto, testFN, ""))
checkError(t, ctx.SetEngineInfo(testProto, "", testHomeDir))
}
func TestContext_Encrypt(t *testing.T) {
ctx, err := New()
checkError(t, err)
keys, err := FindKeys("test@example.com", true)
checkError(t, err)
plain, err := NewDataBytes([]byte(testData))
checkError(t, err)
var buf bytes.Buffer
cipher, err := NewDataWriter(&buf)
checkError(t, err)
checkError(t, ctx.Encrypt(keys, 0, plain, cipher))
if buf.Len() < 1 {
t.Error("Expected encrypted bytes, got empty buffer")
}
}
func TestContext_Decrypt(t *testing.T) {
ctx := ctxWithCallback(t)
cipher, err := NewDataBytes([]byte(testCipherText))
checkError(t, err)
var buf bytes.Buffer
plain, err := NewDataWriter(&buf)
checkError(t, err)
checkError(t, ctx.Decrypt(cipher, plain))
diff(t, buf.Bytes(), []byte("Test message\n"))
}
func TestContext_DecryptVerify(t *testing.T) {
ctx := ctxWithCallback(t)
cipher, err := NewDataBytes([]byte(textSignedCipherText))
checkError(t, err)
var buf bytes.Buffer
plain, err := NewDataWriter(&buf)
checkError(t, err)
checkError(t, ctx.DecryptVerify(cipher, plain))
diff(t, buf.Bytes(), []byte("Test message\n"))
}
func TestContext_Sign(t *testing.T) {
ctx := ctxWithCallback(t)
key, err := ctx.GetKey("test@example.com", true)
checkError(t, err)
plain, err := NewDataBytes([]byte(testData))
checkError(t, err)
var buf bytes.Buffer
signed, err := NewDataWriter(&buf)
checkError(t, err)
checkError(t, ctx.Sign([]*Key{key}, plain, signed, SigModeNormal))
if buf.Len() < 1 {
t.Error("Expected signed bytes, got empty buffer")
}
}
func TestContext_Verify(t *testing.T) {
ctx, err := New()
checkError(t, err)
_, err = ctx.GetKey("test@example.com", false)
checkError(t, err)
signed, err := NewDataBytes([]byte(testSignedText))
checkError(t, err)
var buf bytes.Buffer
plain, err := NewDataWriter(&buf)
checkError(t, err)
_, sigs, err := ctx.Verify(signed, nil, plain)
checkError(t, err)
if len(sigs) != 1 {
t.Error("Expected 1 signature")
}
sig := sigs[0]
// Normalize
expectedSig := Signature{
Summary: SigSumValid | SigSumGreen,
Fingerprint: "44B646DC347C31E867FF4F450327FFB0229F6136",
Status: nil,
Timestamp: sig.Timestamp, // Ignore in comparison
ExpTimestamp: sig.ExpTimestamp, // Ignore in comparison
WrongKeyUsage: false,
PKATrust: 0,
ChainModel: false,
Validity: ValidityFull,
ValidityReason: nil,
PubkeyAlgo: sig.PubkeyAlgo, // Ignore in comparison
HashAlgo: sig.HashAlgo, // Ignore in comparison
}
if sig != expectedSig {
t.Errorf("Signature verification does not match: %#v vs. %#v", sig, expectedSig)
}
diff(t, buf.Bytes(), []byte("Test message\n"))
}
func TestContext_Import(t *testing.T) {
homeDir, err := ioutil.TempDir("", "gpgme-import-test")
checkError(t, err)
defer os.RemoveAll(homeDir)
ctx, err := New()
checkError(t, err)
checkError(t, ctx.SetEngineInfo(ProtocolOpenPGP, "", homeDir))
f, err := os.Open("./testdata/pubkeys.gpg")
checkError(t, err)
defer f.Close()
dh, err := NewDataFile(f)
checkError(t, err)
defer dh.Close()
res, err := ctx.Import(dh)
checkError(t, err)
for _, v := range []struct {
Name string
Value int
Expected int
}{
{"Considered", res.Considered, 1},
{"NoUserID", res.NoUserID, 0},
{"Imported", res.Imported, 1},
// Do not test ImportedRSA, as of gnupg 2.1.11 the value is always 0.
{"Unchanged", res.Unchanged, 0},
{"NewUserIDs", res.NewUserIDs, 0},
{"NewSubKeys", res.NewSubKeys, 0},
{"NewSignatures", res.NewSignatures, 0},
{"NewRevocations", res.NewRevocations, 0},
{"SecretRead", res.SecretRead, 0},
{"SecretImported", res.SecretImported, 0},
{"SecretUnchanged", res.SecretUnchanged, 0},
{"NotImported", res.NotImported, 0},
} {
if v.Value != v.Expected {
t.Errorf("Unexpected import result field %s, value %d, expected %d", v.Name, v.Value, v.Expected)
}
}
expectedStatus := ImportStatus{
Fingerprint: "44B646DC347C31E867FF4F450327FFB0229F6136",
Result: nil,
Status: ImportNew,
}
if len(res.Imports) != 1 {
t.Errorf("Unexpected number of import status values %d", len(res.Imports))
} else if res.Imports[0] != expectedStatus {
t.Errorf("Import status does not match: %#v vs. %#v", res.Imports[0], expectedStatus)
}
}
func TestContext_Export(t *testing.T) {
ctx, err := New()
checkError(t, err)
data, err := NewData()
checkError(t, err)
err = ctx.Export("", 0, data)
checkError(t, err)
_, _ = data.Seek(0, 0)
allKeys, err := ioutil.ReadAll(data)
checkError(t, err)
if len(allKeys) < 1 {
t.Error("Expected exported keys, got empty buffer")
}
}
func TestContext_AssuanSend(t *testing.T) {
// Launch a gpg-agent in daemon mode
cmd := exec.Command("gpg-connect-agent", "--verbose", "/bye")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
t.Skip(err)
}
ctx, err := New()
checkError(t, err)
err = ctx.SetProtocol(ProtocolAssuan)
checkError(t, err)
// NOP should simply succeed
err = ctx.AssuanSend("NOP", nil, nil, nil)
checkError(t, err)
// GETINFO should return a version in Data
var versionData []byte
err = ctx.AssuanSend("GETINFO version",
func(data []byte) error {
versionData = data
return nil
},
nil,
nil,
)
checkError(t, err)
if len(versionData) == 0 {
t.Error("Expected version data")
}
err = ctx.AssuanSend("KILLAGENT", nil, nil, nil)
checkError(t, err)
}
func isVersion(t testing.TB, version string) bool {
t.Helper()
var info *EngineInfo
info, err := GetEngineInfo()
checkError(t, err)
for info != nil {
if info.Protocol() == ProtocolOpenPGP {
if strings.Contains(info.FileName(), "gpg") && strings.HasPrefix(info.Version(), version) {
return true
}
return false
}
info = info.Next()
}
return false
}
var gpgBins = []string{"gpg2", "gpg1", "gpg"}
// ensureVersion tries to setup gpgme with a specific version or skip
func ensureVersion(t testing.TB, version, msg string) {
t.Helper()
if isVersion(t, version) {
return
}
for _, bin := range gpgBins {
path, err := exec.LookPath(bin)
if err != nil {
continue
}
if err := SetEngineInfo(ProtocolOpenPGP, path, absTestGPGHome()); err != nil {
continue
}
if isVersion(t, version) {
return
}
}
t.Skip(msg)
}
func diff(t testing.TB, dst, src []byte) {
t.Helper()
line := 1
offs := 0 // line offset
for i := 0; i < len(dst) && i < len(src); i++ {
d := dst[i]
s := src[i]
if d != s {
t.Errorf("dst:%d: %s\n", line, dst[offs:i+1])
t.Errorf("src:%d: %s\n", line, src[offs:i+1])
return
}
if s == '\n' {
line++
offs = i + 1
}
}
if len(dst) != len(src) {
t.Errorf("len(dst) = %d, len(src) = %d\nsrc = %q", len(dst), len(src), src)
}
}
func checkError(t testing.TB, err error) {
t.Helper()
if err == nil {
return
}
t.Fatal(err)
}
func absTestGPGHome() string {
f, err := filepath.Abs(testGPGHome)
if err != nil {
panic(err)
}
return f
}