dkim 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/bin/dkimsign.rb +23 -0
- data/dkim.gemspec +21 -0
- data/lib/dkim.rb +34 -0
- data/lib/dkim/body.rb +20 -0
- data/lib/dkim/header.rb +29 -0
- data/lib/dkim/header_list.rb +19 -0
- data/lib/dkim/signed_mail.rb +112 -0
- data/lib/dkim/version.rb +3 -0
- metadata +65 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/dkimsign.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
if ARGV.length != 2 && ARGV.length != 3
|
4
|
+
puts "Usage: dkimsign.rb SELECTOR KEYFILE [MAILFILE]"
|
5
|
+
exit 0
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'dkim'
|
9
|
+
|
10
|
+
selector, keyfile,mailfile = ARGV
|
11
|
+
|
12
|
+
keyfile = File.open(keyfile)
|
13
|
+
mailfile = mailfile ? File.open(mailfile) : STDIN
|
14
|
+
|
15
|
+
mail = mailfile.read.gsub(/\r?\n/, "\r\n")
|
16
|
+
key = keyfile.read
|
17
|
+
|
18
|
+
Dkim::selector = selector
|
19
|
+
Dkim::private_key = key
|
20
|
+
|
21
|
+
print Dkim::SignedMail.new(mail).to_s
|
22
|
+
|
23
|
+
|
data/dkim.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "dkim/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dkim"
|
7
|
+
s.version = Dkim::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["John Hawthorn"]
|
10
|
+
s.email = ["john.hawthorn@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/jhawthorn/dkim"
|
12
|
+
s.summary = %q{DKIM library in ruby}
|
13
|
+
s.description = %q{gem for adding DKIM signatures to email messages}
|
14
|
+
|
15
|
+
s.rubyforge_project = "dkim"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
data/lib/dkim.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'dkim/header'
|
3
|
+
require 'dkim/header_list'
|
4
|
+
require 'dkim/body'
|
5
|
+
require 'dkim/signed_mail'
|
6
|
+
|
7
|
+
module Dkim
|
8
|
+
DefaultHeaders = %w{
|
9
|
+
From Sender Reply-To Subject Date
|
10
|
+
Message-ID To Cc MIME-Version
|
11
|
+
Content-Type Content-Transfer-Encoding Content-ID Content-Description
|
12
|
+
Resent-Date Resent-From Resent-Sender Resent-To Resent-cc
|
13
|
+
Resent-Message-ID
|
14
|
+
In-Reply-To References
|
15
|
+
List-Id List-Help List-Unsubscribe List-Subscribe
|
16
|
+
List-Post List-Owner List-Archive}
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :signing_algorithm, :signable_headers, :domain, :selector
|
20
|
+
|
21
|
+
attr_reader :private_key
|
22
|
+
def private_key= key
|
23
|
+
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
24
|
+
@private_key = key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
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
|
34
|
+
|
data/lib/dkim/body.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Dkim
|
2
|
+
class Body < Struct.new(:body)
|
3
|
+
def to_canonical
|
4
|
+
body = self.body.dup
|
5
|
+
|
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)/, '')
|
8
|
+
|
9
|
+
# Reduces all sequences of WSP within a line to a single SP character.
|
10
|
+
body.gsub!(/[ \t]+/, ' ')
|
11
|
+
|
12
|
+
# Ignores all empty lines at the end of the message body.
|
13
|
+
body.gsub!(/(\r?\n)*\z/, '')
|
14
|
+
body += "\r\n"
|
15
|
+
end
|
16
|
+
def to_s
|
17
|
+
self.body.dup
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/dkim/header.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dkim
|
2
|
+
class Header < Struct.new(:key, :value)
|
3
|
+
def to_canonical
|
4
|
+
key = self.key.dup
|
5
|
+
value = self.value.dup
|
6
|
+
|
7
|
+
#Convert all header field names (not the header field values) to lowercase. For example, convert "SUBJect: AbC" to "subject: AbC".
|
8
|
+
key.downcase!
|
9
|
+
|
10
|
+
# Unfold all header field continuation lines as described in [RFC2822]
|
11
|
+
value.gsub!(/\r?\n[ \t]+/, ' ')
|
12
|
+
|
13
|
+
# Convert all sequences of one or more WSP characters to a single SP character.
|
14
|
+
value.gsub!(/[ \t]+/, ' ')
|
15
|
+
|
16
|
+
# Delete all WSP characters at the end of each unfolded header field value.
|
17
|
+
value.gsub!(/[ \t]*\z/, '')
|
18
|
+
|
19
|
+
# Delete any WSP characters remaining before and after the colon separating the header field name from the header field value.
|
20
|
+
value.gsub!(/\A[ \t]*/, '')
|
21
|
+
key.gsub!(/[ \t]*\z/, '')
|
22
|
+
|
23
|
+
"#{key}:#{value}"
|
24
|
+
end
|
25
|
+
def to_s
|
26
|
+
"#{key}:#{value}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dkim
|
2
|
+
class HeaderList
|
3
|
+
include Enumerable
|
4
|
+
def initialize headers
|
5
|
+
@headers = headers.split(/\r?\n(?!([ \t]))/).map do |header|
|
6
|
+
key, value = header.split(':', 2)
|
7
|
+
Header.new(key, value)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
def [](key)
|
11
|
+
@headers.detect do |header|
|
12
|
+
header.key == key
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def each(&block)
|
16
|
+
@headers.each(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Dkim
|
5
|
+
class SignedMail
|
6
|
+
EMAIL_REGEX = /[A-Z0-9._%+-]+@([A-Z0-9.-]+\.[A-Z]{2,6})/i
|
7
|
+
|
8
|
+
def initialize message, options={}
|
9
|
+
message = message.gsub(/\r?\n/, "\r\n")
|
10
|
+
headers, body = message.split(/\r?\n\r?\n/, 2)
|
11
|
+
@headers = HeaderList.new headers
|
12
|
+
@body = Body.new body
|
13
|
+
|
14
|
+
@signable_headers = options[:signable_headers]
|
15
|
+
@domain = options[:domain]
|
16
|
+
@selector = options[:selector]
|
17
|
+
@time = options[:time]
|
18
|
+
@signing_algorithm = options[:signing_algorithm]
|
19
|
+
@private_key = options[:private_key]
|
20
|
+
end
|
21
|
+
|
22
|
+
# options for signatures
|
23
|
+
attr_writer :signing_algorithm, :signable_headers, :domain, :selector, :time
|
24
|
+
|
25
|
+
def private_key= key
|
26
|
+
key = OpenSSL::PKey::RSA.new(key) if key.is_a?(String)
|
27
|
+
@private_key = key
|
28
|
+
end
|
29
|
+
def private_key
|
30
|
+
@private_key || Dkim::private_key
|
31
|
+
end
|
32
|
+
def signing_algorithm
|
33
|
+
@signing_algorithm || Dkim::signing_algorithm
|
34
|
+
end
|
35
|
+
def signable_headers
|
36
|
+
@signable_headers || Dkim::signable_headers
|
37
|
+
end
|
38
|
+
def domain
|
39
|
+
@domain || Dkim::domain || (@headers['From'].value =~ EMAIL_REGEX && $1)
|
40
|
+
end
|
41
|
+
def selector
|
42
|
+
@selector || Dkim::selector
|
43
|
+
end
|
44
|
+
def time
|
45
|
+
@time ||= Time.now
|
46
|
+
end
|
47
|
+
|
48
|
+
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)
|
71
|
+
end
|
72
|
+
def canonical_header
|
73
|
+
headers = signed_headers.map do |key|
|
74
|
+
@headers[key]
|
75
|
+
end
|
76
|
+
headers << dkim_header('')
|
77
|
+
headers.map(&:to_canonical).join("\r\n")
|
78
|
+
end
|
79
|
+
def canonical_body
|
80
|
+
@body.to_canonical
|
81
|
+
end
|
82
|
+
|
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)
|
88
|
+
end
|
89
|
+
def to_s
|
90
|
+
headers = @headers.to_a + [dkim_header]
|
91
|
+
headers.map(&:to_s).join("\r\n") +
|
92
|
+
"\r\n\r\n" +
|
93
|
+
@body.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def base64_encode data
|
98
|
+
Base64.encode64(data).gsub("\n",'')
|
99
|
+
end
|
100
|
+
def digest_alg
|
101
|
+
case signing_algorithm
|
102
|
+
when 'rsa-sha1'
|
103
|
+
OpenSSL::Digest::SHA1.new
|
104
|
+
when 'rsa-sha256'
|
105
|
+
OpenSSL::Digest::SHA256.new
|
106
|
+
else
|
107
|
+
raise "Unknown digest algorithm: '#{signing_algorithm}'"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
data/lib/dkim/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dkim
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Hawthorn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-10 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: gem for adding DKIM signatures to email messages
|
17
|
+
email:
|
18
|
+
- john.hawthorn@gmail.com
|
19
|
+
executables:
|
20
|
+
- dkimsign.rb
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- Gemfile
|
28
|
+
- Rakefile
|
29
|
+
- bin/dkimsign.rb
|
30
|
+
- dkim.gemspec
|
31
|
+
- lib/dkim.rb
|
32
|
+
- lib/dkim/body.rb
|
33
|
+
- lib/dkim/header.rb
|
34
|
+
- lib/dkim/header_list.rb
|
35
|
+
- lib/dkim/signed_mail.rb
|
36
|
+
- lib/dkim/version.rb
|
37
|
+
homepage: https://github.com/jhawthorn/dkim
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project: dkim
|
60
|
+
rubygems_version: 1.8.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: DKIM library in ruby
|
64
|
+
test_files: []
|
65
|
+
|