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 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