action_mailer_x509 0.7.0 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- action_mailer_x509 (0.6.2)
4
+ action_mailer_x509 (0.7.2)
5
5
  actionmailer
6
6
  mail
7
7
  rake
@@ -32,7 +32,7 @@ GEM
32
32
  mail (2.5.4)
33
33
  mime-types (~> 1.16)
34
34
  treetop (~> 1.4.8)
35
- mime-types (1.23)
35
+ mime-types (1.25)
36
36
  minitest (4.7.5)
37
37
  multi_json (1.7.9)
38
38
  polyglot (0.3.3)
@@ -59,7 +59,7 @@ GEM
59
59
  thor (0.18.1)
60
60
  thread_safe (0.1.2)
61
61
  atomic
62
- treetop (1.4.14)
62
+ treetop (1.4.15)
63
63
  polyglot
64
64
  polyglot (>= 0.3.1)
65
65
  tzinfo (0.3.37)
@@ -79,7 +79,7 @@ Put this line in your Gemfile
79
79
 
80
80
  gem 'actionmailer_x509'
81
81
 
82
- == USING
82
+ == SETTINGS
83
83
 
84
84
  First for all - run install generator, which adds configuration file
85
85
 
@@ -120,10 +120,42 @@ And you may also change the default configuration in any place of the program:
120
120
 
121
121
  2) Change only for decoding and verifyng if you use Mail object
122
122
 
123
- Mail.new(encoded_text).proceed(configuration)
123
+ Mail.new(encoded_text).proceed(result_type, configuration)
124
124
 
125
125
  If you set configuration as nil in "proceed" func, "ActionMailerX509.default_configuration" will be used.
126
126
 
127
+
128
+ == USING
129
+
130
+ Encoding/Sign
131
+
132
+ You may set simpy set configuration for all funcs of mailer
133
+
134
+ class SendCrypted < ActionMailer::Base
135
+ x509_configuration :test
136
+ end
137
+
138
+ or set it how param of 'mail' func for each mailer method
139
+
140
+ class SendCrypted < ActionMailer::Base
141
+ def prepare_mail(from, to, subject, body, x509_configuration = ActionMailerX509.default_configuration)
142
+ mail(
143
+ from: from,
144
+ to: to,
145
+ subject: subject,
146
+ x509_configuration: x509_configuration
147
+ )do |format|
148
+ #format.text { render text: body }
149
+ format.html { render text: body }
150
+ end
151
+ end
152
+ end
153
+
154
+ Decoding/Verify
155
+
156
+ Mail.new(encoded_mail).proceed(result_type, configuration)
157
+
158
+
127
159
  == HELPERS
128
160
 
129
161
  You may use some helpers for fast encoding in case when you proceed received email. Helper has the following signature:
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'action_mailer_x509'
3
- s.version = '0.7.0'
3
+ s.version = '0.7.2'
4
4
  s.authors = ['Jenua Boiko']
5
5
  s.email = 'jeyboy1985@gmail.com'
6
6
  s.files = `git ls-files`.split("\n")
@@ -14,6 +14,5 @@ Gem::Specification.new do |s|
14
14
  s.add_dependency 'rake'
15
15
  s.add_dependency 'actionmailer'
16
16
  s.add_dependency 'mail'
17
- #s.add_development_dependency 'rspec'
18
17
  end
19
18
 
@@ -1,12 +1,6 @@
1
- class Configuration
2
- ATTRS = { 'C' => :country,
3
- 'ST' => :state,
4
- 'L' => :location,
5
- 'O' => :organization,
6
- 'OU' => :organizational_unit,
7
- 'CN' => :common_name,
8
- 'emailAddress' => :email}
1
+ require 'action_mailer_x509/security_object'
9
2
 
3
+ class Configuration
10
4
  def initialize(params = {})
11
5
  params.symbolize_keys!
12
6
  params.each_pair { |k, v| self.send("#{k}=".to_sym, v) }
@@ -88,7 +82,7 @@ class Configuration
88
82
 
89
83
  subject_attrs = worker.certificate.subject.to_a
90
84
  subject_attrs = subject_attrs.each_with_object({}) do |attr, obj|
91
- obj.update(ATTRS[attr.first] => attr[1])
85
+ obj.update(SecurityObject::ATTRS[attr.first] => attr[1])
92
86
  end
93
87
 
94
88
  {
@@ -0,0 +1,179 @@
1
+ class SecurityObject
2
+ ATTRS = { 'C' => :country,
3
+ 'ST' => :state,
4
+ 'L' => :location,
5
+ 'O' => :organization,
6
+ 'OU' => :organizational_unit,
7
+ 'CN' => :common_name,
8
+ 'emailAddress' => :email}
9
+
10
+ IATTRS = ATTRS.invert
11
+
12
+ #params: Hash
13
+ #key_length int - length of the rsa key
14
+ #password string - passphrase for key/certificate
15
+ #pack_key_with_pass bool - force repack rsa key with given password
16
+ #subject Hash - fields represented by ATTRS const
17
+ #time_from Time - certificate start time
18
+ #time_length int - certificate life time in seconds
19
+ #description string - p12 description
20
+ #file string - file path - where have be save new certificate
21
+ class << self
22
+ private
23
+ def to_file(data, path)
24
+ File.open(path, 'wb') do |file|
25
+ file.write(data)
26
+ end
27
+ end
28
+
29
+ def subject(params)
30
+ params ||= {}
31
+ params.symbolize_keys!
32
+ subject = params.each_with_object('') do |attr, obj|
33
+ obj << "/#{IATTRS[attr.first]}=#{attr[1]}"
34
+ end
35
+ OpenSSL::X509::Name.parse subject
36
+ end
37
+
38
+ def repack_key(rsa_key, password)
39
+ cipher = OpenSSL::Cipher::Cipher.new('des3')
40
+ private_key = rsa_key.to_pem(cipher, password)
41
+ public_key = rsa_key.public_key.to_pem
42
+ OpenSSL::PKey::RSA.new(private_key + public_key, password)
43
+ end
44
+
45
+ def rsa_key(params={})
46
+ key = OpenSSL::PKey::RSA.new(params[:key_length] || 4096) # the CA's public/private key
47
+ (repack_key(key, params[:password]) if params[:pack_key_with_pass]) || key
48
+ end
49
+
50
+ def certificate(params = {}, root_certificate = nil)
51
+ root_key = rsa_key(params)
52
+
53
+ root_ca = OpenSSL::X509::Certificate.new
54
+ root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
55
+ root_ca.serial = 1
56
+ root_ca.subject = subject(params[:subject])
57
+ root_ca.issuer = (root_certificate || root_ca).subject # root CA's are "self-signed"
58
+ root_ca.public_key = root_key.public_key
59
+ root_ca.not_before = params[:time_from] || Time.now
60
+ root_ca.not_after = root_ca.not_before + (params[:time_length] || 1.year) #2 * 365 * 24 * 60 * 60 # 2 years validity
61
+
62
+ [ root_key, root_ca ]
63
+ end
64
+ public
65
+
66
+ def self_signed_certificate(params = {})
67
+ params.symbolize_keys!
68
+
69
+ root_key, root_ca = certificate(params)
70
+ root_ca.serial = 1
71
+
72
+ ef = OpenSSL::X509::ExtensionFactory.new
73
+ ef.subject_certificate = root_ca
74
+ ef.issuer_certificate = root_ca
75
+ root_ca.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true))
76
+ root_ca.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true))
77
+ root_ca.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
78
+ root_ca.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always', false))
79
+ root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
80
+
81
+
82
+ [ root_key, root_ca ]
83
+
84
+
85
+ # key = OpenSSL::PKey::RSA.new(1024)
86
+ # public_key = key.public_key
87
+ #
88
+ # subject = "/C=BE/O=Test/OU=Test/CN=Test"
89
+ #
90
+ # cert = OpenSSL::X509::Certificate.new
91
+ # cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
92
+ # cert.not_before = Time.now
93
+ # cert.not_after = Time.now + 365 * 24 * 60 * 60
94
+ # cert.public_key = public_key
95
+ # cert.serial = 0x0
96
+ # cert.version = 2
97
+ #
98
+ # ef = OpenSSL::X509::ExtensionFactory.new
99
+ # ef.subject_certificate = cert
100
+ # ef.issuer_certificate = cert
101
+ # cert.extensions = [
102
+ # ef.create_extension("basicConstraints","CA:TRUE", true),
103
+ # ef.create_extension("subjectKeyIdentifier", "hash"),
104
+ ## ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
105
+ # ]
106
+ # cert.add_extension ef.create_extension("authorityKeyIdentifier",
107
+ # "keyid:always,issuer:always")
108
+ #
109
+ # cert.sign key, OpenSSL::Digest::SHA1.new
110
+ end
111
+
112
+ def signed_certificate(params = {})
113
+ params.symbolize_keys!
114
+ root_key, root_ca = self_signed_certificate(params)
115
+ key, cert = certificate(params, root_ca)
116
+ cert.serial = 2
117
+
118
+ ef = OpenSSL::X509::ExtensionFactory.new
119
+ ef.subject_certificate = cert
120
+ ef.issuer_certificate = root_ca
121
+ cert.add_extension(ef.create_extension('keyUsage', 'digitalSignature', true))
122
+ cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
123
+ cert.sign(root_key, OpenSSL::Digest::SHA256.new)
124
+
125
+ [ key, cert ]
126
+ end
127
+
128
+
129
+ def p12_certificate(params = {})
130
+ params.symbolize_keys!
131
+
132
+ key, cert = signed_certificate(params)
133
+
134
+ #root_key = OpenSSL::PKey::RSA.new 4096 # the CA's public/private key
135
+ #root_ca = OpenSSL::X509::Certificate.new
136
+ #root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
137
+ #root_ca.serial = 1
138
+ #root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA"
139
+ #root_ca.issuer = root_ca.subject # root CA's are "self-signed"
140
+ #root_ca.public_key = root_key.public_key
141
+ #root_ca.not_before = Time.now
142
+ #root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
143
+ #
144
+ #ef = OpenSSL::X509::ExtensionFactory.new
145
+ #ef.subject_certificate = root_ca
146
+ #ef.issuer_certificate = root_ca
147
+ #root_ca.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true))
148
+ #root_ca.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true))
149
+ #root_ca.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
150
+ #root_ca.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always', false))
151
+ #root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
152
+ #
153
+ ## The next step is to create the end-entity certificate using the root CA
154
+ ## certificate.
155
+ ##
156
+ #key = OpenSSL::PKey::RSA.new 4096
157
+ #cert = OpenSSL::X509::Certificate.new
158
+ #cert.version = 2
159
+ #cert.serial = 2
160
+ #cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate"
161
+ #cert.issuer = root_ca.subject # root CA is the issuer
162
+ #cert.public_key = key.public_key
163
+ #cert.not_before = Time.now
164
+ #cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity
165
+ #
166
+ #ef = OpenSSL::X509::ExtensionFactory.new
167
+ #ef.subject_certificate = cert
168
+ #ef.issuer_certificate = root_ca
169
+ #cert.add_extension(ef.create_extension('keyUsage', 'digitalSignature', true))
170
+ #cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
171
+ #cert.sign(root_key, OpenSSL::Digest::SHA256.new)
172
+
173
+ p12 = OpenSSL::PKCS12.create(params[:password], params[:description] || 'My Name', key, cert)
174
+ bytes = p12.to_der
175
+ to_file(bytes, path) if params[:file]
176
+ bytes
177
+ end
178
+ end
179
+ end
@@ -44,14 +44,13 @@ module ActionMailerX509
44
44
  end
45
45
 
46
46
  def sign(text)
47
- write OpenSSL::PKCS7.sign(certificate, rsa_key, text, [], OpenSSL::PKCS7::DETACHED)
48
- #OpenSSL::PKCS7.sign(certificate, rsa_key, text, [], OpenSSL::PKCS7::BINARY)
47
+ write OpenSSL::PKCS7.sign(certificate, rsa_key, text, [], OpenSSL::PKCS7::DETACHED|OpenSSL::PKCS7::BINARY)
49
48
  end
50
49
 
51
50
  def verify(text)
52
- result = read(text).verify(nil, @certificate_store, nil, nil)
53
- #read(text).verify(nil, @certificate_store, nil, OpenSSL::PKCS7::NOVERIFY)
54
- result ? read(text).data : raise(VerificationError.new('Wrong args'))
51
+ #set the signer's certificates are not chain verified.
52
+ result = read(text).verify(nil, @certificate_store, nil, OpenSSL::PKCS7::NOVERIFY)
53
+ result ? read(text).data : raise(VerificationError.new('Verification failed !!!'))
55
54
  rescue => e
56
55
  raise VerificationError.new(e.message)
57
56
  end
@@ -3,6 +3,14 @@ require 'action_mailer_x509'
3
3
  module Mail #:nodoc:
4
4
  class Message #:nodoc:
5
5
 
6
+ #fix mail gem bug on decoding
7
+ def subject( val = nil )
8
+ val ||= header[:subject].default
9
+ val.gsub!(' ', '_') if val && val.start_with?('=?')
10
+ default( :subject, val )
11
+ header[:subject].default
12
+ end
13
+
6
14
  def proceed(result_type = 'html', configuration = nil)
7
15
  proceed_part(_proceed(configuration), result_type)
8
16
  end
@@ -26,6 +34,13 @@ module Mail #:nodoc:
26
34
  parts.empty? ? to_part : body.encoded
27
35
  end
28
36
 
37
+ def get_states
38
+ {
39
+ crypted: (body.to_s =~ /application\/(x-)?pkcs[0-9]+-mime/).present?,
40
+ signed: (body.to_s =~ /application\/(x-)?pkcs[0-9]+-signature/).present?
41
+ }
42
+ end
43
+
29
44
  protected
30
45
  # Returns true if the message is a multipart/alternative
31
46
  def alternative?(part)
@@ -57,16 +72,18 @@ module Mail #:nodoc:
57
72
 
58
73
  _decode_body(result_type, @new_part || part)
59
74
  else
60
- part.decoded.to_s unless part.attachment?
75
+ part.decoded unless part.attachment?
61
76
  end
62
- end
77
+ end.to_s
63
78
 
64
79
  # we need manually split body on parts and decode each part separate
65
80
  def _decode_body(result_type, obj = self)
66
- obj.body.split(obj.boundary) if obj.parts.blank? && obj.boundary.present?
81
+ obj.body.split!(obj.boundary) if obj.parts.blank? && obj.boundary.present?
82
+
67
83
 
68
84
  if obj.parts.present?
69
- obj.parts.map {|part| proceed_part(part, result_type)}.join(break_line(result_type))
85
+ proceed_parts = obj.parts.map {|part| proceed_part(part, result_type).force_encoding('utf-8')}
86
+ proceed_parts.join(break_line(result_type))
70
87
  else
71
88
  obj.decoded
72
89
  end
@@ -101,10 +118,14 @@ module Mail #:nodoc:
101
118
 
102
119
  def is_crypted?
103
120
  (header['Content-Type'].encoded =~ /application\/(x-)?pkcs[0-9]+-mime/).present?
121
+ rescue
122
+ false
104
123
  end
105
124
 
106
125
  def is_signed?
107
126
  (header['Content-Type'].encoded =~ /application\/(x-)?pkcs[0-9]+-signature/).present?
127
+ rescue
128
+ false
108
129
  end
109
130
  end
110
131
  end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Test additional functions' do
4
+ describe 'Correct' do
5
+ describe 'Certificate generation' do
6
+ it 'Must generate p12' do
7
+ p12_bytes = SecurityObject.p12_certificate({:password => 'demo'})
8
+ lambda { OpenSSL::PKCS12.new p12_bytes, 'demo' }.should_not raise_error
9
+ end
10
+ end
11
+ end
12
+
13
+ describe 'Incorrect' do
14
+ it 'Must dont open p12 with wrong pass' do
15
+ p12_bytes = SecurityObject.p12_certificate({:password => 'demo'})
16
+ lambda { OpenSSL::PKCS12.new p12_bytes, 'wrong' }.should raise_error
17
+ end
18
+ end
19
+ end
@@ -16,7 +16,7 @@ describe 'Test basic functions' do
16
16
  end
17
17
 
18
18
  it 'Verification check' do
19
- verified = @mail.proceed(Notifier.x509_configuration)
19
+ verified = @mail.proceed('plain', Notifier.x509_configuration)
20
20
  verified.should eql @raw_mail.body.to_s
21
21
  end
22
22
  end
@@ -35,7 +35,7 @@ describe 'Test basic functions' do
35
35
  end
36
36
 
37
37
  it 'Must generate crypted text' do
38
- decrypted = @mail.proceed(Notifier.x509_configuration)
38
+ decrypted = @mail.proceed('plain', Notifier.x509_configuration)
39
39
  decrypted.to_s.should eql @raw_mail.body.decoded
40
40
  end
41
41
  end
@@ -47,7 +47,7 @@ describe 'Test basic functions' do
47
47
  add_config
48
48
  mail = Notifier.fufu('<destination@foobar.com>', '<demo@foobar.com>')
49
49
 
50
- decrypted = mail.proceed(Notifier.x509_configuration)
50
+ decrypted = mail.proceed('plain', Notifier.x509_configuration)
51
51
  decrypted.to_s.should eql raw_mail.body.decoded
52
52
  end
53
53
 
@@ -58,7 +58,7 @@ describe 'Test basic functions' do
58
58
  add_config(true, true, true)
59
59
  mail = Notifier.fufu('<destination@foobar.com>', '<demo@foobar.com>')
60
60
 
61
- decrypted = mail.proceed(Notifier.x509_configuration)
61
+ decrypted = mail.proceed('plain', Notifier.x509_configuration)
62
62
  decrypted.to_s.should eql raw_mail.body.decoded
63
63
  end
64
64
  end
@@ -71,7 +71,7 @@ describe 'Test basic functions' do
71
71
  mail.body.to_s.should_not be_empty
72
72
 
73
73
  set_config_param(sign_passphrase: 'wrong')
74
- -> { mail.proceed(Notifier.x509_configuration) }.should raise_error OpenSSL::PKey::RSAError
74
+ -> { mail.proceed('plain', Notifier.x509_configuration) }.should raise_error OpenSSL::PKey::RSAError
75
75
  end
76
76
 
77
77
  describe 'incorrect text' do
@@ -79,14 +79,14 @@ describe 'Test basic functions' do
79
79
  add_config(true, false)
80
80
  mail = Notifier.fufu('<destination@foobar.com>', '<demo@foobar.com>')
81
81
  mail.body = mail.body.to_s.gsub(/[0-9]/, 'g')
82
- -> { mail.proceed(Notifier.x509_configuration) }.should raise_error VerificationError
82
+ -> { mail.proceed('plain', Notifier.x509_configuration) }.should raise_error VerificationError
83
83
  end
84
84
 
85
85
  it 'crypt' do
86
86
  add_config(false, true)
87
87
  mail = Notifier.fufu('<destination@foobar.com>', '<demo@foobar.com>')
88
88
  mail.body = mail.body.to_s.gsub(/[0-9]/, 'g')
89
- -> { mail.proceed(Notifier.x509_configuration) }.should raise_error DecodeError
89
+ -> { mail.proceed('plain', Notifier.x509_configuration) }.should raise_error DecodeError
90
90
  end
91
91
  end
92
92
 
@@ -97,7 +97,7 @@ describe 'Test basic functions' do
97
97
  crypt_key: 'cert.key')
98
98
  mail = Notifier.fufu('<destination@foobar.com>', '<demo@foobar.com>')
99
99
  mail.body = mail.body.to_s.gsub(/[0-9]/, 'g')
100
- -> { mail.proceed(Notifier.x509_configuration) }.should raise_error VerificationError
100
+ -> { mail.proceed('plain', Notifier.x509_configuration) }.should raise_error VerificationError
101
101
  end
102
102
 
103
103
  it 'crypt' do
@@ -4,6 +4,8 @@ require 'active_support/all'
4
4
  require 'action_mailer'
5
5
  require 'action_mailer_x509'
6
6
  require 'action_mailer_x509/x509'
7
+ require 'action_mailer_x509/security_object'
8
+ require 'action_mailer_x509/configuration'
7
9
  require 'models/notifier'
8
10
 
9
11
  RSpec.configure do |config|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_mailer_x509
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.2
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-08-28 00:00:00.000000000 Z
12
+ date: 2013-09-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -82,6 +82,7 @@ files:
82
82
  - lib/action_mailer_x509.rb
83
83
  - lib/action_mailer_x509/configuration.rb
84
84
  - lib/action_mailer_x509/railtie.rb
85
+ - lib/action_mailer_x509/security_object.rb
85
86
  - lib/action_mailer_x509/x509.rb
86
87
  - lib/generators/action_mailer_x509/install_generator.rb
87
88
  - lib/generators/action_mailer_x509/templates/x509_settings.rb
@@ -90,6 +91,7 @@ files:
90
91
  - lib/overrides/mail/message.rb
91
92
  - lib/tasks/action_mailer_x509.rake
92
93
  - lib/views/notifier/fufu.erb
94
+ - spec/adds_spec.rb
93
95
  - spec/ops_spec.rb
94
96
  - spec/spec_helper.rb
95
97
  homepage: http://github.com/jeyboy/action_mailer_x509
@@ -105,12 +107,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
107
  - - ! '>='
106
108
  - !ruby/object:Gem::Version
107
109
  version: '0'
110
+ segments:
111
+ - 0
112
+ hash: -205818147
108
113
  required_rubygems_version: !ruby/object:Gem::Requirement
109
114
  none: false
110
115
  requirements:
111
116
  - - ! '>='
112
117
  - !ruby/object:Gem::Version
113
118
  version: '0'
119
+ segments:
120
+ - 0
121
+ hash: -205818147
114
122
  requirements: []
115
123
  rubyforge_project:
116
124
  rubygems_version: 1.8.24