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