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