dkim 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -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
+
@@ -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::selector = selector
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
@@ -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 -- {test,spec,features}/*`.split("\n")
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
@@ -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 = Dkim::DefaultHeaders
30
- Dkim::domain = nil
31
- Dkim::selector = nil
32
- Dkim::signing_algorithm = 'rsa-sha256'
33
- Dkim::private_key = nil
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
 
@@ -1,20 +1,32 @@
1
+ require 'dkim/canonicalizable'
2
+
1
3
  module Dkim
2
4
  class Body < Struct.new(:body)
3
- def to_canonical
4
- body = self.body.dup
5
+ include Canonicalizable
5
6
 
6
- # Ignores all whitespace at the end of lines. Implementations MUST NOT remove the CRLF at the end of the line.
7
- body.gsub!(/[ \t]+(?=\r\n|\z)/, '')
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!(/(\r?\n)*\z/, '')
20
+ body.gsub!(/[ \r\n]*\z/, '')
21
+
14
22
  body += "\r\n"
15
23
  end
16
- def to_s
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,14 @@
1
+ module Dkim
2
+ module Canonicalizable
3
+ def to_s form='simple'
4
+ case form
5
+ when 'simple'
6
+ canonical_simple
7
+ when 'relaxed'
8
+ canonical_relaxed
9
+ else
10
+ raise "Unknown canonicalization: #{form}"
11
+ end
12
+ end
13
+ end
14
+ 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
@@ -1,12 +1,23 @@
1
+ require 'dkim/canonicalizable'
2
+
1
3
  module Dkim
2
4
  class Header < Struct.new(:key, :value)
3
- def to_canonical
4
- key = self.key.dup
5
- value = self.value.dup
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 before and after the colon separating the header field name from the header field value.
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
- "#{key}:#{value}"
33
+ value
34
+ end
35
+ def canonical_relaxed
36
+ "#{relaxed_key}:#{relaxed_value}"
24
37
  end
25
- def to_s
38
+ def canonical_simple
26
39
  "#{key}:#{value}"
27
40
  end
28
41
  end
@@ -9,7 +9,7 @@ module Dkim
9
9
  end
10
10
  def [](key)
11
11
  @headers.detect do |header|
12
- header.key == key
12
+ header.relaxed_key == key
13
13
  end
14
14
  end
15
15
  def each(&block)
@@ -1,10 +1,12 @@
1
1
  require 'openssl'
2
- require 'base64'
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 || (@headers['From'].value =~ EMAIL_REGEX && $1)
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 ||= Time.now
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(&:key) & signable_headers).sort
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.to_canonical
67
+ @body.to_s(body_canonicalization)
81
68
  end
82
69
 
83
- def header_signature
84
- base64_encode private_key.sign(digest_alg, canonical_header)
85
- end
86
- def body_hash
87
- base64_encode digest_alg.digest(canonical_body)
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
- Base64.encode64(data).gsub("\n",'')
110
+ [data].pack('m0*').gsub("\n",'')
99
111
  end
100
112
  def digest_alg
101
113
  case signing_algorithm
@@ -1,3 +1,3 @@
1
1
  module Dkim
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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
+
@@ -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
- version: 0.0.1
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-10 00:00:00 Z
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.8.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