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 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
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ gemfiles/*.lock
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise 'mail-2.6' do
2
+ gem 'mail', '2.6.6'
3
+ end
4
+
5
+ appraise 'mail-2.7' do
6
+ gem 'mail', '2.7.0'
7
+ end
data/CHANGELOG.md CHANGED
@@ -1,9 +1,34 @@
1
1
  # dkim Changelog
2
2
 
3
- ## 2011.07.25, Version 0.0.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
- ## 2011.06.01, Version 0.0.2
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
- ## 2011.05.10, Version 0.0.1
39
+ ## 0.0.1 (2011-05-10)
15
40
 
16
41
  * Initial release
17
42
 
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in dkim.gemspec
4
4
  gemspec
5
+
6
+ platforms :jruby do
7
+ gem 'jruby-openssl'
8
+ end
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
- Usage
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 = <<eos
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
- eos
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
- Necessary configuration
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
- Dkim::domain = 'example.com'
47
- Dkim::selector = 'mail'
48
- Dkim::private_key = open('private.pem').read
76
+ Specific configuration
77
+ ========================
49
78
 
50
- Options can be overridden per message.
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
- rfc4871 states that signers SHOULD sign using rsa-sha256. For this reason, dkim will *not* use rsa-sha1 as a fallback if the openssl library does not support sha256.
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 4871](http://tools.ietf.org/html/rfc4871)
102
+ * [RFC 6376](http://tools.ietf.org/html/rfc6376)
112
103
  * Inspired by perl's [Mail-DKIM](http://dkimproxy.sourceforge.net/)
113
104
 
114
- Copyright
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,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "mail", "2.6.6"
6
+
7
+ platforms :jruby do
8
+ gem "jruby-openssl"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "mail", "2.7.0"
6
+
7
+ platforms :jruby do
8
+ gem "jruby-openssl"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -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
@@ -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
- @values = values.to_a.flatten.each_slice(2).to_a
11
+ @list = TagValueList.new values
9
12
  end
10
13
  def value
11
- @values.map do |(k, v)|
12
- " #{k}=#{v}"
13
- end.join(';')
14
+ " #{@list}"
14
15
  end
15
16
  def [] k
16
- value = @values.detect {|(a,b)| a == k }
17
- value && value[1]
17
+ encoder_for(k).decode(@list[k])
18
18
  end
19
19
  def []= k, v
20
- value = @values.detect {|(a,b)| a == k }
21
- if !value
22
- value = [k, nil]
23
- @values << value
24
- end
25
- value[1] = v
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,12 @@
1
+ module Dkim
2
+ module Encodings
3
+ class Base64
4
+ def decode data
5
+ data.gsub(/\s/, '').unpack('m0')[0]
6
+ end
7
+ def encode data
8
+ [data].pack('m0').gsub("\n", '')
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,8 @@
1
+ module Dkim
2
+ module Encodings
3
+ class PlainText
4
+ def encode v; v; end
5
+ alias_method :decode, :encode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ require 'dkim/encodings/base64.rb'
2
+ require 'dkim/encodings/dkim_quoted_printable.rb'
3
+ require 'dkim/encodings/plain_text.rb'
data/lib/dkim/header.rb CHANGED
@@ -38,5 +38,12 @@ module Dkim
38
38
  def canonical_simple
39
39
  "#{key}:#{value}"
40
40
  end
41
+
42
+ def self.parse header_string
43
+ header_string.split(/\r?\n(?!([ \t]))/).map do |header|
44
+ key, value = header.split(':', 2)
45
+ new(key, value)
46
+ end
47
+ end
41
48
  end
42
49
  end
@@ -3,7 +3,19 @@ module Dkim
3
3
  class Interceptor
4
4
  def self.delivering_email(message)
5
5
  require 'mail/dkim_field'
6
- message.header.fields << Mail::DkimField.new(SignedMail.new(message.encoded).dkim_header.value)
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
@@ -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