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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be888e240c37308c063950bd2bab4b67c28d0b62
4
- data.tar.gz: 2726298b7ec6e5ac4dc7c00392908de20ad33744
3
+ metadata.gz: ab8514ab9b191b18c0d9eab8d3f37b246555f4fd
4
+ data.tar.gz: 0d6203cb46b3f520f63f8b301da4c586208076ac
5
5
  SHA512:
6
- metadata.gz: 33a309ba077205dd8d3683a6460a9bc89afc6177cbef24601628b2368886ebd81fae09ed500b6333bc7973f91e4c3d7a5297514fc0615e60f3ceb14e966acd15
7
- data.tar.gz: 7078d239bbc4d4a2efbccbcd1c36b7267945d70150937245f1397d463da747837e65f6e77c80592077e7108104655b76717ebb6aa427071bd95037e20faf2d17
6
+ metadata.gz: b7eb314c2b983b38c2cdf98acb94c5197c3c122559e1eb0dc613dea75fec14ea3fa2da7aff2025ee20767a1e1962ba51dafb29935df0b2b8ae3fe51e1299a5b2
7
+ data.tar.gz: 5c0c5ce840fa47bf365f6bb2822af363fe720a980f7e0203bef47b3675e7a98a463adae22d973bb8e7b58590b97607c03360d786775d36d32bc1d7f444701d3a
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  pydkim
2
+ ruby_failures_python_successes.txt
2
3
  *.gem
3
4
  Gemfile.lock # becuase this is a gem and apparently you're not supposed to check that in for gems
4
5
  Gemfile.lock
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.1'
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 InvalidDkimSignature < DkimError; end
24
- class DkimVerificationFailure < DkimError; end
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
- dkim_signature_str = @headers["DKIM-Signature"].value.to_s
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
- canonicalized_headers = Dkim.canonicalize_headers(header_fields_to_include.map{|header_name| [header_name, @headers[header_name].value] }, @how_to_canonicalize_headers)
123
- # def _remove(s, t):
124
- # i = s.find(t)
125
- # assert i >= 0
126
- # return s[:i] + s[i+len(t):]
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["DKIM-Signature"].name.to_s,
133
- @headers["DKIM-Signature"].value.to_s.split(@dkim_signature['b']).join('')
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
- puts "couldn't figure out the right algorithm to use"
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: #{ Base64.encode64(digest) }" unless $debuglog.nil?
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::DkimError
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.1
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-25 00:00:00.000000000 Z
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
  - - ">="