dkim 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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