dkim 0.1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|