Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 5 commits
  • 12 files changed
  • 0 commit comments
  • 1 contributor
View
5 Gemfile
@@ -2,3 +2,8 @@ source "http://rubygems.org"
# Specify your gem's dependencies in dkim.gemspec
gemspec
+
+gem "rake"
+gem 'guard-minitest'
+gem 'mail'
+
View
5 Guardfile
@@ -0,0 +1,5 @@
+
+guard 'minitest' do
+ watch(%r|^test/(.*)\.rb|) { "test" }
+ watch(%r|^lib/(.*)\.rb|) { "test" }
+end
View
2  README.md
@@ -84,7 +84,7 @@ For rails, create an initializer (for example `config/initializers/dkim.rb`) wit
Dkim::private_key = open('private.pem').read
# This will sign all ActionMailer deliveries
- ActionMailer::Base.register_interceptor('Dkim::Interceptor')
+ ActionMailer::Base.register_interceptor(Dkim::Interceptor)
Example executable
==================
View
0  bin/dkimsign.rb 100644 → 100755
File mode changed
View
9 lib/dkim.rb
@@ -1,5 +1,6 @@
require 'dkim/signed_mail'
+require 'dkim/options'
require 'dkim/interceptor'
module Dkim
@@ -14,13 +15,7 @@ module Dkim
List-Post List-Owner List-Archive}
class << self
- attr_accessor :signing_algorithm, :signable_headers, :domain, :selector, :header_canonicalization, :body_canonicalization
-
- attr_reader :private_key
- def private_key= key
- key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
- @private_key = key
- end
+ include Dkim::Options
def sign message, options={}
SignedMail.new(message, options).to_s
View
18 lib/dkim/options.rb
@@ -0,0 +1,18 @@
+module Dkim
+ module Options
+ ATTRIBUTES = :time, :private_key, :signing_algorithm, :signable_headers, :domain, :selector, :time, :header_canonicalization, :body_canonicalization
+ def options
+ @options ||= {}
+ end
+
+ ATTRIBUTES.each do |attribute_name|
+ define_method(attribute_name){options[attribute_name]}
+ define_method("#{attribute_name}="){|value| options[attribute_name] = value}
+ end
+
+ def private_key= key
+ key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
+ options[:private_key] = key
+ end
+ end
+end
View
47 lib/dkim/signed_mail.rb
@@ -4,55 +4,20 @@
require 'dkim/dkim_header'
require 'dkim/header'
require 'dkim/header_list'
+require 'dkim/options'
module Dkim
class SignedMail
+ include Options
+
def initialize message, options={}
message = message.to_s.gsub(/\r?\n/, "\r\n")
headers, body = message.split(/\r?\n\r?\n/, 2)
@headers = HeaderList.new headers
@body = Body.new body
- @signable_headers = options[:signable_headers]
- @domain = options[:domain]
- @selector = options[:selector]
- @time = options[:time]
- @signing_algorithm = options[:signing_algorithm]
- @private_key = options[:private_key]
- @header_canonicalization = options[:header_canonicalization]
- @body_canonicalization = options[:body_canonicalization]
- end
-
- # options for signatures
- attr_writer :signing_algorithm, :signable_headers, :domain, :selector, :time, :header_canonicalization, :body_canonicalization
-
- def private_key= key
- key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
- @private_key = key
- end
- def private_key
- @private_key || Dkim::private_key
- end
- def signing_algorithm
- @signing_algorithm || Dkim::signing_algorithm
- end
- def signable_headers
- @signable_headers || Dkim::signable_headers
- end
- def domain
- @domain || Dkim::domain
- end
- def selector
- @selector || Dkim::selector
- end
- def time
- @time
- end
- def header_canonicalization
- @header_canonicalization || Dkim::header_canonicalization
- end
- def body_canonicalization
- @body_canonicalization || Dkim::body_canonicalization
+ # default options from Dkim.options
+ @options = Dkim.options.merge(options)
end
def signed_headers
@@ -113,7 +78,7 @@ def digest_alg
case signing_algorithm
when 'rsa-sha1'
OpenSSL::Digest::SHA1.new
- when 'rsa-sha256'
+ when 'rsa-sha256'
OpenSSL::Digest::SHA256.new
else
raise "Unknown digest algorithm: '#{signing_algorithm}'"
View
40 test/dkim_header_test.rb
@@ -0,0 +1,40 @@
+
+require 'test_helper'
+
+class DkimHeaderTest < Test::Unit::TestCase
+ def setup
+ @header = Dkim::DkimHeader.new
+
+ # from Appendix A of RFC 4871
+ @header['v'] = '1'
+ @header['a'] = 'rsa-sha256'
+ @header['s'] = 'brisbane'
+ @header['d'] = 'example.com'
+ @header['c'] = 'simple/simple'
+ @header['q'] = 'dns/txt'
+ @header['i'] = 'joe@football.example.com'
+ @header['h'] = 'Received : From : To : Subject : Date : Message-ID'
+ @header['bh']= '2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
+ @header['b'] = 'AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHutKVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV4bmp/YzhwvcubU4='
+ end
+
+ def test_correct_format
+ header = @header.to_s
+
+ # result from RFC 4871 minus trailing ';'
+ expected = %{
+ DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com;
+ c=simple/simple; q=dns/txt; i=joe@football.example.com;
+ h=Received : From : To : Subject : Date : Message-ID;
+ bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
+ b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
+ 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
+ KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
+ 4bmp/YzhwvcubU4=
+ }
+
+ # compare removing whitespace
+ assert_equal expected.gsub(/\s/,''), header.gsub(/\s/,'')
+ end
+end
+
View
51 test/dkim_interceptor_test.rb
@@ -0,0 +1,51 @@
+require 'test_helper'
+
+require 'mail'
+
+class DkimInterceptorTest < Test::Unit::TestCase
+ def setup
+ mail = EXAMPLEEMAIL.dup
+
+ Dkim::selector = 'brisbane'
+ @mail = Mail.new(mail)
+ end
+
+ def teardown
+ Dkim::selector = nil
+ end
+
+ def test_header_with_relaxed
+ Dkim.header_canonicalization = 'relaxed'
+ Dkim.body_canonicalization = 'relaxed'
+ Dkim.signing_algorithm = 'rsa-sha256'
+ Dkim::Interceptor.delivering_email(@mail)
+ dkim_header = @mail['Dkim-Signature']
+ assert_not_nil dkim_header
+ assert_includes dkim_header.to_s, 'rsa-sha256'
+ assert_includes dkim_header.to_s, 's=brisbane'
+ assert_includes dkim_header.to_s, 'd=example.com'
+ assert_includes dkim_header.to_s, 'c=relaxed/relaxed'
+ assert_includes dkim_header.to_s, 'q=dns/txt'
+ assert_includes dkim_header.to_s, 'bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
+
+ # TODO: double check signing of 'b' header
+ end
+
+ def test_header_with_relaxed
+ Dkim.header_canonicalization = 'simple'
+ Dkim.body_canonicalization = 'simple'
+ Dkim.signing_algorithm = 'rsa-sha256'
+ Dkim::Interceptor.delivering_email(@mail)
+ dkim_header = @mail['Dkim-Signature']
+ assert_not_nil dkim_header
+ assert_includes dkim_header.to_s, 'rsa-sha256'
+ assert_includes dkim_header.to_s, 's=brisbane'
+ assert_includes dkim_header.to_s, 'd=example.com'
+ assert_includes dkim_header.to_s, 'c=simple/simple'
+ assert_includes dkim_header.to_s, 'q=dns/txt'
+ assert_includes dkim_header.to_s, 'bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8='
+
+ # TODO: double check signing of 'b' header
+ end
+end
+
View
37 test/options_test.rb
@@ -0,0 +1,37 @@
+module Dkim
+ class OptionsTest < Test::Unit::TestCase
+ def setup
+ klass = Class.new
+ klass.send :include, Options
+ @options = klass.new
+ end
+ def test_defaults_empty
+ assert_equal({}, @options.options)
+ end
+
+ def test_all_options
+ @options.signing_algorithm = 'abc123'
+ assert_equal({:signing_algorithm => 'abc123'}, @options.options)
+
+ desired_options = {
+ :signing_algorithm => 'abc123',
+ :signable_headers => [],
+ :domain => 'example.net',
+ :selector => 'selector',
+ :time => 'time',
+ :header_canonicalization => 'simple',
+ :body_canonicalization => 'simple'
+ }
+
+ desired_options.each do |key, value|
+ @options.send("#{key}=", value)
+ end
+
+ assert_equal(desired_options, @options.options)
+
+ desired_options.each do |key, value|
+ assert_equal value, @options.send("#{key}")
+ end
+ end
+ end
+end
View
48 test/signed_mail_test.rb
@@ -0,0 +1,48 @@
+
+require 'test_helper'
+
+module Dkim
+ class SignedMailTest < Test::Unit::TestCase
+ def setup
+ @mail = EXAMPLEEMAIL.dup
+ end
+
+ def test_defaults
+ signed_mail = SignedMail.new(@mail, :time => Time.at(1234567890))
+ dkim_header = signed_mail.dkim_header
+
+ assert_equal 'rsa-sha256', dkim_header['a']
+ assert_equal 'brisbane', dkim_header['s']
+ assert_equal 'example.com', dkim_header['d']
+ assert_equal 'relaxed/relaxed', dkim_header['c']
+ assert_equal 'dns/txt', dkim_header['q']
+
+ # bh value from RFC4871
+ assert_equal '2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=', dkim_header['bh']
+ assert_equal 'mamSUb17FQSZY2lfkeAsH/DvmpHsXdaFAu6BfbVblGBQ5+2yIPCx+clF5wClVBj97utSZb1WwOM0iup1JL37FI/UG+bxHo+MdGLqbLR63THGEdVF8FVeST4o4EQTWe0H3P/sU2rRZ61+M2SrTS94QkKAgj89QNOG48xSAO9xdfs=', dkim_header['b']
+ end
+
+ def test_overrides
+ options = {
+ :domain => 'example.org',
+ :selector => 'sidney',
+ :time => Time.now,
+ :signing_algorithm => 'rsa-sha1',
+ :header_canonicalization => 'simple',
+ :body_canonicalization => 'simple',
+ :time => Time.at(1234567890)
+ }
+ signed_mail = SignedMail.new(@mail, options)
+ dkim_header = signed_mail.dkim_header
+
+ assert_equal 'rsa-sha1', dkim_header['a']
+ assert_equal 'sidney', dkim_header['s']
+ assert_equal 'example.org', dkim_header['d']
+ assert_equal 'simple/simple', dkim_header['c']
+ assert_equal 'dns/txt', dkim_header['q']
+ assert_equal 'yk6W9pJJilr5MMgeEdSd7J3IaJI=', dkim_header['bh']
+ assert_equal 'sqYGmen+fouyIj83HuJ1v+1x40xp481bLxxcgAWMFsWYEwG05KYl+o0ZWn8jqgd1coKlX29o9iFjcMtZHudT8KpOdcLVYpY3gxzNfEgH79eRz32/ieGgroSK2GoMA/aV1QkxfUZexLUdj9oOX8uaMYXDkj8RGmlEGi+NDz/e4sE=', dkim_header['b']
+ end
+ end
+end
+
View
32 test/test_helper.rb
@@ -23,7 +23,37 @@ def rfc_format
end
end
+EXAMPLEEMAIL = %{
+From: Joe SixPack <IIVyTowbcT@www.brandonchecketts.com>
+To: Suzie Q <suzie@shopping.example.net>
+Subject: Is dinner ready?
+Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
+Message-ID: <20030712040037.46341.5F8J@football.example.com>
+
+Hi.
+
+We lost the game. Are you hungry yet?
+
+Joe.}.gsub(/\A\n/,'')
+
# examples used in rfc
Dkim::domain = 'example.com'
-
+Dkim::selector = 'brisbane'
+Dkim::private_key = %{
+-----BEGIN RSA PRIVATE KEY-----
+MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
+jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb
+to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB
+AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX
+/1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ
+gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO
+n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m
+3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/
+eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj
+7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA
+qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf
+eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX
+GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
+-----END RSA PRIVATE KEY-----
+}

No commit comments for this range

Something went wrong with that request. Please try again.