dkim 0.1.0 → 0.2.0

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