enmail 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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