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 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
  - - ">="