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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +20 -0
- data/.gitignore +1 -0
- data/Appraisals +7 -0
- data/CHANGELOG.md +28 -3
- data/Gemfile +5 -1
- data/README.md +52 -61
- data/Rakefile +12 -0
- data/bin/dkimsign.rb +0 -0
- data/dkim.gemspec +5 -0
- data/gemfiles/mail_2.6.gemfile +11 -0
- data/gemfiles/mail_2.7.gemfile +11 -0
- data/lib/dkim/canonicalized_headers.rb +26 -0
- data/lib/dkim/dkim_header.rb +21 -12
- data/lib/dkim/encodings/base64.rb +12 -0
- data/lib/dkim/encodings/dkim_quoted_printable.rb +19 -0
- data/lib/dkim/encodings/plain_text.rb +8 -0
- data/lib/dkim/encodings.rb +3 -0
- data/lib/dkim/header.rb +7 -0
- data/lib/dkim/interceptor.rb +13 -1
- data/lib/dkim/options.rb +84 -0
- data/lib/dkim/signed_mail.rb +31 -56
- data/lib/dkim/tag_value_list.rb +28 -0
- data/lib/dkim/version.rb +1 -1
- data/lib/dkim.rb +4 -8
- data/lib/mail/dkim_field.rb +4 -4
- data/test/dkim/canonicalization_test.rb +80 -0
- data/test/dkim/canonicalized_headers_test.rb +51 -0
- data/test/dkim/dkim_header_test.rb +43 -0
- data/test/dkim/encodings_test.rb +24 -0
- data/test/dkim/interceptor_test.rb +129 -0
- data/test/dkim/options_test.rb +40 -0
- data/test/dkim/signed_mail_test.rb +112 -0
- data/test/dkim/tag_value_list_test.rb +28 -0
- data/test/test_helper.rb +36 -4
- metadata +95 -18
- data/lib/dkim/header_list.rb +0 -19
- data/test/canonicalization_test.rb +0 -78
data/lib/dkim/signed_mail.rb
CHANGED
@@ -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/
|
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
|
-
@
|
20
|
+
@original_message = message
|
21
|
+
@headers = Header.parse headers
|
14
22
|
@body = Body.new body
|
15
23
|
|
16
|
-
|
17
|
-
@
|
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
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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']=
|
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
|
-
|
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
|
-
|
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
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
|
-
|
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
|
data/lib/mail/dkim_field.rb
CHANGED
@@ -9,17 +9,17 @@ module Mail
|
|
9
9
|
|
10
10
|
def initialize(value = nil, charset = 'utf-8')
|
11
11
|
self.charset = charset
|
12
|
-
|
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
|