dkim 0.0.1 → 0.0.2
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.
- data/CHANGELOG.md +14 -0
- data/README.md +123 -0
- data/Rakefile +12 -0
- data/bin/dkimsign.rb +4 -3
- data/dkim.gemspec +1 -1
- data/lib/dkim.rb +12 -9
- data/lib/dkim/body.rb +19 -7
- data/lib/dkim/canonicalizable.rb +14 -0
- data/lib/dkim/dkim_header.rb +28 -0
- data/lib/dkim/header.rb +20 -7
- data/lib/dkim/header_list.rb +1 -1
- data/lib/dkim/signed_mail.rb +51 -39
- data/lib/dkim/version.rb +1 -1
- data/test/canonicalization_test.rb +78 -0
- data/test/test_helper.rb +29 -0
- metadata +25 -5
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# dkim Changelog
|
2
|
+
|
3
|
+
## 2011.06.01, Version 0.0.2
|
4
|
+
|
5
|
+
* add convenience method Dkim.sign
|
6
|
+
* support for the simple canonicalization algorithm
|
7
|
+
* domain now must be specified as an option
|
8
|
+
* correct handling of an empty message body
|
9
|
+
|
10
|
+
|
11
|
+
## 2011.05.10, Version 0.0.1
|
12
|
+
|
13
|
+
* Initial release
|
14
|
+
|
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
dkim
|
2
|
+
====
|
3
|
+
|
4
|
+
A DKIM signing library in ruby.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
============
|
8
|
+
|
9
|
+
sudo gem install dkim
|
10
|
+
|
11
|
+
Usage
|
12
|
+
=====
|
13
|
+
|
14
|
+
Calling `Dkim.sign` on a string representing an email message returns the message with a DKIM signature inserted.
|
15
|
+
|
16
|
+
For example
|
17
|
+
|
18
|
+
mail = <<eos
|
19
|
+
To: someone@example.com
|
20
|
+
From: john@example.com
|
21
|
+
Subject: hi
|
22
|
+
|
23
|
+
Howdy
|
24
|
+
eos
|
25
|
+
|
26
|
+
Dkim.sign(mail)
|
27
|
+
|
28
|
+
# =>
|
29
|
+
# To: someone@example.com
|
30
|
+
# From: john@example.com
|
31
|
+
# Subject: hi
|
32
|
+
# DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; q=dns/txt; s=mail; t=1305917829;
|
33
|
+
# bh=qZxwTnSM1ywsrq0Ag9UhQSOtVIG+sW5zDkB+hPbuX08=; h=from:subject:to;
|
34
|
+
# b=0mKnNOkxFGiww63Zu4t46J7eZc3Uak3I9km3IH2Le3XcnSNtWJgxiwBX26IZ5yzcT
|
35
|
+
# VwJzcCnPKCScIJMQ7yfbfXmNsKVIOV6eSUqu1YvJ1fgzlSAXuDEMNFTjoto5rrdA+
|
36
|
+
# BgX849hEY/bWHDl1JJgNpiwtpl4t0Q7M4BVJUd7Lo=
|
37
|
+
#
|
38
|
+
# Howdy
|
39
|
+
|
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
|
49
|
+
|
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
|
+
------------------------
|
56
|
+
|
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
|
67
|
+
|
68
|
+
Dkim::signable_headers = Dkim::DefaultHeaders - %w{Message-Id Resent-Message-ID Date Return-Path Bounces-To}
|
69
|
+
|
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.
|
71
|
+
If you wish to override this behaviour and use whichever algorithm is available you can use this snippet (**not recommended**).
|
72
|
+
|
73
|
+
Dkim::signing_algorithm = defined?(OpenSSL::Digest::SHA256) ? 'rsa-sha256' : 'rsa-sha1'
|
74
|
+
|
75
|
+
Example executable
|
76
|
+
==================
|
77
|
+
|
78
|
+
The library includes a `dkimsign.rb` executable suitable for testing the library or performing simple signatures.
|
79
|
+
|
80
|
+
`dkimsign.rb DOMAIN SELECTOR KEYFILE [MAILFILE]`
|
81
|
+
|
82
|
+
If MAILFILE is not specified `dkimsign.rb` will read the mail message from standard in.
|
83
|
+
|
84
|
+
Limitations
|
85
|
+
===========
|
86
|
+
|
87
|
+
* Strictly a DKIM signing library. No support for signature verification. *(none planned)*
|
88
|
+
* No support for the older Yahoo! DomainKeys standard ([RFC 4870](http://tools.ietf.org/html/rfc4870)) *(none planned)*
|
89
|
+
* No support for specifying DKIM identity `i=` *(planned)*
|
90
|
+
* No support for body length `l=` *(planned)*
|
91
|
+
* No support for signature expiration `x=` *(planned)*
|
92
|
+
* No support for copied header fields `z=` *(not immediately planned)*
|
93
|
+
|
94
|
+
Resources
|
95
|
+
=========
|
96
|
+
|
97
|
+
* [RFC 4871](http://tools.ietf.org/html/rfc4871)
|
98
|
+
* Inspired by perl's [Mail-DKIM](http://dkimproxy.sourceforge.net/)
|
99
|
+
|
100
|
+
Copyright
|
101
|
+
=========
|
102
|
+
|
103
|
+
(The MIT License)
|
104
|
+
|
105
|
+
Copyright (c) 2011 [John Hawthorn](http://www.johnhawthorn.com/)
|
106
|
+
|
107
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
108
|
+
a copy of this software and associated documentation files (the
|
109
|
+
"Software"), to deal in the Software without restriction, including
|
110
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
111
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
112
|
+
permit persons to whom the Software is furnished to do so, subject to
|
113
|
+
the following conditions:
|
114
|
+
|
115
|
+
The above copyright notice and this permission notice shall be
|
116
|
+
included in all copies or substantial portions of the Software.
|
117
|
+
|
118
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
119
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
120
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
121
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
122
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
123
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
data/Rakefile
CHANGED
@@ -1,2 +1,14 @@
|
|
1
|
+
require 'rake/testtask'
|
1
2
|
require 'bundler'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
|
6
|
+
desc 'Run tests.'
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << 'lib' << 'test'
|
9
|
+
t.pattern = 'test/**/*_test.rb'
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
2
13
|
Bundler::GemHelper.install_tasks
|
14
|
+
|
data/bin/dkimsign.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
3
|
if ARGV.length != 2 && ARGV.length != 3
|
4
|
-
puts "Usage: dkimsign.rb SELECTOR KEYFILE [MAILFILE]"
|
4
|
+
puts "Usage: dkimsign.rb DOMAIN SELECTOR KEYFILE [MAILFILE]"
|
5
5
|
exit 0
|
6
6
|
end
|
7
7
|
|
8
8
|
require 'dkim'
|
9
9
|
|
10
|
-
selector, keyfile,mailfile = ARGV
|
10
|
+
domain, selector, keyfile, mailfile = ARGV
|
11
11
|
|
12
12
|
keyfile = File.open(keyfile)
|
13
13
|
mailfile = mailfile ? File.open(mailfile) : STDIN
|
@@ -15,7 +15,8 @@ mailfile = mailfile ? File.open(mailfile) : STDIN
|
|
15
15
|
mail = mailfile.read.gsub(/\r?\n/, "\r\n")
|
16
16
|
key = keyfile.read
|
17
17
|
|
18
|
-
Dkim::
|
18
|
+
Dkim::domain = domain
|
19
|
+
Dkim::selector = selector
|
19
20
|
Dkim::private_key = key
|
20
21
|
|
21
22
|
print Dkim::SignedMail.new(mail).to_s
|
data/dkim.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.rubyforge_project = "dkim"
|
16
16
|
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
|
-
s.test_files = `git ls-files --
|
18
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
end
|
data/lib/dkim.rb
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
|
2
|
-
require 'dkim/header'
|
3
|
-
require 'dkim/header_list'
|
4
|
-
require 'dkim/body'
|
5
2
|
require 'dkim/signed_mail'
|
6
3
|
|
7
4
|
module Dkim
|
@@ -16,19 +13,25 @@ module Dkim
|
|
16
13
|
List-Post List-Owner List-Archive}
|
17
14
|
|
18
15
|
class << self
|
19
|
-
attr_accessor :signing_algorithm, :signable_headers, :domain, :selector
|
16
|
+
attr_accessor :signing_algorithm, :signable_headers, :domain, :selector, :header_canonicalization, :body_canonicalization
|
20
17
|
|
21
18
|
attr_reader :private_key
|
22
19
|
def private_key= key
|
23
20
|
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
24
21
|
@private_key = key
|
25
22
|
end
|
23
|
+
|
24
|
+
def sign message, options={}
|
25
|
+
SignedMail.new(message, options).to_s
|
26
|
+
end
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
|
-
Dkim::signable_headers
|
30
|
-
Dkim::domain
|
31
|
-
Dkim::selector
|
32
|
-
Dkim::signing_algorithm
|
33
|
-
Dkim::private_key
|
30
|
+
Dkim::signable_headers = Dkim::DefaultHeaders
|
31
|
+
Dkim::domain = nil
|
32
|
+
Dkim::selector = nil
|
33
|
+
Dkim::signing_algorithm = 'rsa-sha256'
|
34
|
+
Dkim::private_key = nil
|
35
|
+
Dkim::header_canonicalization = 'relaxed'
|
36
|
+
Dkim::body_canonicalization = 'relaxed'
|
34
37
|
|
data/lib/dkim/body.rb
CHANGED
@@ -1,20 +1,32 @@
|
|
1
|
+
require 'dkim/canonicalizable'
|
2
|
+
|
1
3
|
module Dkim
|
2
4
|
class Body < Struct.new(:body)
|
3
|
-
|
4
|
-
body = self.body.dup
|
5
|
+
include Canonicalizable
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
def canonical_relaxed
|
8
|
+
# special case from errata 1377
|
9
|
+
return "" if self.body.empty?
|
10
|
+
|
11
|
+
body = self.body.dup
|
8
12
|
|
9
13
|
# Reduces all sequences of WSP within a line to a single SP character.
|
10
14
|
body.gsub!(/[ \t]+/, ' ')
|
11
15
|
|
16
|
+
# Ignores all whitespace at the end of lines. Implementations MUST NOT remove the CRLF at the end of the line.
|
17
|
+
body.gsub!(/ \r\n/, "\r\n")
|
18
|
+
|
12
19
|
# Ignores all empty lines at the end of the message body.
|
13
|
-
body.gsub!(/
|
20
|
+
body.gsub!(/[ \r\n]*\z/, '')
|
21
|
+
|
14
22
|
body += "\r\n"
|
15
23
|
end
|
16
|
-
def
|
17
|
-
self.body.dup
|
24
|
+
def canonical_simple
|
25
|
+
body = self.body.dup
|
26
|
+
|
27
|
+
# Ignores all empty lines at the end of the message body.
|
28
|
+
body.gsub!(/(\r?\n)*\z/, '')
|
29
|
+
body += "\r\n"
|
18
30
|
end
|
19
31
|
end
|
20
32
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
require 'dkim/header'
|
3
|
+
|
4
|
+
module Dkim
|
5
|
+
class DkimHeader < Header
|
6
|
+
def initialize values={}
|
7
|
+
self.key = 'DKIM-Signature'
|
8
|
+
@values = values.to_a.flatten.each_slice(2).to_a
|
9
|
+
end
|
10
|
+
def value
|
11
|
+
@values.map do |(k, v)|
|
12
|
+
" #{k}=#{v}"
|
13
|
+
end.join(';')
|
14
|
+
end
|
15
|
+
def [] k
|
16
|
+
value = @values.detect {|(a,b)| a == k }
|
17
|
+
value && value[1]
|
18
|
+
end
|
19
|
+
def []= k, v
|
20
|
+
value = @values.detect {|(a,b)| a == k }
|
21
|
+
if !value
|
22
|
+
value = [k, nil]
|
23
|
+
@values << value
|
24
|
+
end
|
25
|
+
value[1] = v
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/dkim/header.rb
CHANGED
@@ -1,12 +1,23 @@
|
|
1
|
+
require 'dkim/canonicalizable'
|
2
|
+
|
1
3
|
module Dkim
|
2
4
|
class Header < Struct.new(:key, :value)
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
include Canonicalizable
|
6
|
+
|
7
|
+
def relaxed_key
|
8
|
+
key = self.key.dup
|
6
9
|
|
7
10
|
#Convert all header field names (not the header field values) to lowercase. For example, convert "SUBJect: AbC" to "subject: AbC".
|
8
11
|
key.downcase!
|
9
12
|
|
13
|
+
# Delete any WSP characters remaining before the colon separating the header field name from the header field value.
|
14
|
+
key.gsub!(/[ \t]*\z/, '')
|
15
|
+
|
16
|
+
key
|
17
|
+
end
|
18
|
+
def relaxed_value
|
19
|
+
value = self.value.dup
|
20
|
+
|
10
21
|
# Unfold all header field continuation lines as described in [RFC2822]
|
11
22
|
value.gsub!(/\r?\n[ \t]+/, ' ')
|
12
23
|
|
@@ -16,13 +27,15 @@ module Dkim
|
|
16
27
|
# Delete all WSP characters at the end of each unfolded header field value.
|
17
28
|
value.gsub!(/[ \t]*\z/, '')
|
18
29
|
|
19
|
-
# Delete any WSP characters remaining
|
30
|
+
# Delete any WSP characters remaining after the colon separating the header field name from the header field value.
|
20
31
|
value.gsub!(/\A[ \t]*/, '')
|
21
|
-
key.gsub!(/[ \t]*\z/, '')
|
22
32
|
|
23
|
-
|
33
|
+
value
|
34
|
+
end
|
35
|
+
def canonical_relaxed
|
36
|
+
"#{relaxed_key}:#{relaxed_value}"
|
24
37
|
end
|
25
|
-
def
|
38
|
+
def canonical_simple
|
26
39
|
"#{key}:#{value}"
|
27
40
|
end
|
28
41
|
end
|
data/lib/dkim/header_list.rb
CHANGED
data/lib/dkim/signed_mail.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'openssl'
|
2
|
-
|
2
|
+
|
3
|
+
require 'dkim/body'
|
4
|
+
require 'dkim/dkim_header'
|
5
|
+
require 'dkim/header'
|
6
|
+
require 'dkim/header_list'
|
3
7
|
|
4
8
|
module Dkim
|
5
9
|
class SignedMail
|
6
|
-
EMAIL_REGEX = /[A-Z0-9._%+-]+@([A-Z0-9.-]+\.[A-Z]{2,6})/i
|
7
|
-
|
8
10
|
def initialize message, options={}
|
9
11
|
message = message.gsub(/\r?\n/, "\r\n")
|
10
12
|
headers, body = message.split(/\r?\n\r?\n/, 2)
|
@@ -17,10 +19,12 @@ module Dkim
|
|
17
19
|
@time = options[:time]
|
18
20
|
@signing_algorithm = options[:signing_algorithm]
|
19
21
|
@private_key = options[:private_key]
|
22
|
+
@header_canonicalization = options[:header_canonicalization]
|
23
|
+
@body_canonicalization = options[:body_canonicalization]
|
20
24
|
end
|
21
25
|
|
22
26
|
# options for signatures
|
23
|
-
attr_writer :signing_algorithm, :signable_headers, :domain, :selector, :time
|
27
|
+
attr_writer :signing_algorithm, :signable_headers, :domain, :selector, :time, :header_canonicalization, :body_canonicalization
|
24
28
|
|
25
29
|
def private_key= key
|
26
30
|
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
@@ -36,57 +40,65 @@ module Dkim
|
|
36
40
|
@signable_headers || Dkim::signable_headers
|
37
41
|
end
|
38
42
|
def domain
|
39
|
-
@domain || Dkim::domain
|
43
|
+
@domain || Dkim::domain
|
40
44
|
end
|
41
45
|
def selector
|
42
46
|
@selector || Dkim::selector
|
43
47
|
end
|
44
48
|
def time
|
45
|
-
@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
|
46
56
|
end
|
47
57
|
|
48
58
|
def signed_headers
|
49
|
-
(@headers.map(&:
|
50
|
-
end
|
51
|
-
def dkim_header_values(b)
|
52
|
-
[
|
53
|
-
'v', 1,
|
54
|
-
'a', signing_algorithm,
|
55
|
-
'c', 'relaxed/relaxed',
|
56
|
-
'd', domain,
|
57
|
-
'q', 'dns/txt',
|
58
|
-
's', selector,
|
59
|
-
't', time.to_i,
|
60
|
-
'bh', body_hash,
|
61
|
-
'h', signed_headers.join(':'),
|
62
|
-
'b', b
|
63
|
-
]
|
64
|
-
end
|
65
|
-
def dkim_header(b=nil)
|
66
|
-
b ||= header_signature
|
67
|
-
v = dkim_header_values(b).each_slice(2).map do |(key, value)|
|
68
|
-
"#{key}=#{value}"
|
69
|
-
end.join('; ')
|
70
|
-
Header.new('DKIM-Signature', v)
|
59
|
+
(@headers.map(&:relaxed_key) & signable_headers.map(&:downcase)).sort
|
71
60
|
end
|
72
61
|
def canonical_header
|
73
62
|
headers = signed_headers.map do |key|
|
74
|
-
@headers[key]
|
75
|
-
end
|
76
|
-
headers << dkim_header('')
|
77
|
-
headers.map(&:to_canonical).join("\r\n")
|
63
|
+
@headers[key].to_s(header_canonicalization) + "\r\n"
|
64
|
+
end.join
|
78
65
|
end
|
79
66
|
def canonical_body
|
80
|
-
@body.
|
67
|
+
@body.to_s(body_canonicalization)
|
81
68
|
end
|
82
69
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
70
|
+
def dkim_header
|
71
|
+
dkim_header = DkimHeader.new
|
72
|
+
|
73
|
+
raise "A private key is required" unless private_key
|
74
|
+
raise "A domain is required" unless domain
|
75
|
+
raise "A selector is required" unless selector
|
76
|
+
|
77
|
+
# Add basic DKIM info
|
78
|
+
dkim_header['v'] = '1'
|
79
|
+
dkim_header['a'] = signing_algorithm
|
80
|
+
dkim_header['c'] = "#{header_canonicalization}/#{body_canonicalization}"
|
81
|
+
dkim_header['d'] = domain
|
82
|
+
dkim_header['q'] = 'dns/txt'
|
83
|
+
dkim_header['s'] = selector
|
84
|
+
dkim_header['t'] = (time || Time.now).to_i
|
85
|
+
|
86
|
+
# Add body hash and blank signature
|
87
|
+
dkim_header['bh']= base64_encode digest_alg.digest(canonical_body)
|
88
|
+
dkim_header['h'] = signed_headers.join(':')
|
89
|
+
dkim_header['b'] = ''
|
90
|
+
|
91
|
+
# Calculate signature based on intermediate signature header
|
92
|
+
headers = canonical_header
|
93
|
+
headers << dkim_header.to_s(header_canonicalization)
|
94
|
+
signature = base64_encode private_key.sign(digest_alg, headers)
|
95
|
+
dkim_header['b'] = signature
|
96
|
+
|
97
|
+
dkim_header
|
88
98
|
end
|
99
|
+
|
89
100
|
def to_s
|
101
|
+
# Return the original message with the calculated header
|
90
102
|
headers = @headers.to_a + [dkim_header]
|
91
103
|
headers.map(&:to_s).join("\r\n") +
|
92
104
|
"\r\n\r\n" +
|
@@ -95,7 +107,7 @@ module Dkim
|
|
95
107
|
|
96
108
|
private
|
97
109
|
def base64_encode data
|
98
|
-
|
110
|
+
[data].pack('m0*').gsub("\n",'')
|
99
111
|
end
|
100
112
|
def digest_alg
|
101
113
|
case signing_algorithm
|
data/lib/dkim/version.rb
CHANGED
@@ -0,0 +1,78 @@
|
|
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
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require 'dkim'
|
4
|
+
|
5
|
+
class String
|
6
|
+
# Parse the format used in rfc4871
|
7
|
+
#
|
8
|
+
# In the following examples, actual whitespace is used only for
|
9
|
+
# clarity. The actual input and output text is designated using
|
10
|
+
# bracketed descriptors: "<SP>" for a space character, "<HTAB>" for a
|
11
|
+
# tab character, and "<CRLF>" for a carriage-return/line-feed sequence.
|
12
|
+
# For example, "X <SP> Y" and "X<SP>Y" represent the same three
|
13
|
+
# characters.
|
14
|
+
def rfc_format
|
15
|
+
str = self.dup
|
16
|
+
str.gsub!(/\s/,'')
|
17
|
+
str.gsub!(/<SP>/i,' ')
|
18
|
+
str.gsub!(/<CR>/i,"\r")
|
19
|
+
str.gsub!(/<LF>/i,"\n")
|
20
|
+
str.gsub!(/<CRLF>/i,"\r\n")
|
21
|
+
str.gsub!(/<HTAB>/i,"\t")
|
22
|
+
str
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# examples used in rfc
|
27
|
+
Dkim::domain = 'example.com'
|
28
|
+
|
29
|
+
|
metadata
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dkim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
4
5
|
prerelease:
|
5
|
-
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- John Hawthorn
|
@@ -10,7 +15,8 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date: 2011-05-
|
18
|
+
date: 2011-05-31 00:00:00 -07:00
|
19
|
+
default_executable:
|
14
20
|
dependencies: []
|
15
21
|
|
16
22
|
description: gem for adding DKIM signatures to email messages
|
@@ -24,16 +30,23 @@ extra_rdoc_files: []
|
|
24
30
|
|
25
31
|
files:
|
26
32
|
- .gitignore
|
33
|
+
- CHANGELOG.md
|
27
34
|
- Gemfile
|
35
|
+
- README.md
|
28
36
|
- Rakefile
|
29
37
|
- bin/dkimsign.rb
|
30
38
|
- dkim.gemspec
|
31
39
|
- lib/dkim.rb
|
32
40
|
- lib/dkim/body.rb
|
41
|
+
- lib/dkim/canonicalizable.rb
|
42
|
+
- lib/dkim/dkim_header.rb
|
33
43
|
- lib/dkim/header.rb
|
34
44
|
- lib/dkim/header_list.rb
|
35
45
|
- lib/dkim/signed_mail.rb
|
36
46
|
- lib/dkim/version.rb
|
47
|
+
- test/canonicalization_test.rb
|
48
|
+
- test/test_helper.rb
|
49
|
+
has_rdoc: true
|
37
50
|
homepage: https://github.com/jhawthorn/dkim
|
38
51
|
licenses: []
|
39
52
|
|
@@ -47,19 +60,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
60
|
requirements:
|
48
61
|
- - ">="
|
49
62
|
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
50
66
|
version: "0"
|
51
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
68
|
none: false
|
53
69
|
requirements:
|
54
70
|
- - ">="
|
55
71
|
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
56
75
|
version: "0"
|
57
76
|
requirements: []
|
58
77
|
|
59
78
|
rubyforge_project: dkim
|
60
|
-
rubygems_version: 1.
|
79
|
+
rubygems_version: 1.6.2
|
61
80
|
signing_key:
|
62
81
|
specification_version: 3
|
63
82
|
summary: DKIM library in ruby
|
64
|
-
test_files:
|
65
|
-
|
83
|
+
test_files:
|
84
|
+
- test/canonicalization_test.rb
|
85
|
+
- test/test_helper.rb
|