dkim 0.1.0 → 1.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/.github/workflows/test.yml +20 -0
- data/.gitignore +1 -0
- data/Appraisals +7 -0
- data/CHANGELOG.md +28 -3
- data/Gemfile +5 -1
- data/README.md +52 -61
- data/Rakefile +12 -0
- data/bin/dkimsign.rb +0 -0
- data/dkim.gemspec +5 -0
- data/gemfiles/mail_2.6.gemfile +11 -0
- data/gemfiles/mail_2.7.gemfile +11 -0
- data/lib/dkim/canonicalized_headers.rb +26 -0
- data/lib/dkim/dkim_header.rb +21 -12
- data/lib/dkim/encodings/base64.rb +12 -0
- data/lib/dkim/encodings/dkim_quoted_printable.rb +19 -0
- data/lib/dkim/encodings/plain_text.rb +8 -0
- data/lib/dkim/encodings.rb +3 -0
- data/lib/dkim/header.rb +7 -0
- data/lib/dkim/interceptor.rb +13 -1
- data/lib/dkim/options.rb +84 -0
- data/lib/dkim/signed_mail.rb +31 -56
- data/lib/dkim/tag_value_list.rb +28 -0
- data/lib/dkim/version.rb +1 -1
- data/lib/dkim.rb +4 -8
- data/lib/mail/dkim_field.rb +4 -4
- data/test/dkim/canonicalization_test.rb +80 -0
- data/test/dkim/canonicalized_headers_test.rb +51 -0
- data/test/dkim/dkim_header_test.rb +43 -0
- data/test/dkim/encodings_test.rb +24 -0
- data/test/dkim/interceptor_test.rb +129 -0
- data/test/dkim/options_test.rb +40 -0
- data/test/dkim/signed_mail_test.rb +112 -0
- data/test/dkim/tag_value_list_test.rb +28 -0
- data/test/test_helper.rb +36 -4
- metadata +95 -18
- data/lib/dkim/header_list.rb +0 -19
- data/test/canonicalization_test.rb +0 -78
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f9ac50ad199771571d27da93d35d17d404e6cf24b18642856b8d8e3574ecb7f9
|
4
|
+
data.tar.gz: 7d1248e2086cea45176e60867ac15c4de7eb39f8cd5767c04e682f406e7901eb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0161d656b0d464c2dced244ce241077181700c42451178bb7e6ccc5af5d63790b993e15f0d7f4cec579830b9e52bbde1aafa80d7ee908ea3310fdaba821884e8
|
7
|
+
data.tar.gz: 9d6d1b6218d2fdf7e1a7935c72de032fe7db7f2178f257f3bfa9ebc2ae810dde46988416e6a40b711616b328b30f2762412b796b403459738f2aa534fdc811f8
|
@@ -0,0 +1,20 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on: [push]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v1
|
12
|
+
- name: Set up Ruby 2.6
|
13
|
+
uses: actions/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: 2.6.x
|
16
|
+
- name: Build and test with Rake
|
17
|
+
run: |
|
18
|
+
gem install bundler
|
19
|
+
bundle install --jobs 4 --retry 3
|
20
|
+
bundle exec rake
|
data/.gitignore
CHANGED
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,34 @@
|
|
1
1
|
# dkim Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
## 1.1.0 (2021-12-05)
|
6
|
+
* Add support for DKIM expiration tag
|
7
|
+
|
8
|
+
## 1.0.1 (2013-01-15)
|
9
|
+
* Fix Minitest
|
10
|
+
* Add support for identity, the "i=" tag
|
11
|
+
* Fix problem with `strip_field` in mail gem 2.7.0
|
12
|
+
|
13
|
+
## 1.0.0 (2013-01-15)
|
14
|
+
* DKIM-Signature header is now prepended rather than appended
|
15
|
+
* Headers are signed in the order they appear
|
16
|
+
* Correct signing of repeated headers
|
17
|
+
* Correct signing of missing headers
|
18
|
+
|
19
|
+
## 0.2.0 (2012-04-15)
|
20
|
+
* Warn and strip existing signatures in Dkim::Interceptor
|
21
|
+
* Dkim options can be accessed and modified using new Dkim.options or signed_mail.options hash
|
22
|
+
* Refactoring and better testing
|
23
|
+
* Improved documentation
|
24
|
+
|
25
|
+
## 0.1.0 (2011-12-10)
|
26
|
+
* Ensure header lines are not folded using Dkim::Interceptor
|
27
|
+
|
28
|
+
## 0.0.3 (2011-07-25)
|
4
29
|
* add Dkim::Interceptor class for integration with rails and [mail](https://github.com/mikel/mail)
|
5
30
|
|
6
|
-
##
|
31
|
+
## 0.0.2 (2011-06-01)
|
7
32
|
|
8
33
|
* add convenience method Dkim.sign
|
9
34
|
* support for the simple canonicalization algorithm
|
@@ -11,7 +36,7 @@
|
|
11
36
|
* correct handling of an empty message body
|
12
37
|
|
13
38
|
|
14
|
-
##
|
39
|
+
## 0.0.1 (2011-05-10)
|
15
40
|
|
16
41
|
* Initial release
|
17
42
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,30 +1,64 @@
|
|
1
1
|
dkim
|
2
2
|
====
|
3
|
-
|
4
3
|
A DKIM signing library in ruby.
|
5
4
|
|
5
|
+
[Documentation](http://rubydoc.info/github/jhawthorn/dkim)
|
6
|
+
|
6
7
|
Installation
|
7
8
|
============
|
8
9
|
|
9
10
|
sudo gem install dkim
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
Necessary configuration
|
13
|
+
=======================
|
14
|
+
A private key, a domain, and a selector need to be specified in order to sign messages.
|
15
|
+
|
16
|
+
These can be specified globally
|
17
|
+
|
18
|
+
Dkim::domain = 'example.com'
|
19
|
+
Dkim::selector = 'mail'
|
20
|
+
Dkim::private_key = open('private.pem').read
|
21
|
+
|
22
|
+
Options can be overridden per message.
|
23
|
+
|
24
|
+
Dkim.sign(mail, :selector => 'mail2', :private_key => OpenSSL::PKey::RSA.new(open('private2.pem').read))
|
25
|
+
|
26
|
+
For more details see {Dkim::Options}
|
27
|
+
|
28
|
+
Usage With Rails
|
29
|
+
================
|
30
|
+
|
31
|
+
Dkim contains `Dkim::Interceptor` which can be used to sign all mail delivered
|
32
|
+
by [mail](https://github.com/mikel/mail), which is used by actionmailer in
|
33
|
+
rails >= 3.
|
34
|
+
|
35
|
+
For rails, create an initializer (for example `config/initializers/dkim.rb`)
|
36
|
+
with the following template.
|
37
|
+
|
38
|
+
# Configure dkim globally (see above)
|
39
|
+
Dkim::domain = 'example.com'
|
40
|
+
Dkim::selector = 'mail'
|
41
|
+
Dkim::private_key = open('private.pem').read
|
42
|
+
|
43
|
+
# This will sign all ActionMailer deliveries
|
44
|
+
ActionMailer::Base.register_interceptor(Dkim::Interceptor)
|
45
|
+
|
46
|
+
Standalone Usage
|
47
|
+
================
|
13
48
|
|
14
49
|
Calling `Dkim.sign` on a string representing an email message returns the message with a DKIM signature inserted.
|
15
50
|
|
16
51
|
For example
|
17
52
|
|
18
|
-
mail = <<
|
53
|
+
mail = Dkim.sign(<<EOS)
|
19
54
|
To: someone@example.com
|
20
55
|
From: john@example.com
|
21
56
|
Subject: hi
|
22
|
-
|
57
|
+
|
23
58
|
Howdy
|
24
|
-
|
59
|
+
EOS
|
25
60
|
|
26
61
|
Dkim.sign(mail)
|
27
|
-
|
28
62
|
# =>
|
29
63
|
# To: someone@example.com
|
30
64
|
# From: john@example.com
|
@@ -34,67 +68,25 @@ For example
|
|
34
68
|
# b=0mKnNOkxFGiww63Zu4t46J7eZc3Uak3I9km3IH2Le3XcnSNtWJgxiwBX26IZ5yzcT
|
35
69
|
# VwJzcCnPKCScIJMQ7yfbfXmNsKVIOV6eSUqu1YvJ1fgzlSAXuDEMNFTjoto5rrdA+
|
36
70
|
# BgX849hEY/bWHDl1JJgNpiwtpl4t0Q7M4BVJUd7Lo=
|
37
|
-
#
|
71
|
+
#
|
38
72
|
# Howdy
|
39
73
|
|
40
|
-
|
41
|
-
-----------------------
|
42
|
-
A private key, a domain, and a selector need to be specified in order to sign messages.
|
43
|
-
|
44
|
-
These can be specified globally
|
74
|
+
More flexibility can be found using {Dkim::SignedMail} directly.
|
45
75
|
|
46
|
-
|
47
|
-
|
48
|
-
Dkim::private_key = open('private.pem').read
|
76
|
+
Specific configuration
|
77
|
+
========================
|
49
78
|
|
50
|
-
|
51
|
-
|
52
|
-
Dkim.sign(mail, :selector => 'mail2', :private_key => open('private2.pem').read)
|
53
|
-
|
54
|
-
Additional configuration
|
55
|
-
------------------------
|
56
|
-
|
57
|
-
The following is the default configuration
|
58
|
-
|
59
|
-
Dkim::signable_headers = Dkim::DefaultHeaders # Sign only the specified headers
|
60
|
-
Dkim::signing_algorithm = 'rsa-sha256' # can be rsa-sha1 or rsa-sha256 (default)
|
61
|
-
Dkim::header_canonicalization = 'relaxed' # Can be simple or relaxed (default)
|
62
|
-
Dkim::body_canonicalization = 'relaxed' # Can be simple or relaxed (default)
|
63
|
-
|
64
|
-
The defaults should fit most users needs; however, certain use cases will need them to be customized.
|
65
|
-
|
66
|
-
For example, for sending mesages through amazon SES, certain headers should not be signed
|
79
|
+
For sending mesages through Amazon SES, certain headers should not be signed
|
67
80
|
|
68
81
|
Dkim::signable_headers = Dkim::DefaultHeaders - %w{Message-ID Resent-Message-ID Date Return-Path Bounces-To}
|
69
82
|
|
70
|
-
|
83
|
+
Some OpenSSL's don't have sha256 support.
|
84
|
+
RFC 6376 states that signers SHOULD sign using rsa-sha256.
|
85
|
+
For this reason, dkim will *not* use rsa-sha1 as a fallback.
|
71
86
|
If you wish to override this behaviour and use whichever algorithm is available you can use this snippet (**not recommended**).
|
72
87
|
|
73
88
|
Dkim::signing_algorithm = defined?(OpenSSL::Digest::SHA256) ? 'rsa-sha256' : 'rsa-sha1'
|
74
89
|
|
75
|
-
Usage With Rails
|
76
|
-
================
|
77
|
-
|
78
|
-
Dkim contains `Dkim::Interceptor` which can be used to sign all mail delivered by the [mail gem](https://github.com/mikel/mail) or rails 3, which uses mail.
|
79
|
-
For rails, create an initializer (for example `config/initializers/dkim.rb`) with the following template.
|
80
|
-
|
81
|
-
# Configure dkim globally (see above)
|
82
|
-
Dkim::domain = 'example.com'
|
83
|
-
Dkim::selector = 'mail'
|
84
|
-
Dkim::private_key = open('private.pem').read
|
85
|
-
|
86
|
-
# This will sign all ActionMailer deliveries
|
87
|
-
ActionMailer::Base.register_interceptor('Dkim::Interceptor')
|
88
|
-
|
89
|
-
Example executable
|
90
|
-
==================
|
91
|
-
|
92
|
-
The library includes a `dkimsign.rb` executable suitable for testing the library or performing simple signatures.
|
93
|
-
|
94
|
-
`dkimsign.rb DOMAIN SELECTOR KEYFILE [MAILFILE]`
|
95
|
-
|
96
|
-
If MAILFILE is not specified `dkimsign.rb` will read the mail message from standard in.
|
97
|
-
|
98
90
|
Limitations
|
99
91
|
===========
|
100
92
|
|
@@ -102,17 +94,16 @@ Limitations
|
|
102
94
|
* No support for the older Yahoo! DomainKeys standard ([RFC 4870](http://tools.ietf.org/html/rfc4870)) *(none planned)*
|
103
95
|
* No support for specifying DKIM identity `i=` *(planned)*
|
104
96
|
* No support for body length `l=` *(planned)*
|
105
|
-
* No support for signature expiration `x=` *(planned)*
|
106
97
|
* No support for copied header fields `z=` *(not immediately planned)*
|
107
98
|
|
108
99
|
Resources
|
109
100
|
=========
|
110
101
|
|
111
|
-
* [RFC
|
102
|
+
* [RFC 6376](http://tools.ietf.org/html/rfc6376)
|
112
103
|
* Inspired by perl's [Mail-DKIM](http://dkimproxy.sourceforge.net/)
|
113
104
|
|
114
|
-
|
115
|
-
|
105
|
+
License
|
106
|
+
=======
|
116
107
|
|
117
108
|
(The MIT License)
|
118
109
|
|
data/Rakefile
CHANGED
@@ -12,3 +12,15 @@ end
|
|
12
12
|
|
13
13
|
Bundler::GemHelper.install_tasks
|
14
14
|
|
15
|
+
desc 'Open an pry (or irb) session preloaded with Dkim'
|
16
|
+
task :console do
|
17
|
+
begin
|
18
|
+
require 'pry'
|
19
|
+
sh 'pry -I lib -r dkim.rb'
|
20
|
+
rescue LoadError => _
|
21
|
+
sh 'irb -rubygems -I lib -r dkim.rb'
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
task c: :console
|
data/bin/dkimsign.rb
CHANGED
File without changes
|
data/dkim.gemspec
CHANGED
@@ -18,4 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'minitest', '~> 5.0'
|
24
|
+
s.add_development_dependency 'mail'
|
25
|
+
s.add_development_dependency 'appraisal'
|
21
26
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Dkim
|
2
|
+
class CanonicalizedHeaders
|
3
|
+
include Enumerable
|
4
|
+
def initialize header_list, signed_headers
|
5
|
+
@header_list = header_list
|
6
|
+
@signed_headers = signed_headers.map(&:downcase)
|
7
|
+
end
|
8
|
+
def each(&block)
|
9
|
+
header_hash = Hash.new {|h,k| h[k] = []}
|
10
|
+
@header_list.each do |header|
|
11
|
+
header_hash[header.relaxed_key] << header
|
12
|
+
end
|
13
|
+
|
14
|
+
@signed_headers.each do |key|
|
15
|
+
if header = header_hash[key].pop
|
16
|
+
yield header
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
def to_s(canonicalization)
|
21
|
+
map do |header|
|
22
|
+
header.to_s(canonicalization) + "\r\n"
|
23
|
+
end.join
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/dkim/dkim_header.rb
CHANGED
@@ -1,28 +1,37 @@
|
|
1
1
|
|
2
2
|
require 'dkim/header'
|
3
|
+
require 'dkim/tag_value_list'
|
4
|
+
require 'dkim/encodings'
|
3
5
|
|
4
6
|
module Dkim
|
5
7
|
class DkimHeader < Header
|
8
|
+
attr_reader :list
|
6
9
|
def initialize values={}
|
7
10
|
self.key = 'DKIM-Signature'
|
8
|
-
@
|
11
|
+
@list = TagValueList.new values
|
9
12
|
end
|
10
13
|
def value
|
11
|
-
@
|
12
|
-
" #{k}=#{v}"
|
13
|
-
end.join(';')
|
14
|
+
" #{@list}"
|
14
15
|
end
|
15
16
|
def [] k
|
16
|
-
|
17
|
-
value && value[1]
|
17
|
+
encoder_for(k).decode(@list[k])
|
18
18
|
end
|
19
19
|
def []= k, v
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
@list[k] = encoder_for(k).encode(v)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def encoder_for key
|
25
|
+
case key
|
26
|
+
when *%w{v a c d h l q s t x}
|
27
|
+
Encodings::PlainText
|
28
|
+
when *%w{i z}
|
29
|
+
Encodings::DkimQuotedPrintable
|
30
|
+
when *%w{b bh}
|
31
|
+
Encodings::Base64
|
32
|
+
else
|
33
|
+
raise "unknown key: #{key}"
|
34
|
+
end.new
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module Dkim
|
3
|
+
module Encodings
|
4
|
+
# Implements DKIM-Quoted-Printable as described in rfc6376 section 2.11
|
5
|
+
class DkimQuotedPrintable
|
6
|
+
DkimUnafeChar = /[^\x21-\x3A\x3C\x3E-\x7E]/
|
7
|
+
def encode string
|
8
|
+
string.gsub(DkimUnafeChar) do |char|
|
9
|
+
"=%.2x" % char.unpack('C')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
def decode string
|
13
|
+
string.gsub(/=([0-9A-F]{2})/) do
|
14
|
+
$1.hex.chr
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/dkim/header.rb
CHANGED
data/lib/dkim/interceptor.rb
CHANGED
@@ -3,7 +3,19 @@ module Dkim
|
|
3
3
|
class Interceptor
|
4
4
|
def self.delivering_email(message)
|
5
5
|
require 'mail/dkim_field'
|
6
|
-
|
6
|
+
|
7
|
+
# strip any existing signatures
|
8
|
+
if message['DKIM-Signature']
|
9
|
+
warn "WARNING: Dkim::Interceptor given a message with an existing signature, which it has replaced."
|
10
|
+
warn "If you really want to add a second signature to the message, you should be using the dkim gem directly."
|
11
|
+
message['DKIM-Signature'] = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# generate new signature
|
15
|
+
dkim_signature = SignedMail.new(message.encoded).dkim_header.value
|
16
|
+
|
17
|
+
# prepend signature to message
|
18
|
+
message.header.fields.unshift Mail::DkimField.new(dkim_signature)
|
7
19
|
message
|
8
20
|
end
|
9
21
|
end
|
data/lib/dkim/options.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Dkim
|
2
|
+
module Options
|
3
|
+
private
|
4
|
+
def self.define_option_method attribute_name
|
5
|
+
define_method(attribute_name){options[attribute_name]}
|
6
|
+
define_method("#{attribute_name}="){|value| options[attribute_name] = value}
|
7
|
+
end
|
8
|
+
public
|
9
|
+
|
10
|
+
# @attribute [rw]
|
11
|
+
# Hash of all options
|
12
|
+
# @return [Hash]
|
13
|
+
def options
|
14
|
+
@options ||= {}
|
15
|
+
end
|
16
|
+
attr_writer :options
|
17
|
+
|
18
|
+
# @attribute [rw]
|
19
|
+
# This corresponds to the t= tag in the dkim header.
|
20
|
+
# The default (nil) is to use the current time at signing.
|
21
|
+
# @return [Time,#to_i] A Time object or seconds since the epoch
|
22
|
+
define_option_method :time
|
23
|
+
|
24
|
+
# @attribute [rw]
|
25
|
+
# Signature expiration.
|
26
|
+
# This corresponds to the x= tag in the dkim header.
|
27
|
+
# @return [Time,#to_i] A Time object or seconds since the epoch
|
28
|
+
define_option_method :expire
|
29
|
+
|
30
|
+
# @attribute [rw]
|
31
|
+
# The signing algorithm for dkim. Valid values are 'rsa-sha1' and 'rsa-sha256' (default).
|
32
|
+
# This corresponds to the a= tag in the dkim header.
|
33
|
+
# @return [String] signing algorithm
|
34
|
+
define_option_method :signing_algorithm
|
35
|
+
|
36
|
+
# @attribute [rw]
|
37
|
+
# Configures which headers should be signed.
|
38
|
+
# Defaults to {Dkim::DefaultHeaders Dkim::DefaultHeaders}
|
39
|
+
# @return [Array<String>] signable headers
|
40
|
+
define_option_method :signable_headers
|
41
|
+
|
42
|
+
# @attribute [rw]
|
43
|
+
# The domain used for signing.
|
44
|
+
# This corresponds to the d= tag in the dkim header.
|
45
|
+
# @return [String] domain
|
46
|
+
define_option_method :domain
|
47
|
+
|
48
|
+
# @attribute [rw]
|
49
|
+
# The identity used for signing.
|
50
|
+
# This corresponds to the i= tag in the dkim header.
|
51
|
+
# @return [String] identity
|
52
|
+
define_option_method :identity
|
53
|
+
|
54
|
+
# @attribute [rw]
|
55
|
+
# Selector used for signing.
|
56
|
+
# This corresponds to the s= tag in the dkim header.
|
57
|
+
# @return [String] selector
|
58
|
+
define_option_method :selector
|
59
|
+
|
60
|
+
# @attribute [rw]
|
61
|
+
# Header canonicalization algorithm.
|
62
|
+
# Valid values are 'simple' and 'relaxed' (default)
|
63
|
+
# This corresponds to the first half of the c= tag in the dkim header.
|
64
|
+
# @return [String] header canonicalization algorithm
|
65
|
+
define_option_method :header_canonicalization
|
66
|
+
|
67
|
+
# @attribute [rw]
|
68
|
+
# Body canonicalization algorithm.
|
69
|
+
# Valid values are 'simple' and 'relaxed' (default)
|
70
|
+
# This corresponds to the second half of the c= tag in the dkim header.
|
71
|
+
# @return [String] body canonicalization algorithm
|
72
|
+
define_option_method :body_canonicalization
|
73
|
+
|
74
|
+
# @attribute [rw]
|
75
|
+
# RSA private key for signing.
|
76
|
+
# Can be assigned either an {OpenSSL::PKey::RSA} private key or a valid PEM format string.
|
77
|
+
# @return [OpenSSL::PKey::RSA] private key
|
78
|
+
define_option_method :private_key
|
79
|
+
def private_key= key
|
80
|
+
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
81
|
+
options[:private_key] = key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|