actionmailer_x509 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,7 @@ petRUShka, Fabien Penso
4
4
 
5
5
  == DESCRIPTION
6
6
 
7
- This plugin allows you to send X509 signed mails with Rails 3 ActionMailer. If you want to sign mails with Rails 2, you should use original plugin: https://github.com/penso/actionmailer_x509
7
+ This plugin allows you to send X509 signed and\or crypted mails with Rails 3 ActionMailer. If you want to sign mails with Rails 2, you should use the original plugin: https://github.com/penso/actionmailer_x509
8
8
 
9
9
  It has been tested with Rails 3.0.3.
10
10
 
@@ -67,10 +67,15 @@ your ActionMailer model.
67
67
  def sending_method(email, from , subject = "Empty subject for signed")
68
68
  # If you want to sign the mail
69
69
  x509_sign true
70
- x509_cert "certs/yourwebsite.crt"
71
- x509_key "certs/yourwebsite.key"
70
+ x509_sign_cert "certs/yourwebsite.crt"
71
+ x509_sign_key "certs/yourwebsite.key"
72
72
  # In case your certificate has a passphrase
73
- x509_passphrase "my passphrase for the certificate"
73
+ x509_sign_passphrase "my passphrase for the certificate"
74
+ # If you want to crypt the mail
75
+ x509_crypt true
76
+ x509_crypt_cert "certs/crypt.crt"
77
+ # In case you want to set non DES cipher
78
+ x509_crypt_cipher "AES-128-CBC"
74
79
 
75
80
  mail(:subject => subject, :to => email, :from => from)
76
81
  end
@@ -80,6 +85,8 @@ You can also specify the certificate and key in your environment file:
80
85
  ActionMailer::Base.default_x509_sign = true
81
86
  ActionMailer::Base.default_x509_cert = "certs/server.crt"
82
87
  ActionMailer::Base.default_x509_key = "certs/server.key"
88
+ ActionMailer::Base.default_x509_crypt = true
89
+ ActionMailer::Base.default_x509_crypt_cert = "certs/crypt.crt"
83
90
 
84
91
  == USING TEST
85
92
 
@@ -122,12 +129,12 @@ readable.
122
129
 
123
130
  == AUTHORS
124
131
 
125
- Porting to Rails 3\\Mail from Rails 2\\Tmail and wrap code in gem was done by petRUShka <petrushkin@yandex.ru>.
132
+ Porting to Rails 3\\Mail from Rails 2\\Tmail, adding crypting and wrap code into the gem was done by petRUShka <petrushkin@yandex.ru>.
126
133
 
127
134
  Original development for Rails 2.0.1 was done by Fabien Penso <fabien.penso@conovae.com> from
128
135
  CONOVAE http://www.conovae.com for Dimelo http://www.dimelo.fr
129
136
 
130
- Special thanks to Geal[http://stackoverflow.com/users/203955/geal] for noticing on error.
137
+ Special thanks to Geal[http://stackoverflow.com/users/203955/geal] for noticing on content-id error.
131
138
 
132
139
  This code is under the BSD license.
133
140
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "actionmailer_x509"
3
- s.version = "0.3.0"
3
+ s.version = "0.4.0"
4
4
  s.authors = ["petRUShka", "Fabien Penso", "CONOVAE"]
5
5
  s.email = "petrushkin@yandex.ru"
6
6
  s.files = `git ls-files`.split("\n")
@@ -8,6 +8,6 @@ Gem::Specification.new do |s|
8
8
  s.homepage = "http://github.com/petRUShka/actionmailer_x509"
9
9
  s.require_path = "lib"
10
10
  s.rubygems_version = "1.3.5"
11
- s.summary = "This Rails 3 plugin allows you to send X509 signed mails."
11
+ s.summary = "This Rails 3 plugin allows you to send X509 signed and\\or crypted mails."
12
12
  end
13
13
 
@@ -33,61 +33,67 @@ require "openssl"
33
33
  module ActionMailer #:nodoc:
34
34
  class Base #:nodoc:
35
35
  @@default_x509_sign = false
36
- @@default_x509_crypt = false # not used, for later.
37
- @@default_x509_cert = nil
38
- @@default_x509_key = nil
39
- @@default_x509_sign_method = :smime
40
- @@default_x509_crypt_method = :smime # not used, for later.
41
- @@default_x509_passphrase = nil
36
+ @@default_x509_sign_cert = nil
37
+ @@default_x509_sign_key = nil
38
+ @@default_x509_sign_passphrase = nil
39
+
40
+ @@default_x509_crypt = false
41
+ @@default_x509_crypt_cert = nil
42
+ @@default_x509_crypt_cipher = "des"
43
+
44
+ @@default_x509_sign_and_crypt_method = :smime
42
45
 
43
46
  # Should we sign the outgoing mail?
44
47
  adv_attr_accessor :x509_sign
45
48
 
46
- # Should we crypt the outgoing mail?. NOTE: not used yet.
49
+ # Should we crypt the outgoing mail?
47
50
  adv_attr_accessor :x509_crypt
48
51
 
49
52
  # Which certificate will be used for signing.
50
- adv_attr_accessor :x509_cert
53
+ adv_attr_accessor :x509_sign_cert
51
54
 
52
55
  # Which private key will be used for signing.
53
- adv_attr_accessor :x509_key
54
-
55
- # Which signing method is used. NOTE: For later, if needed.
56
- adv_attr_accessor :x509_sign_method
56
+ adv_attr_accessor :x509_sign_key
57
57
 
58
- # Which crypting method is used. NOTE: not used yet.
59
- adv_attr_accessor :x509_crypt_method
58
+ # Which certificate will be used for crypting.
59
+ adv_attr_accessor :x509_crypt_cert
60
60
 
61
- # Passphrase for the key, if needed.
62
- adv_attr_accessor :x509_passphrase
61
+ # Which encryption algorithm will be used for crypting.
62
+ adv_attr_accessor :x509_crypt_cipher
63
63
 
64
+ # Which signing method is used. NOTE: For later, if needed.
65
+ adv_attr_accessor :x509_sign_and_crypt_method
64
66
 
67
+ # Passphrase for the sign key, if needed.
68
+ adv_attr_accessor :x509_sign_passphrase
65
69
 
66
- # We replace the create! methods and run a new method if signing is required
67
- def initialize_with_sign(method_name, *parameters)
68
- mail = initialize_without_sign(method_name, *parameters)
70
+ # We replace the initialize methods and run a new method if signing or crypting is required
71
+ def initialize_with_sign_and_crypt(method_name, *parameters)
72
+ mail = initialize_without_sign_and_crypt(method_name, *parameters)
69
73
 
70
74
  x509_initvar()
71
75
 
72
76
  # If we need to sign the outgoing mail.
73
- if should_sign?
77
+ if should_sign? or should_crypt?
74
78
  if logger
75
- logger.debug("actionmailer_x509: We should sign the mail with #{@x509_sign_method} method.")
79
+ logger.debug("actionmailer_x509: We should sign and\or crypt the mail with #{@x509_sign_and_crypt_method} method.")
76
80
  end
77
- __send__("x509_sign_#{@x509_sign_method}", mail)
81
+ __send__("x509_#{@x509_sign_and_crypt_method}", mail)
78
82
  end
79
83
 
80
84
  end
81
- alias_method_chain :initialize, :sign
85
+ alias_method_chain :initialize, :sign_and_crypt
82
86
 
83
- # X509 SMIME signing
84
- def x509_sign_smime(mail)
87
+ # X509 SMIME signing and\or crypting
88
+ def x509_smime(mail)
85
89
  if logger
86
- logger.debug("actionmailer_x509: X509 SMIME signing with cert #{@x509_cert} and key #{@x509_key}")
90
+ logger.debug("actionmailer_x509: X509 SMIME signing with cert #{@x509_cert} and key #{@x509_key}") if should_sign?
91
+ logger.debug("actionmailer_x509: X509 SMIME crypt with cert #{@x509_cert}") if should_crypt?
87
92
  end
88
93
 
89
94
  # We should set content_id, otherwise Mail will set content_id after signing and will broke sign
90
95
  mail.content_id ||= nil
96
+ mail.parts.each {|p| p.content_id ||= nil}
91
97
 
92
98
  # We can remove the headers from the older mail we encapsulate.
93
99
  # Leaving allows to have the headers signed too within the encapsulated
@@ -103,14 +109,24 @@ module ActionMailer #:nodoc:
103
109
  # mail.mime_version = nil
104
110
 
105
111
  # We load certificate and private key
106
- cert = OpenSSL::X509::Certificate.new( File::read(@x509_cert) )
107
- prv_key = OpenSSL::PKey::RSA.new( File::read(@x509_key), @x509_passphrase)
112
+ if should_sign?
113
+ sign_cert = OpenSSL::X509::Certificate.new( File::read(@x509_sign_cert) )
114
+ sign_prv_key = OpenSSL::PKey::RSA.new( File::read(@x509_sign_key), @x509_sign_passphrase)
115
+ end
116
+
117
+ if should_crypt?
118
+ crypt_cert = OpenSSL::X509::Certificate.new( File::read(@x509_crypt_cert) )
119
+ cipher = OpenSSL::Cipher.new(@x509_crypt_cipher)
120
+ end
121
+
122
+ # begin
123
+ # Sign and crypt the mail
108
124
 
109
- begin
110
- # Sign the mail
111
125
  # NOTE: the one following line is the slowest part of this code, signing is sloooow
112
- p7sign = OpenSSL::PKCS7.sign(cert,prv_key,mail.encoded, [], OpenSSL::PKCS7::DETACHED)
113
- smime0 = OpenSSL::PKCS7::write_smime(p7sign)
126
+ p7 = mail.encoded
127
+ p7 = OpenSSL::PKCS7.sign(sign_cert,sign_prv_key, p7, [], OpenSSL::PKCS7::DETACHED) if should_sign?
128
+ p7 = OpenSSL::PKCS7.encrypt([crypt_cert], (should_sign? ? OpenSSL::PKCS7::write_smime(p7) : p7), cipher, nil) if should_crypt?
129
+ smime0 = OpenSSL::PKCS7::write_smime(p7)
114
130
 
115
131
  # Adding the signature part to the older mail
116
132
  newm = Mail.new(smime0)
@@ -140,25 +156,24 @@ module ActionMailer #:nodoc:
140
156
  # newm.parts << signature
141
157
 
142
158
  @_message = newm
143
- rescue Exception => detail
144
- logger.error("Error while SMIME signing the mail : #{detail}")
145
- end
159
+ # rescue Exception => detail
160
+ # logger.error("Error while SMIME signing and\or crypting the mail : #{detail}")
161
+ # end
146
162
 
147
163
  ## logger.debug("x509_sign_smime, resulted email\n-------------( test X509 )----------\n#{m.encoded}\n-------------( test X509 )----------")
148
164
 
149
165
  end
150
166
 
151
- # X509 SMIME crypting
152
- def x509_crypt_smime(mail)
153
- logger.debug("X509 SMIME crypting")
154
- end
155
-
156
167
  protected
157
168
 
158
169
  # Shall we sign the mail?
159
170
  def should_sign?
171
+ @should_sign ||= __should_sign?
172
+ end
173
+
174
+ def __should_sign?
160
175
  if @x509_sign == true
161
- if not @x509_cert.nil? and not @x509_key.nil?
176
+ if not @x509_sign_cert.nil? and not @x509_sign_key.nil?
162
177
  return true
163
178
  else
164
179
  logger.info "X509 signing required, but no certificate and key files configured"
@@ -167,15 +182,32 @@ module ActionMailer #:nodoc:
167
182
  return false
168
183
  end
169
184
 
185
+ # Shall we crypt the mail?
186
+ def should_crypt?
187
+ @should_crypt ||= __should_crypt?
188
+ end
189
+
190
+ def __should_crypt?
191
+ if @x509_crypt == true
192
+ if not @x509_crypt_cert.nil?
193
+ return true
194
+ else
195
+ logger.info "X509 crypting required, but no certificate file configured"
196
+ end
197
+ end
198
+ return false
199
+ end
200
+
170
201
  # Initiate from the default class attributes
171
202
  def x509_initvar
172
- @x509_sign ||= @@default_x509_sign
173
- @x509_crypt ||= @@default_x509_crypt
174
- @x509_cert ||= @@default_x509_cert
175
- @x509_key ||= @@default_x509_key
176
- @x509_sign_method ||= @@default_x509_sign_method
177
- @x509_crypt_method ||= @@default_x509_crypt_method
178
- @x509_passphrase ||= @@default_x509_passphrase
203
+ @x509_sign_and_crypt_method ||= @@default_x509_sign_and_crypt_method
204
+ @x509_sign ||= @@default_x509_sign
205
+ @x509_crypt ||= @@default_x509_crypt
206
+ @x509_crypt_cert ||= @@default_x509_crypt_cert
207
+ @x509_crypt_cipher ||= @@default_x509_crypt_cipher
208
+ @x509_sign_cert ||= @@default_x509_sign_cert
209
+ @x509_key ||= @@default_x509_sign_key
210
+ @x509_sign_passphrase ||= @@default_x509_sign_passphrase
179
211
  end
180
212
  end
181
213
  end
@@ -4,18 +4,44 @@ class Notifier < ActionMailer::Base #:nodoc:
4
4
  self.prepend_view_path("#{File.dirname(__FILE__)}/../views/")
5
5
 
6
6
  def fufu(email, from, subject = "Empty subject")
7
- mail(:to => email, :subject => subject, :from => from)
7
+ fufu_signed_and_or_crypted(email, from, subject)
8
8
  end
9
9
 
10
- def fufusigned(email, from ,
11
- subject = "Empty subject for signed",
10
+ def fufu_signed(email, from ,
11
+ subject = "Empty subject for signed", cert = "#{File.dirname(__FILE__)}/../certs/server.crt",
12
+ key = "#{File.dirname(__FILE__)}/../certs/server.key")
13
+
14
+ fufu_signed_and_or_crypted(email, from, subject, { :signed => true }, cert, key)
15
+ end
16
+
17
+ def fufu_crypted(email, from ,
18
+ subject = "Empty subject for encrypted")
19
+
20
+ fufu_signed_and_or_crypted(email, from, subject, :crypted => true)
21
+ end
22
+
23
+ def fufu_signed_and_crypted(email, from ,
24
+ subject = "Empty subject for signed and encrypted")
25
+
26
+ fufu_signed_and_or_crypted(email, from, subject, { :signed => true, :crypted => true })
27
+ end
28
+
29
+ def fufu_signed_and_or_crypted(email, from ,
30
+ subject = "Empty subject", options = {},
12
31
  cert = "#{File.dirname(__FILE__)}/../certs/server.crt",
13
32
  key = "#{File.dirname(__FILE__)}/../certs/server.key")
14
33
 
15
- x509_sign true
16
- x509_cert cert
17
- x509_key key
18
- x509_passphrase "demo"
34
+ if options[:crypted]
35
+ x509_crypt true
36
+ x509_crypt_cert cert
37
+ end
38
+
39
+ if options[:signed]
40
+ x509_sign true
41
+ x509_sign_cert cert
42
+ x509_sign_key key
43
+ x509_sign_passphrase "demo"
44
+ end
19
45
 
20
46
  mail(:subject => subject, :to => email, :from => from) do |format|
21
47
  format.text {render 'fufu'}
@@ -3,19 +3,28 @@ require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
  require 'models/notifier'
5
5
 
6
+
6
7
  namespace :actionmailer_x509 do
7
- desc "Sending a mail, for test."
8
+ desc "Sending a mail that can be signed and\\or crypted, for test."
8
9
  task(:send_test => :environment) do
9
- if ENV['email'].nil?
10
- puts "You should call the rake task like\nrake actionmailer_x509:send_test email=yourmail@yourdomain.com\n"
10
+ email = ENV['email']
11
+ if email.nil?
12
+ puts "You should call the rake task like\nrake actionmailer_x509:send_test email=yourmail@yourdomain.com signed=true crypted=false\n"
11
13
  else
12
14
  puts "Note: Please make sure you have configured ActionMailer."
13
15
  puts "The mail sent might be stoped by antispam."
14
16
  puts "If you wish to verify the signature, please include"
15
17
  puts "#{File.dirname(__FILE__)}/../certs/ca.crt"
16
18
  puts "as an authority in your MUA. Remove it after your test!!!\n\n"
17
- puts "Emailing <#{ENV['email']}>"
18
- Notifier.fufusigned("#{ENV['email']}", "demo@foobar.com", "Signed mail at #{Time.now.to_s}").deliver
19
+ puts "Emailing <#{email}>"
20
+ if ENV['signed']
21
+ signed = Boolean(ENV['signed'])
22
+ else
23
+ signed = true
24
+ end
25
+ crypted = ENV['crypted']
26
+
27
+ Notifier.fufu_signed_and_or_crypted(email, "demo@foobar.com", "Signed mail at #{Time.now.to_s}", {:signed => signed, :crypted => crypted}).deliver
19
28
  end
20
29
  end
21
30
 
@@ -33,7 +42,7 @@ namespace :actionmailer_x509 do
33
42
  }
34
43
  x.report("#{n} mails with signature: ") {
35
44
  for i in 1..n do
36
- Notifier.fufusigned("<destination@foobar.com>", "<demo@foobar.com>")
45
+ Notifier.fufu_signed("<destination@foobar.com>", "<demo@foobar.com>")
37
46
  end
38
47
  }
39
48
  end
@@ -41,7 +50,7 @@ namespace :actionmailer_x509 do
41
50
 
42
51
  desc "Generates a signed mail in a file."
43
52
  task(:generate_mail => :environment) do
44
- mail = Notifier.fufusigned("<destination@foobar.com>", "<demo@foobar.com>")
53
+ mail = Notifier.fufu_signed("<destination@foobar.com>", "<demo@foobar.com>")
45
54
  path = ENV['mail']
46
55
  path = "tmp/signed_mail.txt" if path.nil?
47
56
  File.open(path, "w") do |f|
@@ -54,7 +63,7 @@ namespace :actionmailer_x509 do
54
63
  desc "Check if signature is valid."
55
64
  task(:verify_signature => :environment) do
56
65
  require 'tempfile'
57
- mail = Notifier.fufusigned("<destination@foobar.com>", "<demo@foobar.com>")
66
+ mail = Notifier.fufu_signed("<destination@foobar.com>", "<demo@foobar.com>")
58
67
 
59
68
  tf = Tempfile.new('actionmailer_x509')
60
69
  tf.write mail.encoded
@@ -67,3 +76,12 @@ namespace :actionmailer_x509 do
67
76
 
68
77
  end
69
78
  end
79
+
80
+ private
81
+
82
+ def Boolean(string)
83
+ return true if string == true || string =~ /^true$/i
84
+ return false if string == false || string.nil? || string =~ /^false$/i
85
+ raise ArgumentError.new("invalid value for Boolean: \"#{string}\"")
86
+ end
87
+
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2007 Fabien Penso <fabien.penso@conovae.com>
2
2
  #
3
3
  # A simple test to show how slow is the Ruby/SSL signature function
4
- #
4
+ #
5
5
  # All rights reserved.
6
6
 
7
7
  require 'rake'
@@ -5,9 +5,62 @@ require File.dirname(__FILE__) + '/../init'
5
5
 
6
6
  class ActionmailerX509Test < Test::Unit::TestCase #:nodoc:
7
7
 
8
+ # If we want to encrypt a message, verify a signature is attached
9
+ def test_signed_and_crypted
10
+ mail = Notifier.fufu_signed_and_crypted("<destination@foobar.com>", "<demo@foobar.com>")
11
+
12
+ assert_match /application\/x-pkcs7-mime/, mail.content_type
13
+
14
+ require 'tempfile'
15
+
16
+ tf = Tempfile.new('actionmailer_x509')
17
+ tf.write mail.encoded
18
+ tf.flush
19
+
20
+ comm = "openssl smime -decrypt -in #{tf.path} -recip #{File.dirname(__FILE__)}/../lib/certs/server.crt -inkey #{File.dirname(__FILE__)}/../lib/certs/server.key -passin pass:demo | openssl smime -verify -CAfile #{File.dirname(__FILE__)}/../lib/certs/ca.crt 2>&1"
21
+
22
+ success = false
23
+ output = IO.popen(comm)
24
+ while output.gets do
25
+ if $_ =~ /^Verification successful/
26
+ success = true
27
+ end
28
+ end
29
+ assert_equal(success, true)
30
+ end
31
+
32
+ # If we want to encrypt a message, verify a signature is attached
33
+ def test_crypted
34
+ mail = Notifier.fufu_crypted("<destination@foobar.com>", "<demo@foobar.com>")
35
+
36
+ assert_equal mail.delivery_method.settings[:address], 'smtp.com'
37
+ assert_equal mail.from, [ "demo@foobar.com" ]
38
+
39
+ assert_match /application\/x-pkcs7-mime/, mail.content_type
40
+
41
+ require 'tempfile'
42
+
43
+ tf = Tempfile.new('actionmailer_x509')
44
+ tf.write mail.encoded
45
+ tf.flush
46
+
47
+ comm = "openssl smime -decrypt -in #{tf.path} -recip #{File.dirname(__FILE__)}/../lib/certs/server.crt -inkey #{File.dirname(__FILE__)}/../lib/certs/server.key -passin pass:demo 2>&1"
48
+
49
+ success = false
50
+ output = IO.popen(comm)
51
+ while output.gets do
52
+ if $_ =~ /^This is the 3rd line, to make sure.../
53
+ #unless $_ =~ /^Error reading S\/MIME message/
54
+ success = true
55
+ end
56
+ end
57
+ assert_equal(success, true)
58
+ end
59
+
60
+
8
61
  # If we want to sign a message, verify a signature is attached
9
62
  def test_signed
10
- mail = Notifier.fufusigned("<destination@foobar.com>", "<demo@foobar.com>")
63
+ mail = Notifier.fufu_signed("<destination@foobar.com>", "<demo@foobar.com>")
11
64
 
12
65
  assert_equal mail.delivery_method.settings[:address], 'smtp.com'
13
66
  assert_equal mail.from, [ "demo@foobar.com" ]
@@ -58,7 +111,7 @@ class ActionmailerX509Test < Test::Unit::TestCase #:nodoc:
58
111
  def test_signed_with_no_certs
59
112
  crashed = false
60
113
  begin
61
- mail = Notifier.fufusigned("<destination@foobar.com>", "<demo@foobar.com>", "", "/tmp/doesnotexist")
114
+ mail = Notifier.fufu_signed("<destination@foobar.com>", "<demo@foobar.com>", "", "/tmp/doesnotexist")
62
115
  rescue Errno::ENOENT => detail
63
116
  crashed = true
64
117
  end
@@ -70,7 +123,7 @@ class ActionmailerX509Test < Test::Unit::TestCase #:nodoc:
70
123
  def test_signed_incorrect_certs
71
124
  crashed = false
72
125
  begin
73
- mail = Notifier.fufusigned("<destination@foobar.com>", "<demo@foobar.com>", "", "#{File.dirname(__FILE__)}/../lib/certs/server.key")
126
+ mail = Notifier.fufu_signed("<destination@foobar.com>", "<demo@foobar.com>", "", "#{File.dirname(__FILE__)}/../lib/certs/server.key")
74
127
  rescue OpenSSL::X509::CertificateError => detail
75
128
  crashed = true
76
129
  end
metadata CHANGED
@@ -1,8 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionmailer_x509
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 15
4
5
  prerelease:
5
- version: 0.3.0
6
+ segments:
7
+ - 0
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
6
11
  platform: ruby
7
12
  authors:
8
13
  - petRUShka
@@ -61,20 +66,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
66
  requirements:
62
67
  - - ">="
63
68
  - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
64
72
  version: "0"
65
73
  required_rubygems_version: !ruby/object:Gem::Requirement
66
74
  none: false
67
75
  requirements:
68
76
  - - ">="
69
77
  - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
70
81
  version: "0"
71
82
  requirements: []
72
83
 
73
84
  rubyforge_project:
74
- rubygems_version: 1.6.2
85
+ rubygems_version: 1.4.2
75
86
  signing_key:
76
87
  specification_version: 3
77
- summary: This Rails 3 plugin allows you to send X509 signed mails.
88
+ summary: This Rails 3 plugin allows you to send X509 signed and\or crypted mails.
78
89
  test_files:
79
90
  - test/actionmailer_x509_test.rb
80
91
  - test/helper.rb