dkim 0.1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,70 +3,50 @@ require 'openssl'
3
3
  require 'dkim/body'
4
4
  require 'dkim/dkim_header'
5
5
  require 'dkim/header'
6
- require 'dkim/header_list'
6
+ require 'dkim/options'
7
+ require 'dkim/canonicalized_headers'
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
- @headers = HeaderList.new headers
20
+ @original_message = message
21
+ @headers = Header.parse headers
14
22
  @body = Body.new body
15
23
 
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
+ # default options from Dkim.options
25
+ @options = Dkim.options.merge(options)
24
26
  end
25
27
 
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
28
+ def canonicalized_headers
29
+ CanonicalizedHeaders.new(@headers, signed_headers)
56
30
  end
57
31
 
32
+ # @return [Array<String>] lowercased names of headers in the order they are signed
58
33
  def signed_headers
59
- (@headers.map(&:relaxed_key) & signable_headers.map(&:downcase)).sort
34
+ @headers.map(&:relaxed_key).select do |key|
35
+ signable_headers.map(&:downcase).include?(key)
36
+ end
60
37
  end
38
+
39
+ # @return [String] Signed headers of message in their canonical forms
61
40
  def canonical_header
62
- headers = signed_headers.map do |key|
63
- @headers[key].to_s(header_canonicalization) + "\r\n"
64
- end.join
41
+ canonicalized_headers.to_s(header_canonicalization)
65
42
  end
43
+
44
+ # @return [String] Body of message in its canonical form
66
45
  def canonical_body
67
46
  @body.to_s(body_canonicalization)
68
47
  end
69
48
 
49
+ # @return [DkimHeader] Constructed signature for the mail message
70
50
  def dkim_header
71
51
  dkim_header = DkimHeader.new
72
52
 
@@ -79,41 +59,36 @@ module Dkim
79
59
  dkim_header['a'] = signing_algorithm
80
60
  dkim_header['c'] = "#{header_canonicalization}/#{body_canonicalization}"
81
61
  dkim_header['d'] = domain
62
+ dkim_header['i'] = identity if identity
82
63
  dkim_header['q'] = 'dns/txt'
83
64
  dkim_header['s'] = selector
84
65
  dkim_header['t'] = (time || Time.now).to_i
66
+ dkim_header['x'] = expire.to_i if expire
85
67
 
86
68
  # Add body hash and blank signature
87
- dkim_header['bh']= base64_encode digest_alg.digest(canonical_body)
69
+ dkim_header['bh']= digest_alg.digest(canonical_body)
88
70
  dkim_header['h'] = signed_headers.join(':')
89
71
  dkim_header['b'] = ''
90
72
 
91
73
  # Calculate signature based on intermediate signature header
92
74
  headers = canonical_header
93
75
  headers << dkim_header.to_s(header_canonicalization)
94
- signature = base64_encode private_key.sign(digest_alg, headers)
95
- dkim_header['b'] = signature
76
+ dkim_header['b'] = private_key.sign(digest_alg, headers)
96
77
 
97
78
  dkim_header
98
79
  end
99
80
 
81
+ # @return [String] Message combined with calculated dkim header signature
100
82
  def to_s
101
- # Return the original message with the calculated header
102
- headers = @headers.to_a + [dkim_header]
103
- headers.map(&:to_s).join("\r\n") +
104
- "\r\n\r\n" +
105
- @body.to_s
83
+ dkim_header.to_s + "\r\n" + @original_message
106
84
  end
107
85
 
108
86
  private
109
- def base64_encode data
110
- [data].pack('m0*').gsub("\n",'')
111
- 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}'"
@@ -0,0 +1,28 @@
1
+ module Dkim
2
+ class TagValueList
3
+ def initialize values={}
4
+ @keys = values.keys
5
+ @values = values.dup
6
+ end
7
+ def to_s
8
+ @keys.map do |k|
9
+ "#{k}=#{@values[k]}"
10
+ end.join('; ')
11
+ end
12
+ def [] k
13
+ @values[k]
14
+ end
15
+ def []= k, v
16
+ @keys << k unless self[k]
17
+ @values[k] = v
18
+ end
19
+ def self.parse string
20
+ list = new
21
+ string.split(';').each do |keyval|
22
+ key, value = keyval.split('=', 2)
23
+ list[key.strip] = value.strip
24
+ end
25
+ list
26
+ end
27
+ end
28
+ end
data/lib/dkim/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dkim
2
- VERSION = "0.1.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/dkim.rb CHANGED
@@ -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,8 +23,9 @@ 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
28
+ Dkim::identity = nil
33
29
  Dkim::selector = nil
34
30
  Dkim::signing_algorithm = 'rsa-sha256'
35
31
  Dkim::private_key = nil
@@ -9,17 +9,17 @@ module Mail
9
9
 
10
10
  def initialize(value = nil, charset = 'utf-8')
11
11
  self.charset = charset
12
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
12
+ value = strip_field(FIELD_NAME, value) if respond_to?(:strip_field)
13
+ super(CAPITALIZED_FIELD, value, charset)
13
14
  self
14
15
  end
15
16
 
16
17
  def encoded
17
- "#{name}:#{value}\n"
18
+ "#{name}:#{value}\r\n"
18
19
  end
19
20
 
20
21
  def decoded
21
- "#{name}:#{value}\n"
22
+ "#{name}:#{value}\r\n"
22
23
  end
23
24
  end
24
25
  end
25
-
@@ -0,0 +1,80 @@
1
+
2
+ require 'test_helper'
3
+
4
+ module Dkim
5
+ class CanonicalizationTest < Minitest::Test
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,51 @@
1
+
2
+ require 'test_helper'
3
+
4
+ module Dkim
5
+ class CanonicalizedHeadersTest < Minitest::Test
6
+ def test_maintains_order
7
+ headers = "ABCD".chars.map {|c| Header.new(c, c) }
8
+ header_keys = headers.map &:relaxed_key
9
+ header_keys.permutation.each do |signed_headers|
10
+ ch = CanonicalizedHeaders.new(headers, signed_headers)
11
+ assert_equal signed_headers, ch.map(&:relaxed_key)
12
+ end
13
+ end
14
+
15
+ def test_repeated_headers
16
+ headers = [
17
+ Header.new('A', '1'),
18
+ Header.new('B', '2'),
19
+ Header.new('C', '3'),
20
+ Header.new('A', '4'),
21
+ Header.new('D', '5')
22
+ ]
23
+ ch = CanonicalizedHeaders.new(headers, %w{A A B C D})
24
+ assert_equal %w{4 1 2 3 5}, ch.map(&:value)
25
+ assert_equal <<-eos.rfc_format, ch.to_s('simple')
26
+ A:4<CRLF>
27
+ A:1<CRLF>
28
+ B:2<CRLF>
29
+ C:3<CRLF>
30
+ D:5<CRLF>
31
+ eos
32
+ end
33
+
34
+ # missing headers should be ignored
35
+ def test_missing_headers
36
+ headers = [
37
+ Header.new('A', '1'),
38
+ Header.new('B', '2'),
39
+ Header.new('C', '3'),
40
+ ]
41
+ ch = CanonicalizedHeaders.new(headers, %w{A A B C})
42
+ assert_equal %w{a b c}, ch.map(&:relaxed_key)
43
+ assert_equal <<-eos.rfc_format, ch.to_s('simple')
44
+ A:1<CRLF>
45
+ B:2<CRLF>
46
+ C:3<CRLF>
47
+ eos
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,43 @@
1
+
2
+ require 'test_helper'
3
+ require 'base64'
4
+
5
+ module Dkim
6
+ class DkimHeaderTest < Minitest::Test
7
+ def setup
8
+ @header = DkimHeader.new
9
+
10
+ # from Appendix A of RFC 6376
11
+ @header['v'] = '1'
12
+ @header['a'] = 'rsa-sha256'
13
+ @header['s'] = 'brisbane'
14
+ @header['d'] = 'example.com'
15
+ @header['c'] = 'simple/simple'
16
+ @header['q'] = 'dns/txt'
17
+ @header['i'] = 'joe@football.example.com'
18
+ @header['h'] = 'Received : From : To : Subject : Date : Message-ID'
19
+ @header['bh']= Base64.decode64 '2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
20
+ @header['b'] = Base64.decode64 'AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV4bmp/YzhwvcubU4='
21
+ end
22
+
23
+ def test_correct_format
24
+ header = @header.to_s
25
+
26
+ # result from RFC 6376 minus trailing ';'
27
+ expected = %{
28
+ DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com;
29
+ c=simple/simple; q=dns/txt; i=joe@football.example.com;
30
+ h=Received : From : To : Subject : Date : Message-ID;
31
+ bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
32
+ b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
33
+ 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
34
+ KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
35
+ 4bmp/YzhwvcubU4=
36
+ }
37
+
38
+ # compare removing whitespace
39
+ assert_equal expected.gsub(/\s/,''), header.gsub(/\s/,'')
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,24 @@
1
+ module Dkim
2
+ class EncodingsTest < Minitest::Test
3
+ def test_plain_text
4
+ @encoder = Encodings::PlainText.new
5
+ assert_equal 'testing123', @encoder.encode('testing123')
6
+ assert_equal 'testing123', @encoder.decode('testing123')
7
+ end
8
+ def test_base64
9
+ @encoder = Encodings::Base64.new
10
+ assert_equal 'dGVzdGluZzEyMw==', @encoder.encode('testing123')
11
+ assert_equal 'testing123', @encoder.decode('dGVzdGluZzEyMw==')
12
+ end
13
+ def test_quoted_printable
14
+ @encoder = Encodings::DkimQuotedPrintable.new
15
+ assert_equal 'testing123', @encoder.encode('testing123')
16
+ assert_equal 'testing123', @encoder.decode('testing123')
17
+
18
+ encoded = 'From:foo@eng.example.net|To:joe@example.com|Subject:demo=20run|Date:July=205,=202005=203:44:08=20PM=20-0700'
19
+ decoded = 'From:foo@eng.example.net|To:joe@example.com|Subject:demo run|Date:July 5, 2005 3:44:08 PM -0700'
20
+ assert_equal encoded, @encoder.encode(decoded)
21
+ assert_equal decoded, @encoder.decode(encoded)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,129 @@
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::Test
40
+ def setup
41
+ @original_options = Dkim.options.dup
42
+
43
+ # ensure time does not change
44
+ Dkim.time = Time.now
45
+
46
+ mail = EXAMPLEEMAIL.dup
47
+
48
+ @mail = Mail.new(mail)
49
+ end
50
+
51
+ def teardown
52
+ Dkim.options = @original_options
53
+ end
54
+
55
+ def test_header_with_relaxed
56
+ Dkim.header_canonicalization = 'relaxed'
57
+ Dkim.body_canonicalization = 'relaxed'
58
+ Dkim.signing_algorithm = 'rsa-sha256'
59
+ Dkim.identity = '@example.com'
60
+ Interceptor.delivering_email(@mail)
61
+ dkim_header = @mail['DKIM-Signature']
62
+
63
+ assert dkim_header
64
+ assert_includes dkim_header.to_s, 'rsa-sha256'
65
+ assert_includes dkim_header.to_s, 's=brisbane'
66
+ assert_includes dkim_header.to_s, 'd=example.com'
67
+ assert_includes dkim_header.to_s, 'i=@example.com'
68
+ assert_includes dkim_header.to_s, 'c=relaxed/relaxed'
69
+ assert_includes dkim_header.to_s, 'q=dns/txt'
70
+ assert_includes dkim_header.to_s, 'bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
71
+
72
+ # TODO: double check signing of 'b' header
73
+ end
74
+
75
+ def test_header_with_simple
76
+ Dkim.header_canonicalization = 'simple'
77
+ Dkim.body_canonicalization = 'simple'
78
+ Dkim.signing_algorithm = 'rsa-sha256'
79
+ Dkim.identity = '@example.com'
80
+ Interceptor.delivering_email(@mail)
81
+ dkim_header = @mail['DKIM-Signature']
82
+ assert dkim_header
83
+ assert_includes dkim_header.to_s, 'rsa-sha256'
84
+ assert_includes dkim_header.to_s, 's=brisbane'
85
+ assert_includes dkim_header.to_s, 'd=example.com'
86
+ assert_includes dkim_header.to_s, 'i=@example.com'
87
+ assert_includes dkim_header.to_s, 'c=simple/simple'
88
+ assert_includes dkim_header.to_s, 'q=dns/txt'
89
+ assert_includes dkim_header.to_s, 'bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
90
+
91
+ # TODO: double check signing of 'b' header
92
+ end
93
+
94
+ def test_strips_exsting_headers
95
+ warnings = ""
96
+ klass = Class.new(Interceptor)
97
+ klass.class.send(:define_method, :warn) do |message|
98
+ warnings << message << "\n"
99
+ end
100
+ @mail = Mail.new(SIGNEDMAIL)
101
+
102
+ assert_equal 2, @mail.header.fields.count { |field| field.name =~ /^DKIM-Signature$/i }
103
+ assert_equal 2, @mail.encoded.scan('DKIM-Signature').count
104
+
105
+ klass.delivering_email(@mail)
106
+
107
+ # should give a warning
108
+ assert_includes warnings, 'Interceptor'
109
+
110
+ assert_equal 1, @mail.header.fields.count { |field| field.name =~ /^DKIM-Signature$/i }
111
+ assert_equal 1, @mail.encoded.scan('DKIM-Signature').count
112
+ end
113
+
114
+ def test_same_output_as_direct_usage
115
+ dkim_header = @mail['DKIM-Signature']
116
+
117
+ # Most necessary under simple
118
+ Dkim.header_canonicalization = 'simple'
119
+ Dkim.body_canonicalization = 'simple'
120
+
121
+ expected = Dkim.sign @mail.to_s
122
+
123
+ Interceptor.delivering_email(@mail)
124
+
125
+ assert_equal expected, @mail.to_s
126
+ end
127
+ end
128
+ end
129
+
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ module Dkim
4
+ class OptionsTest < Minitest::Test
5
+ def setup
6
+ klass = Class.new
7
+ klass.send :include, Options
8
+ @options = klass.new
9
+ end
10
+ def test_defaults_empty
11
+ assert_equal({}, @options.options)
12
+ end
13
+
14
+ def test_all_options
15
+ @options.signing_algorithm = 'abc123'
16
+ assert_equal({:signing_algorithm => 'abc123'}, @options.options)
17
+
18
+ desired_options = {
19
+ :signing_algorithm => 'abc123',
20
+ :signable_headers => [],
21
+ :domain => 'example.net',
22
+ :identity => '@example.net',
23
+ :selector => 'selector',
24
+ :time => 'time',
25
+ :header_canonicalization => 'simple',
26
+ :body_canonicalization => 'simple'
27
+ }
28
+
29
+ desired_options.each do |key, value|
30
+ @options.send("#{key}=", value)
31
+ end
32
+
33
+ assert_equal(desired_options, @options.options)
34
+
35
+ desired_options.each do |key, value|
36
+ assert_equal value, @options.send("#{key}")
37
+ end
38
+ end
39
+ end
40
+ end