From 63db443949b92eed5f5d2ded5f430bda96312b43 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sun, 20 Oct 2024 00:01:58 +0200 Subject: gpg: do not sign with untrusted keys During encryption gpg was ignoring trust model of the user in favor of always using the insecure "always". This change removes this override and adds a helpful message if a user tries to encrypt a message with an untrusted key. To revert to the previous behavior users can add "trust-model always" to their gpg.conf file (default ~/.gnupg/gpg.conf). Signed-off-by: Marcin Serwin Acked-by: Tim Culverhouse --- lib/crypto/gpg/gpg_test.go | 57 ++++++++++++++++-------------- lib/crypto/gpg/gpgbin/encrypt.go | 5 +-- lib/crypto/gpg/gpgbin/gpgbin.go | 5 +++ lib/crypto/gpg/gpgbin/import-ownertrust.go | 16 +++++++++ lib/crypto/gpg/reader_test.go | 7 +++- lib/crypto/gpg/writer_test.go | 30 ++++++++++++---- 6 files changed, 82 insertions(+), 38 deletions(-) create mode 100644 lib/crypto/gpg/gpgbin/import-ownertrust.go (limited to 'lib/crypto/gpg') diff --git a/lib/crypto/gpg/gpg_test.go b/lib/crypto/gpg/gpg_test.go index 25d73693..2cd0c3dc 100644 --- a/lib/crypto/gpg/gpg_test.go +++ b/lib/crypto/gpg/gpg_test.go @@ -126,32 +126,35 @@ hsgCjGTIAOQ5lNwpLEMjwLias6e5sM6hcK9Wo+A9Sw23f8lMau5clOZTJeyAUAff const testPublicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- -mQENBF5FJf8BCACvlKhSSsv4P8C3Wbv391SrNUBtFquoMuWKtuCr/Ks6KHuofGLn -bM55uBSQp908aITBDPkaOPsQ3OvwgF7SM8bNIDVpO7FHzCEg2Ysp99iPET/+LsbY -ugc8oYSuvA5aFFIOMYbAbI+HmbIBuCs+xp0AcU1cemAPzPBDCZs4xl5Y+/ce2yQz -ZGK9O/tQQIKoBUOWLo/0byAWyD6Gwn/Le3fVxxK6RPeeizDV6VfzHLxhxBNkMgmd -QUkBkvqF154wYxhzsHn72ushbJpspKz1LQN7d5u6QOq3h2sLwcLbT457qbMfZsZs -HLhoOibOd+yJ7C6TRbbyC4sQRr+K1CNGcvhJABEBAAG0NEpvaG4gRG9lIChUaGlz -IGlzIGEgdGVzdCBrZXkpIDxqb2huLmRvZUBleGFtcGxlLm9yZz6JAU4EEwEIADgW -IQSxqGaTVBU7eZ8iF78wchXBPfepZAUCXkUl/wIbAwULCQgHAgYVCgkICwIEFgID -AQIeAQIXgAAKCRAwchXBPfepZF4iB/49B7q4AfO3xHEa8LK2H+f7Mnm4dRfS2YPo -v2p6TRe1h2DxwpTevNQUhXw2U0nfRIEKBAZqgb7NVktkoh0DWtKatms2yHMAS+ah -lQoHb2gRgXa9M9Tq0x5u9sl0NYnx7Wu5uu6Ybw9luPKoAfO91T0vei0p3eMn3fIV -0O012ITvmgKJPppQDKFJHGZJMbVDO4TNxP89HgyhB41RO7AZadvu73S00x2K6x+O -R4s/++4Y98vScCPm3DUOXeoHXKGqFcNYTxJL9bsE2I0uYgvJSxNoK1dVnmvxp3zz -hcxAdzizgMz0ufY6YLMCjy5MDOzPARkmYPXdkJ6jceOIqGLUw1kquQENBF5FJf8B -CACpsh5cyHB7eEwQvLzJVsXpTW0Rh/Fe36AwC2Vz13WeE6GFrOvw1qATvtYB1919 -M4B44YH9J7I5SrFZad86Aw4n5Gi0BwLlGNa/oCMvYzlNHaTXURA271ghJqdZizqV -UETj3WNoaYm4mYMfb0dcayDJvVPWP7InzOsdIRU9WXBUSyVMxNMXccr2UvIuhdPg -lmVT8NtsWR+q8xBoL2Dp0ojYLVD3MlwKe1pE5mEwasYCkWePLWyGdfDW1MhUDsPH -3K1IjpPLWU9FBk8KM4z8WooY9/kyMIyRw39MvOHGfgcFBpiZwlELNZGSFhbRun03 -PMk2Qd3k+0FGV1IhFAYsr7QRABEBAAGJATYEGAEIACAWIQSxqGaTVBU7eZ8iF78w -chXBPfepZAUCXkUl/wIbDAAKCRAwchXBPfepZOtqB/9xsGEgQgm70KYID39H91k4 -ef/RlpRDY1ndC0MoPfqE03IEXTC/MjtU+ksPKEoZeQsxVaUJ2WBueI5WGJ3Y73pO -HAd7N0SyGHT5s6gK1FSx29be1qiPwUu5KR2jpm3RjgpbymnOWe4C6iiYCFQ85IX+ -LzpE+p9bB02PUrmzOb4MBV6E5mg30UjXIX01+bwZq5XSB4/FaUrQOAxLuRvVRjK0 -CEcFbPGIlkPSW6s4M9xCC2sQi7caFKVK6Zqf78KbOwAHqfS0x9u2jtTIhsgCjGTI -AOQ5lNwpLEMjwLias6e5sM6hcK9Wo+A9Sw23f8lMau5clOZTJeyAUAff+5anTnUn -=ZjQT +mQENBGcUGPEBCACox9bw5BiN9M+1qVtU90bkHl5xzPDl8SqX/2ieYSx0ZfUpmRAH +9EbW4j54cTFM6mX18Yv2LRWQhHjzslPietJ1Lb3PGY2ffDDxJsq/uQHK/ztqePc7 +omJJjUuF5D7BjuOq/MFyu7dWSCXOrj8soY9HIS96pPNTF9ykLDhqKWIqGA7pORKk +RFczMLmEojLKefHvgtp9ikNNbIJyq/P5hNHr/DfC7rFaMTrXNc2xP2MD7MYNdVmT +N2NN/X676rTsu8ltUi96F5PR33mGez6Z66yMjJf863bd+muq8552ExoQGQ/uGo5y +wvwoEOF7hx1Z6JYl56hAICXPL/ZOZTPdBf+9ABEBAAG0NEphbmUgRG9lIChUaGlz +IGlzIGEgdGVzdCBrZXkpIDxqYW5lLmRvZUBleGFtcGxlLm9yZz6JAVEEEwEIADsW +IQSoQ3iEudN9vdxgn6xy8nGZUc/d5AUCZxQY8QIbAwULCQgHAgIiAgYVCgkICwIE +FgIDAQIeBwIXgAAKCRBy8nGZUc/d5ConB/9Z39ufzGmplm0m9ylN+x8iNYJJ5rk6 +WhnwDsKSEDPoYnSUuESQ7zxhPkqr2amgAcFWba6vm+GvdFBB+y8JzSGIBmNmQfuw +dtBd5EI+cTSTzuXo4NXR7TrMJGPP8IvJNSrliG61JnW3kcz9U9dywum+XF57+2X1 +KCt3npJI64sMX39QZ1ReaRbKWrKcBdCWZqW79KbFn4yl4ooMS9aKggQQP91feMA9 +dP3onL+TWLRKVMQ657OngTKi8rIez+RasRmVV3Av+GMl0Tdcg3sWHrlliBexmC/X +mHzbl/PR8HAjWxie+pObGPz1aodJpeI0Lr5LQgJxZtx49kov9Ua5xVUxuQENBGcU +GPEBCACmVEII6Igka7AVqCrUrdRonSzuelT6X6/VToBoJMER7q5MENtqWd0iby4N +kIJxaJQFyXY7mYyZqf2aRbCu+cvh/F77iSZEOzNoJuut5sjPg7MM+s/9GRlYboq9 +RGqDJwoT7+k6cdUJON5UPvdJj8GnFGGu9ZFs/cOz2psggzfeV4YbTKXzFm2yKMpx +LdeBeLXLYG46d0ChZMmKyBLLJWtUb71MU2TTWyrmtDoN02bxDQpAeJu+3Qp6lq+/ +CGe5f407jkx2PDKvV6HkuYzjs8apVFVZsBkDlhkaX5YdFI2r1TxIbxC9k2UG9VLJ +lGNeqO3iUCsjuKd7iaiLGGBIeqKnABEBAAGJATYEGAEIACAWIQSoQ3iEudN9vdxg +n6xy8nGZUc/d5AUCZxQY8QIbDAAKCRBy8nGZUc/d5OxbB/sEqrdtCMFrXLOU7dur +or1lfrlYaOIaOup+/SnTSi688O0ixZ2XjV7CW3z1E8JjWAVsQPdfpC2QOZATWZ/q +ZMuEMwNpzhCVZDwBJR7nw+Pv/xFv9DvLEiJYHCyBrQtQ6vopG0t2yxJ4R/R48fQC +m2xT54mb4flIV/C8zRy3eK2wY/kR5FVxnLwwFlYayR7+wuLTiHqqxRyeZA3hQcF3 +YDOgvRu3YzmESPtIBI6iNphfSSAAtkUqNJnwPAIxyky8xEInUZ7maOADRWgEH8uG ++1FjPta6cgZ1tJzFtJ7Bwa2///UAp7BQqDl7DyMQAfOZGkUI9mqEXdra4YqMv5X0 +Y2UQ +=QL1U -----END PGP PUBLIC KEY BLOCK----- ` + +const testOwnertrust = "B1A8669354153B799F2217BF307215C13DF7A964:6:\n" diff --git a/lib/crypto/gpg/gpgbin/encrypt.go b/lib/crypto/gpg/gpgbin/encrypt.go index fa33e466..712bb327 100644 --- a/lib/crypto/gpg/gpgbin/encrypt.go +++ b/lib/crypto/gpg/gpgbin/encrypt.go @@ -8,13 +8,10 @@ import ( "git.sr.ht/~rjarry/aerc/models" ) -// Encrypt runs gpg --encrypt [--sign] -r [recipient]. The default is to have -// --trust-model always set +// Encrypt runs gpg --encrypt [--sign] -r [recipient] func Encrypt(r io.Reader, to []string, from string) ([]byte, error) { - // TODO probably shouldn't have --trust-model always a default args := []string{ "--armor", - "--trust-model", "always", } if from != "" { args = append(args, "--sign", "--default-key", from) diff --git a/lib/crypto/gpg/gpgbin/gpgbin.go b/lib/crypto/gpg/gpgbin/gpgbin.go index a63dd886..a7eafacd 100644 --- a/lib/crypto/gpg/gpgbin/gpgbin.go +++ b/lib/crypto/gpg/gpgbin/gpgbin.go @@ -206,6 +206,11 @@ func parse(r io.Reader, md *models.MessageDetails) error { md.SignatureError = "gpg: unsupported algorithm" } md.SignedBy = "(unknown signer)" + case "INV_RECP": + t := strings.SplitN(line, " ", 4) + if t[2] == "10" { + return fmt.Errorf("gpg: public key of %s is not trusted", t[3]) + } case "BEGIN_ENCRYPTION": msgCollecting = true case "SIG_CREATED": diff --git a/lib/crypto/gpg/gpgbin/import-ownertrust.go b/lib/crypto/gpg/gpgbin/import-ownertrust.go new file mode 100644 index 00000000..05499917 --- /dev/null +++ b/lib/crypto/gpg/gpgbin/import-ownertrust.go @@ -0,0 +1,16 @@ +package gpgbin + +import ( + "io" +) + +// Import runs gpg --import-ownertrust and thus imports trusts for keys +func ImportOwnertrust(r io.Reader) error { + args := []string{"--import-ownertrust"} + g := newGpg(r, args) + err := g.cmd.Run() + if err != nil { + return err + } + return nil +} diff --git a/lib/crypto/gpg/reader_test.go b/lib/crypto/gpg/reader_test.go index 957a727d..fdb5c452 100644 --- a/lib/crypto/gpg/reader_test.go +++ b/lib/crypto/gpg/reader_test.go @@ -18,6 +18,11 @@ func importPublicKey() { gpgbin.Import(r) } +func importOwnertrust() { + r := strings.NewReader(testOwnertrust) + gpgbin.ImportOwnertrust(r) +} + type readerTestCase struct { name string want models.MessageDetails @@ -26,8 +31,8 @@ type readerTestCase struct { func TestReader(t *testing.T) { initGPGtest(t) - importPublicKey() importSecretKey() + importOwnertrust() testCases := []readerTestCase{ { diff --git a/lib/crypto/gpg/writer_test.go b/lib/crypto/gpg/writer_test.go index 0355db5e..26f3def7 100644 --- a/lib/crypto/gpg/writer_test.go +++ b/lib/crypto/gpg/writer_test.go @@ -16,26 +16,38 @@ func init() { } type writerTestCase struct { - name string - method string - body string + name string + method string + body string + to []string + expectedErr string } func TestWriter(t *testing.T) { initGPGtest(t) - importPublicKey() importSecretKey() + importPublicKey() + importOwnertrust() testCases := []writerTestCase{ { name: "Encrypt", method: "encrypt", body: "This is an encrypted message!\r\n", + to: []string{"john.doe@example.org"}, }, { name: "Sign", method: "sign", body: "This is a signed message!\r\n", + to: []string{"john.doe@example.org"}, + }, + { + name: "Encrypt to untrusted", + method: "encrypt", + body: "This is an encrypted message!\r\n", + to: []string{"jane.doe@example.org"}, + expectedErr: "gpg: failure to encrypt: gpg: public key of jane.doe@example.org is not trusted. check public key(s)", }, } var h textproto.Header @@ -45,18 +57,18 @@ func TestWriter(t *testing.T) { var header textproto.Header header.Set("Content-Type", "text/plain") - to := []string{"john.doe@example.org"} from := "john.doe@example.org" var err error for _, tc := range testCases { + t.Logf("Test case: %s", tc.name) var ( buf bytes.Buffer cleartext io.WriteCloser ) switch tc.method { case "encrypt": - cleartext, err = Encrypt(&buf, h, to, from) + cleartext, err = Encrypt(&buf, h, tc.to, from) if err != nil { t.Fatalf("Encrypt() = %v", err) } @@ -73,8 +85,14 @@ func TestWriter(t *testing.T) { t.Fatalf("io.WriteString() = %v", err) } if err = cleartext.Close(); err != nil { + if err.Error() == tc.expectedErr { + continue + } t.Fatalf("ciphertext.Close() = %v", err) } + if tc.expectedErr != "" { + t.Fatalf("Expected error %v, but got %v", tc.expectedErr, err) + } switch tc.method { case "encrypt": validateEncrypt(t, buf) -- cgit