dkimverify 0.0.1 → 0.0.2
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/.gitignore +1 -0
- data/README.md +16 -0
- data/dkimverify.gemspec +2 -2
- data/dkimverify.rb +38 -24
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab8514ab9b191b18c0d9eab8d3f37b246555f4fd
|
4
|
+
data.tar.gz: 0d6203cb46b3f520f63f8b301da4c586208076ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7eb314c2b983b38c2cdf98acb94c5197c3c122559e1eb0dc613dea75fec14ea3fa2da7aff2025ee20767a1e1962ba51dafb29935df0b2b8ae3fe51e1299a5b2
|
7
|
+
data.tar.gz: 5c0c5ce840fa47bf365f6bb2822af363fe720a980f7e0203bef47b3675e7a98a463adae22d973bb8e7b58590b97607c03360d786775d36d32bc1d7f444701d3a
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -5,6 +5,21 @@ 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
|
+
how to use
|
9
|
+
-----------
|
10
|
+
````Dkim::Verifier.new(eml_filepath).verify!````
|
11
|
+
|
12
|
+
the `verify!` method will return:
|
13
|
+
|
14
|
+
- `true` if the signature verifies
|
15
|
+
- `false` if no signature is present, and,
|
16
|
+
- raise `Dkim::DkimError` (or a child error) if the signature is present but does not verify.
|
17
|
+
|
18
|
+
loading emails from a string is not yet implemented, but would be really easy (send me a PR!)
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
8
23
|
with a debt of gratitude to:
|
9
24
|
----------------------------
|
10
25
|
|
@@ -19,6 +34,7 @@ not yet implemented
|
|
19
34
|
checking expiration dates (x=, t=)
|
20
35
|
accounting for length limits (l= tag)
|
21
36
|
tests (which I really ought to add)
|
37
|
+
checking multiple dkim signature header lines (probably easy)
|
22
38
|
|
23
39
|
by
|
24
40
|
--
|
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.2'
|
6
6
|
gem.authors = ["Jeremy B. Merrill"]
|
7
7
|
gem.license = "MIT"
|
8
8
|
gem.email = ["jeremybmerrill@gmail.com"]
|
@@ -10,7 +10,7 @@ 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 = ["dkim-query"]
|
13
|
+
gem.require_paths = ["dkim-query", "."]
|
14
14
|
gem.add_dependency "mail", "2.6.4"
|
15
15
|
gem.add_dependency "parslet", "~> 1.6"
|
16
16
|
end
|
data/dkimverify.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'mail'
|
2
1
|
require 'digest'
|
3
2
|
require 'openssl'
|
4
3
|
require 'base64'
|
@@ -7,6 +6,15 @@ require_relative './dkim-query/lib/dkim/query'
|
|
7
6
|
|
8
7
|
# TODO make this an option somehow
|
9
8
|
$debuglog = nil # alternatively, set this to `STDERR` to log to stdout.
|
9
|
+
require 'mail'
|
10
|
+
|
11
|
+
module Mail
|
12
|
+
class Header
|
13
|
+
def first_field(name)
|
14
|
+
self[name].class == Array ? self[name].first : self[name]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
10
18
|
|
11
19
|
module Dkim
|
12
20
|
# what are these magic numbers?!
|
@@ -20,15 +28,23 @@ module Dkim
|
|
20
28
|
HASHID_SHA256 = OpenSSL::ASN1::ObjectId.new('sha256')
|
21
29
|
|
22
30
|
class DkimError < StandardError; end
|
23
|
-
class
|
24
|
-
class
|
31
|
+
class DkimTempFail < DkimError; end
|
32
|
+
class DkimPermFail < DkimError; end
|
33
|
+
class InvalidDkimSignature < DkimPermFail; end
|
34
|
+
class DkimVerificationFailure < DkimPermFail; end
|
25
35
|
|
26
36
|
class Verifier
|
27
37
|
def initialize(email_filename)
|
28
|
-
mail = Mail.read(email_filename)
|
38
|
+
mail = Mail.read(email_filename) # TODO make this `mail` not `@mail`
|
29
39
|
@headers = mail.header
|
30
40
|
@body = mail.body.raw_source
|
31
|
-
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def verify!
|
45
|
+
return false if @headers["DKIM-Signature"].nil?
|
46
|
+
|
47
|
+
dkim_signature_str = @headers.first_field("DKIM-Signature").value.to_s
|
32
48
|
@dkim_signature = {}
|
33
49
|
dkim_signature_str.split(/\s*;\s*/).each do |key_val|
|
34
50
|
if m = key_val.match(/(\w+)\s*=\s*(.*)/)
|
@@ -36,10 +52,7 @@ module Dkim
|
|
36
52
|
end
|
37
53
|
end
|
38
54
|
validate_signature! # just checking to make sure we have all the ingredients we need to actually verify the signature
|
39
|
-
end
|
40
|
-
|
41
55
|
|
42
|
-
def verify!
|
43
56
|
figure_out_canonicalization_methods!
|
44
57
|
verify_body_hash!
|
45
58
|
|
@@ -68,7 +81,7 @@ module Dkim
|
|
68
81
|
elsif @dkim_signature['a'] == "rsa-sha256"
|
69
82
|
[Digest::SHA256, HASHID_SHA256]
|
70
83
|
else
|
71
|
-
puts "couldn't figure out the right algorithm to use"
|
84
|
+
$debuglog.puts "couldn't figure out the right algorithm to use"
|
72
85
|
exit 1
|
73
86
|
end
|
74
87
|
|
@@ -109,6 +122,7 @@ module Dkim
|
|
109
122
|
# here we're getting the website's actual public key from the DNS system
|
110
123
|
# s = dnstxt(sig['s']+"._domainkey."+sig['d']+".")
|
111
124
|
dkim_record_from_dns = DKIM::Query::Domain.query(@dkim_signature['d'], {:selectors => [@dkim_signature['s']]}).keys[@dkim_signature['s']]
|
125
|
+
raise DkimTempFail.new("couldn't get public key from DNS system for #{@dkim_signature['s']}/#{@dkim_signature['d']}") if dkim_record_from_dns.nil? || dkim_record_from_dns.class == DKIM::Query::MalformedKey
|
112
126
|
x = OpenSSL::ASN1.decode(Base64.decode64(dkim_record_from_dns.public_key.to_s))
|
113
127
|
publickey = x.value[1].value
|
114
128
|
end
|
@@ -119,18 +133,19 @@ module Dkim
|
|
119
133
|
header_fields_to_include = @dkim_signature['h'].split(/\s*:\s*/)
|
120
134
|
$debuglog.puts "header_fields_to_include: #{header_fields_to_include}" unless $debuglog.nil?
|
121
135
|
canonicalized_headers = []
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
136
|
+
header_fields_to_include_with_values = header_fields_to_include.map do |header_name|
|
137
|
+
[header_name, @headers.first_field(header_name).instance_eval { unfold(split(@raw_value)[1]) } ]
|
138
|
+
# .value and .instance_eval { unfold(split(@raw_value)[1]) } return subtly different values
|
139
|
+
# if the value of the Date header is a date with a single-digit day.
|
140
|
+
# see https://github.com/mikel/mail/issues/1075
|
141
|
+
# incidentally, .instance_variable_get("@value") gives a third subtly different value in a way that I don't understand.
|
142
|
+
end
|
143
|
+
canonicalized_headers = Dkim.canonicalize_headers(header_fields_to_include_with_values, @how_to_canonicalize_headers)
|
128
144
|
|
129
|
-
# The call to _remove() assumes that the signature b= only appears once in the signature header
|
130
145
|
canonicalized_headers += Dkim.canonicalize_headers([
|
131
146
|
[
|
132
|
-
@headers
|
133
|
-
@headers
|
147
|
+
@headers.first_field("DKIM-Signature").name.to_s,
|
148
|
+
@headers.first_field("DKIM-Signature").value.to_s.split(@dkim_signature['b']).join('')
|
134
149
|
]
|
135
150
|
], @how_to_canonicalize_headers).map{|x| [x[0], x[1].rstrip()] }
|
136
151
|
|
@@ -144,8 +159,7 @@ module Dkim
|
|
144
159
|
elsif @dkim_signature['a'] == "rsa-sha256"
|
145
160
|
Digest::SHA256
|
146
161
|
else
|
147
|
-
|
148
|
-
exit 1
|
162
|
+
raise InvalidDkimSignature.new "couldn't figure out the right algorithm to use"
|
149
163
|
end.new
|
150
164
|
headers_to_sign.each do |header|
|
151
165
|
hasher.update(header[0])
|
@@ -153,7 +167,7 @@ module Dkim
|
|
153
167
|
hasher.update(header[1])
|
154
168
|
end
|
155
169
|
digest = hasher.digest
|
156
|
-
$debuglog.puts "verify digest: #{
|
170
|
+
$debuglog.puts "verify digest: #{ digest.each_byte.map { |b| b.to_s(16) }.join ' ' }" unless $debuglog.nil?
|
157
171
|
digest
|
158
172
|
end
|
159
173
|
|
@@ -247,10 +261,10 @@ end
|
|
247
261
|
if __FILE__ == $0
|
248
262
|
emlfn = ARGV[0] || "/Users/204434/code/stevedore-uploader-internal/inputs/podesta-part36/59250.eml"
|
249
263
|
begin
|
250
|
-
Dkim::Verifier.new(emlfn).verify!
|
251
|
-
rescue Dkim::
|
264
|
+
ret = Dkim::Verifier.new(emlfn).verify!
|
265
|
+
rescue Dkim::DkimPermFail
|
252
266
|
puts "uh oh, something went wrong, the signature did not verify correctly"
|
253
267
|
exit 1
|
254
268
|
end
|
255
|
-
puts "signature verified correctly"
|
269
|
+
puts ret ? "DKIM signature verified correctly" : "DKIM signature absent"
|
256
270
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.2
|
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-01-
|
11
|
+
date: 2017-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mail
|
@@ -85,6 +85,7 @@ post_install_message:
|
|
85
85
|
rdoc_options: []
|
86
86
|
require_paths:
|
87
87
|
- dkim-query
|
88
|
+
- "."
|
88
89
|
required_ruby_version: !ruby/object:Gem::Requirement
|
89
90
|
requirements:
|
90
91
|
- - ">="
|