enmail 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.hound.yml +3 -0
- data/.rubocop.yml +637 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/README.md +115 -0
- data/REQUIREMENTS.md +214 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/rspec +16 -0
- data/bin/setup +8 -0
- data/enmail.gemspec +31 -0
- data/lib/enmail.rb +7 -0
- data/lib/enmail/certificate_finder.rb +75 -0
- data/lib/enmail/config.rb +21 -0
- data/lib/enmail/configuration.rb +80 -0
- data/lib/enmail/enmailable.rb +43 -0
- data/lib/enmail/key.rb +53 -0
- data/lib/enmail/mail_ext/message.rb +18 -0
- data/lib/enmail/version.rb +3 -0
- data/lib/mail/secure/mail_interceptors/pgp.rb +53 -0
- data/lib/mail/secure/models/key.rb +5 -0
- data/lib/mail/secure/pgp_mailable.rb +107 -0
- metadata +138 -0
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at ronald.tse@ribose.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# EnMail
|
2
|
+
|
3
|
+
[](https://travis-ci.org/riboseinc/enmail)
|
5
|
+
|
6
|
+
EnMail (Encrypted mail) helps the Ruby mail gem send secure encrypted messages.
|
7
|
+
|
8
|
+
The two ways for secure mail are:
|
9
|
+
* OpenPGP
|
10
|
+
* S/MIME
|
11
|
+
|
12
|
+
This gem allows you to select different OpenPGP implementations
|
13
|
+
including NetPGP and GnuPG as different OpenPGP adapters, and also
|
14
|
+
S/MIME.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem "enmail"
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
```sh
|
27
|
+
bundle install
|
28
|
+
```
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
```sh
|
33
|
+
gem install enmail
|
34
|
+
```
|
35
|
+
|
36
|
+
## Configure
|
37
|
+
|
38
|
+
The `EnMail` gem provides a very easier interface to set custom configurations
|
39
|
+
or configure the underlying dependencies, we can configure it by adding an
|
40
|
+
initializer with the following code
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
EnMail.configure do |config|
|
44
|
+
config.sign_message = true
|
45
|
+
config.smime_adapter = :openssl
|
46
|
+
config.secret_key = "Secret Key String"
|
47
|
+
|
48
|
+
config.certificates_path = "CERTIFICATES_ROOT_PAH"
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
Or
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
EnMail.configuration.certificates_path = "CERTIFICATES_ROOT_PAH"
|
56
|
+
```
|
57
|
+
|
58
|
+
## Usage
|
59
|
+
|
60
|
+
```sh
|
61
|
+
bin/console
|
62
|
+
```
|
63
|
+
|
64
|
+
## Development
|
65
|
+
|
66
|
+
We are following Sandi Metz's Rules for this gem, you can read the
|
67
|
+
[description of the rules here][sandimetz]. All new code should follow these
|
68
|
+
rules. If you make changes in a pre-existing file that violates these rules you
|
69
|
+
should fix the violations as part of your contribution.
|
70
|
+
|
71
|
+
### Setup
|
72
|
+
|
73
|
+
Clone the repository.
|
74
|
+
|
75
|
+
```sh
|
76
|
+
git clone https://github.com/riboseinc/enmail
|
77
|
+
```
|
78
|
+
|
79
|
+
Setup your environment.
|
80
|
+
|
81
|
+
```sh
|
82
|
+
bin/setup
|
83
|
+
```
|
84
|
+
|
85
|
+
Run the test suite
|
86
|
+
|
87
|
+
```sh
|
88
|
+
bin/rspec
|
89
|
+
```
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
First, thank you for contributing! We love pull requests from everyone. By
|
94
|
+
participating in this project, you hereby grant [Ribose Inc.][ribose] the
|
95
|
+
right to grant or transfer an unlimited number of non exclusive licenses or
|
96
|
+
sub-licenses to third parties, under the copyright covering the contribution
|
97
|
+
to use the contribution by all means.
|
98
|
+
|
99
|
+
Here are a few technical guidelines to follow:
|
100
|
+
|
101
|
+
1. Open an [issue][issues] to discuss a new feature.
|
102
|
+
1. Write tests to support your new feature.
|
103
|
+
1. Make sure the entire test suite passes locally and on CI.
|
104
|
+
1. Open a Pull Request.
|
105
|
+
1. [Squash your commits][squash] after receiving feedback.
|
106
|
+
1. Party!
|
107
|
+
|
108
|
+
## Credits
|
109
|
+
|
110
|
+
This gem is developed, maintained and funded by [Ribose Inc.][ribose]
|
111
|
+
|
112
|
+
[ribose]: https://www.ribose.com
|
113
|
+
[issues]: https://github.com/abunashir/enmail/issues
|
114
|
+
[squash]: https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature
|
115
|
+
[sandimetz]: http://robots.thoughtbot.com/post/50655960596/sandi-metz-rules-for-developers
|
data/REQUIREMENTS.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Ruby mail extension for sending/receiving secure mail
|
2
|
+
|
3
|
+
[//]: # (TODO: JL add interceptor)
|
4
|
+
|
5
|
+
OpenPGP [RFC 4880] and S/MIME [RFC 5751] are two methods for secure mail
|
6
|
+
delivery today.
|
7
|
+
|
8
|
+
Currently there is no existing mail gem extension (Ruby Gem) that allows
|
9
|
+
switching between the methods and are usually not well structured.
|
10
|
+
|
11
|
+
This work is to complete implementation of the mail-secure gem by
|
12
|
+
leveraging existing OpenPGP and S/MIME mail extension code to allow
|
13
|
+
secure email sending via either standard (or a combination of both).
|
14
|
+
|
15
|
+
The mail-secure gem uses the concept of "adapters" to support different
|
16
|
+
underlying libraries.
|
17
|
+
|
18
|
+
* For OpenPGP, there are two existing implementations: GnuPG and NetPGP.
|
19
|
+
* For S/MIME, the default Ruby OpenSSL implementation should be used.
|
20
|
+
|
21
|
+
|
22
|
+
## Implementations to support:
|
23
|
+
|
24
|
+
* OpenPGP
|
25
|
+
* NetPGP
|
26
|
+
* GnuPG
|
27
|
+
* S/MIME
|
28
|
+
* OpenSSL
|
29
|
+
|
30
|
+
This gem allows you to select different OpenPGP implementations
|
31
|
+
including NetPGP and GnuPG as different OpenPGP adapters, and also
|
32
|
+
S/MIME.
|
33
|
+
|
34
|
+
## References and notes
|
35
|
+
|
36
|
+
* <https://github.com/jkraemer/mail-gpg>
|
37
|
+
* <https://github.com/bluerail/mr_smime>
|
38
|
+
|
39
|
+
The `mail-gpg` gem hacks the default `mail` gem Deliverer to ensure the
|
40
|
+
OpenPGP encryption/signing step is done at the last. This is extremely
|
41
|
+
dirty and fragile.
|
42
|
+
|
43
|
+
The `mr_smime` gem uses an interceptor hook (supplied by the `mail` gem)
|
44
|
+
to encrypt/sign the email. The catch is the `mail` gem supports
|
45
|
+
multiple interceptors (like Rack middlewares) so there is no guarantee
|
46
|
+
that it is the last interceptor.
|
47
|
+
|
48
|
+
A better approach is to use the interceptor pattern, and hack the
|
49
|
+
interceptor methods in `mail` to force a particular interceptor (the one
|
50
|
+
to implement) to be at the very end.
|
51
|
+
|
52
|
+
Technically, this resulting implementation could allow usage of OpenPGP
|
53
|
+
to sign/encrypt a message, then use S/MIME to sign (and/or encrypt) the
|
54
|
+
OpenPGP-encoded message at the same time.
|
55
|
+
|
56
|
+
|
57
|
+
## OpenPGP Example code
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
message = Mail.new
|
61
|
+
message.decrypt
|
62
|
+
|
63
|
+
signed_mail = message.sign(key)
|
64
|
+
encrypted_mail = message.encrypt(identity)
|
65
|
+
|
66
|
+
signed_email.signature_valid? # => true, false
|
67
|
+
signed_email.secure? # => true, false
|
68
|
+
signed_email.pgp # => :inline, :mime, nil
|
69
|
+
signed_email.pgp? # => true, false
|
70
|
+
signed_email.pgp_inline? # => true, false
|
71
|
+
signed_email.pgp_mime? # => true, false
|
72
|
+
encrypted_email.secure # => "OpenPGP RFC 4880", nil
|
73
|
+
encrypted_email.secure? # => true, false
|
74
|
+
signed_email.smime? # => true, false
|
75
|
+
|
76
|
+
Mail::Secure::OpenPGP.signature_valid?(signed_email) # => true, false
|
77
|
+
```
|
78
|
+
|
79
|
+
## OpenPGP Configuration
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
Mail::Secure.configuration = {
|
83
|
+
method: :openpgp,
|
84
|
+
implementation: :netpgp, # :gpgme
|
85
|
+
|
86
|
+
# Specify PGP key in 3 ways.
|
87
|
+
# Only providing the key:
|
88
|
+
key: "ASCII-ARMORED-PGP-KEY",
|
89
|
+
key: "non-armored-raw-bytes-pgp-key",
|
90
|
+
# or:
|
91
|
+
key_id: "key-id",
|
92
|
+
keyring: "/Users/whoami/keyring-location",
|
93
|
+
# or infer "keyring" from GNUPGHOME:
|
94
|
+
key_id: "key-id",
|
95
|
+
}
|
96
|
+
|
97
|
+
```
|
98
|
+
|
99
|
+
### Sample mail object
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
Mail.new do
|
103
|
+
to 'jane@doe.net'
|
104
|
+
from 'john@doe.net'
|
105
|
+
subject 'gpg test'
|
106
|
+
body "encrypt me!"
|
107
|
+
add_file "some_attachment.zip"
|
108
|
+
|
109
|
+
# Using default :method configuration:
|
110
|
+
secure encrypt: true, encrypt_for: Key.find("mike@kite.com")
|
111
|
+
|
112
|
+
# #secure's first argument is optional: :openpgp | :smime
|
113
|
+
# If missing, then use the default configuration.
|
114
|
+
secure :openpgp, encrypt: true, encrypt_for: Key.find("mike@kite.com")
|
115
|
+
secure :smime, encrypt: true, encrypt_for: Key.find("mike@kite.com")
|
116
|
+
|
117
|
+
# #secure as a long-hand:
|
118
|
+
secure :openpgp, sign: true
|
119
|
+
secure :openpgp, encrypt: true, encrypt_for: PrivateKey.find("myself")
|
120
|
+
secure :openpgp, sign: true, sign_as: PrivateKey.find("myself")
|
121
|
+
secure :openpgp, encrypt: true
|
122
|
+
secure :openpgp, encrypt: true, passphrase: "secret"
|
123
|
+
secure :openpgp, encrypt: true, passphrase_callback: ->(...) {}
|
124
|
+
|
125
|
+
# #openpgp or #smime as short-hand:
|
126
|
+
smime sign: true
|
127
|
+
smime encrypt: true, encrypt_for: PrivateKey.find("myself")
|
128
|
+
smime sign: true, sign_as: PrivateKey.find("myself")
|
129
|
+
openpgp encrypt: true
|
130
|
+
openpgp encrypt: true, passphrase: "secret"
|
131
|
+
openpgp encrypt: true, passphrase_callback: ->(...) {}
|
132
|
+
|
133
|
+
# encrypt and sign message with sender's private key, using the given
|
134
|
+
# passphrase to decrypt the key
|
135
|
+
openpgp encrypt: true, sign: true, password: 'secret'
|
136
|
+
|
137
|
+
# encrypt and sign message using a different key
|
138
|
+
openpgp encrypt: true, sign_as: 'joe@otherdomain.com', password: 'secret'
|
139
|
+
|
140
|
+
|
141
|
+
# encrypt and sign message and use a callback function to provide the
|
142
|
+
# passphrase.
|
143
|
+
openpgp encrypt: true, sign_as: 'joe@otherdomain.com',
|
144
|
+
passphrase_callback: ->(obj, uid_hint, passphrase_info, prev_was_bad, fd) {
|
145
|
+
puts "Enter passphrase for #{passphrase_info}: "
|
146
|
+
(IO.for_fd(fd, 'w') << readline.chomp).flush
|
147
|
+
}
|
148
|
+
end.deliver
|
149
|
+
|
150
|
+
```
|
151
|
+
|
152
|
+
#### Encrypt mail using OpenPGP public key directly
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
johns_key = <<-END
|
156
|
+
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
157
|
+
Version: GnuPG vX.X.XX (GNU/Linux)
|
158
|
+
|
159
|
+
mQGiBEk39msRBADw1ExmrLD1OUMdfvA7cnVVYTC7CyqfNvHUVuuBDhV7azs
|
160
|
+
....
|
161
|
+
END
|
162
|
+
|
163
|
+
Mail.new do
|
164
|
+
to 'john@foo.bar'
|
165
|
+
gpg encrypt: true, keys: { 'john@foo.bar' => johns_key }
|
166
|
+
end
|
167
|
+
|
168
|
+
```
|
169
|
+
|
170
|
+
### Decrypting mail using passphrase
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
|
174
|
+
mail = Mail.first
|
175
|
+
mail.subject # subject is never encrypted
|
176
|
+
if mail.encrypted?
|
177
|
+
# decrypt using your private key, protected by the given passphrase
|
178
|
+
plaintext_mail = mail.decrypt(password: 'abc')
|
179
|
+
# the plaintext_mail, is a full Mail::Message object, just decrypted
|
180
|
+
end
|
181
|
+
|
182
|
+
```
|
183
|
+
|
184
|
+
### Signing mail (simplest case)
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
|
188
|
+
|
189
|
+
Mail.new do
|
190
|
+
to 'jane@doe.net'
|
191
|
+
gpg sign: true
|
192
|
+
end.deliver
|
193
|
+
|
194
|
+
```
|
195
|
+
|
196
|
+
### Verifying signature
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
|
200
|
+
mail = Mail.first
|
201
|
+
if !mail.encrypted? && mail.signed?
|
202
|
+
verified = mail.verify
|
203
|
+
puts "signature(s) valid: #{verified.signature_valid?}"
|
204
|
+
puts "message signed by: #{verified.signatures.map{|sig|sig.from}.join("\n")}"
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
if mail.encrypted?
|
209
|
+
decrypted = mail.decrypt(verify: true, password: 's3cr3t')
|
210
|
+
puts "signature(s) valid: #{decrypted.signature_valid?}"
|
211
|
+
puts "message signed by: #{decrypted.signatures.map{|sig|sig.from}.join("\n")}"
|
212
|
+
end
|
213
|
+
|
214
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'rspec' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
require "pathname"
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||=
|
11
|
+
File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "bundler/setup"
|
15
|
+
|
16
|
+
load Gem.bin_path("rspec-core", "rspec")
|