github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/pgp_warnings_test.go (about) 1 package engine 2 3 import ( 4 "crypto" 5 "io" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/go-crypto/openpgp" 14 "github.com/keybase/go-crypto/openpgp/clearsign" 15 "github.com/keybase/go-crypto/openpgp/packet" 16 "github.com/keybase/go-crypto/openpgp/s2k" 17 ) 18 19 const pgpWarningsMsg = `Consonantia, there live the blind texts. Separated they live in Bookmarksgrove 20 right at the coast of the Semantics, a large language ocean. A small river named 21 Duden flows by their place and supplies it with the necessary regelialia. It is 22 a paradisematic country, in which roasted parts of sentences fly into your 23 mouth. Even the all-powerful Pointing has no control about the blind texts it is 24 an almost unorthographic life One day however a small line of blind text by the 25 name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox 26 advised her not to do so, because there were thousands of bad Commas, wild 27 Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. 28 She packed her seven versalia, put her initial into the belt and made herself on 29 the way. When she reached the first hills of the Italic Mountains, she had a 30 last view back on the skyline of her hometown Bookmarksgrove, the headline of 31 Alphabet Village and the subline of her own road, the Line Lane. Pityful a 32 rethoric question ran over her cheek, then she continued her way. On her way she 33 met a copy. The copy warned the Little Blind Text, that where it came from it 34 would have been rewritten a thousand times and everything that was left from its 35 origin would be the word "and" and the Little Blind Text should turn around and 36 return to its own, safe country. But nothing the copy said could convince her 37 and so it didn’t take long until a few insidious Copy Writers ambushed her, made 38 her drunk with Longe and Parole and dragged her into their agency, where they 39 abused her for their projects again and again. And if she hasn’t been rewritten, 40 then they are still using her.Far far away, behind the word mountains, far from 41 the countries Vokalia and Consonantia, there live the blind texts. Separated 42 they live in Bookmarksgrove right at the coast of the Semantics, a large 43 language ocean. A small river named Duden flows by their place and supplies it 44 with the necessary regelialia. It is a paradisematic country, in which roasted 45 parts of sentences fly into your mouth. Even the all-powerful Pointing has no 46 control about the blind texts it is an almost unorthographic life One` 47 48 func generateOpenPGPEntity(ts time.Time, hash crypto.Hash, accepts []crypto.Hash) (*openpgp.Entity, error) { 49 // All test keys are RSA-768 because of the ease of generation 50 cfg := &packet.Config{ 51 DefaultHash: hash, 52 Time: func() time.Time { return ts }, 53 RSABits: 768, 54 } 55 hashName := libkb.HashToName[hash] 56 hashLower := strings.ToLower(hashName) 57 entity, err := openpgp.NewEntity("Test "+hashName, "", hashLower+"@example.com", cfg) 58 if err != nil { 59 return nil, err 60 } 61 62 acceptsConverted := []uint8{} 63 for _, h := range accepts { 64 if v, ok := s2k.HashToHashId(h); ok { 65 acceptsConverted = append(acceptsConverted, v) 66 } 67 } 68 69 // Sign all the identities... 70 for _, identity := range entity.Identities { 71 identity.SelfSignature.PreferredHash = acceptsConverted 72 if err := identity.SelfSignature.SignUserId(identity.UserId.Id, entity.PrimaryKey, entity.PrivateKey, cfg); err != nil { 73 panic(err) 74 } 75 } 76 // and the subkeys... 77 for _, subkey := range entity.Subkeys { 78 if err := subkey.Sig.SignKey(subkey.PublicKey, entity.PrivateKey, cfg); err != nil { 79 panic(err) 80 } 81 } 82 83 return entity, nil 84 } 85 86 type encryptTest struct { 87 Name string // Name of the test 88 89 DigestHash crypto.Hash // Hash used for msg digests 90 AlicesHash crypto.Hash 91 BobsHash crypto.Hash 92 Count int // How many warnings are expected 93 94 Mode string // either encrypt, encrypt-and-sign 95 Known bool // whether to check for the key on keybase 96 } 97 98 type pgpWarningsUserBundle struct { 99 tc libkb.TestContext 100 key *libkb.PGPKeyBundle 101 user *FakeUser 102 } 103 104 func (e encryptTest) test(t *testing.T, users map[string]map[crypto.Hash]*pgpWarningsUserBundle) { 105 t.Logf("Processing PGP warnings test - %s", e.Name) 106 107 now := time.Now() 108 supportedHashes := []crypto.Hash{crypto.SHA1, crypto.SHA256} 109 110 // Alice is the: 111 // 1) Party encrypting (and optionally signing) to Bob in "encrypt" 112 // 2) The verifier / decrypter in all other scenarios 113 if _, ok := users["alice"]; !ok { 114 users["alice"] = map[crypto.Hash]*pgpWarningsUserBundle{} 115 } 116 if _, ok := users["alice"][e.AlicesHash]; !ok { 117 tc := SetupEngineTest(t, "PGPEncrypt") 118 tc.Tp.APIHeaders = map[string]string{"X-Keybase-Sigchain-Compatibility": "1"} 119 120 // Generate Alice's keypair. It'll always be SHA256 as we don't allow 121 // the generation of weaker selfsigs in Keybase itself. 122 aliceKeys, err := generateOpenPGPEntity(now, e.AlicesHash, supportedHashes) 123 require.NoError(t, err, "alice's keys generation") 124 aliceBundle := libkb.NewPGPKeyBundle(aliceKeys) 125 126 u := createFakeUserWithPGPSibkeyPregen(tc, aliceBundle) 127 128 users["alice"][e.AlicesHash] = &pgpWarningsUserBundle{ 129 tc: tc, 130 key: aliceBundle, 131 user: u, 132 } 133 } 134 alice := users["alice"][e.AlicesHash] 135 136 // We'll only run engines as Alice because Bob ~is a phony who uses SHA1~. 137 // 24-01-2019: We're also phonies :( 138 m := NewMetaContextForTest(alice.tc).WithUIs(libkb.UIs{ 139 LogUI: alice.tc.G.UI.GetLogUI(), 140 IdentifyUI: &FakeIdentifyUI{ 141 Proofs: map[string]string{}, 142 }, 143 SecretUI: alice.user.NewSecretUI(), 144 PgpUI: &TestPgpUI{}, 145 }) 146 147 // Bob is the: 148 // 1) Recipient in the "encrypt" scenario 149 // 2) Sender in all other scenarios 150 // Bob's signatures (both identity sigs and msg digests) can be SHA1. 151 // Bob has a cousin called Anonybob who refuses to publish his keys to Keybase, 152 // so he sends them over email (the "unknown user" scenario). 153 bobName := "bob" 154 if !e.Known { 155 bobName = "anonybob" 156 } 157 if _, ok := users[bobName]; !ok { 158 users[bobName] = map[crypto.Hash]*pgpWarningsUserBundle{} 159 } 160 if _, ok := users[bobName][e.BobsHash]; !ok { 161 tc := SetupEngineTest(t, "PGPEncrypt") 162 tc.Tp.APIHeaders = map[string]string{"X-Keybase-Sigchain-Compatibility": "1"} 163 164 bobKeys, err := generateOpenPGPEntity(now, e.BobsHash, supportedHashes) 165 require.NoError(t, err, "bob's keys generation") 166 bobBundle := libkb.NewPGPKeyBundle(bobKeys) 167 168 var u *FakeUser 169 if e.Known { 170 u = createFakeUserWithPGPSibkeyPregen(tc, bobBundle) 171 } else { 172 importEng := NewPGPKeyImportEngine(alice.tc.G, PGPKeyImportEngineArg{ 173 Pregen: bobBundle, 174 OnlySave: true, 175 }) 176 require.NoError(t, RunEngine2(m, importEng), "importing pubkey failed") 177 } 178 179 users[bobName][e.BobsHash] = &pgpWarningsUserBundle{ 180 tc: tc, 181 key: bobBundle, 182 user: u, 183 } 184 } 185 bob := users[bobName][e.BobsHash] 186 187 // Both "encrypt" and "encrypt-and-sign" simply run engine.PGPEncrypt 188 if e.Mode == "encrypt" || e.Mode == "encrypt-and-sign" { 189 sink := libkb.NewBufferCloser() 190 arg := &PGPEncryptArg{ 191 Recips: []string{bob.user.Username}, 192 Source: strings.NewReader(pgpWarningsMsg), 193 Sink: sink, 194 NoSign: e.Mode == "encrypt", 195 } 196 eng := NewPGPEncrypt(alice.tc.G, arg) 197 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 198 199 require.Greaterf(t, len(sink.Bytes()), 0, "no output [%s]", e.Name) 200 require.Lenf(t, eng.warnings, e.Count, "warnings count [%s]", e.Name) 201 return 202 } 203 204 // "Sign" simply runs engine.PGPSign 205 if e.Mode == "sign" { 206 sink := libkb.NewBufferCloser() 207 arg := &PGPSignArg{ 208 Source: io.NopCloser(strings.NewReader(pgpWarningsMsg)), 209 Sink: sink, 210 } 211 eng := NewPGPSignEngine(alice.tc.G, arg) 212 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 213 214 require.Greaterf(t, len(sink.Bytes()), 0, "no output [%s]", e.Name) 215 require.Lenf(t, eng.warnings, e.Count, "warnings count [%s]", e.Name) 216 return 217 } 218 219 cfg := &packet.Config{ 220 DefaultHash: e.DigestHash, 221 Time: func() time.Time { return now }, 222 } 223 224 if e.Mode == "verify" { 225 // Rather than using PGPSign, we use our custom wrapper methods to make 226 // sure that we can set the low level variables. 227 228 // This time there's no recipient, we're generating a message for the 229 // sender / signer / verifier using only the recipient's key. 230 var ( 231 clearsignSink = libkb.NewBufferCloser() 232 attachedSink = libkb.NewBufferCloser() 233 detachedSink = libkb.NewBufferCloser() 234 ) 235 236 var signedBy string 237 if e.Known { 238 signedBy = bob.user.Username 239 } 240 241 // Start with the clearsign sig 242 clearsignInput, err := clearsign.Encode( 243 clearsignSink, 244 bob.key.PrivateKey, 245 cfg, 246 ) 247 require.NoErrorf(t, err, "clearsign failure [%s]", e.Name) 248 _, err = clearsignInput.Write([]byte(pgpWarningsMsg)) 249 require.NoErrorf(t, err, "writing to clearsign [%s]", e.Name) 250 require.NoErrorf(t, clearsignInput.Close(), "finishing clearsign [%s]", e.Name) 251 arg := &PGPVerifyArg{ 252 Source: clearsignSink, 253 SignedBy: signedBy, 254 } 255 eng := NewPGPVerify(alice.tc.G, arg) 256 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 257 require.Lenf(t, eng.SignatureStatus().Warnings, e.Count, "warnings count [%s]", e.Name) 258 259 // Then process the attached sig 260 attachedInput, _, err := libkb.ArmoredAttachedSign( 261 attachedSink, 262 *bob.key.Entity, 263 nil, 264 cfg, 265 ) 266 require.NoErrorf(t, err, "attached sign failure [%s]", e.Name) 267 _, err = attachedInput.Write([]byte(pgpWarningsMsg)) 268 require.NoErrorf(t, err, "writing to attached signer [%s]", e.Name) 269 require.NoErrorf(t, attachedInput.Close(), "writing to attached sign [%s]", e.Name) 270 arg = &PGPVerifyArg{ 271 Source: attachedSink, 272 SignedBy: signedBy, 273 } 274 eng = NewPGPVerify(alice.tc.G, arg) 275 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 276 require.Lenf(t, eng.SignatureStatus().Warnings, e.Count, "warnings count [%s]", e.Name) 277 278 // Detached signatures are probably the easiest 279 require.NoError(t, openpgp.ArmoredDetachSignText( 280 detachedSink, 281 bob.key.Entity, 282 strings.NewReader(pgpWarningsMsg), 283 cfg, 284 ), "detached sign failure") 285 arg = &PGPVerifyArg{ 286 Source: strings.NewReader(pgpWarningsMsg), 287 Signature: detachedSink.Bytes(), 288 SignedBy: signedBy, 289 } 290 eng = NewPGPVerify(alice.tc.G, arg) 291 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 292 require.Lenf(t, eng.SignatureStatus().Warnings, e.Count, "warnings count [%s]", e.Name) 293 294 return 295 } 296 297 if e.Mode == "decrypt" { 298 // Mostly the same as decrypt, except we're using different code paths 299 // to achieve roughly the same effect. 300 301 var ( 302 clearsignSink = libkb.NewBufferCloser() 303 clearsignOutputSink = libkb.NewBufferCloser() 304 attachedSink = libkb.NewBufferCloser() 305 attachedOutputSink = libkb.NewBufferCloser() 306 ) 307 308 var signedBy string 309 if e.Known { 310 signedBy = bob.user.Username 311 } 312 313 // Start with the clearsign sig, which technically isn't even something 314 // decryptable. 315 clearsignInput, err := clearsign.Encode( 316 clearsignSink, 317 bob.key.PrivateKey, 318 cfg, 319 ) 320 require.NoErrorf(t, err, "clearsign failure [%s]", e.Name) 321 _, err = clearsignInput.Write([]byte(pgpWarningsMsg)) 322 require.NoErrorf(t, err, "writing to clearsign [%s]", e.Name) 323 require.NoErrorf(t, clearsignInput.Close(), "finishing clearsign [%s]", e.Name) 324 arg := &PGPDecryptArg{ 325 Sink: clearsignOutputSink, 326 Source: clearsignSink, 327 AssertSigned: true, 328 SignedBy: signedBy, 329 } 330 eng := NewPGPDecrypt(alice.tc.G, arg) 331 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 332 333 // TODO: Y2K-1334 Fix this test 334 require.Lenf(t, eng.SignatureStatus().Warnings, e.Count, "warnings count [%s]", e.Name) 335 // require.Equalf(t, []byte(pgpWarningsMsg), clearsignOutputSink.Bytes(), "output should be the same as the input [%s]", e.Name) 336 337 // Then process the attached sig 338 attachedInput, _, err := libkb.ArmoredAttachedSign( 339 attachedSink, 340 *bob.key.Entity, 341 nil, 342 cfg, 343 ) 344 require.NoErrorf(t, err, "attached sign failure [%s]", e.Name) 345 _, err = attachedInput.Write([]byte(pgpWarningsMsg)) 346 require.NoErrorf(t, err, "writing to attached signer [%s]", e.Name) 347 require.NoErrorf(t, attachedInput.Close(), "closing the attached signer [%s]", e.Name) 348 arg = &PGPDecryptArg{ 349 Sink: attachedOutputSink, 350 Source: attachedSink, 351 AssertSigned: true, 352 SignedBy: signedBy, 353 } 354 eng = NewPGPDecrypt(alice.tc.G, arg) 355 require.NoErrorf(t, RunEngine2(m, eng), "engine failure [%s]", e.Name) 356 require.Lenf(t, eng.SignatureStatus().Warnings, e.Count, "warnings count [%s]", e.Name) 357 require.Equalf(t, []byte(pgpWarningsMsg), attachedOutputSink.Bytes(), "output should be the same as the input [%s]", e.Name) 358 359 return 360 } 361 } 362 363 func TestPGPWarnings(t *testing.T) { 364 users := map[string]map[crypto.Hash]*pgpWarningsUserBundle{} 365 366 // g, _ := errgroup.WithContext(context.Background()) 367 for _, x := range []encryptTest{ 368 // Encrypt 369 { 370 Name: "Encrypt to a SHA1 recipient", 371 AlicesHash: crypto.SHA256, 372 BobsHash: crypto.SHA1, 373 Count: 1, 374 Mode: "encrypt", 375 Known: true, 376 }, 377 378 // Encrypt and sign 379 { 380 Name: "Encrypt and sign to a SHA1 recipient", 381 AlicesHash: crypto.SHA256, 382 BobsHash: crypto.SHA1, 383 Count: 1, 384 Mode: "encrypt-and-sign", 385 Known: true, 386 }, 387 { 388 Name: "Encrypt and sign to a SHA256 recipient", 389 AlicesHash: crypto.SHA256, 390 BobsHash: crypto.SHA256, 391 Count: 0, 392 Mode: "encrypt-and-sign", 393 Known: true, 394 }, 395 { 396 Name: "Encrypt and sign from SHA1 to a SHA1 recipient", 397 AlicesHash: crypto.SHA1, 398 BobsHash: crypto.SHA1, 399 Count: 2, 400 Mode: "encrypt-and-sign", 401 Known: true, 402 }, 403 { 404 Name: "Encrypt and sign from SHA1 to a SHA256 recipient", 405 AlicesHash: crypto.SHA1, 406 BobsHash: crypto.SHA256, 407 Count: 1, 408 Mode: "encrypt-and-sign", 409 Known: true, 410 }, 411 412 // Sign 413 { 414 Name: "Sign using SHA1", 415 AlicesHash: crypto.SHA1, 416 BobsHash: crypto.SHA256, // unused 417 Count: 1, 418 Mode: "sign", 419 Known: true, 420 }, 421 { 422 Name: "Sign using SHA256", 423 AlicesHash: crypto.SHA256, 424 BobsHash: crypto.SHA256, // unused 425 Count: 0, 426 Mode: "sign", 427 Known: true, 428 }, 429 430 // Verify - will run all 3 variants (clearsign / attached / detached) 431 { 432 Name: "Verification of a SHA1 sig with a SHA1 self-sig", 433 DigestHash: crypto.SHA1, 434 AlicesHash: crypto.SHA256, 435 BobsHash: crypto.SHA1, 436 Count: 2, 437 Mode: "verify", 438 Known: true, 439 }, 440 { 441 Name: "Verification of a SHA256 sig with a SHA1 self-sig", 442 DigestHash: crypto.SHA256, 443 AlicesHash: crypto.SHA256, 444 BobsHash: crypto.SHA1, 445 Count: 1, 446 Mode: "verify", 447 }, 448 { 449 Name: "Verification of a SHA1 sig with a SHA256 self-sig", 450 DigestHash: crypto.SHA1, 451 AlicesHash: crypto.SHA256, 452 BobsHash: crypto.SHA256, 453 Count: 1, 454 Mode: "verify", 455 Known: true, 456 }, 457 { 458 Name: "Verification of a SHA1 sig with a SHA256 self-sig (unknown)", 459 DigestHash: crypto.SHA1, 460 AlicesHash: crypto.SHA256, 461 BobsHash: crypto.SHA256, 462 Count: 1, 463 Mode: "verify", 464 Known: false, 465 }, 466 { 467 Name: "Verification of a SHA256 sig with a SHA256 self-sig", 468 DigestHash: crypto.SHA256, 469 AlicesHash: crypto.SHA256, 470 BobsHash: crypto.SHA256, 471 Count: 0, 472 Mode: "verify", 473 }, 474 475 // Decrypt - will run all 2 variants (clearsign / attached) 476 { 477 Name: "Decryption of a SHA1 sig with a SHA1 self-sig", 478 DigestHash: crypto.SHA1, 479 AlicesHash: crypto.SHA256, 480 BobsHash: crypto.SHA1, 481 Count: 2, 482 Mode: "decrypt", 483 Known: true, 484 }, 485 { 486 Name: "Decryption of a SHA256 sig with a SHA1 self-sig", 487 DigestHash: crypto.SHA256, 488 AlicesHash: crypto.SHA256, 489 BobsHash: crypto.SHA1, 490 Count: 1, 491 Mode: "decrypt", 492 }, 493 { 494 Name: "Decryption of a SHA1 sig with a SHA256 self-sig", 495 DigestHash: crypto.SHA1, 496 AlicesHash: crypto.SHA256, 497 BobsHash: crypto.SHA256, 498 Count: 1, 499 Mode: "decrypt", 500 Known: true, 501 }, 502 { 503 Name: "Decryption of a SHA256 sig with a SHA256 self-sig", 504 DigestHash: crypto.SHA256, 505 AlicesHash: crypto.SHA256, 506 BobsHash: crypto.SHA256, 507 Count: 0, 508 Mode: "decrypt", 509 }, 510 } { 511 x.test(t, users) 512 } 513 514 for _, hashesToBundles := range users { 515 for _, textBundle := range hashesToBundles { 516 textBundle.tc.Cleanup() 517 } 518 } 519 }