dkimverify 0.0.4 → 0.0.6
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.
- checksums.yaml +4 -4
- data/README.md +5 -12
- data/dkimverify.gemspec +2 -3
- data/dkimverify.rb +81 -26
- metadata +3 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a30e99530c7485db52e9583ad67b4565b22eadd
|
4
|
+
data.tar.gz: 0876d9ad6008badfc29bf3c05057b2894af84771
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4334b8fb2321ca976c25911447d1f95e3fd2fd7752640f93aa0c19f6622dde2c6d6da27e00ef75cdc3a9089e159d8f1df3d94e6eb4f8171b4333475d28aa574
|
7
|
+
data.tar.gz: 1819a0bef43d2aa4c95e2f69f4c3ec2b037d9c7a85eb6efe14b0bcbda6ab9257f8fcc2b5e0391df41053ef10e84e8a856edb21631ac947bc8f1a2d471cf95ab3
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@ a gem for verifying DKIM signatures in Ruby
|
|
5
5
|
|
6
6
|
this gem does not sign mail messages (but a PR to enable it would likely be accepted, I just have no use for it.)
|
7
7
|
|
8
|
-
|
8
|
+
I'm pretty sure this actually works and I'm using it in production.
|
9
9
|
|
10
10
|
how to use
|
11
11
|
-----------
|
@@ -17,16 +17,10 @@ the `verify!` method will return:
|
|
17
17
|
- `false` if no signature is present, and,
|
18
18
|
- raise `Dkim::DkimError` (or a child error) if the signature is present but does not verify.
|
19
19
|
|
20
|
-
loading emails from a string is not yet implemented, but would be really easy (send me a PR!)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
20
|
with a debt of gratitude to:
|
26
21
|
----------------------------
|
27
22
|
|
28
|
-
- [pydkim](https://github.com/ghewgill/pydkim) by @ghewgill which I used as a
|
29
|
-
- [mail](https://github.com/mikel/mail) by @mikel
|
23
|
+
- [pydkim](https://github.com/ghewgill/pydkim) by @ghewgill which I used as a more-or-less literal source of translation
|
30
24
|
- [carsonreinke's fork of the jhawthorne's dkim gem](https://github.com/carsonreinke/dkim/tree/feature_verification) which I wish I had found before I started this.
|
31
25
|
- [rfc6376 authors](https://tools.ietf.org/html/rfc6376)
|
32
26
|
|
@@ -36,11 +30,10 @@ checking expiration dates (x=, t=)
|
|
36
30
|
accounting for length limits (l= tag)
|
37
31
|
tests (which I really ought to add)
|
38
32
|
checking multiple dkim signature header lines (probably easy)
|
39
|
-
dealing with the "simple" canonicalization method (because I need to strip out the `mail` gem and instead write my own RFC822 parser that is better for maintaining the exact original string)
|
40
33
|
|
41
34
|
by
|
42
35
|
--
|
43
36
|
|
44
|
-
Jeremy B. Merrill
|
45
|
-
The New York Times
|
46
|
-
|
37
|
+
Jeremy B. Merrill
|
38
|
+
The New York Times
|
39
|
+
April 2017
|
data/dkimverify.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.name = "dkimverify"
|
5
|
-
gem.version = '0.0.
|
5
|
+
gem.version = '0.0.6'
|
6
6
|
gem.authors = ["Jeremy B. Merrill"]
|
7
7
|
gem.license = "MIT"
|
8
8
|
gem.email = ["jeremybmerrill@gmail.com"]
|
@@ -10,7 +10,6 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.summary = %q{ A pure-Ruby library for validating/verifying DKIM signatures. }
|
11
11
|
gem.homepage = "https://github.com/jeremybmerrill/dkimverify"
|
12
12
|
gem.files = `git ls-files`.split($/)
|
13
|
-
gem.require_paths = ["
|
14
|
-
gem.add_dependency "mail", "2.6.4"
|
13
|
+
gem.require_paths = ["."]
|
15
14
|
gem.add_dependency "parslet", "~> 1.6"
|
16
15
|
end
|
data/dkimverify.rb
CHANGED
@@ -5,12 +5,72 @@ require 'resolv'
|
|
5
5
|
|
6
6
|
# TODO make this an option somehow
|
7
7
|
$debuglog = nil #STDERR # nil # alternatively, set this to `STDERR` to log to stdout.
|
8
|
-
require 'mail'
|
9
8
|
|
10
9
|
module Mail
|
11
|
-
class
|
12
|
-
|
13
|
-
|
10
|
+
class MessageFormatError < StandardError; end
|
11
|
+
|
12
|
+
class HeaderHash < Hash
|
13
|
+
def get(header_name)
|
14
|
+
self[get_name(header_name)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_name(header_name)
|
18
|
+
keys.find{|k| k.downcase == header_name.downcase }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Message
|
23
|
+
def initialize(msg)
|
24
|
+
@raw_message = msg
|
25
|
+
@raw_headers = []
|
26
|
+
@body = nil
|
27
|
+
@parsed = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers
|
31
|
+
self.parse! unless @parsed
|
32
|
+
@headers
|
33
|
+
end
|
34
|
+
|
35
|
+
def body
|
36
|
+
self.parse! unless @parsed
|
37
|
+
@body
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse!
|
41
|
+
"""Parse a message in RFC822 format.
|
42
|
+
|
43
|
+
@param message: The message in RFC822 format. Either CRLF or LF is an accepted line separator.
|
44
|
+
|
45
|
+
@return Returns a tuple of (headers, body) where headers is a list of (name, value) pairs.
|
46
|
+
The body is a CRLF-separated string.
|
47
|
+
|
48
|
+
"""
|
49
|
+
|
50
|
+
lines = @raw_message.split(/\r?\n/)
|
51
|
+
i = 0
|
52
|
+
while i < lines.size
|
53
|
+
if lines[i].size == 0
|
54
|
+
# End of headers, return what we have plus the body, excluding the blank line.
|
55
|
+
i += 1
|
56
|
+
break
|
57
|
+
end
|
58
|
+
if /[\x09\x20]/.match lines[i][0]
|
59
|
+
@raw_headers[-1][1] += lines[i]+"\r\n"
|
60
|
+
else
|
61
|
+
m = /([\x21-\x7e]+?):/.match lines[i]
|
62
|
+
if m
|
63
|
+
@raw_headers << [m[1], lines[i][m.end(0)..-1]+"\r\n"]
|
64
|
+
elsif lines[i].start_with?("From ")
|
65
|
+
|
66
|
+
else
|
67
|
+
raise MessageFormatError.new("Unexpected characters in RFC822 header: #{lines[i]}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
i += 1
|
71
|
+
end
|
72
|
+
@body = lines[i..-1].join("\r\n") + "\r\n"
|
73
|
+
@headers = HeaderHash[*@raw_headers.reverse.flatten(1)]
|
14
74
|
end
|
15
75
|
end
|
16
76
|
end
|
@@ -35,8 +95,8 @@ module Dkim
|
|
35
95
|
#TODO: what is this kind of key-value string even called?
|
36
96
|
def self.parse_header_kv(input_str)
|
37
97
|
parsed = {}
|
38
|
-
input_str.split(/\s*;\s*/).each do |key_val|
|
39
|
-
if m = key_val.match(/(\w+)\s*=\s*(.*)/)
|
98
|
+
input_str.split(/\s*;\s*/m).each do |key_val|
|
99
|
+
if m = key_val.match(/(\w+)\s*=\s*(.*)/m)
|
40
100
|
parsed[m[1]] = m[2]
|
41
101
|
end
|
42
102
|
end
|
@@ -44,17 +104,17 @@ module Dkim
|
|
44
104
|
end
|
45
105
|
|
46
106
|
class Verifier
|
47
|
-
def initialize(
|
48
|
-
mail = Mail.
|
49
|
-
@headers = mail.
|
50
|
-
@body = mail.body
|
107
|
+
def initialize(email_stringy_thing)
|
108
|
+
mail = Mail::Message.new(email_stringy_thing)
|
109
|
+
@headers = mail.headers
|
110
|
+
@body = mail.body
|
51
111
|
end
|
52
112
|
|
53
113
|
|
54
114
|
def verify!
|
55
|
-
return false if @headers
|
115
|
+
return false if @headers.get("DKIM-Signature").nil?
|
56
116
|
|
57
|
-
dkim_signature_str = @headers.
|
117
|
+
dkim_signature_str = @headers.get("DKIM-Signature").to_s
|
58
118
|
@dkim_signature = Dkim.parse_header_kv(dkim_signature_str)
|
59
119
|
validate_signature! # just checking to make sure we have all the ingredients we need to actually verify the signature
|
60
120
|
|
@@ -110,7 +170,7 @@ module Dkim
|
|
110
170
|
def figure_out_canonicalization_methods!
|
111
171
|
c_match = @dkim_signature['c'].match(/(\w+)(?:\/(\w+))?$/)
|
112
172
|
if not c_match
|
113
|
-
puts "can't figure out canonicalization ('c=')"
|
173
|
+
$debuglog.puts "can't figure out canonicalization ('c=')"
|
114
174
|
return false
|
115
175
|
end
|
116
176
|
@how_to_canonicalize_headers = c_match[1]
|
@@ -142,20 +202,15 @@ module Dkim
|
|
142
202
|
$debuglog.puts "header_fields_to_include: #{header_fields_to_include}" unless $debuglog.nil?
|
143
203
|
canonicalized_headers = []
|
144
204
|
header_fields_to_include_with_values = header_fields_to_include.map do |header_name|
|
145
|
-
|
146
|
-
[header_name,
|
147
|
-
# .value and .instance_eval { unfold(split(@raw_value)[1]) } return subtly different values
|
148
|
-
# if the value of the Date header is a date with a single-digit day.
|
149
|
-
# see https://github.com/mikel/mail/issues/1075
|
150
|
-
# incidentally, .instance_variable_get("@value") gives a third subtly different value in a way that I don't understand.
|
205
|
+
header_val = (hstr = @headers.get(header_name)).nil? ? '' : hstr #.split(":")[1..-1].join(":")
|
206
|
+
[header_name, header_val ]
|
151
207
|
end
|
152
208
|
canonicalized_headers = Dkim.canonicalize_headers(header_fields_to_include_with_values, @how_to_canonicalize_headers)
|
153
|
-
puts @headers.first_field("DKIM-Signature").inspect
|
154
209
|
|
155
210
|
canonicalized_headers += Dkim.canonicalize_headers([
|
156
211
|
[
|
157
|
-
@headers.
|
158
|
-
@headers.
|
212
|
+
@headers.get_name("DKIM-Signature").to_s,
|
213
|
+
@headers.get("DKIM-Signature").to_s.split(@dkim_signature['b']).join('')
|
159
214
|
]
|
160
215
|
], @how_to_canonicalize_headers).map{|x| [x[0], x[1].rstrip()] }
|
161
216
|
|
@@ -272,12 +327,12 @@ module Dkim
|
|
272
327
|
end
|
273
328
|
|
274
329
|
if __FILE__ == $0
|
275
|
-
|
330
|
+
eml = ARGF.read
|
276
331
|
begin
|
277
|
-
ret = Dkim::Verifier.new(
|
332
|
+
ret = Dkim::Verifier.new(eml).verify!
|
278
333
|
rescue Dkim::DkimPermFail
|
279
|
-
puts "uh oh, something went wrong, the signature did not verify correctly"
|
334
|
+
STDERR.puts "uh oh, something went wrong, the signature did not verify correctly"
|
280
335
|
exit 1
|
281
336
|
end
|
282
|
-
puts ret ? "DKIM signature verified correctly" : "DKIM signature absent"
|
337
|
+
STDERR.puts ret ? "DKIM signature verified correctly" : "DKIM signature absent"
|
283
338
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dkimverify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy B. Merrill
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: mail
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 2.6.4
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - '='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 2.6.4
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: parslet
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,7 +44,6 @@ metadata: {}
|
|
58
44
|
post_install_message:
|
59
45
|
rdoc_options: []
|
60
46
|
require_paths:
|
61
|
-
- dkim-query
|
62
47
|
- "."
|
63
48
|
required_ruby_version: !ruby/object:Gem::Requirement
|
64
49
|
requirements:
|
@@ -72,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
57
|
version: '0'
|
73
58
|
requirements: []
|
74
59
|
rubyforge_project:
|
75
|
-
rubygems_version: 2.5.
|
60
|
+
rubygems_version: 2.5.1
|
76
61
|
signing_key:
|
77
62
|
specification_version: 4
|
78
63
|
summary: A pure-Ruby library for validating/verifying DKIM signatures.
|