dkim 0.1.0 → 0.2.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.
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
@@ -1,5 +1,14 @@
1
1
  # dkim Changelog
2
2
 
3
+ ## 2012.04.15, Version 0.2.0
4
+ * Warn and strip existing signatures in Dkim::Interceptor
5
+ * Dkim options can be accessed and modified using new Dkim.options or signed_mail.options hash
6
+ * Refactoring and better testing
7
+ * Improved documentation
8
+
9
+ ## 2011.12.10, Version 0.1.0
10
+ * Ensure header lines are not folded using Dkim::Interceptor
11
+
3
12
  ## 2011.07.25, Version 0.0.3
4
13
  * add Dkim::Interceptor class for integration with rails and [mail](https://github.com/mikel/mail)
5
14
 
data/Gemfile CHANGED
@@ -2,3 +2,13 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in dkim.gemspec
4
4
  gemspec
5
+
6
+ gem "rake"
7
+ gem 'minitest'
8
+ gem 'guard-minitest'
9
+ gem 'mail'
10
+
11
+ platforms :jruby do
12
+ gem 'jruby-openssl'
13
+ end
14
+
@@ -0,0 +1,5 @@
1
+
2
+ guard 'minitest' do
3
+ watch(%r|^test/(.*)\.rb|) { "test" }
4
+ watch(%r|^lib/(.*)\.rb|) { "test" }
5
+ end
data/README.md CHANGED
@@ -1,30 +1,62 @@
1
1
  dkim
2
2
  ====
3
-
4
3
  A DKIM signing library in ruby.
5
4
 
5
+ [![Build Status](https://secure.travis-ci.org/jhawthorn/dkim.png?branch=master)](http://travis-ci.org/jhawthorn/dkim)
6
+
7
+ [Documentation](http://rubydoc.info/github/jhawthorn/dkim)
8
+
6
9
  Installation
7
10
  ============
8
11
 
9
12
  sudo gem install dkim
10
13
 
11
- Usage
12
- =====
14
+ Necessary configuration
15
+ =======================
16
+ A private key, a domain, and a selector need to be specified in order to sign messages.
17
+
18
+ These can be specified globally
19
+
20
+ Dkim::domain = 'example.com'
21
+ Dkim::selector = 'mail'
22
+ Dkim::private_key = open('private.pem').read
23
+
24
+ Options can be overridden per message.
25
+
26
+ Dkim.sign(mail, :selector => 'mail2', :private_key => open('private2.pem').read)
27
+
28
+ For more details see {Dkim::Options}
29
+
30
+ Usage With Rails
31
+ ================
32
+
33
+ Dkim contains `Dkim::Interceptor` which can be used to sign all mail delivered by rails 3 or [mail](https://github.com/mikel/mail).
34
+ For rails, create an initializer (for example `config/initializers/dkim.rb`) with the following template.
35
+
36
+ # Configure dkim globally (see above)
37
+ Dkim::domain = 'example.com'
38
+ Dkim::selector = 'mail'
39
+ Dkim::private_key = open('private.pem').read
40
+
41
+ # This will sign all ActionMailer deliveries
42
+ ActionMailer::Base.register_interceptor(Dkim::Interceptor)
43
+
44
+ Standalone Usage
45
+ ================
13
46
 
14
47
  Calling `Dkim.sign` on a string representing an email message returns the message with a DKIM signature inserted.
15
48
 
16
49
  For example
17
50
 
18
- mail = <<eos
51
+ mail = Dkim.sign(<<EOS)
19
52
  To: someone@example.com
20
53
  From: john@example.com
21
54
  Subject: hi
22
-
55
+
23
56
  Howdy
24
- eos
57
+ EOS
25
58
 
26
59
  Dkim.sign(mail)
27
-
28
60
  # =>
29
61
  # To: someone@example.com
30
62
  # From: john@example.com
@@ -34,67 +66,25 @@ For example
34
66
  # b=0mKnNOkxFGiww63Zu4t46J7eZc3Uak3I9km3IH2Le3XcnSNtWJgxiwBX26IZ5yzcT
35
67
  # VwJzcCnPKCScIJMQ7yfbfXmNsKVIOV6eSUqu1YvJ1fgzlSAXuDEMNFTjoto5rrdA+
36
68
  # BgX849hEY/bWHDl1JJgNpiwtpl4t0Q7M4BVJUd7Lo=
37
- #
69
+ #
38
70
  # Howdy
39
71
 
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
45
-
46
- Dkim::domain = 'example.com'
47
- Dkim::selector = 'mail'
48
- Dkim::private_key = open('private.pem').read
72
+ More flexibility can be found using {Dkim::SignedMail} directly.
49
73
 
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
- ------------------------
74
+ Specific configuration
75
+ ========================
56
76
 
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
77
+ For sending mesages through Amazon SES, certain headers should not be signed
67
78
 
68
79
  Dkim::signable_headers = Dkim::DefaultHeaders - %w{Message-ID Resent-Message-ID Date Return-Path Bounces-To}
69
80
 
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.
81
+ Some OpenSSL's don't have sha256 support.
82
+ RFC 6376 states that signers SHOULD sign using rsa-sha256.
83
+ For this reason, dkim will *not* use rsa-sha1 as a fallback.
71
84
  If you wish to override this behaviour and use whichever algorithm is available you can use this snippet (**not recommended**).
72
85
 
73
86
  Dkim::signing_algorithm = defined?(OpenSSL::Digest::SHA256) ? 'rsa-sha256' : 'rsa-sha1'
74
87
 
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
88
  Limitations
99
89
  ===========
100
90
 
@@ -108,11 +98,11 @@ Limitations
108
98
  Resources
109
99
  =========
110
100
 
111
- * [RFC 4871](http://tools.ietf.org/html/rfc4871)
101
+ * [RFC 6376](http://tools.ietf.org/html/rfc6376)
112
102
  * Inspired by perl's [Mail-DKIM](http://dkimproxy.sourceforge.net/)
113
103
 
114
- Copyright
115
- =========
104
+ License
105
+ =======
116
106
 
117
107
  (The MIT License)
118
108
 
File without changes
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'dkim/signed_mail'
3
+ require 'dkim/options'
3
4
  require 'dkim/interceptor'
4
5
 
5
6
  module Dkim
@@ -14,13 +15,7 @@ module Dkim
14
15
  List-Post List-Owner List-Archive}
15
16
 
16
17
  class << self
17
- attr_accessor :signing_algorithm, :signable_headers, :domain, :selector, :header_canonicalization, :body_canonicalization
18
-
19
- attr_reader :private_key
20
- def private_key= key
21
- key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
22
- @private_key = key
23
- end
18
+ include Dkim::Options
24
19
 
25
20
  def sign message, options={}
26
21
  SignedMail.new(message, options).to_s
@@ -28,7 +23,7 @@ module Dkim
28
23
  end
29
24
  end
30
25
 
31
- Dkim::signable_headers = Dkim::DefaultHeaders
26
+ Dkim::signable_headers = Dkim::DefaultHeaders.dup
32
27
  Dkim::domain = nil
33
28
  Dkim::selector = nil
34
29
  Dkim::signing_algorithm = 'rsa-sha256'
@@ -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
+ # append signature to message
18
+ message.header.fields << Mail::DkimField.new(dkim_signature)
7
19
  message
8
20
  end
9
21
  end
@@ -0,0 +1,72 @@
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
+ # The signing algorithm for dkim. Valid values are 'rsa-sha1' and 'rsa-sha256' (default).
26
+ # This corresponds to the a= tag in the dkim header.
27
+ # @return [String] signing algorithm
28
+ define_option_method :signing_algorithm
29
+
30
+ # @attribute [rw]
31
+ # Configures which headers should be signed.
32
+ # Defaults to {Dkim::DefaultHeaders Dkim::DefaultHeaders}
33
+ # @return [Array<String>] signable headers
34
+ define_option_method :signable_headers
35
+
36
+ # @attribute [rw]
37
+ # The domain used for signing.
38
+ # This corresponds to the d= tag in the dkim header.
39
+ # @return [String] domain
40
+ define_option_method :domain
41
+
42
+ # @attribute [rw]
43
+ # Selector used for signing.
44
+ # This corresponds to the s= tag in the dkim header.
45
+ # @return [String] selector
46
+ define_option_method :selector
47
+
48
+ # @attribute [rw]
49
+ # Header canonicalization algorithm.
50
+ # Valid values are 'simple' and 'relaxed' (default)
51
+ # This corresponds to the first half of the c= tag in the dkim header.
52
+ # @return [String] header canonicalization algorithm
53
+ define_option_method :header_canonicalization
54
+
55
+ # @attribute [rw]
56
+ # Body canonicalization algorithm.
57
+ # Valid values are 'simple' and 'relaxed' (default)
58
+ # This corresponds to the second half of the c= tag in the dkim header.
59
+ # @return [String] body canonicalization algorithm
60
+ define_option_method :body_canonicalization
61
+
62
+ # @attribute [rw]
63
+ # RSA private key for signing.
64
+ # Can be assigned either an {OpenSSL::PKey::RSA} private key or a valid PEM format string.
65
+ # @return [OpenSSL::PKey::RSA] private key
66
+ define_option_method :private_key
67
+ def private_key= key
68
+ key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
69
+ options[:private_key] = key
70
+ end
71
+ end
72
+ end
@@ -4,69 +4,44 @@ require 'dkim/body'
4
4
  require 'dkim/dkim_header'
5
5
  require 'dkim/header'
6
6
  require 'dkim/header_list'
7
+ require 'dkim/options'
7
8
 
8
9
  module Dkim
9
10
  class SignedMail
11
+ include Options
12
+
13
+ # A new instance of SignedMail
14
+ #
15
+ # @param [String,#to_s] message mail message to be signed
16
+ # @param [Hash] options hash of options for signing. Defaults are taken from {Dkim}. See {Options} for details.
10
17
  def initialize message, options={}
11
18
  message = message.to_s.gsub(/\r?\n/, "\r\n")
12
19
  headers, body = message.split(/\r?\n\r?\n/, 2)
13
20
  @headers = HeaderList.new headers
14
21
  @body = Body.new body
15
22
 
16
- @signable_headers = options[:signable_headers]
17
- @domain = options[:domain]
18
- @selector = options[:selector]
19
- @time = options[:time]
20
- @signing_algorithm = options[:signing_algorithm]
21
- @private_key = options[:private_key]
22
- @header_canonicalization = options[:header_canonicalization]
23
- @body_canonicalization = options[:body_canonicalization]
24
- end
25
-
26
- # options for signatures
27
- attr_writer :signing_algorithm, :signable_headers, :domain, :selector, :time, :header_canonicalization, :body_canonicalization
28
-
29
- def private_key= key
30
- key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
31
- @private_key = key
32
- end
33
- def private_key
34
- @private_key || Dkim::private_key
35
- end
36
- def signing_algorithm
37
- @signing_algorithm || Dkim::signing_algorithm
38
- end
39
- def signable_headers
40
- @signable_headers || Dkim::signable_headers
41
- end
42
- def domain
43
- @domain || Dkim::domain
44
- end
45
- def selector
46
- @selector || Dkim::selector
47
- end
48
- def time
49
- @time
50
- end
51
- def header_canonicalization
52
- @header_canonicalization || Dkim::header_canonicalization
53
- end
54
- def body_canonicalization
55
- @body_canonicalization || Dkim::body_canonicalization
23
+ # default options from Dkim.options
24
+ @options = Dkim.options.merge(options)
56
25
  end
57
26
 
27
+ # @return [Array<String>] Signed headers of message in their canonical forms
58
28
  def signed_headers
59
29
  (@headers.map(&:relaxed_key) & signable_headers.map(&:downcase)).sort
60
30
  end
31
+
32
+ # @return [String] Signed headers of message in their canonical forms
61
33
  def canonical_header
62
34
  headers = signed_headers.map do |key|
63
35
  @headers[key].to_s(header_canonicalization) + "\r\n"
64
36
  end.join
65
37
  end
38
+
39
+ # @return [String] Body of message in its canonical form
66
40
  def canonical_body
67
41
  @body.to_s(body_canonicalization)
68
42
  end
69
43
 
44
+ # @return [DkimHeader] Constructed signature for the mail message
70
45
  def dkim_header
71
46
  dkim_header = DkimHeader.new
72
47
 
@@ -97,8 +72,8 @@ module Dkim
97
72
  dkim_header
98
73
  end
99
74
 
75
+ # @return [String] Message combined with calculated dkim header signature
100
76
  def to_s
101
- # Return the original message with the calculated header
102
77
  headers = @headers.to_a + [dkim_header]
103
78
  headers.map(&:to_s).join("\r\n") +
104
79
  "\r\n\r\n" +
@@ -107,13 +82,13 @@ module Dkim
107
82
 
108
83
  private
109
84
  def base64_encode data
110
- [data].pack('m0*').gsub("\n",'')
85
+ [data].pack('m0').gsub("\n",'')
111
86
  end
112
87
  def digest_alg
113
88
  case signing_algorithm
114
89
  when 'rsa-sha1'
115
90
  OpenSSL::Digest::SHA1.new
116
- when 'rsa-sha256'
91
+ when 'rsa-sha256'
117
92
  OpenSSL::Digest::SHA256.new
118
93
  else
119
94
  raise "Unknown digest algorithm: '#{signing_algorithm}'"
@@ -1,3 +1,3 @@
1
1
  module Dkim
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,80 @@
1
+
2
+ require 'test_helper'
3
+
4
+ module Dkim
5
+ class CanonicalizationTest < MiniTest::Unit::TestCase
6
+ # from section 3.4.6 of RFC 6376
7
+ def setup
8
+ @input = <<-eos.rfc_format
9
+ A: <SP> X <CRLF>
10
+ B <SP> : <SP> Y <HTAB><CRLF>
11
+ <HTAB> Z <SP><SP><CRLF>
12
+ <CRLF>
13
+ <SP> C <SP><CRLF>
14
+ D <SP><HTAB><SP> E <CRLF>
15
+ <CRLF>
16
+ <CRLF>
17
+ eos
18
+ @mail = SignedMail.new(@input)
19
+ @mail.signable_headers = ['A', 'B']
20
+ end
21
+ def test_relaxed_header
22
+ @mail.header_canonicalization = 'relaxed'
23
+ expected_header = <<-eos.rfc_format
24
+ a:X <CRLF>
25
+ b:Y <SP> Z <CRLF>
26
+ eos
27
+ assert_equal expected_header, @mail.canonical_header
28
+ end
29
+ def test_relaxed_body
30
+ @mail.body_canonicalization = 'relaxed'
31
+ expected_body = <<-eos.rfc_format
32
+ <SP> C <CRLF>
33
+ D <SP> E <CRLF>
34
+ eos
35
+ assert_equal expected_body, @mail.canonical_body
36
+ end
37
+
38
+ def test_simple_header
39
+ @mail.header_canonicalization = 'simple'
40
+ expected_header = <<-eos.rfc_format
41
+ A: <SP> X <CRLF>
42
+ B <SP> : <SP> Y <HTAB><CRLF>
43
+ <HTAB> Z <SP><SP><CRLF>
44
+ eos
45
+ assert_equal expected_header, @mail.canonical_header
46
+ end
47
+ def test_simple_body
48
+ @mail.body_canonicalization = 'simple'
49
+ expected_body = <<-eos.rfc_format
50
+ <SP> C <SP><CRLF>
51
+ D <SP><HTAB><SP> E <CRLF>
52
+ eos
53
+ assert_equal expected_body, @mail.canonical_body
54
+ end
55
+
56
+ # from errata: empty bodies
57
+ def test_simple_empty_body
58
+ @mail = SignedMail.new("test: test\r\n\r\n")
59
+ @mail.body_canonicalization = 'simple'
60
+
61
+ assert_equal "\r\n", @mail.canonical_body
62
+ end
63
+
64
+ def test_relaxed_empty_body
65
+ @mail = SignedMail.new("test: test\r\n\r\n")
66
+ @mail.body_canonicalization = 'relaxed'
67
+
68
+ assert_equal "", @mail.canonical_body
69
+ end
70
+
71
+ def test_relaxed_errata_1384
72
+ body = "testing<crlf><sp><sp><cr><lf><cr><lf>".rfc_format
73
+ @mail = SignedMail.new("test: test\r\n\r\n#{body}")
74
+ @mail.body_canonicalization = 'relaxed'
75
+
76
+ assert_equal "testing\r\n", @mail.canonical_body
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,42 @@
1
+
2
+ require 'test_helper'
3
+
4
+ module Dkim
5
+ class DkimHeaderTest < MiniTest::Unit::TestCase
6
+ def setup
7
+ @header = DkimHeader.new
8
+
9
+ # from Appendix A of RFC 6376
10
+ @header['v'] = '1'
11
+ @header['a'] = 'rsa-sha256'
12
+ @header['s'] = 'brisbane'
13
+ @header['d'] = 'example.com'
14
+ @header['c'] = 'simple/simple'
15
+ @header['q'] = 'dns/txt'
16
+ @header['i'] = 'joe@football.example.com'
17
+ @header['h'] = 'Received : From : To : Subject : Date : Message-ID'
18
+ @header['bh']= '2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
19
+ @header['b'] = 'AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV4bmp/YzhwvcubU4='
20
+ end
21
+
22
+ def test_correct_format
23
+ header = @header.to_s
24
+
25
+ # result from RFC 6376 minus trailing ';'
26
+ expected = %{
27
+ DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com;
28
+ c=simple/simple; q=dns/txt; i=joe@football.example.com;
29
+ h=Received : From : To : Subject : Date : Message-ID;
30
+ bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
31
+ b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
32
+ 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
33
+ KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
34
+ 4bmp/YzhwvcubU4=
35
+ }
36
+
37
+ # compare removing whitespace
38
+ assert_equal expected.gsub(/\s/,''), header.gsub(/\s/,'')
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,108 @@
1
+ require 'test_helper'
2
+
3
+ require 'mail'
4
+
5
+ module Dkim
6
+ SIGNEDMAIL = <<-eos
7
+ DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com;
8
+ c=simple/simple; q=dns/txt; i=joe@football.example.com;
9
+ h=Received : From : To : Subject : Date : Message-ID;
10
+ bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
11
+ b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
12
+ 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
13
+ KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
14
+ 4bmp/YzhwvcubU4=;
15
+ Received: from client1.football.example.com [192.0.2.1]
16
+ by submitserver.example.com with SUBMISSION;
17
+ Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
18
+ From: Joe SixPack <joe@football.example.com>
19
+ To: Suzie Q <suzie@shopping.example.net>
20
+ Subject: Is dinner ready?
21
+ Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
22
+ Message-ID: <20030712040037.46341.5F8J@football.example.com>
23
+ DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com;
24
+ c=simple/simple; q=dns/txt; i=joe@football.example.com;
25
+ h=Received : From : To : Subject : Date : Message-ID;
26
+ bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
27
+ b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
28
+ 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
29
+ KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
30
+ 4bmp/YzhwvcubU4=;
31
+
32
+ Hi.
33
+
34
+ We lost the game. Are you hungry yet?
35
+
36
+ Joe.
37
+ eos
38
+
39
+ class InterceptorTest < MiniTest::Unit::TestCase
40
+ def setup
41
+ @original_options = Dkim.options.dup
42
+
43
+ mail = EXAMPLEEMAIL.dup
44
+
45
+ @mail = Mail.new(mail)
46
+ end
47
+
48
+ def teardown
49
+ Dkim.options = @original_options
50
+ end
51
+
52
+ def test_header_with_relaxed
53
+ Dkim.header_canonicalization = 'relaxed'
54
+ Dkim.body_canonicalization = 'relaxed'
55
+ Dkim.signing_algorithm = 'rsa-sha256'
56
+ Interceptor.delivering_email(@mail)
57
+ dkim_header = @mail['DKIM-Signature']
58
+
59
+ assert dkim_header
60
+ assert_includes dkim_header.to_s, 'rsa-sha256'
61
+ assert_includes dkim_header.to_s, 's=brisbane'
62
+ assert_includes dkim_header.to_s, 'd=example.com'
63
+ assert_includes dkim_header.to_s, 'c=relaxed/relaxed'
64
+ assert_includes dkim_header.to_s, 'q=dns/txt'
65
+ assert_includes dkim_header.to_s, 'bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
66
+
67
+ # TODO: double check signing of 'b' header
68
+ end
69
+
70
+ def test_header_with_simple
71
+ Dkim.header_canonicalization = 'simple'
72
+ Dkim.body_canonicalization = 'simple'
73
+ Dkim.signing_algorithm = 'rsa-sha256'
74
+ Interceptor.delivering_email(@mail)
75
+ dkim_header = @mail['DKIM-Signature']
76
+ assert dkim_header
77
+ assert_includes dkim_header.to_s, 'rsa-sha256'
78
+ assert_includes dkim_header.to_s, 's=brisbane'
79
+ assert_includes dkim_header.to_s, 'd=example.com'
80
+ assert_includes dkim_header.to_s, 'c=simple/simple'
81
+ assert_includes dkim_header.to_s, 'q=dns/txt'
82
+ assert_includes dkim_header.to_s, 'bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
83
+
84
+ # TODO: double check signing of 'b' header
85
+ end
86
+
87
+ def test_strips_exsting_headers
88
+ warnings = ""
89
+ klass = Class.new(Interceptor)
90
+ klass.class.send(:define_method, :warn) do |message|
91
+ warnings << message << "\n"
92
+ end
93
+ @mail = Mail.new(SIGNEDMAIL)
94
+
95
+ assert_equal 2, @mail.header.fields.count { |field| field.name =~ /^DKIM-Signature$/i }
96
+ assert_equal 2, @mail.encoded.scan('DKIM-Signature').count
97
+
98
+ klass.delivering_email(@mail)
99
+
100
+ # should give a warning
101
+ assert_includes warnings, 'Interceptor'
102
+
103
+ assert_equal 1, @mail.header.fields.count { |field| field.name =~ /^DKIM-Signature$/i }
104
+ assert_equal 1, @mail.encoded.scan('DKIM-Signature').count
105
+ end
106
+ end
107
+ end
108
+
@@ -0,0 +1,37 @@
1
+ module Dkim
2
+ class OptionsTest < MiniTest::Unit::TestCase
3
+ def setup
4
+ klass = Class.new
5
+ klass.send :include, Options
6
+ @options = klass.new
7
+ end
8
+ def test_defaults_empty
9
+ assert_equal({}, @options.options)
10
+ end
11
+
12
+ def test_all_options
13
+ @options.signing_algorithm = 'abc123'
14
+ assert_equal({:signing_algorithm => 'abc123'}, @options.options)
15
+
16
+ desired_options = {
17
+ :signing_algorithm => 'abc123',
18
+ :signable_headers => [],
19
+ :domain => 'example.net',
20
+ :selector => 'selector',
21
+ :time => 'time',
22
+ :header_canonicalization => 'simple',
23
+ :body_canonicalization => 'simple'
24
+ }
25
+
26
+ desired_options.each do |key, value|
27
+ @options.send("#{key}=", value)
28
+ end
29
+
30
+ assert_equal(desired_options, @options.options)
31
+
32
+ desired_options.each do |key, value|
33
+ assert_equal value, @options.send("#{key}")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,66 @@
1
+
2
+ require 'test_helper'
3
+
4
+ module Dkim
5
+ class SignedMailTest < MiniTest::Unit::TestCase
6
+ def setup
7
+ @mail = EXAMPLEEMAIL.dup
8
+ end
9
+
10
+ def test_defaults
11
+ signed_mail = SignedMail.new(@mail, :time => Time.at(1234567890))
12
+ dkim_header = signed_mail.dkim_header
13
+
14
+ assert_equal 'rsa-sha256', dkim_header['a']
15
+ assert_equal 'brisbane', dkim_header['s']
16
+ assert_equal 'example.com', dkim_header['d']
17
+ assert_equal 'relaxed/relaxed', dkim_header['c']
18
+ assert_equal 'dns/txt', dkim_header['q']
19
+
20
+ # bh value from RFC 6376
21
+ assert_equal '2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=', dkim_header['bh']
22
+ assert_equal 'mamSUb17FQSZY2lfkeAsH/DvmpHsXdaFAu6BfbVblGBQ5+2yIPCx+clF5wClVBj97utSZb1WwOM0iup1JL37FI/UG+bxHo+MdGLqbLR63THGEdVF8FVeST4o4EQTWe0H3P/sU2rRZ61+M2SrTS94QkKAgj89QNOG48xSAO9xdfs=', dkim_header['b']
23
+ end
24
+
25
+ def test_overrides
26
+ options = {
27
+ :domain => 'example.org',
28
+ :selector => 'sidney',
29
+ :time => Time.now,
30
+ :signing_algorithm => 'rsa-sha1',
31
+ :header_canonicalization => 'simple',
32
+ :body_canonicalization => 'simple',
33
+ :time => Time.at(1234567890)
34
+ }
35
+ signed_mail = SignedMail.new(@mail, options)
36
+ dkim_header = signed_mail.dkim_header
37
+
38
+ assert_equal 'rsa-sha1', dkim_header['a']
39
+ assert_equal 'sidney', dkim_header['s']
40
+ assert_equal 'example.org', dkim_header['d']
41
+ assert_equal 'simple/simple', dkim_header['c']
42
+ assert_equal 'dns/txt', dkim_header['q']
43
+ assert_equal 'yk6W9pJJilr5MMgeEdSd7J3IaJI=', dkim_header['bh']
44
+ assert_equal 'sqYGmen+fouyIj83HuJ1v+1x40xp481bLxxcgAWMFsWYEwG05KYl+o0ZWn8jqgd1coKlX29o9iFjcMtZHudT8KpOdcLVYpY3gxzNfEgH79eRz32/ieGgroSK2GoMA/aV1QkxfUZexLUdj9oOX8uaMYXDkj8RGmlEGi+NDz/e4sE=', dkim_header['b']
45
+ end
46
+
47
+ def test_empty_body_hashes
48
+ @mail = @mail.split("\n\n").first + "\n\n"
49
+
50
+ # the following are from RFC 6376 section 3.4.3 and 3.4.4
51
+ [
52
+ # [bh, options]
53
+ ['uoq1oCgLlTqpdDX/iUbLy7J1Wic=', {:body_canonicalization => 'simple', :signing_algorithm => 'rsa-sha1' }],
54
+ ['frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=', {:body_canonicalization => 'simple', :signing_algorithm => 'rsa-sha256'}],
55
+ ['2jmj7l5rSw0yVb/vlWAYkK/YBwk=', {:body_canonicalization => 'relaxed', :signing_algorithm => 'rsa-sha1' }],
56
+ ['47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', {:body_canonicalization => 'relaxed', :signing_algorithm => 'rsa-sha256'}],
57
+ ].each do |body_hash, options|
58
+ signed_mail = SignedMail.new(@mail, options)
59
+ dkim_header = signed_mail.dkim_header
60
+
61
+ assert_equal body_hash, dkim_header['bh']
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -1,9 +1,9 @@
1
1
 
2
- require 'test/unit'
2
+ require 'minitest/autorun'
3
3
  require 'dkim'
4
4
 
5
5
  class String
6
- # Parse the format used in rfc4871
6
+ # Parse the format used in RFC 6376
7
7
  #
8
8
  # In the following examples, actual whitespace is used only for
9
9
  # clarity. The actual input and output text is designated using
@@ -23,7 +23,37 @@ class String
23
23
  end
24
24
  end
25
25
 
26
- # examples used in rfc
27
- Dkim::domain = 'example.com'
26
+ # examples used in RFC 6376
27
+ EXAMPLEEMAIL = %{
28
+ From: Joe SixPack <IIVyTowbcT@www.brandonchecketts.com>
29
+ To: Suzie Q <suzie@shopping.example.net>
30
+ Subject: Is dinner ready?
31
+ Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
32
+ Message-ID: <20030712040037.46341.5F8J@football.example.com>
33
+
34
+ Hi.
35
+
36
+ We lost the game. Are you hungry yet?
28
37
 
38
+ Joe.}.gsub(/\A\n/,'')
39
+
40
+ Dkim::domain = 'example.com'
41
+ Dkim::selector = 'brisbane'
42
+ Dkim::private_key = %{
43
+ -----BEGIN RSA PRIVATE KEY-----
44
+ MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
45
+ jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb
46
+ to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB
47
+ AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX
48
+ /1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ
49
+ gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO
50
+ n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m
51
+ 3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/
52
+ eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj
53
+ 7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA
54
+ qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf
55
+ eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX
56
+ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
57
+ -----END RSA PRIVATE KEY-----
58
+ }
29
59
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dkim
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-10 00:00:00.000000000 Z
12
+ date: 2012-04-16 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: gem for adding DKIM signatures to email messages
15
15
  email:
@@ -20,8 +20,10 @@ extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
22
  - .gitignore
23
+ - .travis.yml
23
24
  - CHANGELOG.md
24
25
  - Gemfile
26
+ - Guardfile
25
27
  - README.md
26
28
  - Rakefile
27
29
  - bin/dkimsign.rb
@@ -33,10 +35,15 @@ files:
33
35
  - lib/dkim/header.rb
34
36
  - lib/dkim/header_list.rb
35
37
  - lib/dkim/interceptor.rb
38
+ - lib/dkim/options.rb
36
39
  - lib/dkim/signed_mail.rb
37
40
  - lib/dkim/version.rb
38
41
  - lib/mail/dkim_field.rb
39
- - test/canonicalization_test.rb
42
+ - test/dkim/canonicalization_test.rb
43
+ - test/dkim/dkim_header_test.rb
44
+ - test/dkim/interceptor_test.rb
45
+ - test/dkim/options_test.rb
46
+ - test/dkim/signed_mail_test.rb
40
47
  - test/test_helper.rb
41
48
  homepage: https://github.com/jhawthorn/dkim
42
49
  licenses: []
@@ -50,18 +57,28 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
57
  - - ! '>='
51
58
  - !ruby/object:Gem::Version
52
59
  version: '0'
60
+ segments:
61
+ - 0
62
+ hash: -2701069676603955611
53
63
  required_rubygems_version: !ruby/object:Gem::Requirement
54
64
  none: false
55
65
  requirements:
56
66
  - - ! '>='
57
67
  - !ruby/object:Gem::Version
58
68
  version: '0'
69
+ segments:
70
+ - 0
71
+ hash: -2701069676603955611
59
72
  requirements: []
60
73
  rubyforge_project: dkim
61
- rubygems_version: 1.8.10
74
+ rubygems_version: 1.8.17
62
75
  signing_key:
63
76
  specification_version: 3
64
77
  summary: DKIM library in ruby
65
78
  test_files:
66
- - test/canonicalization_test.rb
79
+ - test/dkim/canonicalization_test.rb
80
+ - test/dkim/dkim_header_test.rb
81
+ - test/dkim/interceptor_test.rb
82
+ - test/dkim/options_test.rb
83
+ - test/dkim/signed_mail_test.rb
67
84
  - test/test_helper.rb
@@ -1,78 +0,0 @@
1
-
2
- require 'test_helper'
3
-
4
- class CanonicalizationTest < Test::Unit::TestCase
5
- # from section 3.4.6 of rfc4871
6
- def setup
7
- @input = <<-eos.rfc_format
8
- A: <SP> X <CRLF>
9
- B <SP> : <SP> Y <HTAB><CRLF>
10
- <HTAB> Z <SP><SP><CRLF>
11
- <CRLF>
12
- <SP> C <SP><CRLF>
13
- D <SP><HTAB><SP> E <CRLF>
14
- <CRLF>
15
- <CRLF>
16
- eos
17
- @mail = Dkim::SignedMail.new(@input)
18
- @mail.signable_headers = ['A', 'B']
19
- end
20
- def test_relaxed_header
21
- @mail.header_canonicalization = 'relaxed'
22
- expected_header = <<-eos.rfc_format
23
- a:X <CRLF>
24
- b:Y <SP> Z <CRLF>
25
- eos
26
- assert_equal expected_header, @mail.canonical_header
27
- end
28
- def test_relaxed_body
29
- @mail.body_canonicalization = 'relaxed'
30
- expected_body = <<-eos.rfc_format
31
- <SP> C <CRLF>
32
- D <SP> E <CRLF>
33
- eos
34
- assert_equal expected_body, @mail.canonical_body
35
- end
36
-
37
- def test_simple_header
38
- @mail.header_canonicalization = 'simple'
39
- expected_header = <<-eos.rfc_format
40
- A: <SP> X <CRLF>
41
- B <SP> : <SP> Y <HTAB><CRLF>
42
- <HTAB> Z <SP><SP><CRLF>
43
- eos
44
- assert_equal expected_header, @mail.canonical_header
45
- end
46
- def test_simple_body
47
- @mail.body_canonicalization = 'simple'
48
- expected_body = <<-eos.rfc_format
49
- <SP> C <SP><CRLF>
50
- D <SP><HTAB><SP> E <CRLF>
51
- eos
52
- assert_equal expected_body, @mail.canonical_body
53
- end
54
-
55
- # from errata: empty bodies
56
- def test_simple_empty_body
57
- @mail = Dkim::SignedMail.new("test: test\r\n\r\n")
58
- @mail.body_canonicalization = 'simple'
59
-
60
- assert_equal "\r\n", @mail.canonical_body
61
- end
62
-
63
- def test_relaxed_empty_body
64
- @mail = Dkim::SignedMail.new("test: test\r\n\r\n")
65
- @mail.body_canonicalization = 'relaxed'
66
-
67
- assert_equal "", @mail.canonical_body
68
- end
69
-
70
- def test_relaxed_errata_1384
71
- body = "testing<crlf><sp><sp><cr><lf><cr><lf>".rfc_format
72
- @mail = Dkim::SignedMail.new("test: test\r\n\r\n#{body}")
73
- @mail.body_canonicalization = 'relaxed'
74
-
75
- assert_equal "testing\r\n", @mail.canonical_body
76
- end
77
- end
78
-