1package integrationtests
2
3import (
4	"bytes"
5	"crypto"
6	"crypto/rand"
7	mathrand "math/rand"
8	"strings"
9	"time"
10
11	"github.com/ProtonMail/go-crypto/openpgp"
12	"github.com/ProtonMail/go-crypto/openpgp/armor"
13	"github.com/ProtonMail/go-crypto/openpgp/packet"
14)
15
16// This function produces random test vectors: generates keys according to the
17// given settings, associates a random message for each key. It returns the
18// test vectors.
19func generateFreshTestVectors() (vectors []testVector, err error) {
20	mathrand.Seed(time.Now().UTC().UnixNano())
21	for i := 0; i < 3; i++ {
22		config := randConfig()
23		// Sample random email, comment, password and message
24		name, email, comment, password, message := randEntityData()
25
26		// Only for verbose display
27		v := "v4"
28		if config.V5Keys {
29			v = "v5"
30		}
31		pkAlgoNames := map[packet.PublicKeyAlgorithm]string{
32			packet.PubKeyAlgoRSA:   "rsa_" + v,
33			packet.PubKeyAlgoEdDSA: "ed25519_" + v,
34		}
35
36		newVector := testVector{
37			config:   config,
38			Name:     pkAlgoNames[config.Algorithm],
39			Password: password,
40			Message:  message,
41		}
42
43		// Generate keys
44		newEntity, errKG := openpgp.NewEntity(name, comment, email, config)
45		if errKG != nil {
46			panic(errKG)
47		}
48
49		// Encrypt private key of entity
50		rawPwd := []byte(password)
51		if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted {
52			if err = newEntity.PrivateKey.Encrypt(rawPwd); err != nil {
53				panic(err)
54			}
55		}
56
57		// Encrypt subkeys of entity
58		for _, sub := range newEntity.Subkeys {
59			if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
60				if err = sub.PrivateKey.Encrypt(rawPwd); err != nil {
61					panic(err)
62				}
63			}
64		}
65
66		w := bytes.NewBuffer(nil)
67		if err = newEntity.SerializePrivateWithoutSigning(w, nil); err != nil {
68			return nil, err
69		}
70
71		serialized := w.Bytes()
72
73		privateKey, _ := armorWithType(serialized, "PGP PRIVATE KEY BLOCK")
74		newVector.PrivateKey = privateKey
75		newVector.PublicKey, _ = publicKey(privateKey)
76		vectors = append(vectors, newVector)
77	}
78	return vectors, err
79}
80
81// armorWithType make bytes input to armor format
82func armorWithType(input []byte, armorType string) (string, error) {
83	var b bytes.Buffer
84	w, err := armor.Encode(&b, armorType, nil)
85	if err != nil {
86		return "", err
87	}
88	if _, err = w.Write(input); err != nil {
89		return "", err
90	}
91	if err := w.Close(); err != nil {
92		return "", err
93	}
94	return b.String(), nil
95}
96
97func publicKey(privateKey string) (string, error) {
98	privKeyReader := strings.NewReader(privateKey)
99	entries, err := openpgp.ReadArmoredKeyRing(privKeyReader)
100	if err != nil {
101		return "", err
102	}
103
104	var outBuf bytes.Buffer
105	for _, e := range entries {
106		err := e.Serialize(&outBuf)
107		if err != nil {
108			return "", err
109		}
110	}
111
112	outString, err := armorWithType(outBuf.Bytes(), "PGP PUBLIC KEY BLOCK")
113	if err != nil {
114		return "", err
115	}
116
117	return outString, nil
118}
119
120var runes = []rune("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKMNOPQRSTUVWXYZ.@-_:;?/!#$%^&*{}[]'\"+~()<>")
121
122func randName() string {
123	firstName := make([]rune, 8)
124	lastName := make([]rune, 8)
125	nameRunes := runes[:26]
126
127	for i := range firstName {
128		firstName[i] = nameRunes[mathrand.Intn(len(nameRunes))]
129	}
130
131	for i := range lastName {
132		lastName[i] = nameRunes[mathrand.Intn(len(nameRunes))]
133	}
134
135	return string(firstName) + " " + string(lastName)
136}
137
138func randFileHints() *openpgp.FileHints {
139	fileNameRunes := runes[:66]
140	fileName := make([]rune, 1+mathrand.Intn(255))
141	for i := range fileName {
142		fileName[i] = fileNameRunes[mathrand.Intn(len(fileNameRunes))]
143	}
144
145	return &openpgp.FileHints{
146		IsBinary: mathrand.Intn(2) == 0,
147		FileName: string(fileName),
148		ModTime: time.Now(),
149	}
150}
151
152func randEmail() string {
153	address := make([]rune, 20)
154	addressRunes := runes[:38]
155	domain := make([]rune, 5)
156	domainRunes := runes[:36]
157	ext := make([]rune, 3)
158	for i := range address {
159		address[i] = addressRunes[mathrand.Intn(len(addressRunes))]
160	}
161	for i := range domain {
162		domain[i] = domainRunes[mathrand.Intn(len(domainRunes))]
163	}
164	for i := range ext {
165		ext[i] = domainRunes[mathrand.Intn(len(domainRunes))]
166	}
167	email := string(address) + "@" + string(domain) + "." + string(ext)
168	return email
169}
170
171// Comment does not allow the following characters: ()<>\x00
172func randComment() string {
173	comment := make([]rune, 140)
174	commentRunes := runes[:84]
175	for i := range comment {
176		comment[i] = commentRunes[mathrand.Intn(len(commentRunes))]
177	}
178	return string(comment)
179}
180
181func randPassword() string {
182	maxPasswordLength := 64
183	password := make([]rune, mathrand.Intn(maxPasswordLength-1)+1)
184	for i := range password {
185		password[i] = runes[mathrand.Intn(len(runes))]
186	}
187	return string(password)
188}
189
190func randMessage() string {
191	maxMessageLength := 1 << 20
192	message := make([]byte, 1+mathrand.Intn(maxMessageLength-1))
193	if _, err := rand.Read(message); err != nil {
194		panic(err)
195	}
196	return string(message)
197}
198
199// Change one char of the input
200func corrupt(input string) string {
201	if input == "" {
202		return string(runes[mathrand.Intn(len(runes))])
203	}
204	output := []rune(input)
205	for string(output) == input {
206		output[mathrand.Intn(len(output))] = runes[mathrand.Intn(len(runes))]
207	}
208	return string(output)
209}
210
211func randEntityData() (string, string, string, string, string) {
212	return randName(), randEmail(), randComment(), randPassword(), randMessage()
213}
214
215func randConfig() *packet.Config {
216	hashes := []crypto.Hash{
217		crypto.SHA256,
218	}
219	hash := hashes[mathrand.Intn(len(hashes))]
220
221	ciphers := []packet.CipherFunction{
222		packet.CipherAES256,
223	}
224	ciph := ciphers[mathrand.Intn(len(ciphers))]
225
226	compAlgos := []packet.CompressionAlgo{
227		packet.CompressionNone,
228		packet.CompressionZIP,
229		packet.CompressionZLIB,
230	}
231	compAlgo := compAlgos[mathrand.Intn(len(compAlgos))]
232
233	pkAlgos := []packet.PublicKeyAlgorithm{
234		packet.PubKeyAlgoRSA,
235		packet.PubKeyAlgoEdDSA,
236	}
237	pkAlgo := pkAlgos[mathrand.Intn(len(pkAlgos))]
238
239	aeadModes := []packet.AEADMode{
240		packet.AEADModeEAX,
241		packet.AEADModeOCB,
242		packet.AEADModeExperimentalGCM,
243	}
244	var aeadConf = packet.AEADConfig{
245		DefaultMode: aeadModes[mathrand.Intn(len(aeadModes))],
246	}
247
248	var rsaBits int
249	if pkAlgo == packet.PubKeyAlgoRSA {
250		switch mathrand.Int() % 4 {
251		case 0:
252			rsaBits = 2048
253		case 1:
254			rsaBits = 3072
255		case 2:
256			rsaBits = 4096
257		default:
258			rsaBits = 0
259		}
260	}
261
262	level := mathrand.Intn(11) - 1
263	compConf := &packet.CompressionConfig{level}
264
265	var v5 bool
266	if mathrand.Int()%2 == 0 {
267		v5 = true
268	}
269
270	return &packet.Config{
271		V5Keys:                 v5,
272		Rand:                   rand.Reader,
273		DefaultHash:            hash,
274		DefaultCipher:          ciph,
275		DefaultCompressionAlgo: compAlgo,
276		CompressionConfig:      compConf,
277		S2KCount:               1024 + mathrand.Intn(65010689),
278		RSABits:                rsaBits,
279		Algorithm:              pkAlgo,
280		AEADConfig:             &aeadConf,
281	}
282}
283