mail-gpg 0.1.0 → 0.1.1
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.
- data/lib/mail/gpg.rb +36 -26
- data/lib/mail/gpg/decrypted_part.rb +2 -2
- data/lib/mail/gpg/delivery_handler.rb +1 -1
- data/lib/mail/gpg/version.rb +1 -1
- data/test/decrypted_part_test.rb +3 -3
- data/test/gpg_test.rb +63 -47
- data/test/message_test.rb +10 -8
- metadata +2 -2
data/lib/mail/gpg.rb
CHANGED
@@ -23,18 +23,18 @@ module Mail
|
|
23
23
|
# local keychain before sending the mail.
|
24
24
|
# :always_trust : send encrypted mail to untrusted receivers, true by default
|
25
25
|
def self.encrypt(cleartext_mail, options = {})
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
construct_mail(cleartext_mail, options) do
|
27
|
+
receivers = []
|
28
|
+
receivers += cleartext_mail.to if cleartext_mail.to
|
29
|
+
receivers += cleartext_mail.cc if cleartext_mail.cc
|
30
|
+
receivers += cleartext_mail.bcc if cleartext_mail.bcc
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
if options[:sign_as]
|
33
|
+
options[:sign] = true
|
34
|
+
options[:signers] = options.delete(:sign_as)
|
35
|
+
elsif options[:sign]
|
36
|
+
options[:signers] = cleartext_mail.from
|
37
|
+
end
|
38
38
|
|
39
39
|
add_part VersionPart.new
|
40
40
|
add_part EncryptedPart.new(cleartext_mail,
|
@@ -44,16 +44,16 @@ module Mail
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
def self.sign(cleartext_mail, options = {})
|
48
|
+
construct_mail(cleartext_mail, options) do
|
49
|
+
options[:sign_as] ||= cleartext_mail.from
|
50
|
+
add_part SignPart.new(cleartext_mail, options)
|
51
|
+
add_part Mail::Part.new(cleartext_mail)
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
content_type "multipart/signed; micalg=pgp-sha1; protocol=\"application/pgp-signature\"; boundary=#{boundary}"
|
54
|
+
body.preamble = options[:preamble] || "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)"
|
55
|
+
end
|
56
|
+
end
|
57
57
|
|
58
58
|
def self.decrypt(encrypted_mail, options = {})
|
59
59
|
if encrypted_mime?(encrypted_mail)
|
@@ -64,7 +64,7 @@ module Mail
|
|
64
64
|
raise EncodingError, "Unsupported encryption format '#{encrypted_mail.content_type}'"
|
65
65
|
end
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def self.encrypted?(mail)
|
69
69
|
return true if encrypted_mime?(mail)
|
70
70
|
return true if encrypted_inline?(mail)
|
@@ -73,7 +73,7 @@ module Mail
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
-
|
76
|
+
def self.construct_mail(cleartext_mail, options, &block)
|
77
77
|
Mail.new do
|
78
78
|
self.perform_deliveries = cleartext_mail.perform_deliveries
|
79
79
|
%w(from to cc bcc subject reply_to in_reply_to).each do |field|
|
@@ -82,9 +82,9 @@ module Mail
|
|
82
82
|
cleartext_mail.header.fields.each do |field|
|
83
83
|
header[field.name] = field.value if field.name =~ /^X-/
|
84
84
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
instance_eval &block
|
86
|
+
end
|
87
|
+
end
|
88
88
|
|
89
89
|
# decrypts PGP/MIME (RFC 3156, section 4) encrypted mail
|
90
90
|
def self.decrypt_pgp_mime(encrypted_mail, options)
|
@@ -95,7 +95,17 @@ module Mail
|
|
95
95
|
if !VersionPart.isVersionPart? encrypted_mail.parts[0]
|
96
96
|
raise EncodingError, "RFC 3136 first part not a valid version part '#{encrypted_mail.parts[0]}'"
|
97
97
|
end
|
98
|
-
Mail.new(DecryptedPart.new(encrypted_mail.parts[1], options))
|
98
|
+
Mail.new(DecryptedPart.new(encrypted_mail.parts[1], options)) do
|
99
|
+
%w(from to cc bcc subject reply_to in_reply_to).each do |field|
|
100
|
+
send field, encrypted_mail.send(field)
|
101
|
+
end
|
102
|
+
# copy header fields
|
103
|
+
# headers from the encrypted part (which are already set by Mail.new
|
104
|
+
# above) will be preserved.
|
105
|
+
encrypted_mail.header.fields.each do |field|
|
106
|
+
header[field.name] = field.value if field.name =~ /^X-/ && header[field.name].nil?
|
107
|
+
end
|
108
|
+
end
|
99
109
|
end
|
100
110
|
|
101
111
|
# decrypts inline PGP encrypted mail
|
@@ -7,9 +7,9 @@ module Mail
|
|
7
7
|
# :verify: decrypt and verify
|
8
8
|
def initialize(cipher_part, options = {})
|
9
9
|
if cipher_part.mime_type != EncryptedPart::CONTENT_TYPE
|
10
|
-
raise EncodingError, "RFC
|
10
|
+
raise EncodingError, "RFC 3156 incorrect mime type for encrypted part '#{cipher_part.mime_type}'"
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
decrypted = GpgmeHelper.decrypt(cipher_part.body.decoded, options)
|
14
14
|
super(decrypted)
|
15
15
|
end
|
@@ -10,7 +10,7 @@ module Mail
|
|
10
10
|
if options.delete(:encrypt)
|
11
11
|
encrypted_mail = Mail::Gpg.encrypt(mail, options)
|
12
12
|
elsif options[:sign] || options[:sign_as]
|
13
|
-
|
13
|
+
encrypted_mail = Mail::Gpg.sign(mail, options)
|
14
14
|
else
|
15
15
|
# encrypt and sign are off -> do not encrypt or sign
|
16
16
|
yield
|
data/lib/mail/gpg/version.rb
CHANGED
data/test/decrypted_part_test.rb
CHANGED
@@ -20,18 +20,18 @@ class DecryptedPartTest < Test::Unit::TestCase
|
|
20
20
|
assert mail.message_id == @mail.message_id
|
21
21
|
assert mail.message_id != @part.message_id
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
should 'decrypt and verify' do
|
25
25
|
assert mail = Mail::Gpg::DecryptedPart.new(@part, { :verify => true, :password => 'abc' })
|
26
26
|
assert mail == @mail
|
27
27
|
assert mail.message_id == @mail.message_id
|
28
28
|
assert mail.message_id != @part.message_id
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
should 'raise encoding error for non gpg mime type' do
|
32
32
|
part = Mail::Part.new(@part)
|
33
33
|
part.content_type = 'text/plain'
|
34
34
|
assert_raise(EncodingError) { Mail::Gpg::DecryptedPart.new(part) }
|
35
|
-
end
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end
|
data/test/gpg_test.rb
CHANGED
@@ -28,20 +28,20 @@ class GpgTest < Test::Unit::TestCase
|
|
28
28
|
assert_equal mail.to_s, clear
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
def check_signature(mail = @mail, signed = @signed)
|
32
|
+
assert signature = signed.parts.detect{|p| p.content_type =~ /signature\.asc/}.body.to_s
|
33
|
+
GPGME::Crypto.new.verify(signature, signed_text: mail.encoded) do |sig|
|
34
|
+
assert true == sig.valid?
|
35
|
+
end
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
def check_mime_structure_signed(mail = @mail, signed = @signed)
|
39
|
+
assert_equal 2, signed.parts.size
|
40
|
+
sign_part, orig_part = signed.parts
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
assert_equal 'application/pgp-signature; name=signature.asc', sign_part.content_type
|
43
|
+
assert_equal orig_part.content_type, @mail.content_type
|
44
|
+
end
|
45
45
|
|
46
46
|
def check_headers_signed(mail = @mail, signed = @signed)
|
47
47
|
assert_equal mail.to, signed.to
|
@@ -57,33 +57,33 @@ class GpgTest < Test::Unit::TestCase
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
60
|
+
context "gpg signed" do
|
61
|
+
setup do
|
62
|
+
@mail = Mail.new do
|
63
|
+
to 'joe@foo.bar'
|
64
|
+
from 'jane@foo.bar'
|
65
|
+
subject 'test test'
|
66
|
+
body 'sign me!'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'simple mail' do
|
71
|
+
setup do
|
72
|
+
@signed = Mail::Gpg.sign(@mail, password: 'abc')
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'have same recipients and subject' do
|
76
|
+
check_headers_signed
|
77
|
+
end
|
78
|
+
|
79
|
+
should 'have proper gpgmime structure' do
|
80
|
+
check_mime_structure_signed
|
81
|
+
end
|
82
|
+
|
83
|
+
should 'have correct signature' do
|
84
|
+
check_signature
|
85
|
+
end
|
86
|
+
end
|
87
87
|
|
88
88
|
context 'mail with custom header' do
|
89
89
|
setup do
|
@@ -153,7 +153,7 @@ class GpgTest < Test::Unit::TestCase
|
|
153
153
|
assert_match /Rakefile/, original_part.parts.last.content_disposition
|
154
154
|
end
|
155
155
|
end
|
156
|
-
|
156
|
+
end
|
157
157
|
|
158
158
|
context "gpg encrypted" do
|
159
159
|
|
@@ -188,7 +188,7 @@ class GpgTest < Test::Unit::TestCase
|
|
188
188
|
assert mail == @mail
|
189
189
|
end
|
190
190
|
end
|
191
|
-
|
191
|
+
|
192
192
|
context 'simple mail (signed)' do
|
193
193
|
setup do
|
194
194
|
@encrypted = Mail::Gpg.encrypt(@mail, { :sign => true, :password => 'abc' })
|
@@ -210,12 +210,13 @@ class GpgTest < Test::Unit::TestCase
|
|
210
210
|
assert mail = Mail::Gpg.decrypt(@encrypted, { :verify => true, :password => 'abc' })
|
211
211
|
assert mail == @mail
|
212
212
|
end
|
213
|
-
end
|
213
|
+
end
|
214
214
|
|
215
215
|
context 'mail with custom header' do
|
216
216
|
setup do
|
217
217
|
@mail.header['X-Custom-Header'] = 'custom value'
|
218
218
|
@encrypted = Mail::Gpg.encrypt(@mail)
|
219
|
+
@encrypted.header['X-Another-Header'] = 'another value'
|
219
220
|
end
|
220
221
|
|
221
222
|
should 'have same recipients and subject' do
|
@@ -233,11 +234,26 @@ class GpgTest < Test::Unit::TestCase
|
|
233
234
|
should 'preserve customer header values' do
|
234
235
|
assert_equal 'custom value', @encrypted.header['X-Custom-Header'].to_s
|
235
236
|
end
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
237
|
+
|
238
|
+
context 'when decrypted' do
|
239
|
+
setup do
|
240
|
+
@decrypted_mail = Mail::Gpg.decrypt(@encrypted, { :password => 'abc' })
|
241
|
+
end
|
242
|
+
|
243
|
+
should 'have same subject and body as the original' do
|
244
|
+
assert_equal @mail.subject, @decrypted_mail.subject
|
245
|
+
assert_equal @mail.body.to_s, @decrypted_mail.body.to_s
|
246
|
+
end
|
247
|
+
|
248
|
+
should 'preserve custom header from encrypted inner mail' do
|
249
|
+
assert_equal 'custom value', @decrypted_mail.header['X-Custom-Header'].to_s
|
250
|
+
end
|
251
|
+
|
252
|
+
should 'preserve custom header from outer mail' do
|
253
|
+
assert_equal 'another value', @decrypted_mail.header['X-Another-Header'].to_s
|
254
|
+
end
|
240
255
|
end
|
256
|
+
|
241
257
|
end
|
242
258
|
|
243
259
|
context 'mail with multiple recipients' do
|
@@ -295,7 +311,7 @@ class GpgTest < Test::Unit::TestCase
|
|
295
311
|
assert_match /encrypt me/, m.parts.first.body.to_s
|
296
312
|
assert_match /Rakefile/, m.parts.last.content_disposition
|
297
313
|
end
|
298
|
-
|
314
|
+
|
299
315
|
should 'decrypt' do
|
300
316
|
assert mail = Mail::Gpg.decrypt(@encrypted, { :password => 'abc' })
|
301
317
|
assert mail == @mail
|
data/test/message_test.rb
CHANGED
@@ -37,10 +37,10 @@ class MessageTest < Test::Unit::TestCase
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
context "with gpg signing only" do
|
41
|
+
setup do
|
42
|
+
@mail.gpg sign: true, password: 'abc'
|
43
|
+
end
|
44
44
|
|
45
45
|
context "" do
|
46
46
|
setup do
|
@@ -56,12 +56,12 @@ class MessageTest < Test::Unit::TestCase
|
|
56
56
|
assert sign_part = m.parts.last
|
57
57
|
assert m = Mail::Message.new(m.parts.last)
|
58
58
|
assert !m.multipart?
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
GPGME::Crypto.new.verify(sign_part.body.to_s, signed_text: @mail.encoded) do |sig|
|
60
|
+
assert true == sig.valid?
|
61
|
+
end
|
62
62
|
end
|
63
63
|
end
|
64
|
-
|
64
|
+
end
|
65
65
|
|
66
66
|
context "with gpg turned on" do
|
67
67
|
setup do
|
@@ -111,7 +111,9 @@ class MessageTest < Test::Unit::TestCase
|
|
111
111
|
assert m.multipart?
|
112
112
|
assert m.encrypted?
|
113
113
|
assert decrypted = m.decrypt(:password => 'abc')
|
114
|
+
assert_equal 'test', decrypted.subject
|
114
115
|
assert decrypted == @mail
|
116
|
+
assert_equal 'i am unencrypted', decrypted.body.to_s
|
115
117
|
end
|
116
118
|
|
117
119
|
should "raise bad passphrase on decrypt" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mail-gpg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-11-
|
12
|
+
date: 2013-11-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mail
|