dkim 0.0.1
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/.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
|
+
|