enmail 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ gem "rnp", *EnMail::DependencyConstraints::RNP
2
+
3
+ require "rnp"
@@ -0,0 +1,9 @@
1
+ # (c) Copyright 2018 Ribose Inc.
2
+ #
3
+
4
+ module EnMail
5
+ module DependencyConstraints
6
+ GPGME = ["~> 2.0"].freeze
7
+ RNP = [">= 1.0.1", "< 2"].freeze
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # (c) Copyright 2018 Ribose Inc.
2
+ #
3
+
4
+ module EnMail
5
+ module Extensions
6
+ module MessageTransportEncodingRestrictions
7
+ def identify_and_set_transfer_encoding
8
+ if @enmail_rfc18467_encoding_restrictions && !multipart?
9
+ str = body.raw_source
10
+ self.content_transfer_encoding = [
11
+ ::Mail::Encodings::Base64,
12
+ ::Mail::Encodings::QuotedPrintable,
13
+ ].min { |a, b| a.cost(str) <=> b.cost(str) }
14
+ else
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,73 @@
1
+ # (c) Copyright 2018 Ribose Inc.
2
+ #
3
+
4
+ module EnMail
5
+ module Helpers
6
+ # A toolbox with common operations for manipulating and reading message
7
+ # properties, potentially useful for all adapters.
8
+ module MessageManipulation
9
+ protected
10
+
11
+ # Returns a new +Mail::Part+ with the same content and MIME headers
12
+ # as the message passed as an argument.
13
+ #
14
+ # Although Mail gem provides +Mail::Message#convert_to_multipart+ method,
15
+ # it works correctly for non-multipart text/plain messages only. This
16
+ # method is more robust, and handles messages containing any content type,
17
+ # be they multipart or not.
18
+ #
19
+ # The message passed as an argument is not altered.
20
+ #
21
+ # TODO Copy MIME headers (ones which start with "Content-")
22
+ # TODO Preserve Content-Transfer-Encoding when possible
23
+ # TODO Set some safe Content-Transfer-Encoding, like quoted-printable
24
+ def body_to_part(message)
25
+ part = ::Mail::Part.new
26
+ part.content_type = message.content_type
27
+ if message.multipart?
28
+ message.body.parts.each { |p| part.add_part p.dup }
29
+ else
30
+ part.body = message.body.decoded
31
+ end
32
+ part
33
+ end
34
+
35
+ # Detects a list of e-mails which should be used to define a list of
36
+ # recipients of encrypted message. All is simply taken from the message
37
+ # +To:+ field.
38
+ #
39
+ # @param [Mail::Message] message
40
+ # @return [Array] an array of e-mails
41
+ def find_recipients_for(message)
42
+ message.to_addrs
43
+ end
44
+
45
+ # Detects e-mail which should be used to find a message signer key.
46
+ # Basically, it is taken from the message +From:+ field, but may be
47
+ # overwritten by +:signer+ adapter option.
48
+ #
49
+ # @param [Mail::Message] message
50
+ # @return [String] an e-mail
51
+ def find_signer_for(message)
52
+ options[:signer] || message.from_addrs.first
53
+ end
54
+
55
+ # Replaces a message body. Clears all the existing body, be it multipart
56
+ # or not, and then appends parts passed as an argument.
57
+ #
58
+ # @param [Mail::Message] message
59
+ # Message which body is expected to be replaced.
60
+ # @param [String] content_type
61
+ # A new content type for message, required, must be kinda multipart.
62
+ # @param [Array] parts
63
+ # List of parts which the new message body is expected to be composed
64
+ # from.
65
+ # @return undefined
66
+ def rewrite_body(message, content_type:, parts: [])
67
+ message.body = nil
68
+ message.content_type = content_type
69
+ parts.each { |p| message.add_part(p) }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,103 @@
1
+ # (c) Copyright 2018 Ribose Inc.
2
+ #
3
+
4
+ module EnMail
5
+ module Helpers
6
+ # Common interface for building adapters conforming to RFC 1847 "Security
7
+ # Multiparts for MIME: Multipart/Signed and Multipart/Encrypted".
8
+ # It provides +sign+ and +encrypt+ public methods.
9
+ module RFC1847
10
+ include MessageManipulation
11
+
12
+ # Encrypts a message in a +multipart/encrypted+ fashion as defined
13
+ # in RFC 1847.
14
+ #
15
+ # @param [Mail::Message] message
16
+ # Message which is expected to be encrypted.
17
+ def encrypt(message)
18
+ source_part = body_to_part(message)
19
+ recipients = find_recipients_for(message)
20
+ encrypted = encrypt_string(source_part.encoded, recipients).to_s
21
+ encrypted_part = build_encrypted_part(encrypted)
22
+ control_part = build_encryption_control_part
23
+
24
+ rewrite_body(
25
+ message,
26
+ content_type: multipart_encrypted_content_type,
27
+ parts: [control_part, encrypted_part],
28
+ )
29
+ end
30
+
31
+ # Signs a message in a +multipart/signed+ fashion as defined in RFC 1847.
32
+ #
33
+ # @param [Mail::Message] message
34
+ # Message which is expected to be signed.
35
+ def sign(message)
36
+ source_part = body_to_part(message)
37
+ restrict_encoding(source_part)
38
+ signer = find_signer_for(message)
39
+ micalg, signature = compute_signature(source_part.encoded, signer)
40
+ signature_part = build_signature_part(signature)
41
+
42
+ rewrite_body(
43
+ message,
44
+ content_type: multipart_signed_content_type(micalg: micalg),
45
+ parts: [source_part, signature_part],
46
+ )
47
+ end
48
+
49
+ protected
50
+
51
+ def restrict_encoding(part)
52
+ if part.multipart?
53
+ part.parts.each { |p| restrict_encoding(p) }
54
+ else
55
+ ivar = "@enmail_rfc18467_encoding_restrictions"
56
+ part.instance_variable_set(ivar, true)
57
+ end
58
+ end
59
+
60
+ # Builds a mail part containing the encrypted message, that is
61
+ # the 2nd subpart of +multipart/encrypted+ as defined in RFC 1847.
62
+ def build_encrypted_part(encrypted)
63
+ part = ::Mail::Part.new
64
+ part.content_type = encrypted_message_content_type
65
+ part.body = encrypted
66
+ part
67
+ end
68
+
69
+ # Builds a mail part containing the control information for encrypted
70
+ # message, that is the 1st subpart of +multipart/encrypted+ as defined in
71
+ # RFC 1847.
72
+ def build_encryption_control_part
73
+ part = ::Mail::Part.new
74
+ part.content_type = encryption_protocol
75
+ part.body = encryption_control_information
76
+ part
77
+ end
78
+
79
+ # Builds a mail part containing the digital signature, that is
80
+ # the 2nd subpart of +multipart/signed+ as defined in RFC 1847.
81
+ def build_signature_part(signature)
82
+ part = ::Mail::Part.new
83
+ part.content_type = sign_protocol
84
+ part.body = signature
85
+ part
86
+ end
87
+
88
+ def multipart_signed_content_type(protocol: sign_protocol, micalg:)
89
+ %[multipart/signed; protocol="#{protocol}"; micalg="#{micalg}"]
90
+ end
91
+
92
+ def multipart_encrypted_content_type(protocol: encryption_protocol)
93
+ %[multipart/encrypted; protocol="#{protocol}"]
94
+ end
95
+
96
+ # The encrypted message must have content type +application/octet-stream+,
97
+ # as defined in RFC 1847 p. 6.
98
+ def encrypted_message_content_type
99
+ "application/octet-stream"
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,60 @@
1
+ # (c) Copyright 2018 Ribose Inc.
2
+ #
3
+
4
+ module EnMail
5
+ module Helpers
6
+ # Common interface for building adapters conforming to RFC 3156 "MIME
7
+ # Security with OpenPGP", which is an implementation of RFC 1847
8
+ # "Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted".
9
+ #
10
+ # See: https://tools.ietf.org/html/rfc3156
11
+ module RFC3156
12
+ include RFC1847
13
+
14
+ # The RFC 3156 explicitly allows for signing and encrypting data in
15
+ # a single OpenPGP message.
16
+ # See: https://tools.ietf.org/html/rfc3156#section-6.2
17
+ #
18
+ # rubocop:disable Metrics/MethodLength
19
+ def sign_and_encrypt_combined(message)
20
+ source_part = body_to_part(message)
21
+ restrict_encoding(source_part)
22
+ signer = find_signer_for(message)
23
+ recipients = find_recipients_for(message)
24
+ encrypted =
25
+ sign_and_encrypt_string(source_part.encoded, signer, recipients).to_s
26
+ encrypted_part = build_encrypted_part(encrypted)
27
+ control_part = build_encryption_control_part
28
+
29
+ rewrite_body(
30
+ message,
31
+ content_type: multipart_encrypted_content_type,
32
+ parts: [control_part, encrypted_part],
33
+ )
34
+ end
35
+ # rubocop:enable Metrics/MethodLength
36
+
37
+ # The RFC 3156 requires that the message is first signed, then encrypted.
38
+ # See: https://tools.ietf.org/html/rfc3156#section-6.1
39
+ def sign_and_encrypt_encapsulated(message)
40
+ sign(message)
41
+ encrypt(message)
42
+ end
43
+
44
+ protected
45
+
46
+ def sign_protocol
47
+ "application/pgp-signature"
48
+ end
49
+
50
+ def encryption_protocol
51
+ "application/pgp-encrypted"
52
+ end
53
+
54
+ # As defined in RFC 3156
55
+ def encryption_control_information
56
+ "Version: 1"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,6 @@
1
+ # (c) Copyright 2018 Ribose Inc.
2
+ #
3
+
1
4
  module EnMail
2
- VERSION = "0.1.0".freeze
5
+ VERSION = "0.2.0".freeze
3
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enmail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - Ronald Tse
7
+ - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-03 00:00:00.000000000 Z
11
+ date: 2019-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mail
@@ -28,30 +28,96 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.14'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3.0'
34
37
  type: :development
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: '1.14'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
41
47
  - !ruby/object:Gem::Dependency
42
- name: rake
48
+ name: gpgme
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
51
  - - "~>"
46
52
  - !ruby/object:Gem::Version
47
- version: '10.0'
53
+ version: '2.0'
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
58
  - - "~>"
53
59
  - !ruby/object:Gem::Version
54
- version: '10.0'
60
+ version: '2.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: pry
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 0.10.3
68
+ - - "<"
69
+ - !ruby/object:Gem::Version
70
+ version: '0.12'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 0.10.3
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.12'
81
+ - !ruby/object:Gem::Dependency
82
+ name: rake
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '10'
88
+ - - "<"
89
+ - !ruby/object:Gem::Version
90
+ version: '13'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '10'
98
+ - - "<"
99
+ - !ruby/object:Gem::Version
100
+ version: '13'
101
+ - !ruby/object:Gem::Dependency
102
+ name: rnp
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 1.0.1
108
+ - - "<"
109
+ - !ruby/object:Gem::Version
110
+ version: '2'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 1.0.1
118
+ - - "<"
119
+ - !ruby/object:Gem::Version
120
+ version: '2'
55
121
  - !ruby/object:Gem::Dependency
56
122
  name: rspec
57
123
  requirement: !ruby/object:Gem::Requirement
@@ -67,50 +133,60 @@ dependencies:
67
133
  - !ruby/object:Gem::Version
68
134
  version: '3.0'
69
135
  - !ruby/object:Gem::Dependency
70
- name: pry
136
+ name: rspec-pgp_matchers
71
137
  requirement: !ruby/object:Gem::Requirement
72
138
  requirements:
73
139
  - - "~>"
74
140
  - !ruby/object:Gem::Version
75
- version: 0.10.3
141
+ version: 0.1.1
76
142
  type: :development
77
143
  prerelease: false
78
144
  version_requirements: !ruby/object:Gem::Requirement
79
145
  requirements:
80
146
  - - "~>"
81
147
  - !ruby/object:Gem::Version
82
- version: 0.10.3
148
+ version: 0.1.1
83
149
  description: Encrypted Email in Ruby
84
150
  email:
85
- - ronald.tse@ribose.com
151
+ - open.source@ribose.com
86
152
  executables: []
87
153
  extensions: []
88
154
  extra_rdoc_files: []
89
155
  files:
156
+ - ".editorconfig"
90
157
  - ".gitignore"
158
+ - ".gitmodules"
91
159
  - ".hound.yml"
92
160
  - ".rubocop.yml"
93
161
  - ".travis.yml"
162
+ - ".yardopts"
94
163
  - CODE_OF_CONDUCT.md
95
164
  - Gemfile
96
- - README.md
165
+ - LICENSE.txt
166
+ - README.adoc
97
167
  - REQUIREMENTS.md
98
168
  - Rakefile
99
169
  - bin/console
100
170
  - bin/rspec
101
171
  - bin/setup
172
+ - ci/install_botan.sh
173
+ - ci/install_json_c.sh
174
+ - ci/install_rnp.sh
175
+ - docs/GPGMEAdapter.adoc
176
+ - docs/RNPAdapter.adoc
102
177
  - enmail.gemspec
103
178
  - lib/enmail.rb
104
- - lib/enmail/certificate_finder.rb
105
- - lib/enmail/config.rb
106
- - lib/enmail/configuration.rb
107
- - lib/enmail/enmailable.rb
108
- - lib/enmail/key.rb
109
- - lib/enmail/mail_ext/message.rb
179
+ - lib/enmail/adapters/base.rb
180
+ - lib/enmail/adapters/gpgme.rb
181
+ - lib/enmail/adapters/gpgme_requirements.rb
182
+ - lib/enmail/adapters/rnp.rb
183
+ - lib/enmail/adapters/rnp_requirements.rb
184
+ - lib/enmail/dependency_constraints.rb
185
+ - lib/enmail/extensions/message_transport_encoding_restrictions.rb
186
+ - lib/enmail/helpers/message_manipulation.rb
187
+ - lib/enmail/helpers/rfc1847.rb
188
+ - lib/enmail/helpers/rfc3156.rb
110
189
  - lib/enmail/version.rb
111
- - lib/mail/secure/mail_interceptors/pgp.rb
112
- - lib/mail/secure/models/key.rb
113
- - lib/mail/secure/pgp_mailable.rb
114
190
  homepage: https://github.com/riboseinc/enmail
115
191
  licenses:
116
192
  - MIT
@@ -130,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
206
  - !ruby/object:Gem::Version
131
207
  version: '0'
132
208
  requirements: []
133
- rubyforge_project:
134
- rubygems_version: 2.5.2
209
+ rubygems_version: 3.0.1
135
210
  signing_key:
136
211
  specification_version: 4
137
212
  summary: Encrypted Email in Ruby