dkimverify 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|