mail-gpg 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +10 -29
- data/History.txt +14 -0
- data/lib/mail/gpg/inline_decrypted_message.rb +7 -1
- data/lib/mail/gpg/inline_signed_message.rb +7 -0
- data/lib/mail/gpg/version.rb +1 -1
- data/test/inline_decrypted_message_test.rb +47 -0
- data/test/inline_signed_message_test.rb +45 -0
- data/test/message_test.rb +17 -15
- data/test/test_helper.rb +25 -10
- metadata +2 -12
- data/test/gpghome/gpg-agent.conf +0 -2
- data/test/gpghome/pubring.gpg +0 -0
- data/test/gpghome/pubring.gpg~ +0 -0
- data/test/gpghome/secring.gpg +0 -0
- data/test/gpghome/trustdb.gpg +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3acca2c34af794c59ff717e0f29d27b26ed1d1e095dd388a6238e0ef31366644
|
|
4
|
+
data.tar.gz: c1161da992e780ecf3c6bea38977c9b8d0c86b2a5530f2095e398f868e7a70c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac9858c3c4838fa909cec047658a37945fa37ebf08a6b8a005ca336aab4543caed549f4a4c54a614195084772b9baa728978e2db80f2e1582767ea9a888ac743
|
|
7
|
+
data.tar.gz: b082facfd7d4c84a62e40bcf3aaba4866121daa0a65d8a962164031f7989eca572b5811187fb8f41dfb4948dbd7191aea4f1cce44d768c488721334d70073223
|
data/.github/workflows/test.yml
CHANGED
|
@@ -10,37 +10,20 @@ jobs:
|
|
|
10
10
|
test:
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
|
|
13
|
-
strategy:
|
|
14
|
-
fail-fast: false
|
|
15
|
-
matrix:
|
|
16
|
-
ruby-version: ["2.7", "3.0", "3.1", "3.2", "3.3", "3.4"]
|
|
17
|
-
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- name: Install GPG
|
|
22
|
-
run: sudo apt-get update && sudo apt-get install -y gnupg
|
|
23
|
-
|
|
24
|
-
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
25
|
-
uses: ruby/setup-ruby@v1
|
|
26
|
-
with:
|
|
27
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
28
|
-
bundler-cache: true
|
|
29
|
-
|
|
30
|
-
- name: Run tests
|
|
31
|
-
run: bundle exec rake
|
|
32
|
-
|
|
33
|
-
test-with-rails:
|
|
34
|
-
runs-on: ubuntu-latest
|
|
35
|
-
|
|
36
13
|
strategy:
|
|
37
14
|
fail-fast: false
|
|
38
15
|
matrix:
|
|
39
16
|
include:
|
|
17
|
+
- ruby-version: "2.7.8"
|
|
18
|
+
rails-version: "5.2.8.1"
|
|
40
19
|
- ruby-version: "3.2"
|
|
41
|
-
rails-version: "
|
|
20
|
+
rails-version: "8.0"
|
|
42
21
|
- ruby-version: "3.4"
|
|
43
|
-
rails-version: "
|
|
22
|
+
rails-version: "8.0"
|
|
23
|
+
|
|
24
|
+
# picked up by the Gemfile: gem 'actionmailer', "~> #{ENV['RAILS']}"
|
|
25
|
+
env:
|
|
26
|
+
RAILS: ${{ matrix.rails-version }}
|
|
44
27
|
|
|
45
28
|
steps:
|
|
46
29
|
- uses: actions/checkout@v4
|
|
@@ -54,7 +37,5 @@ jobs:
|
|
|
54
37
|
ruby-version: ${{ matrix.ruby-version }}
|
|
55
38
|
bundler-cache: true
|
|
56
39
|
|
|
57
|
-
- name: Run tests
|
|
58
|
-
|
|
59
|
-
RAILS: ${{ matrix.rails-version }}
|
|
60
|
-
run: bundle update && bundle exec rake
|
|
40
|
+
- name: Run tests
|
|
41
|
+
run: bundle exec rake
|
data/History.txt
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
== 0.4.6 2026-08-13
|
|
2
|
+
|
|
3
|
+
* fix double transfer-decoding of inline signed / encrypted mails: the
|
|
4
|
+
rebuilt message kept the original Content-Transfer-Encoding header while
|
|
5
|
+
its body was set to the already decoded (verified / decrypted) text, so
|
|
6
|
+
reading the body would corrupt it (e.g. base64-decode plain text)
|
|
7
|
+
* modernize test setup: RSA 2048 test keys generated without passphrase
|
|
8
|
+
protection (current GnuPG refuses to sign with the old DSA 1024 keys, and
|
|
9
|
+
interactive pinentry prompts no longer get in the way); bad passphrase
|
|
10
|
+
handling is now tested with a dedicated protected key via loopback
|
|
11
|
+
pinentry
|
|
12
|
+
* CI runs on GitHub Actions with Ruby 2.7.8 / Rails 5.2.8.1 and
|
|
13
|
+
Ruby 3.2 / 3.4 with Rails 8
|
|
14
|
+
|
|
1
15
|
== 0.4.5 2026-01-20
|
|
2
16
|
|
|
3
17
|
* fix HKP client for Ruby 3.0+ (replaced deprecated URI.escape/decode)
|
|
@@ -59,7 +59,9 @@ module Mail
|
|
|
59
59
|
else
|
|
60
60
|
p.content_type part.content_type
|
|
61
61
|
p.content_transfer_encoding part.content_transfer_encoding
|
|
62
|
-
|
|
62
|
+
# keep the raw (still encoded) body to match the transfer
|
|
63
|
+
# encoding copied above - body.to_s would decode it
|
|
64
|
+
p.body part.body.raw_source
|
|
63
65
|
end
|
|
64
66
|
end
|
|
65
67
|
end
|
|
@@ -72,6 +74,10 @@ module Mail
|
|
|
72
74
|
cipher_mail.header.fields.each do |field|
|
|
73
75
|
header[field.name] = field.value
|
|
74
76
|
end
|
|
77
|
+
# the decrypted body set below is not transfer encoded, so an
|
|
78
|
+
# encoding copied over from the original mail would lead to
|
|
79
|
+
# double decoding
|
|
80
|
+
header['Content-Transfer-Encoding'] = nil
|
|
75
81
|
body decrypted.to_s
|
|
76
82
|
verify_result decrypted.verify_result if options[:verify] && '' != decrypted
|
|
77
83
|
end
|
|
@@ -17,6 +17,10 @@ module Mail
|
|
|
17
17
|
success, vr = GpgmeHelper.inline_verify(signed_text, options)
|
|
18
18
|
p = VerifiedPart.new(part)
|
|
19
19
|
if success
|
|
20
|
+
# the body set here is already decoded, so a transfer
|
|
21
|
+
# encoding copied over from the original part would lead to
|
|
22
|
+
# double decoding
|
|
23
|
+
p.header['Content-Transfer-Encoding'] = nil
|
|
20
24
|
p.body self.class.strip_inline_signature signed_text
|
|
21
25
|
end
|
|
22
26
|
p.verify_result vr
|
|
@@ -33,6 +37,9 @@ module Mail
|
|
|
33
37
|
signed_mail.header.fields.each do |field|
|
|
34
38
|
header[field.name] = field.value
|
|
35
39
|
end
|
|
40
|
+
# the body set below is already decoded, so a transfer encoding
|
|
41
|
+
# copied over from the original mail would lead to double decoding
|
|
42
|
+
header['Content-Transfer-Encoding'] = nil
|
|
36
43
|
signed_text = signed_mail.body.to_s
|
|
37
44
|
success, vr = GpgmeHelper.inline_verify(signed_text, options)
|
|
38
45
|
if success
|
data/lib/mail/gpg/version.rb
CHANGED
|
@@ -31,6 +31,24 @@ class InlineDecryptedMessageTest < MailGpgTestCase
|
|
|
31
31
|
assert sig.to_s=~ /Joe/
|
|
32
32
|
assert sig.valid?
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
should "decrypt base64 encoded body" do
|
|
36
|
+
mail = Mail.new(@mail)
|
|
37
|
+
cipher_text = InlineDecryptedMessageTest.encrypt(mail, mail.body.to_s)
|
|
38
|
+
mail.content_transfer_encoding 'base64'
|
|
39
|
+
mail.body Mail::Encodings::Base64.encode(cipher_text)
|
|
40
|
+
mail = Mail.new(mail.encoded)
|
|
41
|
+
|
|
42
|
+
assert !mail.multipart?
|
|
43
|
+
assert mail.encrypted?
|
|
44
|
+
assert decrypted = mail.decrypt(:password => 'abc', verify: true)
|
|
45
|
+
assert_equal 'i am unencrypted', decrypted.body.decoded,
|
|
46
|
+
"body was double decoded: #{decrypted.body.decoded.inspect}"
|
|
47
|
+
assert vr = decrypted.verify_result
|
|
48
|
+
assert sig = vr.signatures.first
|
|
49
|
+
assert sig.to_s=~ /Joe/
|
|
50
|
+
assert sig.valid?
|
|
51
|
+
end
|
|
34
52
|
end
|
|
35
53
|
|
|
36
54
|
context "multipart/alternative message" do
|
|
@@ -77,6 +95,35 @@ class InlineDecryptedMessageTest < MailGpgTestCase
|
|
|
77
95
|
end
|
|
78
96
|
end
|
|
79
97
|
|
|
98
|
+
context "base64 encoded cleartext part and encrypted attachment message" do
|
|
99
|
+
should "decrypt attachment and not double decode text part" do
|
|
100
|
+
rakefile = File.open('Rakefile') { |file| file.read }
|
|
101
|
+
mail = Mail.new(@mail)
|
|
102
|
+
mail.content_type = 'multipart/mixed'
|
|
103
|
+
mail.body = ''
|
|
104
|
+
mail.part do |p|
|
|
105
|
+
p.content_type 'text/plain'
|
|
106
|
+
p.content_transfer_encoding 'base64'
|
|
107
|
+
p.body Mail::Encodings::Base64.encode('i am unencrypted')
|
|
108
|
+
end
|
|
109
|
+
mail.part do |p|
|
|
110
|
+
p.content_type 'application/octet-stream; name=Rakefile.pgp'
|
|
111
|
+
p.content_transfer_encoding Mail::Encodings::Base64
|
|
112
|
+
p.content_disposition 'attachment; filename="Rakefile.pgp"'
|
|
113
|
+
p.body Mail::Encodings::Base64::encode(InlineDecryptedMessageTest.encrypt(mail, rakefile, false))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
assert mail.multipart?
|
|
117
|
+
assert mail.encrypted?
|
|
118
|
+
assert decrypted = mail.decrypt(password: 'abc')
|
|
119
|
+
assert !decrypted.encrypted?
|
|
120
|
+
assert_equal 2, decrypted.parts.length
|
|
121
|
+
assert_equal 'i am unencrypted', decrypted.parts[0].body.decoded,
|
|
122
|
+
"part was double decoded: #{decrypted.parts[0].body.decoded.inspect}"
|
|
123
|
+
assert_equal rakefile, decrypted.parts[1].body.decoded
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
80
127
|
context "cleartext body and encrypted attachment message" do
|
|
81
128
|
should "decrypt and verify attachment" do
|
|
82
129
|
rakefile = File.open('Rakefile') { |file| file.read }
|
|
@@ -54,9 +54,54 @@ class InlineSignedMessageTest < MailGpgTestCase
|
|
|
54
54
|
assert !sig.valid?
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
should "verify base64 encoded body" do
|
|
58
|
+
mail = Mail.new(@mail)
|
|
59
|
+
signed_text = self.class.inline_sign(mail, mail.body.to_s)
|
|
60
|
+
mail.content_transfer_encoding 'base64'
|
|
61
|
+
mail.body Mail::Encodings::Base64.encode(signed_text)
|
|
62
|
+
mail = Mail.new(mail.encoded)
|
|
63
|
+
assert !mail.multipart?
|
|
64
|
+
assert mail.signed?
|
|
65
|
+
assert verified = mail.verify
|
|
66
|
+
assert verified.signature_valid?
|
|
67
|
+
assert verified.body.decoded.include?('i am unencrypted'),
|
|
68
|
+
"body was double decoded: #{verified.body.decoded.inspect}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
should "keep base64 encoded body readable when signature is invalid" do
|
|
72
|
+
mail = Mail.new(@mail)
|
|
73
|
+
signed_text = self.class.inline_sign(mail, mail.body.to_s).gsub /i am/, 'i was'
|
|
74
|
+
mail.content_transfer_encoding 'base64'
|
|
75
|
+
mail.body Mail::Encodings::Base64.encode(signed_text)
|
|
76
|
+
mail = Mail.new(mail.encoded)
|
|
77
|
+
assert !mail.multipart?
|
|
78
|
+
assert mail.signed?
|
|
79
|
+
assert verified = mail.verify
|
|
80
|
+
assert !verified.signature_valid?
|
|
81
|
+
assert verified.body.decoded.include?('i was unencrypted'),
|
|
82
|
+
"body was double decoded: #{verified.body.decoded.inspect}"
|
|
83
|
+
end
|
|
84
|
+
|
|
57
85
|
end
|
|
58
86
|
|
|
59
87
|
context "message with signed attachment" do
|
|
88
|
+
should "check base64 encoded attachment signature" do
|
|
89
|
+
mail = Mail.new(@mail)
|
|
90
|
+
mail.body = 'foobar'
|
|
91
|
+
signed_text = self.class.inline_sign(mail, 'sign me!')
|
|
92
|
+
mail.part do |p|
|
|
93
|
+
p.content_transfer_encoding 'base64'
|
|
94
|
+
p.body Mail::Encodings::Base64.encode(signed_text)
|
|
95
|
+
end
|
|
96
|
+
mail = Mail.new(mail.encoded)
|
|
97
|
+
assert mail.multipart?
|
|
98
|
+
assert mail.signed?
|
|
99
|
+
assert verified = mail.verify
|
|
100
|
+
assert verified.signature_valid?
|
|
101
|
+
assert verified.parts.last.body.decoded.include?('sign me!'),
|
|
102
|
+
"part was double decoded: #{verified.parts.last.body.decoded.inspect}"
|
|
103
|
+
end
|
|
104
|
+
|
|
60
105
|
should "check attachment signature" do
|
|
61
106
|
mail = Mail.new(@mail)
|
|
62
107
|
mail.body = 'foobar'
|
data/test/message_test.rb
CHANGED
|
@@ -298,24 +298,26 @@ class MessageTest < MailGpgTestCase
|
|
|
298
298
|
end
|
|
299
299
|
|
|
300
300
|
should "raise bad passphrase on decrypt" do
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
# patty's key is passphrase protected; loopback pinentry keeps
|
|
302
|
+
# gpg-agent from prompting interactively
|
|
303
|
+
mail = Mail.new do
|
|
304
|
+
from 'joe@foo.bar'
|
|
305
|
+
to 'patty@foo.bar'
|
|
306
|
+
subject 'test'
|
|
307
|
+
body 'i am unencrypted'
|
|
308
|
+
gpg encrypt: true
|
|
309
|
+
end
|
|
310
|
+
mail.deliver
|
|
311
|
+
assert m = @mails.last
|
|
303
312
|
assert_equal 'test', m.subject
|
|
313
|
+
assert m.encrypted?
|
|
304
314
|
# incorrect passphrase
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
# expected_exception = GPGME::Error::DecryptFailed
|
|
308
|
-
# I dont know why.
|
|
309
|
-
expected_exception = EOFError
|
|
310
|
-
else
|
|
311
|
-
expected_exception = GPGME::Error::BadPassphrase
|
|
312
|
-
end
|
|
313
|
-
assert_raises(expected_exception){
|
|
314
|
-
m.decrypt(:password => 'incorrect')
|
|
315
|
+
assert_raises(GPGME::Error::BadPassphrase){
|
|
316
|
+
m.decrypt(password: 'incorrect', pinentry_mode: GPGME::PINENTRY_MODE_LOOPBACK)
|
|
315
317
|
}
|
|
316
|
-
# no passphrase
|
|
317
|
-
assert_raises(
|
|
318
|
-
m.decrypt
|
|
318
|
+
# no passphrase - gpg gets no data from the loopback pinentry
|
|
319
|
+
assert_raises(GPGME::Error::NoData){
|
|
320
|
+
m.decrypt(pinentry_mode: GPGME::PINENTRY_MODE_LOOPBACK)
|
|
319
321
|
}
|
|
320
322
|
end
|
|
321
323
|
end
|
data/test/test_helper.rb
CHANGED
|
@@ -71,31 +71,46 @@ class GPGTestUtils
|
|
|
71
71
|
def gen_keys
|
|
72
72
|
puts "setting up keydir #{@home}"
|
|
73
73
|
FileUtils.mkdir_p @home
|
|
74
|
-
(File.open(File.join(@home, "gpg-agent.conf"), "wb") << "allow-preset-passphrase\
|
|
74
|
+
(File.open(File.join(@home, "gpg-agent.conf"), "wb") << "allow-preset-passphrase\n").close
|
|
75
75
|
GPGME::Ctx.new do |gpg|
|
|
76
76
|
gpg.generate_key <<-END
|
|
77
77
|
<GnupgKeyParms format="internal">
|
|
78
|
-
|
|
79
|
-
Key-
|
|
80
|
-
|
|
81
|
-
Subkey-
|
|
78
|
+
%no-protection
|
|
79
|
+
Key-Type: RSA
|
|
80
|
+
Key-Length: 2048
|
|
81
|
+
Subkey-Type: RSA
|
|
82
|
+
Subkey-Length: 2048
|
|
82
83
|
Name-Real: Joe Tester
|
|
83
84
|
Name-Comment: with stupid passphrase
|
|
84
85
|
Name-Email: joe@foo.bar
|
|
85
86
|
Expire-Date: 0
|
|
86
|
-
Passphrase: abc
|
|
87
87
|
</GnupgKeyParms>
|
|
88
88
|
END
|
|
89
89
|
gpg.generate_key <<-END
|
|
90
90
|
<GnupgKeyParms format="internal">
|
|
91
|
-
|
|
92
|
-
Key-
|
|
93
|
-
|
|
94
|
-
Subkey-
|
|
91
|
+
%no-protection
|
|
92
|
+
Key-Type: RSA
|
|
93
|
+
Key-Length: 2048
|
|
94
|
+
Subkey-Type: RSA
|
|
95
|
+
Subkey-Length: 2048
|
|
95
96
|
Name-Real: Jane Doe
|
|
96
97
|
Name-Comment: with stupid passphrase
|
|
97
98
|
Name-Email: jane@foo.bar
|
|
98
99
|
Expire-Date: 0
|
|
100
|
+
</GnupgKeyParms>
|
|
101
|
+
END
|
|
102
|
+
# passphrase protected key for testing bad passphrase handling with
|
|
103
|
+
# loopback pinentry
|
|
104
|
+
gpg.generate_key <<-END
|
|
105
|
+
<GnupgKeyParms format="internal">
|
|
106
|
+
Key-Type: RSA
|
|
107
|
+
Key-Length: 2048
|
|
108
|
+
Subkey-Type: RSA
|
|
109
|
+
Subkey-Length: 2048
|
|
110
|
+
Name-Real: Patty Protected
|
|
111
|
+
Name-Comment: with stupid passphrase
|
|
112
|
+
Name-Email: patty@foo.bar
|
|
113
|
+
Expire-Date: 0
|
|
99
114
|
Passphrase: abc
|
|
100
115
|
</GnupgKeyParms>
|
|
101
116
|
END
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mail-gpg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jens Kraemer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: mail
|
|
@@ -190,11 +190,6 @@ files:
|
|
|
190
190
|
- test/decrypted_part_test.rb
|
|
191
191
|
- test/encrypted_part_test.rb
|
|
192
192
|
- test/gpg_test.rb
|
|
193
|
-
- test/gpghome/gpg-agent.conf
|
|
194
|
-
- test/gpghome/pubring.gpg
|
|
195
|
-
- test/gpghome/pubring.gpg~
|
|
196
|
-
- test/gpghome/secring.gpg
|
|
197
|
-
- test/gpghome/trustdb.gpg
|
|
198
193
|
- test/gpgme_helper_test.rb
|
|
199
194
|
- test/hkp_test.rb
|
|
200
195
|
- test/inline_decrypted_message_test.rb
|
|
@@ -231,11 +226,6 @@ test_files:
|
|
|
231
226
|
- test/decrypted_part_test.rb
|
|
232
227
|
- test/encrypted_part_test.rb
|
|
233
228
|
- test/gpg_test.rb
|
|
234
|
-
- test/gpghome/gpg-agent.conf
|
|
235
|
-
- test/gpghome/pubring.gpg
|
|
236
|
-
- test/gpghome/pubring.gpg~
|
|
237
|
-
- test/gpghome/secring.gpg
|
|
238
|
-
- test/gpghome/trustdb.gpg
|
|
239
229
|
- test/gpgme_helper_test.rb
|
|
240
230
|
- test/hkp_test.rb
|
|
241
231
|
- test/inline_decrypted_message_test.rb
|
data/test/gpghome/gpg-agent.conf
DELETED
data/test/gpghome/pubring.gpg
DELETED
|
Binary file
|
data/test/gpghome/pubring.gpg~
DELETED
|
Binary file
|
data/test/gpghome/secring.gpg
DELETED
|
Binary file
|
data/test/gpghome/trustdb.gpg
DELETED
|
Binary file
|