File: end_to_end_test.go

package info (click to toggle)
golang-github-protonmail-go-crypto 1.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, sid, trixie
  • size: 1,932 kB
  • sloc: makefile: 10
file content (382 lines) | stat: -rw-r--r-- 11,034 bytes parent folder | download | duplicates (3)
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
// Copyright (C) 2019 ProtonTech AG

package integrationtests

import (
	"bytes"
	"encoding/json"
	"io"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/ProtonMail/go-crypto/openpgp/armor"
	"github.com/ProtonMail/go-crypto/openpgp/packet"
)

/////////////////////////////////////////////////////////////////////////////
// TODO:
//
// - Move signature line endings test to packet unit tests.
//
/////////////////////////////////////////////////////////////////////////////

type testVector struct {
	Message                string
	Name                   string
	PrivateKey             string
	PublicKey              string
	Password               string
	EncryptedSignedMessage string
	config                 *packet.Config
}

// Takes a set of different keys (some external, some generated here) and test
// interactions between them: encrypt, sign, decrypt, verify random messages.
func TestEndToEnd(t *testing.T) {
	// Fetch foreign test vectors from JSON file
	file, err := os.Open("testdata/test_vectors.json")
	if err != nil {
		panic(err)
	}
	raw, err := io.ReadAll(file)
	if err != nil {
		panic(err)
	}
	var foreignTestVectors []testVector
	err = json.Unmarshal(raw, &foreignTestVectors)
	if err != nil {
		panic(err)
	}

	for i := 0; i < len(foreignTestVectors); i++ {
		foreignTestVectors[i].Name += "_foreign"
	}

	// Generate random test vectors
	freshTestVectors, err := generateFreshTestVectors(20)
	if err != nil {
		t.Fatal("Cannot proceed without generated keys: " + err.Error())
	}
	testVectors := append(foreignTestVectors, freshTestVectors...)

	// For each testVector in testVectors, (1) Decrypt an already existing message,
	// (2) Sign and verify random messages, and (3) Encrypt random messages for
	// each of the other keys and then decrypt on the other end.
	for _, from := range testVectors {
		skFrom := readArmoredSk(t, from.PrivateKey, from.Password)
		pkFrom := readArmoredPk(t, from.PublicKey)
		t.Run(from.Name, func(t *testing.T) {

			// 1. Decrypt the existing message of the given test vector
			t.Run("DecryptPreparedMessage",
				func(t *testing.T) {
					decryptionTest(t, from, skFrom)
				})
			// 2. Sign a message and verify the signature.
			t.Run("signVerify", func(t *testing.T) {
				t.Run("binary", func(t *testing.T) {
					signVerifyTest(t, from, skFrom, pkFrom, true)
				})
				t.Run("text", func(t *testing.T) {
					signVerifyTest(t, from, skFrom, pkFrom, false)
				})
			})
			// 3. Encrypt, decrypt and verify a random message for
			// every other key.
			t.Run("encryptDecrypt",
				func(t *testing.T) {
					encDecTest(t, from, testVectors)
				})
		})
	}
}

// This subtest decrypts the existing encrypted and signed message of each
// testVector.
func decryptionTest(t *testing.T, vector testVector, sk openpgp.EntityList) {
	if vector.EncryptedSignedMessage == "" {
		return
	}
	prompt := func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
		err := keys[0].PrivateKey.Decrypt([]byte(vector.Password))
		if err != nil {
			t.Errorf("prompt: error decrypting key: %s", err)
			return nil, err
		}
		return nil, nil
	}
	sig, err := armor.Decode(strings.NewReader(vector.EncryptedSignedMessage))
	if err != nil {
		t.Fatal(err)
	}
	md, err := openpgp.ReadMessage(sig.Body, sk, prompt, vector.config)
	if err != nil {
		t.Fatal(err)
	}

	body, err := io.ReadAll(md.UnverifiedBody)
	if err != nil {
		t.Fatal(err)
	}

	stringBody := string(body)
	if stringBody != vector.Message {
		t.Fatal("Decrypted body did not match expected body")
	}

	// We'll see a sig error here after reading in the UnverifiedBody above,
	// if there was one to see.
	if err = md.SignatureError; err != nil {
		t.Fatal(err)
	}

	if md.Signature == nil {
		t.Fatal("Expected a signature to be set")
	}
}

// Given a testVector, encrypts random messages for all given testVectors
// (including self) and verifies on the other end.
func encDecTest(t *testing.T, from testVector, testVectors []testVector) {
	skFrom := readArmoredSk(t, from.PrivateKey, from.Password)
	// Decrypt private key if necessary
	err := skFrom.DecryptionKeys()[0].PrivateKey.Decrypt([]byte(from.Password))
	if err != nil {
		t.Error(err)
	}
	pkFrom := readArmoredPk(t, from.PublicKey)
	for _, to := range testVectors {
		t.Run(to.Name, func(t *testing.T) {
			pkTo := readArmoredPk(t, to.PublicKey)
			skTo := readArmoredSk(t, to.PrivateKey, to.Password)
			message := randMessage()
			hints := randFileHints()

			// Encrypt message
			signer := skFrom[0]
			errDec := signer.PrivateKey.Decrypt([]byte(from.Password))
			if errDec != nil {
				t.Error(errDec)
			}
			buf := new(bytes.Buffer)
			w, err := openpgp.Encrypt(buf, pkTo[:1], signer, hints, from.config)
			if err != nil {
				t.Fatalf("Error in Encrypt: %s", err)
			}
			_, err = w.Write([]byte(message))
			if err != nil {
				t.Fatalf("Error writing plaintext: %s", err)
			}
			err = w.Close()
			if err != nil {
				t.Fatalf("Error closing WriteCloser: %s", err)
			}

			// -----------------
			// On the other end:
			// -----------------

			// Decrypt recipient key
			prompt := func(keys []openpgp.Key, symm bool) ([]byte, error) {
				err := keys[0].PrivateKey.Decrypt([]byte(to.Password))
				if err != nil {
					t.Errorf("Prompt: error decrypting key: %s", err)
					return nil, err
				}
				return nil, nil
			}

			// Read message with recipient key
			keyring := append(skTo, pkFrom[:]...)
			md, err := openpgp.ReadMessage(buf, keyring, prompt, to.config)
			if err != nil {
				t.Fatalf("Error reading message: %s", err)
			}

			// Test message details
			if !md.IsEncrypted {
				t.Fatal("The message should be encrypted")
			}
			signKey, _ := signer.SigningKey(time.Now())
			expectedKeyID := signKey.PublicKey.KeyId
			expectedFingerprint := signKey.PublicKey.Fingerprint
			if signKey.PublicKey.Version != 6 && md.SignedByKeyId != expectedKeyID {
				t.Fatalf(
					"Message signed by wrong key id, got: %v, want: %v",
					*md.SignedBy, expectedKeyID)
			}
			if signKey.PublicKey.Version == 6 && !bytes.Equal(md.SignedByFingerprint, expectedFingerprint) {
				t.Fatalf(
					"Message signed by wrong key id, got: %x, want: %x",
					md.SignedByFingerprint, expectedFingerprint)
			}
			if md.SignedBy == nil {
				t.Fatalf("Failed to find the signing Entity")
			}

			plaintext, err := io.ReadAll(md.UnverifiedBody)
			if err != nil {
				t.Fatalf("Error reading encrypted contents: %s", err)
			}
			encryptKey, _ := pkTo[0].EncryptionKey(time.Now())
			expectedEncKeyID := encryptKey.PublicKey.KeyId
			if len(md.EncryptedToKeyIds) != 1 ||
				md.EncryptedToKeyIds[0] != expectedEncKeyID {
				t.Errorf("Expected message to be encrypted to %v, but got %#v",
					expectedKeyID, md.EncryptedToKeyIds)
			}
			// Test decrypted message
			if string(plaintext) != message {
				t.Error("decrypted and expected message do not match")
			}

			if md.SignatureError != nil {
				t.Fatalf("Signature error: %s", md.SignatureError)
			}
			if md.Signature == nil {
				t.Error("Signature missing")
			}
		})
	}
}

// Sign a random message and verify signature against the original message,
// another message with same body but different line endings, and a corrupt
// message.
func signVerifyTest(
	t *testing.T,
	from testVector,
	skFrom, pkFrom openpgp.EntityList,
	binary bool,
) {
	if err := skFrom[0].PrivateKey.Decrypt([]byte(from.Password)); err != nil {
		t.Error(err)
	}

	messageBody := randMessage()

	// ================================================
	// TODO: Move the line ending checks to unit tests
	// ================================================
	// Add line endings to test whether the non-binary version of this
	// signature normalizes the final line endings, see RFC4880bis, sec 5.2.1.
	lineEnding := " \r\n \n \r\n"
	otherLineEnding := " \n \r\n \n"
	message := bytes.NewReader([]byte(messageBody + lineEnding))
	otherMessage := bytes.NewReader([]byte(messageBody + otherLineEnding))

	corruptMessage := bytes.NewReader([]byte(corrupt(messageBody) + lineEnding))

	// Sign the message
	buf := new(bytes.Buffer)
	var errSign error
	if binary {
		errSign = openpgp.ArmoredDetachSign(buf, skFrom[0], message, nil)
	} else {
		errSign = openpgp.ArmoredDetachSignText(buf, skFrom[0], message, nil)
	}
	if errSign != nil {
		t.Error(errSign)
	}

	// Verify the signature against the corrupt message first
	signatureReader := bytes.NewReader(buf.Bytes())
	wrongsigner, err := openpgp.CheckArmoredDetachedSignature(
		pkFrom, corruptMessage, signatureReader, nil)
	if err == nil || wrongsigner != nil {
		t.Fatal("Expected the signature to not verify")
	}

	// Reset the reader and verify against the message with different line
	// endings (should pass in the non-binary case)
	var errSeek error
	_, errSeek = signatureReader.Seek(0, io.SeekStart)
	if errSeek != nil {
		t.Error(errSeek)
	}

	otherSigner, err := openpgp.CheckArmoredDetachedSignature(
		pkFrom, otherMessage, signatureReader, nil)
	if binary {
		if err == nil || otherSigner != nil {
			t.Fatal("Expected the signature to not verify")
			return
		}
	} else {
		if err != nil {
			t.Fatalf("signature error: %s", err)
		}
		if otherSigner == nil {
			t.Fatalf("signer is nil")
		}
		if otherSigner.PrimaryKey.KeyId != skFrom[0].PrimaryKey.KeyId {
			t.Errorf(
				"wrong signer: got %x, expected %x", otherSigner.PrimaryKey.KeyId, 0)
		}
	}

	// Reset the readers and verify against the exact first message.
	_, errSeek = message.Seek(0, io.SeekStart)
	if errSeek != nil {
		t.Error(errSeek)
	}
	_, errSeek = signatureReader.Seek(0, io.SeekStart)
	if errSeek != nil {
		t.Error(errSeek)
	}

	otherSigner, err = openpgp.CheckArmoredDetachedSignature(
		pkFrom, message, signatureReader, nil)

	if err != nil {
		t.Fatalf("signature error: %s", err)
	}
	if otherSigner == nil {
		t.Fatalf("signer is nil")
	}
	if otherSigner.PrimaryKey.KeyId != skFrom[0].PrimaryKey.KeyId {
		t.Errorf(
			"wrong signer: got %x, expected %x",
			skFrom[0].PrimaryKey.KeyId,
			skFrom[0].PrimaryKey.KeyId,
		)
	}
}

func readArmoredPk(t *testing.T, publicKey string) openpgp.EntityList {
	keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKey))
	if err != nil {
		t.Fatal(err)
	}
	if len(keys) < 1 {
		t.Errorf("Failed to read key with good cross signature, %d", len(keys))
	}
	if len(keys[0].Subkeys) < 1 {
		t.Errorf("Failed to read good subkey, %d", len(keys[0].Subkeys))
	}
	return keys
}

func readArmoredSk(t *testing.T, sk string, pass string) openpgp.EntityList {
	keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(sk))
	if err != nil {
		t.Fatal(err)
	}
	if len(keys) != 1 {
		t.Errorf("Failed to read key with good cross signature, %d", len(keys))
	}
	if len(keys[0].Subkeys) < 1 {
		t.Errorf("Failed to read good subkey, %d", len(keys[0].Subkeys))
	}
	keyObject := keys[0].PrivateKey
	if pass != "" {
		corruptPassword := corrupt(pass)
		if err := keyObject.Decrypt([]byte(corruptPassword)); err == nil {
			t.Fatal("Decrypted key with invalid password")
		}
	}
	return keys
}