mail-gpg 0.3.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc0c7bb838506fa1e0e3f01fbce200a95e13e76f
4
- data.tar.gz: a528a571122d6c9b61c5ce2152b37973bdec88d9
3
+ metadata.gz: 96edf0fbe3f22c01808d60328a9d316d4cab6588
4
+ data.tar.gz: dbe492f6351962e3696eb13a8185bc99f0ded52a
5
5
  SHA512:
6
- metadata.gz: 40c3c768a539f4e016fc259cbd13b5d7323b423bde90be8dcb4a970017dc9d33e686d73e5d1a834facd8db82e9816f96f1e1e08eebbdbfa21773eeb435e51493
7
- data.tar.gz: 1b37f11b4beb3311a1c894c2b1c77e626f8cadfd4cacdac74ab9bb96e528c6773ad10a91b093585276677825ed589555e21a7fe1673e82fd6193960e71bb7d87
6
+ metadata.gz: 6ef811441d350386921a18d46d5b4aff71867eacb346565a5a1583eb8e516c3f0643b065cb1e02cca46db7cfa0333b5ed82435aab6da6bed4a9ecf5b34c054aa
7
+ data.tar.gz: 2588e6bbce89a8dd1fde81c582352c40b694a8a9c1de1b71ec57fbbf50bbf75cb770f75da3b822e25034abace1d210958a557bf7b224e8e305cbcaf2c2e7887f
data/.gitignore CHANGED
@@ -16,7 +16,7 @@ rdoc
16
16
  spec/reports
17
17
  test/tmp
18
18
  test/version_tmp
19
- test/gpghome/random_seed
19
+ test/gpghome
20
20
  tmp
21
21
  *.swp
22
22
  tags
@@ -1,18 +1,21 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.1
4
- - 2.2.5
5
- - 2.1.9
3
+ - 2.6.3
4
+ - 2.5.5
5
+ - 2.4.6
6
6
  env:
7
- - RAILS=3.2.22.1
8
- - RAILS=4.1.14.1
9
- - RAILS=4.2.5.1
10
- - RAILS=5.0.0.1
7
+ - RAILS=4.2.11.1 GPG_BIN=/usr/bin/gpg2
8
+ - RAILS=4.2.11.1 GPG_BIN=/usr/bin/gpg
9
+ - RAILS=5.2.3 GPG_BIN=/usr/bin/gpg2
10
+ - RAILS=5.2.3 GPG_BIN=/usr/bin/gpg
11
11
  matrix:
12
12
  exclude:
13
- - rvm: 2.1.9
14
- env: RAILS=5.0.0.1
15
13
  before_install:
16
- - gem update bundler
17
- sudo: false
14
+ - gem install bundler -v "~> 2.0"
15
+ - sudo apt install -y gnupg gnupg2
18
16
  cache: bundler
17
+ dist: xenial
18
+ addons:
19
+ apt:
20
+ update: true
21
+
@@ -1,3 +1,35 @@
1
+ == 0.4.4 2020-07-30
2
+
3
+ * preserve Content-ID header of signed parts (relevant for inline images in
4
+ HTML mails)
5
+
6
+ == 0.4.3 2020-02-12
7
+
8
+ * fix bad signatures of some mails with Mail 2.7.1 by always enforcing
9
+ base64 encoding for signed content
10
+
11
+ == 0.4.2 2019-09-02
12
+
13
+ * do not die on invalid content-transfer encodings when checking if a message
14
+ is inline-signed or encrypted
15
+
16
+ == 0.4.1 2019-07-08
17
+
18
+ * do not modify argument hash #61
19
+ * fix tests on travis and run them with both gpg < 2.0 and >= 2.1
20
+ * gpg 2.0.x apparently has no way of preseeding passphrases and thus will only
21
+ ever work with passphraseless keys.
22
+
23
+ == 0.4.0 2018-05-19
24
+
25
+ * [MIGHT BREAK THINGS] changes to the way keys are looked up #55
26
+ Previously, keys that were not explicitly mentioned but already present in
27
+ the key chain for one of the recipient addresses would have been used
28
+ silently. This is no longer the case, if the :keys option is given, all
29
+ necessary keys have to be specified as either key data, key id, fingerprint
30
+ or GPGME::Key object.
31
+ * fix error when calling encrypt with actual key objects #60
32
+
1
33
  == 0.3.3 2018-04-01
2
34
 
3
35
  * fix broken GpgmeHelper#keys_for_data #59
data/README.md CHANGED
@@ -84,6 +84,13 @@ In theory you only need to specify the key once like that, however doing it
84
84
  every time does not hurt as gpg is clever enough to recognize known keys, only
85
85
  updating it's db when necessary.
86
86
 
87
+ Note: Mail-Gpg in version 0.4 and up is more strict regarding the keys option:
88
+ if it is present, only key material from there (either given as key data like
89
+ above, or as key id, key fingerprint or `GPGMe::Key` object if they have been
90
+ imported before) will be used. Keys already present in the local keychain for
91
+ any of the recipients that are not explicitly mentioned in the `keys` hash will
92
+ be ignored.
93
+
87
94
  You may also want to have a look at the [GPGME](https://github.com/ueno/ruby-gpgme) docs and code base for more info on the various options, especially regarding the `passphrase_callback` arguments.
88
95
 
89
96
 
data/Rakefile CHANGED
@@ -1,57 +1,14 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rake/testtask'
3
3
  require 'gpgme'
4
+ require 'byebug'
4
5
 
5
- def setup_gpghome
6
- gpghome = File.join File.dirname(__FILE__), 'test', 'gpghome'
7
- ENV['GNUPGHOME'] = gpghome
8
- ENV['GPG_AGENT_INFO'] = '' # disable gpg agent
9
- unless File.directory? gpghome
10
- FileUtils.mkdir_p gpghome
11
- GPGME::Ctx.new do |gpg|
12
- gpg.generate_key <<-END
13
- <GnupgKeyParms format="internal">
14
- Key-Type: DSA
15
- Key-Length: 1024
16
- Subkey-Type: ELG-E
17
- Subkey-Length: 1024
18
- Name-Real: Joe Tester
19
- Name-Comment: with stupid passphrase
20
- Name-Email: joe@foo.bar
21
- Expire-Date: 0
22
- Passphrase: abc
23
- </GnupgKeyParms>
24
- END
25
- gpg.generate_key <<-END
26
- <GnupgKeyParms format="internal">
27
- Key-Type: DSA
28
- Key-Length: 1024
29
- Subkey-Type: ELG-E
30
- Subkey-Length: 1024
31
- Name-Real: Jane Doe
32
- Name-Comment: with stupid passphrase
33
- Name-Email: jane@foo.bar
34
- Expire-Date: 0
35
- Passphrase: abc
36
- </GnupgKeyParms>
37
- END
38
- end
39
- end
40
- end
41
-
42
- task :default => ["mail_gpg:tests:setup", :test]
43
-
44
- namespace :mail_gpg do
45
- namespace :tests do
46
- task :setup do
47
- setup_gpghome
48
- end
49
- end
50
- end
6
+ task :default => [:test]
51
7
 
52
8
  Rake::TestTask.new(:test) do |test|
53
9
  test.libs << 'test'
54
10
  test.test_files = FileList['test/**/*_test.rb']
55
11
  test.verbose = true
12
+ test.warning = false
56
13
  end
57
14
 
@@ -17,10 +17,13 @@ require 'mail/gpg/inline_signed_message'
17
17
 
18
18
  module Mail
19
19
  module Gpg
20
+ BEGIN_PGP_MESSAGE_MARKER = /^-----BEGIN PGP MESSAGE-----/
21
+ BEGIN_PGP_SIGNED_MESSAGE_MARKER = /^-----BEGIN PGP SIGNED MESSAGE-----/
22
+
20
23
  # options are:
21
24
  # :sign: sign message using the sender's private key
22
25
  # :sign_as: sign using this key (give the corresponding email address or key fingerprint)
23
- # :passphrase: passphrase for the signing key
26
+ # :password: passphrase for the signing key
24
27
  # :keys: A hash mapping recipient email addresses to public keys or public
25
28
  # key ids. Imports any keys given here that are not already part of the
26
29
  # local keychain before sending the mail.
@@ -198,10 +201,10 @@ module Mail
198
201
  # check if inline PGP (i.e. if any parts of the mail includes
199
202
  # the PGP MESSAGE marker)
200
203
  def self.encrypted_inline?(mail)
201
- return true if mail.body.include?('-----BEGIN PGP MESSAGE-----')
204
+ return true if mail.body.to_s =~ BEGIN_PGP_MESSAGE_MARKER rescue nil
202
205
  if mail.multipart?
203
206
  mail.parts.each do |part|
204
- return true if part.body.include?('-----BEGIN PGP MESSAGE-----')
207
+ return true if part.body.to_s =~ BEGIN_PGP_MESSAGE_MARKER rescue nil
205
208
  return true if part.has_content_type? &&
206
209
  /application\/(?:octet-stream|pgp-encrypted)/ =~ part.mime_type &&
207
210
  /.*\.(?:pgp|gpg|asc)$/ =~ part.content_type_parameters[:name] &&
@@ -223,10 +226,10 @@ module Mail
223
226
  # check if inline PGP (i.e. if any parts of the mail includes
224
227
  # the PGP SIGNED marker)
225
228
  def self.signed_inline?(mail)
226
- return true if mail.body.to_s =~ /^-----BEGIN PGP SIGNED MESSAGE-----/
229
+ return true if mail.body.to_s =~ BEGIN_PGP_SIGNED_MESSAGE_MARKER rescue nil
227
230
  if mail.multipart?
228
231
  mail.parts.each do |part|
229
- return true if part.body.to_s =~ /^-----BEGIN PGP SIGNED MESSAGE-----/
232
+ return true if part.body.to_s =~ BEGIN_PGP_SIGNED_MESSAGE_MARKER rescue nil
230
233
  end
231
234
  end
232
235
  false
@@ -7,7 +7,7 @@ module Mail
7
7
  encrypted_mail = nil
8
8
  begin
9
9
  options = TrueClass === mail.gpg ? { encrypt: true } : mail.gpg
10
- if options.delete(:encrypt)
10
+ if options[: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)
@@ -11,7 +11,10 @@ module Mail
11
11
  # :recipients : array of receiver addresses
12
12
  # :keys : A hash mapping recipient email addresses to public keys or public
13
13
  # key ids. Imports any keys given here that are not already part of the
14
- # local keychain before sending the mail.
14
+ # local keychain before sending the mail. If this option is given, strictly
15
+ # only the key material from this hash is used, ignoring any keys for
16
+ # recipients that might have been added to the local key chain but are
17
+ # not mentioned here.
15
18
  # :always_trust : send encrypted mail to untrusted receivers, true by default
16
19
  # :filename : define a custom name for the encrypted file attachment
17
20
  def initialize(cleartext_mail, options = {})
@@ -115,16 +115,31 @@ module Mail
115
115
 
116
116
  # normalizes the list of recipients' emails, key ids and key data to a
117
117
  # list of Key objects
118
+ #
119
+ # if key_data is given, _only_ key material from there is used,
120
+ # and eventually already imported keys in the keychain are ignored.
118
121
  def self.keys_for_data(emails_or_shas_or_keys, key_data = nil)
119
122
  if key_data
123
+ # in this case, emails_or_shas_or_keys is supposed to be the list of
124
+ # recipients, and key_data the key material to be used.
125
+ # We now map these to whatever we find in key_data for each of these
126
+ # addresses.
120
127
  [emails_or_shas_or_keys].flatten.map do |r|
121
- # import any given keys
122
128
  k = key_data[r]
123
- if k and k =~ /-----BEGIN PGP/
124
- k = GPGME::Key.import(k).imports.map(&:fpr)
125
- k = nil if k.size == 0
126
- end
127
- key_id = k || r
129
+ key_id = case k
130
+ when GPGME::Key
131
+ # assuming this is already imported
132
+ k.fingerprint
133
+ when nil, ''
134
+ # nothing
135
+ nil
136
+ when /-----BEGIN PGP/
137
+ # ASCII key data
138
+ GPGME::Key.import(k).imports.map(&:fpr)
139
+ else
140
+ # key id or fingerprint
141
+ k
142
+ end
128
143
  unless key_id.nil? || key_id.empty?
129
144
  GPGME::Key.find(:public, key_id, :encrypt)
130
145
  end
@@ -24,19 +24,8 @@ module Mail
24
24
  return false
25
25
  end
26
26
 
27
- # Work around the problem that plain_part.raw_source prefixes an
28
- # erroneous CRLF, <https://github.com/mikel/mail/issues/702>.
29
- if ! plain_part.raw_source.empty?
30
- plaintext = [ plain_part.header.raw_source,
31
- "\r\n\r\n",
32
- plain_part.body.raw_source
33
- ].join
34
- else
35
- plaintext = plain_part.encoded
36
- end
37
-
38
27
  signature = signature_part.body.encoded
39
- GpgmeHelper.sign_verify(plaintext, signature, options)
28
+ GpgmeHelper.sign_verify(plain_part.encoded, signature, options)
40
29
  end
41
30
  end
42
31
  end
@@ -8,7 +8,7 @@ module Mail
8
8
 
9
9
  def self.build(cleartext_mail)
10
10
  new do
11
- if cleartext_mail.body.multipart?
11
+ if cleartext_mail.multipart?
12
12
  if cleartext_mail.content_type =~ /^(multipart[^;]+)/
13
13
  # preserve multipart/alternative etc
14
14
  content_type $1
@@ -16,11 +16,21 @@ module Mail
16
16
  content_type 'multipart/mixed'
17
17
  end
18
18
  cleartext_mail.body.parts.each do |p|
19
- add_part p
19
+ add_part Mail::Gpg::SignedPart.build(p)
20
20
  end
21
21
  else
22
22
  content_type cleartext_mail.content_type
23
- body cleartext_mail.body.raw_source
23
+ if disposition = cleartext_mail.content_disposition
24
+ content_disposition disposition
25
+ end
26
+ if id = cleartext_mail.header['Content-ID']
27
+ content_id id
28
+ end
29
+
30
+ # brute force approach to avoid messed up line endings that break
31
+ # signatures with Mail 2.7
32
+ body Mail::Encodings::Base64.encode cleartext_mail.body.to_s
33
+ body.encoding = 'base64'
24
34
  end
25
35
  end
26
36
  end
@@ -1,5 +1,5 @@
1
1
  module Mail
2
2
  module Gpg
3
- VERSION = "0.3.3"
3
+ VERSION = "0.4.4"
4
4
  end
5
5
  end
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "mail", "~> 2.5", ">= 2.5.3"
22
22
  spec.add_dependency "gpgme", "~> 2.0", ">= 2.0.2"
23
- spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "bundler", "~> 2.0"
24
24
  spec.add_development_dependency "test-unit", "~> 3.0"
25
25
  spec.add_development_dependency "rake"
26
26
  spec.add_development_dependency "actionmailer", ">= 3.2.0"
@@ -31,7 +31,7 @@ class MyMailer < ActionMailer::Base
31
31
 
32
32
  end
33
33
 
34
- class ActionMailerTest < Test::Unit::TestCase
34
+ class ActionMailerTest < MailGpgTestCase
35
35
  context 'without return_path' do
36
36
  setup do
37
37
  set_passphrase('abc')
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
  require 'mail/gpg/decrypted_part'
3
3
  require 'mail/gpg/encrypted_part'
4
4
 
5
- class DecryptedPartTest < Test::Unit::TestCase
5
+ class DecryptedPartTest < MailGpgTestCase
6
6
  context 'DecryptedPart' do
7
7
  setup do
8
8
  @mail = Mail.new do
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
  require 'mail/gpg/encrypted_part'
3
3
 
4
- class EncryptedPartTest < Test::Unit::TestCase
4
+ class EncryptedPartTest < MailGpgTestCase
5
5
 
6
6
  def check_key_list(keys)
7
7
  assert_equal 1, keys.size
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class GpgTest < Test::Unit::TestCase
3
+ class GpgTest < MailGpgTestCase
4
4
 
5
5
  def check_headers(mail = @mail, encrypted = @encrypted)
6
6
  assert_equal mail.to, encrypted.to
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class GpgmeHelperTest < Test::Unit::TestCase
3
+ class GpgmeHelperTest < MailGpgTestCase
4
4
 
5
5
  def check_key_list(keys)
6
6
  assert_equal 1, keys.size
@@ -15,6 +15,7 @@ class GpgmeHelperTest < Test::Unit::TestCase
15
15
  assert_equal [], Mail::Gpg::GpgmeHelper.send(:keys_for_data, [])
16
16
  end
17
17
 
18
+ # no keys given, assuming they are already in the keychain
18
19
  context 'with email address' do
19
20
  setup do
20
21
  @email = 'jane@foo.bar'
@@ -29,9 +30,10 @@ class GpgmeHelperTest < Test::Unit::TestCase
29
30
  assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, [@email])
30
31
  check_key_list keys
31
32
  end
32
-
33
33
  end
34
34
 
35
+ # this is a use case we do not really need but it works due to the way
36
+ # Gpgme looks up keys
35
37
  context 'with key id' do
36
38
  setup do
37
39
  @key_id = GPGME::Key.find(:public, 'jane@foo.bar').first.sha
@@ -47,6 +49,8 @@ class GpgmeHelperTest < Test::Unit::TestCase
47
49
  end
48
50
  end
49
51
 
52
+ # this is a use case we do not really need but it works due to the way
53
+ # Gpgme looks up keys
50
54
  context 'with key fingerprint' do
51
55
  setup do
52
56
  @key_fpr = GPGME::Key.find(:public, 'jane@foo.bar').first.fingerprint
@@ -62,17 +66,94 @@ class GpgmeHelperTest < Test::Unit::TestCase
62
66
  end
63
67
  end
64
68
 
65
- context 'with emails and key data' do
69
+ context 'with email addresses' do
66
70
  setup do
67
- @key = GPGME::Key.find(:public, 'jane@foo.bar').first.export(armor: true).to_s
71
+ @key = GPGME::Key.find(:public, 'jane@foo.bar').first
68
72
  @emails = ['jane@foo.bar']
69
- @key_data = { 'jane@foo.bar' => @key }
70
73
  end
71
74
 
72
- should 'resolve to gpg keys' do
73
- assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, @key_data)
74
- check_key_list keys
75
+ # probably the most common use case - one or more recipient addresses and a
76
+ # hash mapping them to public key data that the user pasted into a text
77
+ # field at some point
78
+ context 'and key data' do
79
+ setup do
80
+ @key = @key.export(armor: true).to_s
81
+ @key_data = { 'jane@foo.bar' => @key }
82
+ end
83
+
84
+ should 'resolve to gpg key for single address' do
85
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails.first, @key_data)
86
+ check_key_list keys
87
+ end
88
+
89
+ should 'resolve to gpg keys' do
90
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, @key_data)
91
+ check_key_list keys
92
+ end
93
+
94
+ should 'ignore unknown addresses' do
95
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['john@doe.com'], @key_data)
96
+ assert keys.blank?
97
+ end
98
+
99
+ should 'ignore invalid key data and not use existing key' do
100
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['jane@foo.bar'], { 'jane@foo.bar' => "-----BEGIN PGP\ninvalid key data" })
101
+ assert keys.blank?
102
+ end
75
103
  end
104
+
105
+ context 'and key id or fpr' do
106
+ setup do
107
+ @key_id = @key.sha
108
+ @key_fpr = @key.fingerprint
109
+ @email = @emails.first
110
+ end
111
+
112
+ should 'resolve id to gpg key for single address' do
113
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails.first, { @email => @key_id })
114
+ check_key_list keys
115
+ end
116
+
117
+ should 'resolve id to gpg key' do
118
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, { @email => @key_id })
119
+ check_key_list keys
120
+ end
121
+
122
+ should 'resolve fpr to gpg key' do
123
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, { @email => @key_fpr })
124
+ check_key_list keys
125
+ end
126
+
127
+ should 'ignore unknown addresses' do
128
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['john@doe.com'], { @email => @key_fpr })
129
+ assert keys.blank?
130
+ end
131
+
132
+ should 'ignore invalid key id and not use existing key' do
133
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, { @email => "invalid key id" })
134
+ assert keys.blank?
135
+ end
136
+
137
+ end
138
+
139
+ # mapping email addresses to already retrieved key objects or
140
+ # key fingerprints is also possible.
141
+ context 'and key object' do
142
+ setup do
143
+ @key_data = { 'jane@foo.bar' => @key }
144
+ end
145
+
146
+ should 'resolve to gpg keys for these addresses' do
147
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, @key_data)
148
+ check_key_list keys
149
+ end
150
+
151
+ should 'ignore unknown addresses' do
152
+ assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['john@doe.com'], @key_data)
153
+ assert keys.blank?
154
+ end
155
+ end
156
+
76
157
  end
77
158
  end
78
159
  end
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
  require 'byebug'
3
3
  require 'hkp'
4
4
 
5
- class HkpTest < Test::Unit::TestCase
5
+ class HkpTest < MailGpgTestCase
6
6
 
7
7
  context "hpk client" do
8
8
  {
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  # test cases for PGP inline messages (i.e. non-mime)
4
- class InlineDecryptedMessageTest < Test::Unit::TestCase
4
+ class InlineDecryptedMessageTest < MailGpgTestCase
5
5
 
6
6
  context "InlineDecryptedMessage" do
7
7
 
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  # test cases for PGP inline signed messages (i.e. non-mime)
4
- class InlineSignedMessageTest < Test::Unit::TestCase
4
+ class InlineSignedMessageTest < MailGpgTestCase
5
5
 
6
6
  context "InlineSignedMessage" do
7
7
 
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class MessageTest < Test::Unit::TestCase
3
+ class MessageTest < MailGpgTestCase
4
4
 
5
5
  context "Mail::Message" do
6
6
 
@@ -38,24 +38,98 @@ class MessageTest < Test::Unit::TestCase
38
38
  end
39
39
  end
40
40
 
41
+ context "with multi line utf-8 body and gpg signing only" do
42
+ setup do
43
+ @mail.charset = 'UTF-8'
44
+ @body = <<-END
45
+ one
46
+ two
47
+ euro €
48
+ three
49
+ END
50
+
51
+ @mail.body = @body
52
+ @mail.gpg sign: true, password: 'abc'
53
+ @mail.deliver
54
+ @signed = Mail.new @mails.first.to_s
55
+ @verified = @signed.verify
56
+ # Mail gem from 2.7.1 onwards converts "\n" to "\r\n"
57
+ @body = Mail::Utilities.to_crlf(@body)
58
+ end
59
+
60
+ should 'keep body unchanged' do
61
+ body = @verified.body.to_s.force_encoding 'UTF-8'
62
+ assert_equal @body, body
63
+ end
64
+
65
+ should 'verify signed mail' do
66
+ refute @signed.encrypted?
67
+ assert @signed.multipart?, "message should be multipart"
68
+ assert @signed.signed?, "message should be signed"
69
+ assert sign_part = @signed.parts.last
70
+ GPGME::Crypto.new.verify(sign_part.body.to_s, signed_text: @signed.parts.first.encoded) do |sig|
71
+ assert sig.valid?, "Signature is not valid"
72
+ end
73
+
74
+ assert @verified.signature_valid?, "Signature check failed!"
75
+ refute @verified.multipart?
76
+ end
77
+
78
+ end
79
+
41
80
  context "with gpg signing only" do
42
81
  setup do
43
82
  @mail.gpg sign: true, password: 'abc'
44
83
  end
45
84
 
85
+ context 'with attachment' do
86
+ setup do
87
+ p = Mail::Part.new do
88
+ body "and\nanother part euro €"
89
+ end
90
+ @mail.add_part p
91
+ # if we do not force it to binary, the line ending is changed to CRLF. WTF?
92
+ @attachment_data = "this is\n € not an image".force_encoding(Encoding::BINARY)
93
+ @mail.attachments['test.jpg'] = { mime_type: 'image/jpeg',
94
+ content: @attachment_data }
95
+ @mail.attachments['test.jpg'].header['Content-ID'] = '<image002.jpg@01D665C1.3F756500>'
96
+
97
+ @mail.deliver
98
+ @signed = Mail.new @mails.first.to_s
99
+ @verified = @signed.verify
100
+ end
101
+
102
+ should 'verify signature' do
103
+ assert @verified.signature_valid?
104
+ end
105
+
106
+ should 'have original three parts' do
107
+ assert_equal 3, @verified.parts.size
108
+ assert_equal 'i am unencrypted', @verified.parts[0].body.to_s
109
+ assert_equal "and\r\nanother part euro €", @verified.parts[1].body.to_s.force_encoding('UTF-8')
110
+ assert attachment = @verified.parts[2]
111
+ assert attachment.attachment?
112
+ assert_equal "attachment; filename=test.jpg", attachment.content_disposition
113
+ assert_equal @attachment_data, attachment.body.to_s
114
+ assert_equal '<image002.jpg@01D665C1.3F756500>', attachment.header['Content-ID'].to_s
115
+ end
116
+
117
+ end
118
+
46
119
  context 'with multiple parts' do
47
120
  setup do
48
121
  p = Mail::Part.new do
49
- body 'and another part'
122
+ body "and\nanother part euro €"
50
123
  end
51
124
  @mail.add_part p
52
125
  p = Mail::Part.new do
53
- body 'and a third part'
126
+ content_type "text/html; charset=UTF-8"
127
+ body "and an\nHTML part €"
54
128
  end
55
129
  @mail.add_part p
56
130
 
57
131
  @mail.deliver
58
- @signed = @mails.first
132
+ @signed = Mail.new @mails.first.to_s
59
133
  @verified = @signed.verify
60
134
  end
61
135
 
@@ -67,8 +141,8 @@ class MessageTest < Test::Unit::TestCase
67
141
  assert_equal 3, @mail.parts.size
68
142
  assert_equal 3, @verified.parts.size
69
143
  assert_equal 'i am unencrypted', @verified.parts[0].body.to_s
70
- assert_equal 'and another part', @verified.parts[1].body.to_s
71
- assert_equal 'and a third part', @verified.parts[2].body.to_s
144
+ assert_equal "and\r\nanother part euro €", @verified.parts[1].body.to_s.force_encoding('UTF-8')
145
+ assert_equal "and an\r\nHTML part €", @verified.parts[2].body.to_s.force_encoding('UTF-8')
72
146
  end
73
147
  end
74
148
 
@@ -133,25 +207,42 @@ class MessageTest < Test::Unit::TestCase
133
207
  end
134
208
  end
135
209
 
136
- context 'with encryption and signing' do
210
+ context 'utf-8 with encryption and signing' do
137
211
  setup do
212
+ @body = "one\neuro €"
213
+ @mail.charset = 'UTF-8'
214
+ @mail.body @body
138
215
  @mail.gpg encrypt: true, sign: true, password: 'abc'
139
216
  @mail.deliver
217
+ assert_equal 1, @mails.size
218
+ assert m = @mails.first
219
+ @received = Mail.new m.to_s
140
220
  end
141
221
 
142
222
  should 'decrypt and check signature' do
143
- assert_equal 1, @mails.size
144
- assert m = @mails.first
223
+ m = @received
145
224
  assert_equal 'test', m.subject
146
225
  assert m.multipart?
147
226
  assert m.encrypted?
148
227
  assert decrypted = m.decrypt(:password => 'abc', verify: true)
149
228
  assert_equal 'test', decrypted.subject
150
229
  assert decrypted == @mail
151
- assert_equal 'i am unencrypted', decrypted.body.to_s
230
+ assert_equal "one\r\neuro €", decrypted.body.to_s.force_encoding('UTF-8')
152
231
  assert decrypted.signature_valid?
153
232
  assert_equal 1, decrypted.signatures.size
154
233
  end
234
+
235
+ should 'preserve headers in raw_source output' do
236
+ m = @received
237
+ assert decrypted = m.decrypt(:password => 'abc', verify: true)
238
+ assert s = decrypted.raw_source
239
+ assert s.include?('From: joe@foo.bar')
240
+ assert s.include?('To: jane@foo.bar')
241
+ assert s.include?('Subject: test')
242
+
243
+ body = decrypted.body.to_s.force_encoding('UTF-8')
244
+ assert body.include?('euro €'), s
245
+ end
155
246
  end
156
247
 
157
248
  context "with gpg turned on" do
@@ -213,9 +304,11 @@ class MessageTest < Test::Unit::TestCase
213
304
  assert m = @mails.first
214
305
  assert_equal 'test', m.subject
215
306
  # incorrect passphrase
216
- if GPG21 == true
307
+ if @gpg_utils.preset_passphrases?
217
308
  set_passphrase('incorrect')
218
- expected_exception = GPGME::Error::DecryptFailed
309
+ # expected_exception = GPGME::Error::DecryptFailed
310
+ # I dont know why.
311
+ expected_exception = EOFError
219
312
  else
220
313
  expected_exception = GPGME::Error::BadPassphrase
221
314
  end
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
  require 'mail/gpg/sign_part'
3
3
 
4
- class SignPartTest < Test::Unit::TestCase
4
+ class SignPartTest < MailGpgTestCase
5
5
  context 'SignPart' do
6
6
  setup do
7
7
  set_passphrase('abc')
@@ -4,54 +4,146 @@ require 'shoulda/context'
4
4
  require 'mail-gpg'
5
5
  require 'action_mailer'
6
6
  require 'securerandom'
7
-
8
- begin
9
- require 'pry-nav'
10
- rescue LoadError
11
- end
7
+ require 'byebug'
12
8
 
13
9
  Mail.defaults do
14
10
  delivery_method :test
15
11
  end
16
12
  ActionMailer::Base.delivery_method = :test
17
13
 
18
- def get_keygrip(uid)
19
- `gpg --list-secret-keys --with-colons #{uid} 2>&1`.lines.grep(/^grp/).first.split(':')[9]
20
- end
14
+ class MailGpgTestCase < Test::Unit::TestCase
15
+ def setup
16
+ @gpg_utils = GPGTestUtils.new(ENV['GPG_BIN'])
17
+ @gpg_utils.setup
18
+ end
21
19
 
22
- # Test for and set up GnuPG v2.1
23
- gpg_engine = GPGME::Engine.info.find {|e| e.protocol == GPGME::PROTOCOL_OpenPGP }
24
- if Gem::Version.new(gpg_engine.version) >= Gem::Version.new("2.1.0")
25
- GPG21 = true
26
- libexecdir = `gpgconf --list-dir`.lines.grep(/^libexecdir:/).first.split(':').last.strip
27
- GPPBIN = File.join(libexecdir, 'gpg-preset-passphrase')
28
- KEYGRIP_JANE = get_keygrip('jane@foo.bar')
29
- KEYGRIP_JOE = get_keygrip('joe@foo.bar')
30
- else
31
- GPG21 = false
20
+ def set_passphrase(*args)
21
+ @gpg_utils.set_passphrase(*args)
22
+ end
32
23
  end
33
24
 
34
- # Put passphrase into gpg-agent (required with GnuPG v2).
35
- def set_passphrase(passphrase)
36
- if GPG21
37
- ensure_gpg_agent
38
- call_gpp(KEYGRIP_JANE, passphrase)
39
- call_gpp(KEYGRIP_JOE, passphrase)
25
+ class GPGTestUtils
26
+ attr_reader :gpg_engine
27
+
28
+ def initialize(gpg_bin = nil)
29
+ @home = File.join File.dirname(__FILE__), 'gpghome'
30
+ @gpg_bin = gpg_bin
31
+
32
+ ENV['GPG_AGENT_INFO'] = '' # disable gpg agent
33
+ ENV['GNUPGHOME'] = @home
34
+
35
+ if @gpg_bin
36
+ GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_bin, @home)
37
+ else
38
+ GPGME::Engine.home_dir = @home
39
+ end
40
+
41
+ @gpg_engine = GPGME::Engine.info.find {|e| e.protocol == GPGME::PROTOCOL_OpenPGP }
42
+ @gpg_bin ||= @gpg_engine.file_name
43
+
44
+ if Gem::Version.new(@gpg_engine.version) >= Gem::Version.new("2.1.0")
45
+ @preset_passphrases = true
46
+ else
47
+ @preset_passphrases = false
48
+ end
40
49
  end
41
- end
42
50
 
43
- def ensure_gpg_agent
44
- # Make sure the gpg-agent is running (doesn't start automatically when
45
- # gpg-preset-passphrase is calling).
46
- output = `gpgconf --launch gpg-agent 2>&1`
47
- if ! output.empty?
48
- $stderr.puts "Launching gpg-agent returned: #{output}"
51
+ def preset_passphrases?
52
+ !!@preset_passphrases
49
53
  end
50
- end
51
54
 
52
- def call_gpp(keygrip, passphrase)
53
- output, status = Open3.capture2e(GPPBIN, '--homedir', ENV['GNUPGHOME'], '--preset', keygrip, {stdin_data: passphrase})
54
- if ! output.empty?
55
- $stderr.puts "#{GPPBIN} returned status #{status.exitstatus}: #{output}"
55
+ def setup
56
+ gen_keys unless File.directory? @home
57
+
58
+ if @preset_passphrases
59
+ libexecdir = `gpgconf --list-dir`.lines.grep(/^libexecdir:/).first.split(':').last.strip
60
+ @gpp_bin = File.join(libexecdir, 'gpg-preset-passphrase')
61
+ @keygrip_jane = get_keygrip('jane@foo.bar')
62
+ @keygrip_joe = get_keygrip('joe@foo.bar')
63
+ end
64
+
65
+ end
66
+
67
+ def gen_keys
68
+ puts "setting up keydir #{@home}"
69
+ FileUtils.mkdir_p @home
70
+ (File.open(File.join(@home, "gpg-agent.conf"), "wb") << "allow-preset-passphrase\nbatch\n").close
71
+ GPGME::Ctx.new do |gpg|
72
+ gpg.generate_key <<-END
73
+ <GnupgKeyParms format="internal">
74
+ Key-Type: DSA
75
+ Key-Length: 1024
76
+ Subkey-Type: ELG-E
77
+ Subkey-Length: 1024
78
+ Name-Real: Joe Tester
79
+ Name-Comment: with stupid passphrase
80
+ Name-Email: joe@foo.bar
81
+ Expire-Date: 0
82
+ Passphrase: abc
83
+ </GnupgKeyParms>
84
+ END
85
+ gpg.generate_key <<-END
86
+ <GnupgKeyParms format="internal">
87
+ Key-Type: DSA
88
+ Key-Length: 1024
89
+ Subkey-Type: ELG-E
90
+ Subkey-Length: 1024
91
+ Name-Real: Jane Doe
92
+ Name-Comment: with stupid passphrase
93
+ Name-Email: jane@foo.bar
94
+ Expire-Date: 0
95
+ Passphrase: abc
96
+ </GnupgKeyParms>
97
+ END
98
+ end
99
+ end
100
+
101
+ # Put passphrase into gpg-agent (required with GnuPG v2).
102
+ def set_passphrase(passphrase)
103
+ if preset_passphrases?
104
+ ensure_gpg_agent
105
+ call_gpp(@keygrip_jane, passphrase)
106
+ call_gpp(@keygrip_joe, passphrase)
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def get_keygrip(uid)
113
+ output = `#{@gpg_bin} --list-secret-keys --with-keygrip --with-colons #{uid} 2>&1`
114
+ if line = output.lines.grep(/^grp/).first
115
+ line.split(':')[9]
116
+ else
117
+ puts "malformed key list output:\n#{output}"
118
+ raise
119
+ end
120
+ end
121
+
122
+ def ensure_gpg_agent
123
+ # Make sure the gpg-agent is running (doesn't start automatically when
124
+ # gpg-preset-passphrase is calling).
125
+ output = `gpgconf --launch gpg-agent 2>&1`
126
+ if ! output.empty?
127
+ $stderr.puts "Launching gpg-agent returned: #{output}"
128
+ end
129
+ end
130
+
131
+ def call_gpp(keygrip, passphrase)
132
+ output, status = Open3.capture2e(@gpp_bin, '--homedir', ENV['GNUPGHOME'], '--preset', keygrip, {stdin_data: passphrase})
133
+ if ! output.empty?
134
+ $stderr.puts "#{@gpp_bin} returned status #{status.exitstatus}: #{output}"
135
+ end
56
136
  end
57
137
  end
138
+
139
+ gpg_utils = GPGTestUtils.new(ENV['GPG_BIN'])
140
+ v = Gem::Version.new(gpg_utils.gpg_engine.version)
141
+ if v >= Gem::Version.new("2.1.0")
142
+ puts "Running with GPG >= 2.1"
143
+ elsif v >= Gem::Version.new("2.0.0")
144
+ puts "Running with GPG 2.0, this isn't going well since we cannot set passphrases non-interactively"
145
+ else
146
+ puts "Running with GPG < 2.0"
147
+ end
148
+ gpg_utils.setup
149
+
@@ -1,32 +1,32 @@
1
1
  require 'test_helper'
2
2
  require 'mail/gpg/version_part'
3
3
 
4
- class VersionPartTest < Test::Unit::TestCase
4
+ class VersionPartTest < MailGpgTestCase
5
5
  context 'VersionPart' do
6
6
 
7
7
  should 'roundtrip successfully' do
8
8
  part = Mail::Gpg::VersionPart.new()
9
9
  assert Mail::Gpg::VersionPart.isVersionPart?(part)
10
10
  end
11
-
11
+
12
12
  should 'return false for non gpg mime type' do
13
13
  part = Mail::Gpg::VersionPart.new()
14
14
  part.content_type = 'text/plain'
15
15
  assert !Mail::Gpg::VersionPart.isVersionPart?(part)
16
16
  end
17
-
17
+
18
18
  should 'return false for empty body' do
19
19
  part = Mail::Gpg::VersionPart.new()
20
20
  part.body = nil
21
21
  assert !Mail::Gpg::VersionPart.isVersionPart?(part)
22
22
  end
23
-
23
+
24
24
  should 'return false for foul body' do
25
25
  part = Mail::Gpg::VersionPart.new()
26
26
  part.body = 'non gpg body'
27
27
  assert !Mail::Gpg::VersionPart.isVersionPart?(part)
28
28
  end
29
-
29
+
30
30
  should 'return true for body with extra content' do
31
31
  part = Mail::Gpg::VersionPart.new()
32
32
  part.body = "#{part.body} extra content"
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.3.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jens Kraemer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-31 00:00:00.000000000 Z
11
+ date: 2020-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mail
@@ -56,14 +56,14 @@ dependencies:
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '1.3'
59
+ version: '2.0'
60
60
  type: :development
61
61
  prerelease: false
62
62
  version_requirements: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: '1.3'
66
+ version: '2.0'
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: test-unit
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -210,7 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
210
210
  version: '0'
211
211
  requirements: []
212
212
  rubyforge_project:
213
- rubygems_version: 2.4.5
213
+ rubygems_version: 2.6.14.4
214
214
  signing_key:
215
215
  specification_version: 4
216
216
  summary: GPG/MIME encryption plugin for the Ruby Mail Library