mail-gpg 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|